1 /* $NetBSD: pcf8574.c,v 1.4 2020/12/05 15:02:29 jdc Exp $ */ 2 3 /*- 4 * Copyright (c) 2020 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Julian Coleman. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * A driver for Philips Semiconductor (NXP) PCF8574/PCF857A GPIO's. 34 * Uses device properties to connect pins to the appropriate subsystem. 35 */ 36 37 #include <sys/cdefs.h> 38 __KERNEL_RCSID(0, "$NetBSD: pcf8574.c,v 1.4 2020/12/05 15:02:29 jdc Exp $"); 39 40 #include <sys/param.h> 41 #include <sys/systm.h> 42 #include <sys/device.h> 43 #include <sys/kernel.h> 44 45 #include <dev/sysmon/sysmonvar.h> 46 47 #include <dev/i2c/i2cvar.h> 48 #include <dev/led.h> 49 50 #ifdef PCF8574_DEBUG 51 #define DPRINTF printf 52 #else 53 #define DPRINTF if (0) printf 54 #endif 55 56 struct pcf8574_led { 57 void *cookie; 58 struct led_device *led; 59 uint8_t mask, v_on, v_off; 60 }; 61 62 #define PCF8574_NPINS 8 63 struct pcf8574_softc { 64 device_t sc_dev; 65 i2c_tag_t sc_tag; 66 i2c_addr_t sc_addr; 67 uint8_t sc_state; 68 uint8_t sc_mask; 69 70 int sc_nleds; 71 struct pcf8574_led sc_leds[PCF8574_NPINS]; 72 73 struct sysmon_envsys *sc_sme; 74 envsys_data_t sc_sensor[PCF8574_NPINS]; 75 int sc_pin_sensor[PCF8574_NPINS]; 76 int sc_pin_active[PCF8574_NPINS]; 77 78 #ifdef PCF8574_DEBUG 79 callout_t sc_timer; 80 #endif 81 }; 82 83 static int pcf8574_match(device_t, cfdata_t, void *); 84 static void pcf8574_attach(device_t, device_t, void *); 85 static int pcf8574_detach(device_t, int); 86 87 static int pcf8574_read(struct pcf8574_softc *sc, uint8_t *val); 88 static int pcf8574_write(struct pcf8574_softc *sc, uint8_t val); 89 static void pcf8574_attach_led( 90 struct pcf8574_softc *, char *, int, int, int); 91 void pcf8574_refresh(struct sysmon_envsys *, envsys_data_t *); 92 int pcf8574_get_led(void *); 93 void pcf8574_set_led(void *, int); 94 95 #ifdef PCF8574_DEBUG 96 static void pcf8574_timeout(void *); 97 #endif 98 99 CFATTACH_DECL_NEW(pcf8574io, sizeof(struct pcf8574_softc), 100 pcf8574_match, pcf8574_attach, pcf8574_detach, NULL); 101 102 static const struct device_compatible_entry compat_data[] = { 103 { "i2c-pcf8574", 0 }, 104 { NULL, 0 } 105 }; 106 107 static int 108 pcf8574_match(device_t parent, cfdata_t cf, void *aux) 109 { 110 struct i2c_attach_args *ia = aux; 111 struct pcf8574_softc sc; 112 int match_result; 113 114 if (!iic_use_direct_match(ia, cf, compat_data, &match_result)) 115 return 0; 116 117 /* Try a read so that we don't match on optional components */ 118 if (match_result) { 119 sc.sc_tag = ia->ia_tag; 120 sc.sc_addr = ia->ia_addr; 121 if (pcf8574_read(&sc, &sc.sc_state)) 122 return 0; 123 else 124 return match_result; 125 } 126 127 /* We don't support indirect matches */ 128 return 0; 129 } 130 131 static void 132 pcf8574_attach(device_t parent, device_t self, void *aux) 133 { 134 struct pcf8574_softc *sc = device_private(self); 135 struct i2c_attach_args *ia = aux; 136 prop_dictionary_t dict = device_properties(self); 137 prop_array_t pins; 138 prop_dictionary_t pin; 139 int i, num, def, envc = 0; 140 char name[32]; 141 const char *nptr = NULL, *spptr; 142 bool ok = TRUE, act, sysmon = FALSE; 143 144 sc->sc_tag = ia->ia_tag; 145 sc->sc_addr = ia->ia_addr; 146 sc->sc_dev = self; 147 148 /* 149 * The PCF8574 requires input pins to be written with the value 1, 150 * and then read. Assume that all pins are input initially. 151 * We'll use the mask when we write, because we have to write a 152 * value for every pin, and we don't want to change input pins. 153 */ 154 sc->sc_mask = 0xff; 155 156 pcf8574_read(sc, &sc->sc_state); 157 158 #ifdef PCF8574_DEBUG 159 aprint_normal(": GPIO: state = 0x%02x\n", sc->sc_state); 160 161 callout_init(&sc->sc_timer, CALLOUT_MPSAFE); 162 callout_reset(&sc->sc_timer, hz*30, pcf8574_timeout, sc); 163 #else 164 aprint_normal(": GPIO\n"); 165 #endif 166 167 pins = prop_dictionary_get(dict, "pins"); 168 if (pins == NULL) 169 return; 170 171 for (i = 0; i < prop_array_count(pins); i++) { 172 pin = prop_array_get(pins, i); 173 ok &= prop_dictionary_get_cstring_nocopy(pin, "name", &nptr); 174 ok &= prop_dictionary_get_uint32(pin, "pin", &num); 175 ok &= prop_dictionary_get_bool(pin, "active_high", &act); 176 /* optional default state */ 177 def = -1; 178 prop_dictionary_get_int32(pin, "default_state", &def); 179 if (!ok) 180 continue; 181 /* Extract pin type from the name */ 182 spptr = strstr(nptr, " "); 183 if (spptr == NULL) 184 continue; 185 spptr += 1; 186 strncpy(name, spptr, 31); 187 sc->sc_pin_active[i] = act; 188 if (!strncmp(nptr, "LED ", 4)) { 189 sc->sc_mask &= ~(1 << num); 190 pcf8574_attach_led(sc, name, num, act, def); 191 } 192 if (!strncmp(nptr, "INDICATOR ", 4)) { 193 if (!sysmon) { 194 sc->sc_sme = sysmon_envsys_create(); 195 sysmon = TRUE; 196 } 197 /* envsys sensor # to pin # mapping */ 198 sc->sc_pin_sensor[envc] = num; 199 sc->sc_sensor[i].state = ENVSYS_SINVALID; 200 sc->sc_sensor[i].units = ENVSYS_INDICATOR; 201 strlcpy(sc->sc_sensor[i].desc, name, 202 sizeof(sc->sc_sensor[i].desc)); 203 if (sysmon_envsys_sensor_attach(sc->sc_sme, 204 &sc->sc_sensor[i])) { 205 sysmon_envsys_destroy(sc->sc_sme); 206 sc->sc_sme = NULL; 207 aprint_error_dev(self, 208 "unable to attach pin %d at sysmon\n", i); 209 return; 210 } 211 DPRINTF("%s: added indicator: pin %d sensor %d (%s)\n", 212 device_xname(sc->sc_dev), num, envc, name); 213 envc++; 214 } 215 } 216 217 if (sysmon) { 218 sc->sc_sme->sme_name = device_xname(self); 219 sc->sc_sme->sme_cookie = sc; 220 sc->sc_sme->sme_refresh = pcf8574_refresh; 221 if (sysmon_envsys_register(sc->sc_sme)) { 222 aprint_error_dev(self, 223 "unable to register with sysmon\n"); 224 sysmon_envsys_destroy(sc->sc_sme); 225 sc->sc_sme = NULL; 226 return; 227 } 228 } 229 } 230 231 static int 232 pcf8574_detach(device_t self, int flags) 233 { 234 struct pcf8574_softc *sc = device_private(self); 235 int i; 236 237 if (sc->sc_sme != NULL) { 238 sysmon_envsys_unregister(sc->sc_sme); 239 sc->sc_sme = NULL; 240 } 241 242 for (i = 0; i < sc->sc_nleds; i++) 243 led_detach(sc->sc_leds[i].led); 244 245 #ifdef PCF8574_DEBUG 246 callout_halt(&sc->sc_timer, NULL); 247 callout_destroy(&sc->sc_timer); 248 #endif 249 return 0; 250 } 251 252 static int 253 pcf8574_read(struct pcf8574_softc *sc, uint8_t *val) 254 { 255 int err = 0; 256 257 if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) 258 return err; 259 err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, 260 sc->sc_addr, NULL, 0, val, 1, 0); 261 iic_release_bus(sc->sc_tag, 0); 262 return err; 263 } 264 265 static int 266 pcf8574_write(struct pcf8574_softc *sc, uint8_t val) 267 { 268 int err = 0; 269 270 if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) 271 return err; 272 err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 273 &val, 1, NULL, 0, 0); 274 iic_release_bus(sc->sc_tag, 0); 275 return err; 276 } 277 278 static void 279 pcf8574_attach_led(struct pcf8574_softc *sc, char *n, int pin, int act, int def) 280 { 281 struct pcf8574_led *l; 282 283 l = &sc->sc_leds[sc->sc_nleds]; 284 l->cookie = sc; 285 l->mask = 1 << pin; 286 l->v_on = act ? l->mask : 0; 287 l->v_off = act ? 0 : l->mask; 288 led_attach(n, l, pcf8574_get_led, pcf8574_set_led); 289 if (def != -1) 290 pcf8574_set_led(l, def); 291 DPRINTF("%s: attached LED: %02x %02x %02x def %d\n", 292 device_xname(sc->sc_dev), l->mask, l->v_on, l->v_off, def); 293 sc->sc_nleds++; 294 } 295 296 void 297 pcf8574_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 298 { 299 struct pcf8574_softc *sc = sme->sme_cookie; 300 int pin = sc->sc_pin_sensor[edata->sensor]; 301 int act = sc->sc_pin_active[pin]; 302 303 pcf8574_read(sc, &sc->sc_state); 304 if (act) 305 edata->value_cur = sc->sc_state & 1 << pin ? TRUE : FALSE; 306 else 307 edata->value_cur = sc->sc_state & 1 << pin ? FALSE : TRUE; 308 edata->state = ENVSYS_SVALID; 309 } 310 311 int 312 pcf8574_get_led(void *cookie) 313 { 314 struct pcf8574_led *l = cookie; 315 struct pcf8574_softc *sc = l->cookie; 316 317 return ((sc->sc_state & l->mask) == l->v_on); 318 } 319 320 void 321 pcf8574_set_led(void *cookie, int val) 322 { 323 struct pcf8574_led *l = cookie; 324 struct pcf8574_softc *sc = l->cookie; 325 uint32_t newstate; 326 327 newstate = sc->sc_state & ~l->mask; 328 newstate |= val ? l->v_on : l->v_off; 329 DPRINTF("%s: set LED: %02x -> %02x, %02x %02x %02x\n", 330 device_xname(sc->sc_dev), sc->sc_state, newstate, l->mask, l->v_on, l->v_off); 331 if (newstate != sc->sc_state) { 332 pcf8574_write(sc, newstate | sc->sc_mask); 333 pcf8574_read(sc, &sc->sc_state); 334 } 335 } 336 337 #ifdef PCF8574_DEBUG 338 static void 339 pcf8574_timeout(void *v) 340 { 341 struct pcf8574_softc *sc = v; 342 uint8_t val; 343 344 pcf8574_read(sc, &val); 345 if (val != sc->sc_state) 346 aprint_normal_dev(sc->sc_dev, 347 "status change: 0x%02x > 0x%02x\n", sc->sc_state, val); 348 sc->sc_state = val; 349 350 callout_reset(&sc->sc_timer, hz*60, pcf8574_timeout, sc); 351 } 352 #endif 353