Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F145880594
D44332.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
D44332.id.diff
View Options
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 = ®
+
+ 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
Details
Attached
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)
Attached To
Mode
D44332: pca963x: add support for the PCA9633 4-bit Fm+ I2C-bus LED driver
Attached
Detach File
Event Timeline
Log In to Comment