1 /* $NetBSD: pcagpio.c,v 1.3 2020/02/02 06:43:14 macallan 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.3 2020/02/02 06:43:14 macallan Exp $"); 35 36 #include <sys/param.h> 37 #include <sys/systm.h> 38 #include <sys/device.h> 39 #include <sys/conf.h> 40 #include <sys/bus.h> 41 42 #include <dev/i2c/i2cvar.h> 43 #include <dev/led.h> 44 45 #ifdef PCAGPIO_DEBUG 46 #define DPRINTF printf 47 #else 48 #define DPRINTF if (0) printf 49 #endif 50 51 /* commands */ 52 #define PCAGPIO_INPUT 0x00 /* line status */ 53 #define PCAGPIO_OUTPUT 0x01 /* output status */ 54 #define PCAGPIO_REVERT 0x02 /* revert input if set */ 55 #define PCAGPIO_CONFIG 0x03 /* input if set, output if not */ 56 57 static int pcagpio_match(device_t, cfdata_t, void *); 58 static void pcagpio_attach(device_t, device_t, void *); 59 60 /* we can only pass one cookie to led_attach() but we need several values... */ 61 struct pcagpio_led { 62 void *cookie; 63 uint32_t mask, v_on, v_off; 64 }; 65 66 struct pcagpio_softc { 67 device_t sc_dev; 68 i2c_tag_t sc_i2c; 69 i2c_addr_t sc_addr; 70 71 int sc_is_16bit; 72 uint32_t sc_state; 73 struct pcagpio_led sc_leds[16]; 74 int sc_nleds; 75 }; 76 77 78 static void pcagpio_writereg(struct pcagpio_softc *, int, uint32_t); 79 static uint32_t pcagpio_readreg(struct pcagpio_softc *, int); 80 static void pcagpio_attach_led( 81 struct pcagpio_softc *, char *, int, int, int); 82 static int pcagpio_get(void *); 83 static void pcagpio_set(void *, int); 84 85 CFATTACH_DECL_NEW(pcagpio, sizeof(struct pcagpio_softc), 86 pcagpio_match, pcagpio_attach, NULL, NULL); 87 88 static const struct device_compatible_entry compat_data[] = { 89 { "i2c-pca9555", 1 }, 90 { "pca9555", 1 }, 91 { "i2c-pca9556", 0 }, 92 { "pca9556", 0 }, 93 { NULL, 0 } 94 }; 95 96 static int 97 pcagpio_match(device_t parent, cfdata_t match, void *aux) 98 { 99 struct i2c_attach_args *ia = aux; 100 int match_result; 101 102 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 103 return match_result; 104 105 return 0; 106 } 107 108 #ifdef PCAGPIO_DEBUG 109 static void 110 printdir(uint32_t val, uint32_t mask, char letter) 111 { 112 char flags[17], bits[17]; 113 uint32_t bit = 0x8000; 114 int i; 115 116 val &= mask; 117 for (i = 0; i < 16; i++) { 118 flags[i] = (mask & bit) ? letter : '-'; 119 bits[i] = (val & bit) ? 'X' : ' '; 120 bit = bit >> 1; 121 } 122 flags[16] = 0; 123 bits[16] = 0; 124 printf("dir: %s\n", flags); 125 printf("lvl: %s\n", bits); 126 } 127 #endif 128 129 static void 130 pcagpio_attach(device_t parent, device_t self, void *aux) 131 { 132 struct pcagpio_softc *sc = device_private(self); 133 struct i2c_attach_args *ia = aux; 134 const struct device_compatible_entry *dce; 135 prop_dictionary_t dict = device_properties(self); 136 prop_array_t pins; 137 prop_dictionary_t pin; 138 139 sc->sc_dev = self; 140 sc->sc_i2c = ia->ia_tag; 141 sc->sc_addr = ia->ia_addr; 142 sc->sc_nleds = 0; 143 144 aprint_naive("\n"); 145 sc->sc_is_16bit = 0; 146 if (iic_compatible_match(ia, compat_data, &dce)) 147 sc->sc_is_16bit = dce->data; 148 149 aprint_normal(": %s\n", sc->sc_is_16bit ? "PCA9555" : "PCA9556"); 150 151 sc->sc_state = pcagpio_readreg(sc, PCAGPIO_OUTPUT); 152 153 #ifdef PCAGPIO_DEBUG 154 uint32_t dir, in, out; 155 dir = pcagpio_readreg(sc, PCAGPIO_CONFIG); 156 in = pcagpio_readreg(sc, PCAGPIO_INPUT); 157 out = sc->sc_state; 158 159 out &= ~dir; 160 in &= dir; 161 162 printdir(in, dir, 'I'); 163 printdir(out, ~dir, 'O'); 164 #endif 165 166 pins = prop_dictionary_get(dict, "pins"); 167 if (pins != NULL) { 168 int i, num, def; 169 char name[32]; 170 const char *nptr; 171 bool ok = TRUE, act; 172 173 for (i = 0; i < prop_array_count(pins); i++) { 174 nptr = NULL; 175 pin = prop_array_get(pins, i); 176 ok &= prop_dictionary_get_cstring_nocopy(pin, "name", 177 &nptr); 178 strncpy(name, nptr, 31); 179 ok &= prop_dictionary_get_uint32(pin, "pin", &num); 180 ok &= prop_dictionary_get_bool( 181 pin, "active_high", &act); 182 /* optional default state */ 183 def = -1; 184 prop_dictionary_get_int32(pin, "default_state", &def); 185 if (ok) { 186 pcagpio_attach_led(sc, name, num, act, def); 187 } 188 } 189 } 190 } 191 192 static void 193 pcagpio_writereg(struct pcagpio_softc *sc, int reg, uint32_t val) 194 { 195 uint8_t cmd; 196 197 iic_acquire_bus(sc->sc_i2c, 0); 198 if (sc->sc_is_16bit) { 199 uint16_t creg; 200 cmd = reg << 1; 201 creg = htole16(val); 202 iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 203 sc->sc_addr, &cmd, 1, &creg, 2, 0); 204 } else { 205 uint8_t creg; 206 cmd = reg; 207 creg = (uint8_t)val; 208 iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 209 sc->sc_addr, &cmd, 1, &creg, 1, 0); 210 } 211 if (reg == PCAGPIO_OUTPUT) sc->sc_state = val; 212 iic_release_bus(sc->sc_i2c, 0); 213 } 214 215 static uint32_t pcagpio_readreg(struct pcagpio_softc *sc, int reg) 216 { 217 uint8_t cmd; 218 uint32_t ret; 219 220 iic_acquire_bus(sc->sc_i2c, 0); 221 if (sc->sc_is_16bit) { 222 uint16_t creg; 223 cmd = reg << 1; 224 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 225 sc->sc_addr, &cmd, 1, &creg, 2, 0); 226 ret = le16toh(creg); 227 } else { 228 uint8_t creg; 229 cmd = reg; 230 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 231 sc->sc_addr, &cmd, 1, &creg, 1, 0); 232 ret = creg; 233 } 234 iic_release_bus(sc->sc_i2c, 0); 235 return ret; 236 } 237 238 static void 239 pcagpio_attach_led(struct pcagpio_softc *sc, char *n, int pin, int act, int def) 240 { 241 struct pcagpio_led *l; 242 243 l = &sc->sc_leds[sc->sc_nleds]; 244 l->cookie = sc; 245 l->mask = 1 << pin; 246 l->v_on = act ? l->mask : 0; 247 l->v_off = act ? 0 : l->mask; 248 led_attach(n, l, pcagpio_get, pcagpio_set); 249 if (def != -1) pcagpio_set(l, def); 250 DPRINTF("%s: %04x %04x %04x def %d\n", 251 __func__, l->mask, l->v_on, l->v_off, def); 252 sc->sc_nleds++; 253 } 254 255 static int 256 pcagpio_get(void *cookie) 257 { 258 struct pcagpio_led *l = cookie; 259 struct pcagpio_softc *sc = l->cookie; 260 261 return ((sc->sc_state & l->mask) == l->v_on); 262 } 263 264 static void 265 pcagpio_set(void *cookie, int val) 266 { 267 struct pcagpio_led *l = cookie; 268 struct pcagpio_softc *sc = l->cookie; 269 uint32_t newstate; 270 271 newstate = sc->sc_state & ~l->mask; 272 newstate |= val ? l->v_on : l->v_off; 273 DPRINTF("%s: %04x -> %04x, %04x %04x %04x\n", __func__, 274 sc->sc_state, newstate, l->mask, l->v_on, l->v_off); 275 if (newstate != sc->sc_state) 276 pcagpio_writereg(sc, PCAGPIO_OUTPUT, newstate); 277 } 278