1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) Andriy Gapon 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions, and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 19 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 */ 28 29 #include <sys/cdefs.h> 30 #include "opt_platform.h" 31 32 #include <sys/param.h> 33 #include <sys/bus.h> 34 #include <sys/kernel.h> 35 #include <sys/module.h> 36 #include <sys/sysctl.h> 37 #include <sys/systm.h> 38 39 #include <machine/bus.h> 40 41 #include <dev/iicbus/iicbus.h> 42 #include <dev/iicbus/iiconf.h> 43 44 #ifdef FDT 45 #include <dev/ofw/ofw_bus.h> 46 #include <dev/ofw/ofw_bus_subr.h> 47 #endif 48 49 /* 50 * Driver for PCF8591 I2C 8-bit ADC and DAC. 51 */ 52 #define CTRL_CH_SELECT_MASK 0x03 53 #define CTRL_AUTOINC_EN 0x04 54 #define CTRL_CH_CONFIG_MASK 0x30 55 #define CTRL_CH_CONFIG_4_SINGLE 0x00 56 #define CTRL_CH_CONFIG_3_DIFF 0x10 57 #define CTRL_CH_CONFIG_2_SINGLE_1_DIFF 0x20 58 #define CTRL_CH_CONFIG_2_DIFF 0x30 59 #define CTRL_OUTPUT_EN 0x40 60 61 struct pcf8591_softc { 62 device_t sc_dev; 63 int sc_ch_count; 64 uint8_t sc_addr; 65 uint8_t sc_cfg; 66 uint8_t sc_output; 67 }; 68 69 #ifdef FDT 70 static struct ofw_compat_data compat_data[] = { 71 { "nxp,pcf8591", true }, 72 { NULL, false } 73 }; 74 #endif 75 76 static int 77 pcf8591_set_config(device_t dev) 78 { 79 80 struct iic_msg msg; 81 uint8_t data[2]; 82 struct pcf8591_softc *sc; 83 int error; 84 85 sc = device_get_softc(dev); 86 data[0] = sc->sc_cfg; 87 data[1] = sc->sc_output; 88 msg.slave = sc->sc_addr; 89 msg.flags = IIC_M_WR; 90 msg.len = nitems(data); 91 msg.buf = data; 92 93 error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT); 94 return (error); 95 } 96 97 static int 98 pcf8591_get_reading(device_t dev, uint8_t *reading) 99 { 100 struct iic_msg msg; 101 struct pcf8591_softc *sc; 102 int error; 103 104 sc = device_get_softc(dev); 105 106 msg.slave = sc->sc_addr; 107 msg.flags = IIC_M_RD; 108 msg.len = 1; 109 msg.buf = reading; 110 111 error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT); 112 return (error); 113 } 114 115 static int 116 pcf8591_select_channel(device_t dev, int channel) 117 { 118 struct pcf8591_softc *sc; 119 int error; 120 uint8_t unused; 121 122 sc = device_get_softc(dev); 123 if (channel >= sc->sc_ch_count) 124 return (EINVAL); 125 sc->sc_cfg &= ~CTRL_CH_SELECT_MASK; 126 sc->sc_cfg += channel; 127 error = pcf8591_set_config(dev); 128 if (error != 0) 129 return (error); 130 131 /* 132 * The next read is still for the old channel, 133 * so do it and discard. 134 */ 135 error = pcf8591_get_reading(dev, &unused); 136 return (error); 137 } 138 139 static int 140 pcf8591_channel_sysctl(SYSCTL_HANDLER_ARGS) 141 { 142 device_t dev; 143 int error, channel, val; 144 uint8_t reading; 145 146 dev = arg1; 147 channel = arg2; 148 149 if (req->oldptr != NULL) { 150 error = pcf8591_select_channel(dev, channel); 151 if (error != 0) 152 return (EIO); 153 error = pcf8591_get_reading(dev, &reading); 154 if (error != 0) 155 return (EIO); 156 val = reading; 157 } 158 error = sysctl_handle_int(oidp, &val, 0, req); 159 return (error); 160 } 161 162 static void 163 pcf8591_start(void *arg) 164 { 165 device_t dev; 166 struct pcf8591_softc *sc; 167 struct sysctl_ctx_list *ctx; 168 struct sysctl_oid *tree_node; 169 struct sysctl_oid_list *tree; 170 struct sysctl_oid *inputs_node; 171 struct sysctl_oid_list *inputs; 172 173 sc = arg; 174 dev = sc->sc_dev; 175 176 /* 177 * Set initial -- and, for the time being, fixed -- configuration. 178 * Channel auto-incrementi is disabled, although it could be more 179 * performant and precise for bulk channel queries. 180 * The inputs are configured as four single channels. 181 * The output is disabled. 182 */ 183 sc->sc_cfg = 0; 184 sc->sc_output = 0; 185 sc->sc_ch_count = 4; 186 (void)pcf8591_set_config(dev); 187 188 ctx = device_get_sysctl_ctx(dev); 189 tree_node = device_get_sysctl_tree(dev); 190 tree = SYSCTL_CHILDREN(tree_node); 191 192 inputs_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "inputs", 193 CTLTYPE_NODE, NULL, "Input channels"); 194 inputs = SYSCTL_CHILDREN(inputs_node); 195 for (int i = 0; i < sc->sc_ch_count; i++) { 196 char buf[4]; 197 198 snprintf(buf, sizeof(buf), "%d", i); 199 SYSCTL_ADD_PROC(ctx, inputs, OID_AUTO, buf, 200 CTLTYPE_INT | CTLFLAG_RD, dev, i, 201 pcf8591_channel_sysctl, "I", "Input level from 0 to 255 " 202 "(relative to Vref)"); 203 } 204 } 205 206 static int 207 pcf8591_probe(device_t dev) 208 { 209 #ifdef FDT 210 if (!ofw_bus_status_okay(dev)) 211 return (ENXIO); 212 #endif 213 device_set_desc(dev, "PCF8591 8-bit ADC / DAC"); 214 #ifdef FDT 215 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data) 216 return (BUS_PROBE_GENERIC); 217 #endif 218 return (BUS_PROBE_NOWILDCARD); 219 } 220 221 static int 222 pcf8591_attach(device_t dev) 223 { 224 struct pcf8591_softc *sc; 225 226 sc = device_get_softc(dev); 227 sc->sc_dev = dev; 228 sc->sc_addr = iicbus_get_addr(dev); 229 230 /* 231 * We have to wait until interrupts are enabled. Usually I2C read 232 * and write only works when the interrupts are available. 233 */ 234 config_intrhook_oneshot(pcf8591_start, sc); 235 return (0); 236 } 237 238 static int 239 pcf8591_detach(device_t dev) 240 { 241 return (0); 242 } 243 244 static device_method_t pcf8591_methods[] = { 245 /* Device interface */ 246 DEVMETHOD(device_probe, pcf8591_probe), 247 DEVMETHOD(device_attach, pcf8591_attach), 248 DEVMETHOD(device_detach, pcf8591_detach), 249 250 DEVMETHOD_END 251 }; 252 253 static driver_t pcf8591_driver = { 254 "pcf8591", 255 pcf8591_methods, 256 sizeof(struct pcf8591_softc) 257 }; 258 259 DRIVER_MODULE(pcf8591, iicbus, pcf8591_driver, 0, 0); 260 MODULE_DEPEND(pcf8591, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 261 MODULE_VERSION(pcf8591, 1); 262 #ifdef FDT 263 IICBUS_FDT_PNP_INFO(compat_data); 264 #endif 265