1/* $NetBSD: pcagpio.c,v 1.12 2022/02/12 03:24:35 riastradh Exp $ */ 2 3/*- 4 * Copyright (c) 2020 Michael Lorenz 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29/* 30 * a driver for Philips Semiconductor PCA9555 GPIO controllers 31 */ 32 33#include <sys/cdefs.h> 34__KERNEL_RCSID(0, "$NetBSD: pcagpio.c,v 1.12 2022/02/12 03:24:35 riastradh Exp $"); 35 36#include <sys/param.h> 37#include <sys/systm.h> 38#include <sys/device.h> 39#ifdef PCAGPIO_DEBUG 40#include <sys/kernel.h> 41#endif 42#include <sys/conf.h> 43#include <sys/bus.h> 44 45#include <dev/i2c/i2cvar.h> 46#include <dev/led.h> 47 48#ifdef PCAGPIO_DEBUG 49#define DPRINTF printf 50#else 51#define DPRINTF if (0) printf 52#endif 53 54/* commands */ 55#define PCAGPIO_INPUT 0x00 /* line status */ 56#define PCAGPIO_OUTPUT 0x01 /* output status */ 57#define PCAGPIO_REVERT 0x02 /* revert input if set */ 58#define PCAGPIO_CONFIG 0x03 /* input if set, output if not */ 59 60static int pcagpio_match(device_t, cfdata_t, void *); 61static void pcagpio_attach(device_t, device_t, void *); 62static int pcagpio_detach(device_t, int); 63#ifdef PCAGPIO_DEBUG 64static void pcagpio_timeout(void *); 65#endif 66 67/* we can only pass one cookie to led_attach() but we need several values... */ 68struct pcagpio_led { 69 void *cookie; 70 struct led_device *led; 71 uint32_t mask, v_on, v_off; 72}; 73 74struct pcagpio_softc { 75 device_t sc_dev; 76 i2c_tag_t sc_i2c; 77 i2c_addr_t sc_addr; 78 79 int sc_is_16bit; 80 uint32_t sc_state; 81 struct pcagpio_led sc_leds[16]; 82 int sc_nleds; 83 84#ifdef PCAGPIO_DEBUG 85 uint32_t sc_dir, sc_in; 86 callout_t sc_timer; 87#endif 88}; 89 90 91static void pcagpio_writereg(struct pcagpio_softc *, int, uint32_t); 92static uint32_t pcagpio_readreg(struct pcagpio_softc *, int); 93static void pcagpio_attach_led( 94 struct pcagpio_softc *, char *, int, int, int); 95static int pcagpio_get(void *); 96static void pcagpio_set(void *, int); 97 98CFATTACH_DECL_NEW(pcagpio, sizeof(struct pcagpio_softc), 99 pcagpio_match, pcagpio_attach, pcagpio_detach, NULL); 100 101static const struct device_compatible_entry compat_data[] = { 102 { .compat = "i2c-pca9555", .value = 1 }, 103 { .compat = "pca9555", .value = 1 }, 104 { .compat = "i2c-pca9556", .value = 0 }, 105 { .compat = "pca9556", .value = 0 }, 106 DEVICE_COMPAT_EOL 107}; 108 109static int 110pcagpio_match(device_t parent, cfdata_t match, void *aux) 111{ 112 struct i2c_attach_args *ia = aux; 113 int match_result; 114 115 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 116 return match_result; 117 118 return 0; 119} 120 121#ifdef PCAGPIO_DEBUG 122static void 123printdir(char* name, uint32_t val, uint32_t mask, char letter) 124{ 125 char flags[17], bits[17]; 126 uint32_t bit = 0x8000; 127 int i; 128 129 val &= mask; 130 for (i = 0; i < 16; i++) { 131 flags[i] = (mask & bit) ? letter : '-'; 132 bits[i] = (val & bit) ? 'X' : ' '; 133 bit = bit >> 1; 134 } 135 flags[16] = 0; 136 bits[16] = 0; 137 printf("%s: dir: %s\n", name, flags); 138 printf("%s: lvl: %s\n", name, bits); 139} 140#endif 141 142static void 143pcagpio_attach(device_t parent, device_t self, void *aux) 144{ 145 struct pcagpio_softc *sc = device_private(self); 146 struct i2c_attach_args *ia = aux; 147 const struct device_compatible_entry *dce; 148 prop_dictionary_t dict = device_properties(self); 149 prop_array_t pins; 150 prop_dictionary_t pin; 151 152 sc->sc_dev = self; 153 sc->sc_i2c = ia->ia_tag; 154 sc->sc_addr = ia->ia_addr; 155 sc->sc_nleds = 0; 156 157 aprint_naive("\n"); 158 sc->sc_is_16bit = 0; 159 if ((dce = iic_compatible_lookup(ia, compat_data)) != NULL) 160 sc->sc_is_16bit = dce->value; 161 162 aprint_normal(": %s\n", sc->sc_is_16bit ? "PCA9555" : "PCA9556"); 163 164 sc->sc_state = pcagpio_readreg(sc, PCAGPIO_OUTPUT); 165 166#ifdef PCAGPIO_DEBUG 167 uint32_t in, out; 168 sc->sc_dir = pcagpio_readreg(sc, PCAGPIO_CONFIG); 169 sc->sc_in = pcagpio_readreg(sc, PCAGPIO_INPUT); 170 in = sc-> sc_in; 171 out = sc->sc_state; 172 173 out &= ~sc->sc_dir; 174 in &= sc->sc_dir; 175 176 printdir(device_xname(sc->sc_dev), in, sc->sc_dir, 'I'); 177 printdir(device_xname(sc->sc_dev), out, ~sc->sc_dir, 'O'); 178 179 callout_init(&sc->sc_timer, CALLOUT_MPSAFE); 180 callout_reset(&sc->sc_timer, hz*20, pcagpio_timeout, sc); 181 182#endif 183 184 pins = prop_dictionary_get(dict, "pins"); 185 if (pins != NULL) { 186 int i, num, def; 187 char name[32]; 188 const char *spptr, *nptr; 189 bool ok = TRUE, act; 190 191 for (i = 0; i < prop_array_count(pins); i++) { 192 nptr = NULL; 193 pin = prop_array_get(pins, i); 194 ok &= prop_dictionary_get_string(pin, "name", 195 &nptr); 196 ok &= prop_dictionary_get_uint32(pin, "pin", &num); 197 ok &= prop_dictionary_get_bool( pin, "active_high", 198 &act); 199 /* optional default state */ 200 def = -1; 201 prop_dictionary_get_int32(pin, "default_state", &def); 202 if (!ok) 203 continue; 204 /* Extract pin type from the name */ 205 spptr = strstr(nptr, " "); 206 if (spptr == NULL) 207 continue; 208 spptr += 1; 209 strncpy(name, spptr, 31); 210 if (!strncmp(nptr, "LED ", 4)) 211 pcagpio_attach_led(sc, name, num, act, def); 212 } 213 } 214} 215 216static int 217pcagpio_detach(device_t self, int flags) 218{ 219 struct pcagpio_softc *sc = device_private(self); 220 int i; 221 222 for (i = 0; i < sc->sc_nleds; i++) 223 led_detach(sc->sc_leds[i].led); 224 225#ifdef PCAGPIO_DEBUG 226 callout_halt(&sc->sc_timer, NULL); 227 callout_destroy(&sc->sc_timer); 228#endif 229 230 return 0; 231} 232 233#ifdef PCAGPIO_DEBUG 234static void 235pcagpio_timeout(void *v) 236{ 237 struct pcagpio_softc *sc = v; 238 uint32_t out, dir, in, o_out, o_in; 239 240 out = pcagpio_readreg(sc, PCAGPIO_OUTPUT); 241 dir = pcagpio_readreg(sc, PCAGPIO_CONFIG); 242 in = pcagpio_readreg(sc, PCAGPIO_INPUT); 243 if (out != sc->sc_state || dir != sc->sc_dir || in != sc->sc_in) { 244 aprint_normal_dev(sc->sc_dev, "status change\n"); 245 o_out = sc->sc_state; 246 o_in = sc->sc_in; 247 o_out &= ~sc->sc_dir; 248 o_in &= sc->sc_dir; 249 printdir(device_xname(sc->sc_dev), o_in, sc->sc_dir, 'I'); 250 printdir(device_xname(sc->sc_dev), o_out, ~sc->sc_dir, 'O'); 251 sc->sc_state = out; 252 sc->sc_dir = dir; 253 sc->sc_in = in; 254 out &= ~sc->sc_dir; 255 in &= sc->sc_dir; 256 printdir(device_xname(sc->sc_dev), in, sc->sc_dir, 'I'); 257 printdir(device_xname(sc->sc_dev), out, ~sc->sc_dir, 'O'); 258 } 259 callout_reset(&sc->sc_timer, hz*60, pcagpio_timeout, sc); 260} 261#endif 262 263static void 264pcagpio_writereg(struct pcagpio_softc *sc, int reg, uint32_t val) 265{ 266 uint8_t cmd; 267 268 iic_acquire_bus(sc->sc_i2c, 0); 269 if (sc->sc_is_16bit) { 270 uint16_t creg; 271 cmd = reg << 1; 272 creg = htole16(val); 273 iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 274 sc->sc_addr, &cmd, 1, &creg, 2, 0); 275 } else { 276 uint8_t creg; 277 cmd = reg; 278 creg = (uint8_t)val; 279 iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 280 sc->sc_addr, &cmd, 1, &creg, 1, 0); 281 } 282 if (reg == PCAGPIO_OUTPUT) sc->sc_state = val; 283 iic_release_bus(sc->sc_i2c, 0); 284} 285 286static uint32_t pcagpio_readreg(struct pcagpio_softc *sc, int reg) 287{ 288 uint8_t cmd; 289 uint32_t ret; 290 291 iic_acquire_bus(sc->sc_i2c, 0); 292 if (sc->sc_is_16bit) { 293 uint16_t creg; 294 cmd = reg << 1; 295 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 296 sc->sc_addr, &cmd, 1, &creg, 2, 0); 297 ret = le16toh(creg); 298 } else { 299 uint8_t creg; 300 cmd = reg; 301 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 302 sc->sc_addr, &cmd, 1, &creg, 1, 0); 303 ret = creg; 304 } 305 iic_release_bus(sc->sc_i2c, 0); 306 return ret; 307} 308 309static void 310pcagpio_attach_led(struct pcagpio_softc *sc, char *n, int pin, int act, int def) 311{ 312 struct pcagpio_led *l; 313 314 l = &sc->sc_leds[sc->sc_nleds]; 315 l->cookie = sc; 316 l->mask = 1 << pin; 317 l->v_on = act ? l->mask : 0; 318 l->v_off = act ? 0 : l->mask; 319 l->led = led_attach(n, l, pcagpio_get, pcagpio_set); 320 if (def != -1) pcagpio_set(l, def); 321 DPRINTF("%s: %04x %04x %04x def %d\n", 322 __func__, l->mask, l->v_on, l->v_off, def); 323 sc->sc_nleds++; 324} 325 326static int 327pcagpio_get(void *cookie) 328{ 329 struct pcagpio_led *l = cookie; 330 struct pcagpio_softc *sc = l->cookie; 331 332 return ((sc->sc_state & l->mask) == l->v_on); 333} 334 335static void 336pcagpio_set(void *cookie, int val) 337{ 338 struct pcagpio_led *l = cookie; 339 struct pcagpio_softc *sc = l->cookie; 340 uint32_t newstate; 341 342 newstate = sc->sc_state & ~l->mask; 343 newstate |= val ? l->v_on : l->v_off; 344 DPRINTF("%s: %04x -> %04x, %04x %04x %04x\n", __func__, 345 sc->sc_state, newstate, l->mask, l->v_on, l->v_off); 346 if (newstate != sc->sc_state) 347 pcagpio_writereg(sc, PCAGPIO_OUTPUT, newstate); 348} 349