1 /* $NetBSD: mcp48x1.c,v 1.3 2022/01/19 05:21: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.3 2022/01/19 05:21: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 const char *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 struct spi_handle *sc_sh; 68 69 struct mcp48x1dac_model *sc_dm; /* struct describing DAC model */ 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 struct mcp48x1dac_model mcp48x1_models[] = { 97 { 98 .name = "MCP4801", 99 .resolution = 8, 100 .shift = 4 101 }, 102 { 103 .name = "MCP4811", 104 .resolution = 10, 105 .shift = 2 106 }, 107 { 108 .name = "MCP4821", 109 .resolution = 12, 110 .shift = 0 111 } 112 }; 113 114 115 static int 116 mcp48x1dac_match(device_t parent, cfdata_t cf, void *aux) 117 { 118 119 /* MCP48x1 is a write-only device, so no way to detect it! */ 120 121 return 1; 122 } 123 124 static void 125 mcp48x1dac_attach(device_t parent, device_t self, void *aux) 126 { 127 struct mcp48x1dac_softc *sc; 128 struct spi_attach_args *sa; 129 int error, cf_flags; 130 131 aprint_naive(": Digital to Analog converter\n"); 132 aprint_normal(": MCP48x1 DAC\n"); 133 134 sa = aux; 135 sc = device_private(self); 136 sc->sc_dev = self; 137 sc->sc_sh = sa->sa_handle; 138 cf_flags = device_cfdata(sc->sc_dev)->cf_flags; 139 140 sc->sc_dm = &mcp48x1_models[cf_flags]; /* flag value defines model */ 141 142 error = spi_configure(self, sa->sa_handle, SPI_MODE_0, 20000000); 143 if (error) { 144 return; 145 } 146 147 if(!mcp48x1dac_envsys_attach(sc)) { 148 aprint_error_dev(sc->sc_dev, "failed to attach envsys\n"); 149 return; 150 }; 151 152 sc->sc_dac_data = 0; 153 sc->sc_dac_gain = false; 154 sc->sc_dac_shutdown = false; 155 mcp48x1dac_write(sc); 156 157 mcp48x1dac_setup_sysctl(sc); 158 } 159 160 static void 161 mcp48x1dac_write(struct mcp48x1dac_softc *sc) 162 { 163 int rv; 164 uint16_t reg, regbe; 165 166 reg = 0; 167 168 if (!(sc->sc_dac_gain)) 169 reg |= MCP48X1DAC_GAIN; 170 171 if (!(sc->sc_dac_shutdown)) 172 reg |= MCP48X1DAC_SHDN; 173 174 reg |= sc->sc_dac_data << sc->sc_dm->shift; 175 176 regbe = htobe16(reg); 177 178 #ifdef MCP48X1DAC_DEBUG 179 aprint_normal_dev(sc->sc_dev, "sending %x over SPI\n", regbe); 180 #endif /* MCP48X1DAC_DEBUG */ 181 182 rv = spi_send(sc->sc_sh, 2, (uint8_t*) ®be); /* XXX: ugly cast */ 183 184 if (rv != 0) 185 aprint_error_dev(sc->sc_dev, "error sending data over SPI\n"); 186 } 187 188 static bool 189 mcp48x1dac_envsys_attach(struct mcp48x1dac_softc *sc) 190 { 191 192 sc->sc_sme = sysmon_envsys_create(); 193 sc->sc_sm_vo.units = ENVSYS_SVOLTS_DC; 194 sc->sc_sm_vo.state = ENVSYS_SINVALID; 195 strlcpy(sc->sc_sm_vo.desc, device_xname(sc->sc_dev), 196 sizeof(sc->sc_sm_vo.desc)); 197 if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sm_vo)) { 198 sysmon_envsys_destroy(sc->sc_sme); 199 return false; 200 } 201 202 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 203 sc->sc_sme->sme_refresh = mcp48x1dac_envsys_refresh; 204 sc->sc_sme->sme_cookie = sc; 205 206 if (sysmon_envsys_register(sc->sc_sme)) { 207 aprint_error_dev(sc->sc_dev, "unable to register in sysmon\n"); 208 sysmon_envsys_destroy(sc->sc_sme); 209 } 210 211 return true; 212 } 213 214 static uint16_t 215 mcp48x1dac_regval_to_mv(struct mcp48x1dac_softc *sc) 216 { 217 uint16_t mv; 218 219 mv = (2048 * sc->sc_dac_data / (1 << sc->sc_dm->resolution)); 220 221 if (sc->sc_dac_gain) 222 mv *= 2; 223 224 return mv; 225 } 226 227 static void 228 mcp48x1dac_envsys_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 229 { 230 struct mcp48x1dac_softc *sc; 231 232 sc = sme->sme_cookie; 233 234 edata->value_cur = mcp48x1dac_regval_to_mv(sc); 235 edata->state = ENVSYS_SVALID; 236 } 237 238 static void 239 mcp48x1dac_setup_sysctl(struct mcp48x1dac_softc *sc) 240 { 241 const struct sysctlnode *me = NULL, *node = NULL; 242 243 sysctl_createv(NULL, 0, NULL, &me, 244 CTLFLAG_READWRITE, 245 CTLTYPE_NODE, device_xname(sc->sc_dev), NULL, 246 NULL, 0, NULL, 0, 247 CTL_MACHDEP, CTL_CREATE, CTL_EOL); 248 249 sysctl_createv(NULL, 0, NULL, &node, 250 CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 251 CTLTYPE_INT, "data", "Digital value to convert to analog", 252 sysctl_mcp48x1dac_data, 1, (void *)sc, 0, 253 CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL); 254 255 sysctl_createv(NULL, 0, NULL, &node, 256 CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 257 CTLTYPE_INT, "gain", "Gain 2x enable", 258 sysctl_mcp48x1dac_gain, 1, (void *)sc, 0, 259 CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL); 260 261 } 262 263 264 SYSCTL_SETUP(sysctl_mcp48x1dac_setup, "sysctl mcp48x1dac subtree setup") 265 { 266 sysctl_createv(NULL, 0, NULL, NULL, CTLFLAG_PERMANENT, 267 CTLTYPE_NODE, "machdep", NULL, NULL, 0, NULL, 0, 268 CTL_MACHDEP, CTL_EOL); 269 } 270 271 272 static int 273 sysctl_mcp48x1dac_data(SYSCTLFN_ARGS) 274 { 275 struct sysctlnode node = *rnode; 276 struct mcp48x1dac_softc *sc = node.sysctl_data; 277 int newdata, err; 278 279 node.sysctl_data = &sc->sc_dac_data; 280 if ((err = (sysctl_lookup(SYSCTLFN_CALL(&node)))) != 0) 281 return err; 282 283 if (newp) { 284 newdata = *(int *)node.sysctl_data; 285 if (newdata > (1 << sc->sc_dm->resolution)) 286 return EINVAL; 287 sc->sc_dac_data = (uint16_t) newdata; 288 mcp48x1dac_write(sc); 289 return 0; 290 } else { 291 /* nothing to do, since we can't read from DAC */ 292 node.sysctl_size = 4; 293 } 294 295 return err; 296 } 297 298 static int 299 sysctl_mcp48x1dac_gain(SYSCTLFN_ARGS) 300 { 301 struct sysctlnode node = *rnode; 302 struct mcp48x1dac_softc *sc = node.sysctl_data; 303 int newgain, err; 304 305 node.sysctl_data = &sc->sc_dac_gain; 306 if ((err = (sysctl_lookup(SYSCTLFN_CALL(&node)))) != 0) 307 return err; 308 309 if (newp) { 310 newgain = *(int *)node.sysctl_data; 311 sc->sc_dac_gain = (bool) newgain; 312 mcp48x1dac_write(sc); 313 return 0; 314 } else { 315 /* nothing to do, since we can't read from DAC */ 316 node.sysctl_size = 4; 317 } 318 319 return err; 320 } 321 322