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