Page MenuHomeFreeBSD

D44332.id.diff
No OneTemporary

D44332.id.diff

diff --git a/sys/arm64/conf/std.nxp b/sys/arm64/conf/std.nxp
--- a/sys/arm64/conf/std.nxp
+++ b/sys/arm64/conf/std.nxp
@@ -7,6 +7,7 @@
# I2C
device pca954x # NPX I2C bus multiplexer / switches
+device pca963x # NXP Fm+ I2C-bus LED driver
device pcf8563 # NXP Real-time clock/calendar
device tca64xx # NXP I2C gpio expander
device pcf85063 # NXP Real-time clock
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1820,6 +1820,7 @@
dev/iicbus/sensor/htu21.c optional htu21
dev/iicbus/sensor/lm75.c optional lm75
dev/iicbus/sensor/max44009.c optional max44009
+dev/iicbus/gpio/pca963x.c optional pca963x
dev/iicbus/gpio/pcf8574.c optional pcf8574
dev/iicbus/gpio/tca64xx.c optional tca64xx fdt gpio
dev/iicbus/pmic/fan53555.c optional fan53555 fdt | tcs4525 fdt
diff --git a/sys/dev/iicbus/gpio/pca963x.c b/sys/dev/iicbus/gpio/pca963x.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/iicbus/gpio/pca963x.c
@@ -0,0 +1,546 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Bjoern A. Zeeb
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Highly simplified driver for PCA963x done on 9633.
+ * Ignoring most (PWM, GROUP, etc.) features.
+ * The only thing we do take care off is getting in/out of low-power mode
+ * (MODE1 SLEEP) if no LEDs are on or one is turned on.
+ * We export the LEDs as GPIO pins to enable/disable them at full brightness.
+ */
+
+#include <sys/param.h>
+#include <sys/bitset.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+#include <sys/sysctl.h>
+
+#include <machine/bus.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+
+#include <dev/gpio/gpiobusvar.h>
+
+#include "gpio_if.h"
+
+#define PCA963X_MODE1 0x00
+#define PCA963X_MODE1_SLEEP (1 << 4)
+#define PCA963X_MODE2 0x01
+#define PCA963X_MODE2_INVRT (1 << 4)
+
+/* LEDOUT register address is configured in pca963x_descr "led_out_reg" */
+#define PCA963X_LEDOUT_LDR00 0x00
+#define PCA963X_LEDOUT_LDR00_STATE 0 /* off, power up */
+#define PCA963X_LEDOUT_LDR01 0x01
+#define PCA963X_LEDOUT_LDR01_STATE 1 /* full on */
+#define PCA963X_LEDOUT_LDR10 0x02
+#define PCA963X_LEDOUT_LDR10_STATE 0 /* PWMx controlled (unsupported) */
+#define PCA963X_LEDOUT_LDR11 0x03
+#define PCA963X_LEDOUT_LDR11_STATE 0 /* PWMx/GRPPWM controlled (unsupported) */
+
+#define PCA963X_PINS_PER_REG 4
+
+struct pca963x_descr {
+ const char *partname;
+ const char *description;
+ uint8_t led_out_reg;
+ uint8_t num_pins;
+ uint32_t pin_caps;
+};
+
+static struct pca963x_descr pca9633 = {
+ .partname = "pca9633",
+ .description = "PCA9633 4-bit Fm+ I2C-bus LED driver",
+ .num_pins = 4,
+ .led_out_reg = 0x08,
+};
+
+
+static struct ofw_compat_data compat_data[] = {
+ { "nxp,pca9633", (uintptr_t)&pca9633 },
+ { 0, 0 }
+};
+
+struct pca963x_softc {
+ device_t dev;
+ device_t busdev;
+ struct mtx mtx;
+ uint32_t addr;
+ bool sleep;
+ BITSET_DEFINE_VAR() *ledset;
+
+ const struct pca963x_descr *descr;
+};
+
+static MALLOC_DEFINE(M_PCA963X, "pca963x", "pca963x I2C-bus LED driver");
+static int
+pca963x_read(device_t dev, uint8_t reg, uint8_t *data)
+{
+ struct pca963x_softc *sc;
+ struct iic_msg msgs[2];
+ int error;
+
+ if (data == NULL)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+
+ msgs[0].slave = sc->addr;
+ msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
+ msgs[0].len = 1;
+ msgs[0].buf = &reg;
+
+ msgs[1].slave = sc->addr;
+ msgs[1].flags = IIC_M_RD;
+ msgs[1].len = 1;
+ msgs[1].buf = data;
+
+ error = iicbus_transfer_excl(dev, msgs, 2, IIC_WAIT);
+ return (iic2errno(error));
+}
+
+static int
+pca963x_write(device_t dev, uint8_t reg, uint8_t val)
+{
+ struct pca963x_softc *sc;
+ struct iic_msg msg;
+ uint8_t buf[2];
+ int error;
+
+ sc = device_get_softc(dev);
+
+ buf[0] = reg;
+ buf[1] = val;
+
+ msg.slave = sc->addr;
+ msg.flags = IIC_M_WR;
+ msg.len = 2;
+ msg.buf = buf;
+
+ error = iicbus_transfer_excl(dev, &msg, 1, IIC_WAIT);
+ return (iic2errno(error));
+}
+
+static void
+pca963x_update_led_state_locked(device_t dev, uint32_t pin, uint8_t nval)
+{
+ struct pca963x_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (nval == 0)
+ BIT_CLR(sc->descr->num_pins, pin, sc->ledset);
+ else
+ BIT_SET(sc->descr->num_pins, pin, sc->ledset);
+}
+
+static void
+pca963x_update_mode1_sleep_locked(device_t dev)
+{
+ struct pca963x_softc *sc;
+ int error;
+ uint8_t val;
+ bool empty;
+
+ sc = device_get_softc(dev);
+ empty = BIT_EMPTY(sc->descr->num_pins, sc->ledset);
+
+ if (sc->sleep == empty)
+ return;
+
+ /* Update cached state and toggle oscillator state. */
+ sc->sleep = empty;
+
+ error = pca963x_read(dev, PCA963X_MODE1, &val);
+ if (error != 0) {
+ device_printf(dev, "%s: failed to read MODE1: %d\n",
+ __func__, error);
+ return;
+ }
+ if (sc->sleep)
+ val |= PCA963X_MODE1_SLEEP;
+ else
+ val &= ~PCA963X_MODE1_SLEEP;
+ error = pca963x_write(dev, PCA963X_MODE1, val);
+ if (error != 0)
+ device_printf(dev, "%s: failed to toggle MODE1 SLEEP: %d\n",
+ __func__, error);
+}
+
+
+static device_t
+pca963x_get_bus(device_t dev)
+{
+ struct pca963x_softc *sc;
+
+ sc = device_get_softc(dev);
+ return (sc->busdev);
+}
+
+static int
+pca963x_pin_max(device_t dev, int *maxpin)
+{
+ struct pca963x_softc *sc;
+
+ if (maxpin == NULL)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ *maxpin = sc->descr->num_pins - 1;
+ return (0);
+}
+
+static int
+pca963x_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
+{
+ struct pca963x_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (pin >= sc->descr->num_pins || caps == NULL)
+ return (EINVAL);
+
+ *caps = sc->descr->pin_caps;
+ return (0);
+}
+
+static int
+pca963x_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags)
+{
+ struct pca963x_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (pin >= sc->descr->num_pins || pflags == NULL)
+ return (EINVAL);
+
+ *pflags = GPIO_PIN_OUTPUT;
+
+ return (0);
+}
+
+static int
+pca963x_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
+{
+ struct pca963x_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (pin >= sc->descr->num_pins)
+ return (EINVAL);
+
+ return (ENODEV);
+}
+
+static int
+pca963x_pin_getname(device_t dev, uint32_t pin, char *name)
+{
+ struct pca963x_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (pin >= sc->descr->num_pins || name == NULL)
+ return (EINVAL);
+
+ snprintf(name, GPIOMAXNAME, "gpio_P%d%d", pin / PCA963X_PINS_PER_REG,
+ pin % PCA963X_PINS_PER_REG);
+
+ return (0);
+}
+
+static int
+pca963x_pin_get(device_t dev, uint32_t pin, unsigned int *pval)
+{
+ struct pca963x_softc *sc;
+ int error;
+ uint8_t addr, bitshift, val;
+
+ sc = device_get_softc(dev);
+ if (pin >= sc->descr->num_pins || pval == NULL)
+ return (EINVAL);
+
+ addr = sc->descr->led_out_reg + (pin / PCA963X_PINS_PER_REG);
+
+ mtx_lock(&sc->mtx);
+ error = pca963x_read(dev, addr, &val);
+ mtx_unlock(&sc->mtx);
+ if (error != 0)
+ return (error);
+
+ bitshift = 2 * (pin % PCA963X_PINS_PER_REG);
+ switch ((val >> bitshift) & 0x3) {
+ case PCA963X_LEDOUT_LDR00:
+ *pval = PCA963X_LEDOUT_LDR00_STATE;
+ break;
+ case PCA963X_LEDOUT_LDR01:
+ *pval = PCA963X_LEDOUT_LDR01_STATE;
+ break;
+ case PCA963X_LEDOUT_LDR10:
+ *pval = PCA963X_LEDOUT_LDR10_STATE; /* unsupported, hence defined to off */
+ break;
+ case PCA963X_LEDOUT_LDR11:
+ *pval = PCA963X_LEDOUT_LDR11_STATE; /* unsupported, hence defined to off */
+ break;
+ }
+
+ return (0);
+}
+
+static int
+pca963x_pin_set(device_t dev, uint32_t pin, unsigned int nval)
+{
+ struct pca963x_softc *sc;
+ int error;
+ uint8_t addr, bitshift, val;
+
+ sc = device_get_softc(dev);
+ if (pin >= sc->descr->num_pins || nval > PCA963X_LEDOUT_LDR11)
+ return (EINVAL);
+
+ addr = sc->descr->led_out_reg + (pin / PCA963X_PINS_PER_REG);
+
+ mtx_lock(&sc->mtx);
+ error = pca963x_read(dev, addr, &val);
+ if (error != 0)
+ goto fail;
+
+ bitshift = 2 * (pin % PCA963X_PINS_PER_REG);
+
+ switch ((val >> bitshift) & 0x3) {
+ case PCA963X_LEDOUT_LDR00:
+ nval = PCA963X_LEDOUT_LDR00_STATE;
+ break;
+ case PCA963X_LEDOUT_LDR01:
+ nval = PCA963X_LEDOUT_LDR01_STATE;
+ break;
+ case PCA963X_LEDOUT_LDR10:
+ nval = PCA963X_LEDOUT_LDR10_STATE; /* unsupported, hence always defined to off */
+ break;
+ case PCA963X_LEDOUT_LDR11:
+ nval = PCA963X_LEDOUT_LDR11_STATE; /* unsupported, hence always defined to off */
+ break;
+ default:
+ error = EINVAL;
+ goto fail;
+ /* NOTREACHED */
+ }
+ val &= ~(0x3 << bitshift);
+ val |= (nval & 0x3) << bitshift;
+ error = pca963x_write(dev, addr, val);
+
+ pca963x_update_led_state_locked(dev, pin, nval);
+ pca963x_update_mode1_sleep_locked(dev);
+fail:
+ mtx_unlock(&sc->mtx);
+ return (error);
+}
+
+static int
+pca963x_pin_toggle(device_t dev, uint32_t pin)
+{
+ struct pca963x_softc *sc;
+ int error;
+ uint8_t addr, bitshift, nval, val;
+
+ sc = device_get_softc(dev);
+ if (pin >= sc->descr->num_pins)
+ return (EINVAL);
+
+ addr = sc->descr->led_out_reg + (pin / PCA963X_PINS_PER_REG);
+
+ mtx_lock(&sc->mtx);
+ error = pca963x_read(dev, addr, &val);
+ if (error != 0)
+ goto fail;
+
+ bitshift = 2 * (pin % PCA963X_PINS_PER_REG);
+
+ switch ((val >> bitshift) & 0x3) {
+ case PCA963X_LEDOUT_LDR00: /* off -> on */
+ nval = PCA963X_LEDOUT_LDR01_STATE;
+ break;
+ case PCA963X_LEDOUT_LDR01: /* on -> off */
+ nval = PCA963X_LEDOUT_LDR00_STATE;
+ break;
+ case PCA963X_LEDOUT_LDR10:
+ nval = PCA963X_LEDOUT_LDR10_STATE; /* unsupported, hence always defined to off */
+ break;
+ case PCA963X_LEDOUT_LDR11:
+ nval = PCA963X_LEDOUT_LDR11_STATE; /* unsupported, hence always defined to off */
+ break;
+ default:
+ error = EINVAL;
+ goto fail;
+ /* NOTREACHED */
+ }
+ val &= ~(0x3 << bitshift);
+ val |= (nval & 0x3) << bitshift;
+ error = pca963x_write(dev, addr, val);
+
+ pca963x_update_led_state_locked(dev, pin, nval);
+ pca963x_update_mode1_sleep_locked(dev);
+fail:
+ mtx_unlock(&sc->mtx);
+ return (error);
+}
+
+static int
+pca963x_probe(device_t dev)
+{
+ const struct ofw_compat_data *compat_ptr;
+ const struct pca963x_descr *descr;
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ compat_ptr = ofw_bus_search_compatible(dev, compat_data);
+ if (compat_ptr->ocd_data == 0)
+ return (ENXIO);
+
+ descr = (const struct pca963x_descr *)compat_ptr->ocd_data;
+ device_set_desc(dev, descr->description);
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+pca963x_attach(device_t dev)
+{
+ struct pca963x_softc *sc;
+ const struct ofw_compat_data *compat_ptr;
+ const struct pca963x_descr *descr;
+ int i;
+
+ sc = device_get_softc(dev);
+ compat_ptr = ofw_bus_search_compatible(dev, compat_data);
+ descr = (const struct pca963x_descr *)compat_ptr->ocd_data;
+
+ mtx_init(&sc->mtx, "pca963x_gpio", "gpio", MTX_DEF);
+ sc->dev = dev;
+ sc->addr = iicbus_get_addr(dev);
+ sc->descr = descr;
+ sc->ledset = BITSET_ALLOC(sc->descr->num_pins, M_PCA963X,
+ M_WAITOK | M_ZERO);
+
+ /* Turn everything off for starters... */
+ mtx_lock(&sc->mtx);
+ /*
+ * Turn oscillator off (LPM, should be default), disable auto-incr.,
+ * turn off SUB[123] and ALLCALL i2c (sub-)addresses.
+ */
+ sc->sleep = true;
+ pca963x_write(dev, PCA963X_MODE1, PCA963X_MODE1_SLEEP);
+
+ /* Turn all LEDs off. */
+ for (i = 0; i < (sc->descr->num_pins / PCA963X_PINS_PER_REG); i++)
+ pca963x_write(dev, sc->descr->led_out_reg + i, 0x00);
+ mtx_unlock(&sc->mtx);
+
+ /*
+ * We should probably add a pin for each num_pins manually if
+ * configured from FDT so we can add names, etc. too and only
+ * attach the ones actually connected.
+ * Until we have a better LED infrastructure let's stay with
+ * one pin for one/off and all pins exposed.
+ */
+
+ sc->busdev = gpiobus_attach_bus(dev);
+ if (sc->busdev == NULL) {
+ device_printf(dev, "Could not create busdev child\n");
+ mtx_destroy(&sc->mtx);
+ return (ENXIO);
+ }
+
+ OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev);
+
+
+ return (0);
+}
+
+static int
+pca963x_detach(device_t dev)
+{
+ struct pca963x_softc *sc;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ /* Turn everything off again for desert... */
+ mtx_lock(&sc->mtx);
+ /*
+ * Turn oscillator off (LPM, should be default), disable auto-incr.,
+ * turn off SUB[123] and ALLCALL i2c (sub-)addresses.
+ */
+ sc->sleep = true;
+ pca963x_write(dev, PCA963X_MODE1, PCA963X_MODE1_SLEEP);
+
+ /* Turn all LEDs off. */
+ for (i = 0; i < (sc->descr->num_pins / PCA963X_PINS_PER_REG); i++)
+ pca963x_write(dev, sc->descr->led_out_reg + i, 0x00);
+ mtx_unlock(&sc->mtx);
+
+ if (sc->busdev != NULL)
+ gpiobus_detach_bus(sc->busdev);
+ if (sc->ledset)
+ BITSET_FREE(sc->ledset, M_PCA963X);
+
+ mtx_destroy(&sc->mtx);
+
+ return (0);
+}
+
+static device_method_t pca963x_methods[] = {
+ DEVMETHOD(device_probe, pca963x_probe),
+ DEVMETHOD(device_attach, pca963x_attach),
+ DEVMETHOD(device_detach, pca963x_detach),
+
+ /* GPIO methods */
+ DEVMETHOD(gpio_get_bus, pca963x_get_bus),
+ DEVMETHOD(gpio_pin_max, pca963x_pin_max),
+ DEVMETHOD(gpio_pin_getcaps, pca963x_pin_getcaps),
+ DEVMETHOD(gpio_pin_getflags, pca963x_pin_getflags),
+ DEVMETHOD(gpio_pin_setflags, pca963x_pin_setflags),
+ DEVMETHOD(gpio_pin_getname, pca963x_pin_getname),
+ DEVMETHOD(gpio_pin_get, pca963x_pin_get),
+ DEVMETHOD(gpio_pin_set, pca963x_pin_set),
+ DEVMETHOD(gpio_pin_toggle, pca963x_pin_toggle),
+
+ DEVMETHOD_END
+};
+
+static driver_t pca963x_driver = {
+ "gpio",
+ pca963x_methods,
+ sizeof(struct pca963x_softc)
+};
+
+DRIVER_MODULE(pca963x, iicbus, pca963x_driver, 0, 0);
+MODULE_VERSION(pca963x, 1);

File Metadata

Mime Type
text/plain
Expires
Thu, Feb 26, 3:08 PM (2 h, 32 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29005308
Default Alt Text
D44332.id.diff (14 KB)

Event Timeline