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 /* 30 * Driver for PCF8574 / PCF8574A 8-bit I/O expander 31 * with quasi-bidirectional I/O. 32 * There is no separate mode / configuration register. 33 * Pins are set and queried via a single register. 34 * Because of that we have to maintain the state in the driver 35 * and assume that there is no outside meddling with the device. 36 * See the datasheet for details. 37 */ 38 39 #include <sys/cdefs.h> 40 #include "opt_platform.h" 41 42 #include <sys/param.h> 43 #include <sys/bus.h> 44 #include <sys/gpio.h> 45 #include <sys/kernel.h> 46 #include <sys/module.h> 47 #include <sys/systm.h> 48 #include <sys/sx.h> 49 50 #ifdef FDT 51 #include <dev/ofw/openfirm.h> 52 #include <dev/ofw/ofw_bus.h> 53 #include <dev/ofw/ofw_bus_subr.h> 54 #endif 55 56 #include <dev/iicbus/iicbus.h> 57 #include <dev/iicbus/iiconf.h> 58 59 #include <dev/gpio/gpiobusvar.h> 60 61 #include "gpio_if.h" 62 63 #define NUM_PINS 8 64 #define PIN_CAPS (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT) 65 66 #define dbg_dev_printf(dev, fmt, args...) \ 67 if (bootverbose) device_printf(dev, fmt, ##args) 68 69 struct pcf8574_softc { 70 device_t dev; 71 device_t busdev; 72 struct sx lock; 73 uint8_t addr; 74 uint8_t output_mask; 75 uint8_t output_state; 76 }; 77 78 #ifdef FDT 79 static struct ofw_compat_data compat_data[] = { 80 { "nxp,pcf8574", 1 }, 81 { "nxp,pcf8574a", 1 }, 82 { NULL, 0 } 83 }; 84 #endif 85 86 static int 87 pcf8574_read(struct pcf8574_softc *sc, uint8_t *val) 88 { 89 struct iic_msg msg; 90 int error; 91 92 msg.slave = sc->addr; 93 msg.flags = IIC_M_RD; 94 msg.len = 1; 95 msg.buf = val; 96 97 error = iicbus_transfer_excl(sc->dev, &msg, 1, IIC_WAIT); 98 return (iic2errno(error)); 99 } 100 101 static int 102 pcf8574_write(struct pcf8574_softc *sc, uint8_t val) 103 { 104 struct iic_msg msg; 105 int error; 106 107 msg.slave = sc->addr; 108 msg.flags = IIC_M_WR; 109 msg.len = 1; 110 msg.buf = &val; 111 112 error = iicbus_transfer_excl(sc->dev, &msg, 1, IIC_WAIT); 113 return (iic2errno(error)); 114 } 115 116 static int 117 pcf8574_probe(device_t dev) 118 { 119 120 #ifdef FDT 121 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 122 return (ENXIO); 123 #endif 124 device_set_desc(dev, "PCF8574 I/O expander"); 125 return (BUS_PROBE_DEFAULT); 126 } 127 128 static int 129 pcf8574_attach(device_t dev) 130 { 131 struct pcf8574_softc *sc; 132 133 sc = device_get_softc(dev); 134 sc->dev = dev; 135 sc->addr = iicbus_get_addr(dev); 136 137 /* Treat everything as input because there is no way to tell. */ 138 sc->output_mask = 0; 139 sc->output_state = 0xff; 140 141 /* Put the device to a safe, known state. */ 142 (void)pcf8574_write(sc, 0xff); 143 144 sx_init(&sc->lock, "pcf8574"); 145 sc->busdev = gpiobus_add_bus(dev); 146 if (sc->busdev == NULL) { 147 device_printf(dev, "Could not create busdev child\n"); 148 sx_destroy(&sc->lock); 149 return (ENXIO); 150 } 151 bus_attach_children(dev); 152 return (0); 153 } 154 155 static int 156 pcf8574_detach(device_t dev) 157 { 158 struct pcf8574_softc *sc; 159 160 sc = device_get_softc(dev); 161 162 gpiobus_detach_bus(dev); 163 sx_destroy(&sc->lock); 164 return (0); 165 } 166 167 static device_t 168 pcf8574_get_bus(device_t dev) 169 { 170 struct pcf8574_softc *sc; 171 172 sc = device_get_softc(dev); 173 return (sc->busdev); 174 } 175 176 static int 177 pcf8574_pin_max(device_t dev __unused, int *maxpin) 178 { 179 *maxpin = NUM_PINS - 1; 180 return (0); 181 } 182 183 static int 184 pcf8574_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) 185 { 186 187 if (pin >= NUM_PINS) 188 return (EINVAL); 189 *caps = PIN_CAPS; 190 return (0); 191 } 192 193 static int 194 pcf8574_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) 195 { 196 struct pcf8574_softc *sc; 197 uint8_t val, stale; 198 int error; 199 200 sc = device_get_softc(dev); 201 202 if (pin >= NUM_PINS) 203 return (EINVAL); 204 205 sx_xlock(&sc->lock); 206 error = pcf8574_read(sc, &val); 207 if (error != 0) { 208 dbg_dev_printf(dev, "failed to read from device: %d\n", 209 error); 210 sx_xunlock(&sc->lock); 211 return (error); 212 } 213 214 /* 215 * Check for pins whose read value is one, but they are configured 216 * as outputs with low signal. This is an impossible combination, 217 * so change their view to be inputs. 218 */ 219 stale = val & sc->output_mask & ~sc->output_state; 220 sc->output_mask &= ~stale; 221 sc->output_state |= stale; 222 223 if ((sc->output_mask & (1 << pin)) != 0) 224 *pflags = GPIO_PIN_OUTPUT; 225 else 226 *pflags = GPIO_PIN_INPUT; 227 sx_xunlock(&sc->lock); 228 229 return (0); 230 } 231 232 static int 233 pcf8574_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) 234 { 235 struct pcf8574_softc *sc; 236 int error; 237 uint8_t val; 238 bool update_needed; 239 240 sc = device_get_softc(dev); 241 242 if (pin >= NUM_PINS) 243 return (EINVAL); 244 if ((flags & ~PIN_CAPS) != 0) 245 return (EINVAL); 246 247 sx_xlock(&sc->lock); 248 if ((flags & GPIO_PIN_OUTPUT) != 0) { 249 sc->output_mask |= 1 << pin; 250 update_needed = false; 251 } else if ((flags & GPIO_PIN_INPUT) != 0) { 252 sc->output_mask &= ~(1 << pin); 253 sc->output_state |= 1 << pin; 254 update_needed = true; 255 } else { 256 KASSERT(false, ("both input and output modes requested")); 257 update_needed = false; 258 } 259 260 if (update_needed) { 261 val = sc->output_state | ~sc->output_mask; 262 error = pcf8574_write(sc, val); 263 if (error != 0) 264 dbg_dev_printf(dev, "failed to write to device: %d\n", 265 error); 266 } 267 sx_xunlock(&sc->lock); 268 269 return (0); 270 } 271 272 static int 273 pcf8574_pin_getname(device_t dev, uint32_t pin, char *name) 274 { 275 276 if (pin >= NUM_PINS) 277 return (EINVAL); 278 snprintf(name, GPIOMAXNAME, "P%d", pin); 279 return (0); 280 } 281 282 static int 283 pcf8574_pin_get(device_t dev, uint32_t pin, unsigned int *on) 284 { 285 struct pcf8574_softc *sc; 286 uint8_t val; 287 int error; 288 289 sc = device_get_softc(dev); 290 291 sx_xlock(&sc->lock); 292 if ((sc->output_mask & (1 << pin)) != 0) { 293 *on = (sc->output_state & (1 << pin)) != 0; 294 sx_xunlock(&sc->lock); 295 return (0); 296 } 297 298 error = pcf8574_read(sc, &val); 299 if (error != 0) { 300 dbg_dev_printf(dev, "failed to read from device: %d\n", error); 301 sx_xunlock(&sc->lock); 302 return (error); 303 } 304 sx_xunlock(&sc->lock); 305 306 *on = (val & (1 << pin)) != 0; 307 return (0); 308 } 309 310 static int 311 pcf8574_pin_set(device_t dev, uint32_t pin, unsigned int on) 312 { 313 struct pcf8574_softc *sc; 314 uint8_t val; 315 int error; 316 317 sc = device_get_softc(dev); 318 319 if (pin >= NUM_PINS) 320 return (EINVAL); 321 322 sx_xlock(&sc->lock); 323 324 if ((sc->output_mask & (1 << pin)) == 0) { 325 sx_xunlock(&sc->lock); 326 return (EINVAL); 327 } 328 329 /* 330 * Algorithm: 331 * - set all outputs to their recorded state; 332 * - set all inputs to the high state; 333 * - apply the requested change. 334 */ 335 val = sc->output_state | ~sc->output_mask; 336 val &= ~(1 << pin); 337 val |= (on != 0) << pin; 338 339 error = pcf8574_write(sc, val); 340 if (error != 0) { 341 dbg_dev_printf(dev, "failed to write to device: %d\n", error); 342 sx_xunlock(&sc->lock); 343 return (error); 344 } 345 346 /* 347 * NB: we can record anything as "output" state of input pins. 348 * By convention and for convenience it will be recorded as 1. 349 */ 350 sc->output_state = val; 351 sx_xunlock(&sc->lock); 352 return (0); 353 } 354 355 static int 356 pcf8574_pin_toggle(device_t dev, uint32_t pin) 357 { 358 struct pcf8574_softc *sc; 359 uint8_t val; 360 int error; 361 362 sc = device_get_softc(dev); 363 364 if (pin >= NUM_PINS) 365 return (EINVAL); 366 367 sx_xlock(&sc->lock); 368 369 if ((sc->output_mask & (1 << pin)) == 0) { 370 sx_xunlock(&sc->lock); 371 return (EINVAL); 372 } 373 374 val = sc->output_state | ~sc->output_mask; 375 val ^= 1 << pin; 376 377 error = pcf8574_write(sc, val); 378 if (error != 0) { 379 dbg_dev_printf(dev, "failed to write to device: %d\n", error); 380 sx_xunlock(&sc->lock); 381 return (error); 382 } 383 384 sc->output_state = val; 385 sx_xunlock(&sc->lock); 386 return (0); 387 } 388 389 static device_method_t pcf8574_methods[] = { 390 DEVMETHOD(device_probe, pcf8574_probe), 391 DEVMETHOD(device_attach, pcf8574_attach), 392 DEVMETHOD(device_detach, pcf8574_detach), 393 394 /* GPIO methods */ 395 DEVMETHOD(gpio_get_bus, pcf8574_get_bus), 396 DEVMETHOD(gpio_pin_max, pcf8574_pin_max), 397 DEVMETHOD(gpio_pin_getcaps, pcf8574_pin_getcaps), 398 DEVMETHOD(gpio_pin_getflags, pcf8574_pin_getflags), 399 DEVMETHOD(gpio_pin_setflags, pcf8574_pin_setflags), 400 DEVMETHOD(gpio_pin_getname, pcf8574_pin_getname), 401 DEVMETHOD(gpio_pin_get, pcf8574_pin_get), 402 DEVMETHOD(gpio_pin_set, pcf8574_pin_set), 403 DEVMETHOD(gpio_pin_toggle, pcf8574_pin_toggle), 404 405 DEVMETHOD_END 406 }; 407 408 static driver_t pcf8574_driver = { 409 "gpio", 410 pcf8574_methods, 411 sizeof(struct pcf8574_softc) 412 }; 413 414 DRIVER_MODULE(pcf8574, iicbus, pcf8574_driver, 0, 0); 415 MODULE_DEPEND(pcf8574, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 416 MODULE_DEPEND(pcf8574, gpiobus, 1, 1, 1); 417 MODULE_VERSION(pcf8574, 1); 418 #ifdef FDT 419 IICBUS_FDT_PNP_INFO(compat_data); 420 #endif 421