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