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_attach_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 return (0); 152 } 153 154 static int 155 pcf8574_detach(device_t dev) 156 { 157 struct pcf8574_softc *sc; 158 159 sc = device_get_softc(dev); 160 161 if (sc->busdev != NULL) 162 gpiobus_detach_bus(sc->busdev); 163 164 sx_destroy(&sc->lock); 165 return (0); 166 } 167 168 static device_t 169 pcf8574_get_bus(device_t dev) 170 { 171 struct pcf8574_softc *sc; 172 173 sc = device_get_softc(dev); 174 return (sc->busdev); 175 } 176 177 static int 178 pcf8574_pin_max(device_t dev __unused, int *maxpin) 179 { 180 *maxpin = NUM_PINS - 1; 181 return (0); 182 } 183 184 static int 185 pcf8574_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) 186 { 187 188 if (pin >= NUM_PINS) 189 return (EINVAL); 190 *caps = PIN_CAPS; 191 return (0); 192 } 193 194 static int 195 pcf8574_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) 196 { 197 struct pcf8574_softc *sc; 198 uint8_t val, stale; 199 int error; 200 201 sc = device_get_softc(dev); 202 203 if (pin >= NUM_PINS) 204 return (EINVAL); 205 206 sx_xlock(&sc->lock); 207 error = pcf8574_read(sc, &val); 208 if (error != 0) { 209 dbg_dev_printf(dev, "failed to read from device: %d\n", 210 error); 211 sx_xunlock(&sc->lock); 212 return (error); 213 } 214 215 /* 216 * Check for pins whose read value is one, but they are configured 217 * as outputs with low signal. This is an impossible combination, 218 * so change their view to be inputs. 219 */ 220 stale = val & sc->output_mask & ~sc->output_state; 221 sc->output_mask &= ~stale; 222 sc->output_state |= stale; 223 224 if ((sc->output_mask & (1 << pin)) != 0) 225 *pflags = GPIO_PIN_OUTPUT; 226 else 227 *pflags = GPIO_PIN_INPUT; 228 sx_xunlock(&sc->lock); 229 230 return (0); 231 } 232 233 static int 234 pcf8574_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) 235 { 236 struct pcf8574_softc *sc; 237 int error; 238 uint8_t val; 239 bool update_needed; 240 241 sc = device_get_softc(dev); 242 243 if (pin >= NUM_PINS) 244 return (EINVAL); 245 if ((flags & ~PIN_CAPS) != 0) 246 return (EINVAL); 247 248 sx_xlock(&sc->lock); 249 if ((flags & GPIO_PIN_OUTPUT) != 0) { 250 sc->output_mask |= 1 << pin; 251 update_needed = false; 252 } else if ((flags & GPIO_PIN_INPUT) != 0) { 253 sc->output_mask &= ~(1 << pin); 254 sc->output_state |= 1 << pin; 255 update_needed = true; 256 } else { 257 KASSERT(false, ("both input and output modes requested")); 258 update_needed = false; 259 } 260 261 if (update_needed) { 262 val = sc->output_state | ~sc->output_mask; 263 error = pcf8574_write(sc, val); 264 if (error != 0) 265 dbg_dev_printf(dev, "failed to write to device: %d\n", 266 error); 267 } 268 sx_xunlock(&sc->lock); 269 270 return (0); 271 } 272 273 static int 274 pcf8574_pin_getname(device_t dev, uint32_t pin, char *name) 275 { 276 277 if (pin >= NUM_PINS) 278 return (EINVAL); 279 snprintf(name, GPIOMAXNAME, "P%d", pin); 280 return (0); 281 } 282 283 static int 284 pcf8574_pin_get(device_t dev, uint32_t pin, unsigned int *on) 285 { 286 struct pcf8574_softc *sc; 287 uint8_t val; 288 int error; 289 290 sc = device_get_softc(dev); 291 292 sx_xlock(&sc->lock); 293 if ((sc->output_mask & (1 << pin)) != 0) { 294 *on = (sc->output_state & (1 << pin)) != 0; 295 sx_xunlock(&sc->lock); 296 return (0); 297 } 298 299 error = pcf8574_read(sc, &val); 300 if (error != 0) { 301 dbg_dev_printf(dev, "failed to read from device: %d\n", error); 302 sx_xunlock(&sc->lock); 303 return (error); 304 } 305 sx_xunlock(&sc->lock); 306 307 *on = (val & (1 << pin)) != 0; 308 return (0); 309 } 310 311 static int 312 pcf8574_pin_set(device_t dev, uint32_t pin, unsigned int on) 313 { 314 struct pcf8574_softc *sc; 315 uint8_t val; 316 int error; 317 318 sc = device_get_softc(dev); 319 320 if (pin >= NUM_PINS) 321 return (EINVAL); 322 323 sx_xlock(&sc->lock); 324 325 if ((sc->output_mask & (1 << pin)) == 0) { 326 sx_xunlock(&sc->lock); 327 return (EINVAL); 328 } 329 330 /* 331 * Algorithm: 332 * - set all outputs to their recorded state; 333 * - set all inputs to the high state; 334 * - apply the requested change. 335 */ 336 val = sc->output_state | ~sc->output_mask; 337 val &= ~(1 << pin); 338 val |= (on != 0) << pin; 339 340 error = pcf8574_write(sc, val); 341 if (error != 0) { 342 dbg_dev_printf(dev, "failed to write to device: %d\n", error); 343 sx_xunlock(&sc->lock); 344 return (error); 345 } 346 347 /* 348 * NB: we can record anything as "output" state of input pins. 349 * By convention and for convenience it will be recorded as 1. 350 */ 351 sc->output_state = val; 352 sx_xunlock(&sc->lock); 353 return (0); 354 } 355 356 static int 357 pcf8574_pin_toggle(device_t dev, uint32_t pin) 358 { 359 struct pcf8574_softc *sc; 360 uint8_t val; 361 int error; 362 363 sc = device_get_softc(dev); 364 365 if (pin >= NUM_PINS) 366 return (EINVAL); 367 368 sx_xlock(&sc->lock); 369 370 if ((sc->output_mask & (1 << pin)) == 0) { 371 sx_xunlock(&sc->lock); 372 return (EINVAL); 373 } 374 375 val = sc->output_state | ~sc->output_mask; 376 val ^= 1 << pin; 377 378 error = pcf8574_write(sc, val); 379 if (error != 0) { 380 dbg_dev_printf(dev, "failed to write to device: %d\n", error); 381 sx_xunlock(&sc->lock); 382 return (error); 383 } 384 385 sc->output_state = val; 386 sx_xunlock(&sc->lock); 387 return (0); 388 } 389 390 static device_method_t pcf8574_methods[] = { 391 DEVMETHOD(device_probe, pcf8574_probe), 392 DEVMETHOD(device_attach, pcf8574_attach), 393 DEVMETHOD(device_detach, pcf8574_detach), 394 395 /* GPIO methods */ 396 DEVMETHOD(gpio_get_bus, pcf8574_get_bus), 397 DEVMETHOD(gpio_pin_max, pcf8574_pin_max), 398 DEVMETHOD(gpio_pin_getcaps, pcf8574_pin_getcaps), 399 DEVMETHOD(gpio_pin_getflags, pcf8574_pin_getflags), 400 DEVMETHOD(gpio_pin_setflags, pcf8574_pin_setflags), 401 DEVMETHOD(gpio_pin_getname, pcf8574_pin_getname), 402 DEVMETHOD(gpio_pin_get, pcf8574_pin_get), 403 DEVMETHOD(gpio_pin_set, pcf8574_pin_set), 404 DEVMETHOD(gpio_pin_toggle, pcf8574_pin_toggle), 405 406 DEVMETHOD_END 407 }; 408 409 static driver_t pcf8574_driver = { 410 "gpio", 411 pcf8574_methods, 412 sizeof(struct pcf8574_softc) 413 }; 414 415 DRIVER_MODULE(pcf8574, iicbus, pcf8574_driver, 0, 0); 416 MODULE_DEPEND(pcf8574, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 417 MODULE_DEPEND(pcf8574, gpiobus, 1, 1, 1); 418 MODULE_VERSION(pcf8574, 1); 419 #ifdef FDT 420 IICBUS_FDT_PNP_INFO(compat_data); 421 #endif 422