1 /* $NetBSD: mcp48x1.c,v 1.7 2025/09/13 14:10:44 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2014 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Radoslaw Kujawa. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: mcp48x1.c,v 1.7 2025/09/13 14:10:44 thorpej Exp $"); 34 35 /* 36 * Driver for Microchip MCP4801/MCP4811/MCP4821 DAC. 37 * 38 * XXX: needs more testing. 39 */ 40 41 #include <sys/param.h> 42 #include <sys/systm.h> 43 #include <sys/device.h> 44 #include <sys/kernel.h> 45 #include <sys/types.h> 46 #include <sys/sysctl.h> 47 48 #include <dev/sysmon/sysmonvar.h> 49 50 #include <dev/spi/spivar.h> 51 52 #define MCP48X1DAC_DEBUG 0 53 54 #define MCP48X1DAC_WRITE __BIT(15) /* active low */ 55 #define MCP48X1DAC_GAIN __BIT(13) /* active low */ 56 #define MCP48X1DAC_SHDN __BIT(12) /* active low */ 57 #define MCP48X1DAC_DATA __BITS(11,0) /* data */ 58 59 struct mcp48x1dac_model { 60 unsigned int name; 61 uint8_t resolution; 62 uint8_t shift; /* data left shift during write */ 63 }; 64 65 struct mcp48x1dac_softc { 66 device_t sc_dev; 67 spi_handle_t sc_sh; 68 69 const struct mcp48x1dac_model *sc_dm; 70 71 uint16_t sc_dac_data; 72 bool sc_dac_gain; 73 bool sc_dac_shutdown; 74 75 struct sysmon_envsys *sc_sme; 76 envsys_data_t sc_sm_vo; /* envsys "sensor" (Vo) */ 77 }; 78 79 static int mcp48x1dac_match(device_t, cfdata_t, void *); 80 static void mcp48x1dac_attach(device_t, device_t, void *); 81 82 static bool mcp48x1dac_envsys_attach(struct mcp48x1dac_softc *sc); 83 static void mcp48x1dac_envsys_refresh(struct sysmon_envsys *, 84 envsys_data_t *); 85 86 static void mcp48x1dac_write(struct mcp48x1dac_softc *); 87 static uint16_t mcp48x1dac_regval_to_mv(struct mcp48x1dac_softc *); 88 89 static void mcp48x1dac_setup_sysctl(struct mcp48x1dac_softc *sc); 90 static int sysctl_mcp48x1dac_data(SYSCTLFN_ARGS); 91 static int sysctl_mcp48x1dac_gain(SYSCTLFN_ARGS); 92 93 CFATTACH_DECL_NEW(mcp48x1dac, sizeof(struct mcp48x1dac_softc), 94 mcp48x1dac_match, mcp48x1dac_attach, NULL, NULL); 95 96 static const struct mcp48x1dac_model mcp4801 = { 97 .name = 4801, 98 .resolution = 8, 99 .shift = 4 100 }; 101 102 static const struct mcp48x1dac_model mcp4811 = { 103 .name = 4811, 104 .resolution = 10, 105 .shift = 2 106 }; 107 108 static const struct mcp48x1dac_model mcp4821 = { 109 .name = 4821, 110 .resolution = 12, 111 .shift = 0 112 }; 113 114 /* 115 * N.B. The order of this table is important! It matches the order 116 * of the legacy mcp48x1_models[] array, which is used to manually 117 * select the device type in the kernel configuration file when 118 * direct configuration is not available. 119 */ 120 static const struct device_compatible_entry compat_data[] = { 121 { .compat = "microchip,mcp4801", .data = &mcp4801 }, 122 { .compat = "microchip,mcp4811", .data = &mcp4811 }, 123 { .compat = "microchip,mcp4821", .data = &mcp4821 }, 124 125 DEVICE_COMPAT_EOL 126 }; 127 static const int mcp48x1_nmodels = __arraycount(compat_data) - 1; 128 129 static const struct mcp48x1dac_model * 130 mcp48x1dac_lookup(const struct spi_attach_args *sa, const cfdata_t cf) 131 { 132 const struct mcp48x1dac_model *model = NULL; 133 const struct device_compatible_entry *dce = 134 spi_compatible_lookup(sa, compat_data); 135 136 if (dce != NULL) { 137 model = dce->data; 138 } else if (cf->cf_flags >= 0 && cf->cf_flags < mcp48x1_nmodels) { 139 model = compat_data[cf->cf_flags].data; 140 } 141 return model; 142 } 143 144 static int 145 mcp48x1dac_match(device_t parent, cfdata_t cf, void *aux) 146 { 147 struct spi_attach_args *sa = aux; 148 int match_result; 149 150 if (spi_use_direct_match(sa, compat_data, &match_result)) { 151 return match_result; 152 } 153 154 /* 155 * If we're doing indirect config, the user must 156 * have specified a valid model. 157 */ 158 if (mcp48x1dac_lookup(sa, cf) == NULL) { 159 return 0; 160 } 161 162 return SPI_MATCH_DEFAULT; 163 } 164 165 static void 166 mcp48x1dac_attach(device_t parent, device_t self, void *aux) 167 { 168 struct mcp48x1dac_softc *sc = device_private(self); 169 struct spi_attach_args *sa = aux; 170 const struct mcp48x1dac_model *model; 171 int error; 172 173 sc->sc_dev = self; 174 sc->sc_sh = sa->sa_handle; 175 176 model = mcp48x1dac_lookup(sa, device_cfdata(self)); 177 KASSERT(model != NULL); 178 179 sc->sc_dm = model; 180 181 aprint_naive(": Digital to Analog converter\n"); 182 aprint_normal(": MCP%u DAC\n", model->name); 183 184 /* 185 * XXX Deal with other properties described in the device 186 * tree bindings. 187 * 188 * https://www.kernel.org/doc/Documentation/devicetree/bindings/iio/dac/microchip%2Cmcp4821.yaml 189 */ 190 191 error = spi_configure(self, sa->sa_handle, SPI_MODE_0, 192 SPI_FREQ_MHz(20)); 193 if (error) { 194 return; 195 } 196 197 if (!mcp48x1dac_envsys_attach(sc)) { 198 aprint_error_dev(sc->sc_dev, "failed to attach envsys\n"); 199 return; 200 }; 201 202 sc->sc_dac_data = 0; 203 sc->sc_dac_gain = false; 204 sc->sc_dac_shutdown = false; 205 mcp48x1dac_write(sc); 206 207 mcp48x1dac_setup_sysctl(sc); 208 } 209 210 static void 211 mcp48x1dac_write(struct mcp48x1dac_softc *sc) 212 { 213 int rv; 214 uint16_t reg, regbe; 215 216 reg = 0; 217 218 if (!(sc->sc_dac_gain)) 219 reg |= MCP48X1DAC_GAIN; 220 221 if (!(sc->sc_dac_shutdown)) 222 reg |= MCP48X1DAC_SHDN; 223 224 reg |= sc->sc_dac_data << sc->sc_dm->shift; 225 226 regbe = htobe16(reg); 227 228 #ifdef MCP48X1DAC_DEBUG 229 aprint_normal_dev(sc->sc_dev, "sending %x over SPI\n", regbe); 230 #endif /* MCP48X1DAC_DEBUG */ 231 232 rv = spi_send(sc->sc_sh, 2, (uint8_t*) ®be); /* XXX: ugly cast */ 233 234 if (rv != 0) 235 aprint_error_dev(sc->sc_dev, "error sending data over SPI\n"); 236 } 237 238 static bool 239 mcp48x1dac_envsys_attach(struct mcp48x1dac_softc *sc) 240 { 241 242 sc->sc_sme = sysmon_envsys_create(); 243 sc->sc_sm_vo.units = ENVSYS_SVOLTS_DC; 244 sc->sc_sm_vo.state = ENVSYS_SINVALID; 245 strlcpy(sc->sc_sm_vo.desc, device_xname(sc->sc_dev), 246 sizeof(sc->sc_sm_vo.desc)); 247 if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sm_vo)) { 248 sysmon_envsys_destroy(sc->sc_sme); 249 return false; 250 } 251 252 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 253 sc->sc_sme->sme_refresh = mcp48x1dac_envsys_refresh; 254 sc->sc_sme->sme_cookie = sc; 255 256 if (sysmon_envsys_register(sc->sc_sme)) { 257 aprint_error_dev(sc->sc_dev, "unable to register in sysmon\n"); 258 sysmon_envsys_destroy(sc->sc_sme); 259 } 260 261 return true; 262 } 263 264 static uint16_t 265 mcp48x1dac_regval_to_mv(struct mcp48x1dac_softc *sc) 266 { 267 uint16_t mv; 268 269 mv = (2048 * sc->sc_dac_data / (1 << sc->sc_dm->resolution)); 270 271 if (sc->sc_dac_gain) 272 mv *= 2; 273 274 return mv; 275 } 276 277 static void 278 mcp48x1dac_envsys_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 279 { 280 struct mcp48x1dac_softc *sc; 281 282 sc = sme->sme_cookie; 283 284 edata->value_cur = mcp48x1dac_regval_to_mv(sc); 285 edata->state = ENVSYS_SVALID; 286 } 287 288 static void 289 mcp48x1dac_setup_sysctl(struct mcp48x1dac_softc *sc) 290 { 291 const struct sysctlnode *me = NULL, *node = NULL; 292 293 sysctl_createv(NULL, 0, NULL, &me, 294 CTLFLAG_READWRITE, 295 CTLTYPE_NODE, device_xname(sc->sc_dev), NULL, 296 NULL, 0, NULL, 0, 297 CTL_MACHDEP, CTL_CREATE, CTL_EOL); 298 299 sysctl_createv(NULL, 0, NULL, &node, 300 CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 301 CTLTYPE_INT, "data", "Digital value to convert to analog", 302 sysctl_mcp48x1dac_data, 1, (void *)sc, 0, 303 CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL); 304 305 sysctl_createv(NULL, 0, NULL, &node, 306 CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 307 CTLTYPE_INT, "gain", "Gain 2x enable", 308 sysctl_mcp48x1dac_gain, 1, (void *)sc, 0, 309 CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL); 310 311 } 312 313 314 SYSCTL_SETUP(sysctl_mcp48x1dac_setup, "sysctl mcp48x1dac subtree setup") 315 { 316 sysctl_createv(NULL, 0, NULL, NULL, CTLFLAG_PERMANENT, 317 CTLTYPE_NODE, "machdep", NULL, NULL, 0, NULL, 0, 318 CTL_MACHDEP, CTL_EOL); 319 } 320 321 322 static int 323 sysctl_mcp48x1dac_data(SYSCTLFN_ARGS) 324 { 325 struct sysctlnode node = *rnode; 326 struct mcp48x1dac_softc *sc = node.sysctl_data; 327 int newdata, err; 328 329 node.sysctl_data = &sc->sc_dac_data; 330 if ((err = (sysctl_lookup(SYSCTLFN_CALL(&node)))) != 0) 331 return err; 332 333 if (newp) { 334 newdata = *(int *)node.sysctl_data; 335 if (newdata > (1 << sc->sc_dm->resolution)) 336 return EINVAL; 337 sc->sc_dac_data = (uint16_t) newdata; 338 mcp48x1dac_write(sc); 339 return 0; 340 } else { 341 /* nothing to do, since we can't read from DAC */ 342 node.sysctl_size = 4; 343 } 344 345 return err; 346 } 347 348 static int 349 sysctl_mcp48x1dac_gain(SYSCTLFN_ARGS) 350 { 351 struct sysctlnode node = *rnode; 352 struct mcp48x1dac_softc *sc = node.sysctl_data; 353 int newgain, err; 354 355 node.sysctl_data = &sc->sc_dac_gain; 356 if ((err = (sysctl_lookup(SYSCTLFN_CALL(&node)))) != 0) 357 return err; 358 359 if (newp) { 360 newgain = *(int *)node.sysctl_data; 361 sc->sc_dac_gain = (bool) newgain; 362 mcp48x1dac_write(sc); 363 return 0; 364 } else { 365 /* nothing to do, since we can't read from DAC */ 366 node.sysctl_size = 4; 367 } 368 369 return err; 370 } 371 372