summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authorthorpej <thorpej@NetBSD.org>2022-01-17 16:31:23 +0000
committerthorpej <thorpej@NetBSD.org>2022-01-17 16:31:23 +0000
commit4ee342adb1e668d202bdaabb31a9aad6d7a37ba1 (patch)
tree9d5be055c08b9195be1e4b8d34cce36882f6e042 /sys/dev
parentd7984d8dc37a31541f1d7268f514428d86b8e807 (diff)
Re-factor and overhaul the "mcp23s17gpio" driver as "mcpgpio", and
add support for 8-bit and I2C variants of the chip: - MCP23008 / MCP23S08: 8-bit (I2C / SPI) - MCP23017 / MCP23S17: 16-bit (I2C / SPI) - MCP23018 / MCP23S18: 16-bit (I2C / SPI), open-drain outputs The MCP23x17 and MCP23x18 are essentially identical, software-wise; we merely report different GPIO pin capabilities (no push-pull output for MCP23x18). Also, remove the tri-state capability that was previously advertised by the old version of this driver; these chips have no way to put the pin into a HI-Z mode. All 3 I2C versions are supported, but the SPI front-end still only supports the MCP23S17 for now (SPI autoconfiguration needs an overhaul). mcp23s17gpio(4) remains present as a link to the new mcpgpio(4) man page. XXX Still to-do: FDT integration, interrupt suppoort.
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/i2c/files.i2c6
-rw-r--r--sys/dev/i2c/mcp23xxxgpio_i2c.c176
-rw-r--r--sys/dev/ic/mcp23xxxgpio.c355
-rw-r--r--sys/dev/ic/mcp23xxxgpioreg.h118
-rw-r--r--sys/dev/ic/mcp23xxxgpiovar.h90
-rw-r--r--sys/dev/spi/files.spi9
-rw-r--r--sys/dev/spi/mcp23s17.c301
-rw-r--r--sys/dev/spi/mcp23s17.h96
-rw-r--r--sys/dev/spi/mcp23xxxgpio_spi.c298
9 files changed, 1046 insertions, 403 deletions
diff --git a/sys/dev/i2c/files.i2c b/sys/dev/i2c/files.i2c
index bca1c726ddd..a40ef7a53d2 100644
--- a/sys/dev/i2c/files.i2c
+++ b/sys/dev/i2c/files.i2c
@@ -1,4 +1,4 @@
-# $NetBSD: files.i2c,v 1.121 2021/12/27 23:04:20 andvar Exp $
+# $NetBSD: files.i2c,v 1.122 2022/01/17 16:31:23 thorpej Exp $
obsolete defflag opt_i2cbus.h I2C_SCAN
define i2cbus { }
@@ -204,6 +204,10 @@ attach tps65217pmic at iic
attach tps65217reg at tps65217pmic
file dev/i2c/tps65217pmic.c tps65217pmic needs-flag
+# Microchip MCP23008 / MCP23017 I/O Expander
+attach mcpgpio at iic with mcpgpio_i2c
+file dev/i2c/mcp23xxxgpio_i2c.c mcpgpio_i2c
+
# Microchip MCP980x
device mcp980x: sysmon_envsys
attach mcp980x at iic
diff --git a/sys/dev/i2c/mcp23xxxgpio_i2c.c b/sys/dev/i2c/mcp23xxxgpio_i2c.c
new file mode 100644
index 00000000000..3c479b98f8c
--- /dev/null
+++ b/sys/dev/i2c/mcp23xxxgpio_i2c.c
@@ -0,0 +1,176 @@
+/* $NetBSD: mcp23xxxgpio_i2c.c,v 1.1 2022/01/17 16:31:23 thorpej Exp $ */
+
+/*-
+ * Copyright (c) 2022 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jason R. Thorpe.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: mcp23xxxgpio_i2c.c,v 1.1 2022/01/17 16:31:23 thorpej Exp $");
+
+/*
+ * Driver for Microchip serial I/O expanders:
+ *
+ * MCP23008 8-bit, I2C interface
+ * MCP23017 16-bit, I2C interface
+ * MCP23018 16-bit (open-drain outputs), I2C interface
+ *
+ * Data sheet:
+ *
+ * https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf
+ */
+
+#include <sys/types.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+
+#include <dev/ic/mcp23xxxgpioreg.h>
+#include <dev/ic/mcp23xxxgpiovar.h>
+
+#include <dev/i2c/i2cvar.h>
+
+struct mcpgpio_i2c_softc {
+ struct mcpgpio_softc sc_mcpgpio;
+
+ i2c_tag_t sc_i2c;
+ i2c_addr_t sc_addr;
+};
+
+#define MCPGPIO_TO_I2C(sc) \
+ container_of((sc), struct mcpgpio_i2c_softc, sc_mcpgpio)
+
+static const struct mcpgpio_variant mcp23008 = {
+ .name = "MCP23008",
+ .type = MCPGPIO_TYPE_23x08,
+};
+
+static const struct mcpgpio_variant mcp23017 = {
+ .name = "MCP23017",
+ .type = MCPGPIO_TYPE_23x17,
+};
+
+static const struct mcpgpio_variant mcp23018 = {
+ .name = "MCP23018",
+ .type = MCPGPIO_TYPE_23x18,
+};
+
+static const struct device_compatible_entry compat_data[] = {
+ { .compat = "microchip,mcp23008", .data = &mcp23008 },
+ { .compat = "microchip,mcp23017", .data = &mcp23017 },
+ { .compat = "microchip,mcp23018", .data = &mcp23018 },
+ DEVICE_COMPAT_EOL
+};
+
+static int
+mcpgpio_i2c_lock(struct mcpgpio_softc *sc)
+{
+ struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc);
+
+ return iic_acquire_bus(isc->sc_i2c, 0);
+}
+
+static void
+mcpgpio_i2c_unlock(struct mcpgpio_softc *sc)
+{
+ struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc);
+
+ iic_release_bus(isc->sc_i2c, 0);
+}
+
+static int
+mcpgpio_i2c_read(struct mcpgpio_softc *sc, unsigned int bank,
+ uint8_t reg, uint8_t *valp)
+{
+ struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc);
+
+ /* Only one chip per hardware address in i2c. */
+ KASSERT((bank & ~1U) == 0);
+
+ return iic_exec(isc->sc_i2c, I2C_OP_READ_WITH_STOP, isc->sc_addr,
+ &reg, 1, valp, 1, 0);
+}
+
+static int
+mcpgpio_i2c_write(struct mcpgpio_softc *sc, unsigned int bank,
+ uint8_t reg, uint8_t val)
+{
+ struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc);
+
+ /* Only one chip per hardware address in i2c. */
+ KASSERT((bank & ~1U) == 0);
+
+ return iic_exec(isc->sc_i2c, I2C_OP_WRITE_WITH_STOP, isc->sc_addr,
+ &reg, 1, &val, 1, 0);
+}
+
+static const struct mcpgpio_accessops mcpgpio_i2c_accessops = {
+ .lock = mcpgpio_i2c_lock,
+ .unlock = mcpgpio_i2c_unlock,
+ .read = mcpgpio_i2c_read,
+ .write = mcpgpio_i2c_write,
+};
+
+static int
+mcpgpio_i2c_match(device_t parent, cfdata_t match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+ int match_result;
+
+ if (iic_use_direct_match(ia, match, compat_data, &match_result)) {
+ return match_result;
+ }
+ return 0;
+}
+
+static void
+mcpgpio_i2c_attach(device_t parent, device_t self, void *aux)
+{
+ struct mcpgpio_i2c_softc *isc = device_private(self);
+ struct mcpgpio_softc *sc = &isc->sc_mcpgpio;
+ struct i2c_attach_args *ia = aux;
+ const struct device_compatible_entry *dce;
+
+ isc->sc_i2c = ia->ia_tag;
+ isc->sc_addr = ia->ia_addr;
+
+ dce = iic_compatible_lookup(ia, compat_data);
+ KASSERT(dce != NULL);
+
+ sc->sc_dev = self;
+ sc->sc_variant = dce->data;
+ sc->sc_iocon = IOCON_SEQOP;
+ sc->sc_phandle = ia->ia_cookie;
+ sc->sc_accessops = &mcpgpio_i2c_accessops;
+
+ aprint_naive("\n");
+ aprint_normal(": %s I/O Expander\n", sc->sc_variant->name);
+
+ mcpgpio_attach(sc);
+}
+
+CFATTACH_DECL_NEW(mcpgpio_i2c, sizeof(struct mcpgpio_i2c_softc),
+ mcpgpio_i2c_match, mcpgpio_i2c_attach, NULL, NULL);
diff --git a/sys/dev/ic/mcp23xxxgpio.c b/sys/dev/ic/mcp23xxxgpio.c
new file mode 100644
index 00000000000..0803f8f2c0d
--- /dev/null
+++ b/sys/dev/ic/mcp23xxxgpio.c
@@ -0,0 +1,355 @@
+/* $NetBSD: mcp23xxxgpio.c,v 1.1 2022/01/17 16:31:23 thorpej Exp $ */
+
+/*-
+ * Copyright (c) 2014, 2022 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Frank Kardel, and by Jason R. Thorpe.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: mcp23xxxgpio.c,v 1.1 2022/01/17 16:31:23 thorpej Exp $");
+
+/*
+ * Driver for Microchip serial I/O expansers:
+ *
+ * MCP23008 8-bit, I2C interface
+ * MCP23S08 8-bit, SPI interface
+ * MCP23017 16-bit, I2C interface
+ * MCP23S17 16-bit, SPI interface
+ * MCP23018 16-bit (open-drain outputs), I2C interface
+ * MCP23S18 16-bit (open-drain outputs), SPI interface
+ *
+ * Data sheet:
+ *
+ * https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf
+ */
+
+#include "gpio.h"
+
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/kmem.h>
+
+#include <dev/ic/mcp23xxxgpioreg.h>
+#include <dev/ic/mcp23xxxgpiovar.h>
+
+#define PIN_BANK(p) ((p) / MCPGPIO_PINS_PER_BANK)
+#define PIN_EPIN(p) ((p) % MCPGPIO_PINS_PER_BANK)
+
+static uint8_t
+mcpgpio_regaddr(struct mcpgpio_softc *sc, uint8_t bank, uint8_t reg)
+{
+
+ if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) {
+ return reg;
+ }
+ if (sc->sc_iocon & IOCON_BANK) {
+ return REGADDR_BANK1(bank & 1, reg);
+ }
+ return REGADDR_BANK0(bank & 1, reg);
+}
+
+static const char *
+mcpgpio_regname(uint8_t reg)
+{
+ static const char * const regnames[] = {
+ [REG_IODIR] = "IODIR",
+ [REG_IPOL] = "IPOL",
+ [REG_GPINTEN] = "GPINTEN",
+ [REG_DEFVAL] = "DEFVAL",
+ [REG_INTCON] = "INTCON",
+ [REG_IOCON] = "IOCON",
+ [REG_GPPU] = "GPPU",
+ [REG_INTF] = "INTF",
+ [REG_INTCAP] = "INTCAP",
+ [REG_GPIO] = "GPIO",
+ [REG_OLAT] = "OLAT",
+ };
+ KASSERT(reg <= REG_OLAT);
+ return regnames[reg];
+}
+
+static const char *
+mcpgpio_bankname(struct mcpgpio_softc *sc, uint8_t bank)
+{
+ static const char * const banknames[] = { "A", "B" };
+
+ if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) {
+ return "";
+ }
+ return banknames[bank & 1];
+}
+
+static int
+mcpgpio__lock(struct mcpgpio_softc *sc, const char *fn)
+{
+ int error;
+
+ error = sc->sc_accessops->lock(sc);
+ if (__predict_false(error != 0)) {
+ aprint_error_dev(sc->sc_dev,
+ "%s: unable to lock device, error=%d\n", fn, error);
+ }
+ return error;
+}
+
+#define mcpgpio_lock(sc) \
+ mcpgpio__lock((sc), __func__)
+
+static void
+mcpgpio_unlock(struct mcpgpio_softc *sc)
+{
+ sc->sc_accessops->unlock(sc);
+}
+
+static int
+mcpgpio__read(struct mcpgpio_softc *sc, const char *fn,
+ uint8_t bank, uint8_t reg, uint8_t *valp)
+{
+ int error;
+ uint8_t regaddr = mcpgpio_regaddr(sc, bank, reg);
+
+ error = sc->sc_accessops->read(sc, bank, regaddr, valp);
+ if (__predict_false(error != 0)) {
+ aprint_error_dev(sc->sc_dev,
+ "%s: unable to read %s%s[0x%02x], error=%d\n", fn,
+ mcpgpio_regname(reg), mcpgpio_bankname(sc, bank),
+ regaddr, error);
+ }
+ return error;
+}
+
+#define mcpgpio_read(sc, b, r, v) \
+ mcpgpio__read((sc), __func__, (b), (r), (v))
+
+static int
+mcpgpio__write(struct mcpgpio_softc *sc, const char *fn,
+ uint8_t bank, uint8_t reg, uint8_t val)
+{
+ int error;
+ uint8_t regaddr = mcpgpio_regaddr(sc, bank, reg);
+
+ error = sc->sc_accessops->write(sc, bank, regaddr, val);
+ if (__predict_false(error != 0)) {
+ aprint_error_dev(sc->sc_dev,
+ "%s: unable to write %s%s[0x%02x], error=%d\n", fn,
+ mcpgpio_regname(reg), mcpgpio_bankname(sc, bank),
+ regaddr, error);
+ }
+ return error;
+}
+
+#define mcpgpio_write(sc, b, r, v) \
+ mcpgpio__write((sc), __func__, (b), (r), (v))
+
+#if NGPIO > 0
+/* GPIO support functions */
+static int
+mcpgpio_gpio_pin_read(void *arg, int pin)
+{
+ struct mcpgpio_softc *sc = arg;
+ uint8_t data;
+ int val;
+ int error;
+
+ KASSERT(pin >= 0 && pin < sc->sc_npins);
+
+ const uint8_t bank = PIN_BANK(pin);
+ const uint8_t epin = PIN_EPIN(pin);
+
+ error = mcpgpio_lock(sc);
+ if (__predict_false(error != 0)) {
+ return GPIO_PIN_LOW;
+ }
+ error = mcpgpio_read(sc, bank, REG_GPIO, &data);
+ if (error) {
+ data = 0;
+ }
+ mcpgpio_unlock(sc);
+
+ val = data & __BIT(epin) ? GPIO_PIN_HIGH : GPIO_PIN_LOW;
+
+ return val;
+}
+
+static void
+mcpgpio_gpio_pin_write(void *arg, int pin, int value)
+{
+ struct mcpgpio_softc *sc = arg;
+ uint8_t data;
+ int error;
+
+ KASSERT(pin >= 0 && pin < sc->sc_npins);
+
+ const uint8_t bank = PIN_BANK(pin);
+ const uint8_t epin = PIN_EPIN(pin);
+
+ error = mcpgpio_lock(sc);
+ if (__predict_false(error != 0)) {
+ return;
+ }
+
+ error = mcpgpio_read(sc, bank, REG_OLAT, &data);
+ if (__predict_true(error == 0)) {
+ if (value == GPIO_PIN_HIGH) {
+ data |= __BIT(epin);
+ } else {
+ data &= ~__BIT(epin);
+ }
+ (void) mcpgpio_write(sc, bank, REG_OLAT, data);
+ }
+
+ mcpgpio_unlock(sc);
+}
+
+static void
+mcpgpio_gpio_pin_ctl(void *arg, int pin, int flags)
+{
+ struct mcpgpio_softc *sc = arg;
+ uint8_t iodir, ipol, gppu;
+ int error;
+
+ KASSERT(pin >= 0 && pin < sc->sc_npins);
+
+ const uint8_t bank = PIN_BANK(pin);
+ const uint8_t epin = PIN_EPIN(pin);
+ const uint8_t bit = __BIT(epin);
+
+ error = mcpgpio_lock(sc);
+ if (__predict_false(error != 0)) {
+ return;
+ }
+
+ if ((error = mcpgpio_read(sc, bank, REG_IODIR, &iodir)) != 0 ||
+ (error = mcpgpio_read(sc, bank, REG_IPOL, &ipol)) != 0 ||
+ (error = mcpgpio_read(sc, bank, REG_GPPU, &gppu)) != 0) {
+ return;
+ }
+
+ if (flags & (GPIO_PIN_OUTPUT|GPIO_PIN_INPUT)) {
+ if ((flags & GPIO_PIN_INPUT) || !(flags & GPIO_PIN_OUTPUT)) {
+ /* for safety INPUT will override output */
+ iodir |= bit;
+ } else {
+ iodir &= ~bit;
+ }
+ }
+
+ if (flags & GPIO_PIN_INVIN) {
+ ipol |= bit;
+ } else {
+ ipol &= ~bit;
+ }
+
+ if (flags & GPIO_PIN_PULLUP) {
+ gppu |= bit;
+ } else {
+ gppu &= ~bit;
+ }
+
+ (void) mcpgpio_write(sc, bank, REG_IODIR, iodir);
+ (void) mcpgpio_write(sc, bank, REG_IPOL, ipol);
+ (void) mcpgpio_write(sc, bank, REG_GPPU, gppu);
+
+ mcpgpio_unlock(sc);
+}
+#endif /* NGPIO > 0 */
+
+void
+mcpgpio_attach(struct mcpgpio_softc *sc)
+{
+ int error;
+
+ KASSERT(sc->sc_variant != NULL);
+
+ /*
+ * The SPI front-end provides the logical pin count to
+ * deal with muliple chips on one chip select.
+ */
+ if (sc->sc_npins == 0) {
+ sc->sc_npins = sc->sc_variant->type == MCPGPIO_TYPE_23x08
+ ? MCP23x08_GPIO_NPINS : MCP23x17_GPIO_NPINS;
+ }
+ sc->sc_gpio_pins =
+ kmem_zalloc(sc->sc_npins * sizeof(*sc->sc_gpio_pins), KM_SLEEP);
+
+ /*
+ * Perform the basic setup of the device. We program the IOCON
+ * register once for each bank, even though the data sheet is
+ * not clear that this is strictly necessary.
+ */
+ if (mcpgpio_lock(sc) != 0) {
+ return;
+ }
+ error = mcpgpio_write(sc, 0, REG_IOCON, sc->sc_iocon);
+ if (error == 0 && sc->sc_variant->type != MCPGPIO_TYPE_23x08) {
+ error = mcpgpio_write(sc, 1, REG_IOCON, sc->sc_iocon);
+ }
+ mcpgpio_unlock(sc);
+ if (error) {
+ return;
+ }
+
+ /* XXX FDT glue. */
+
+#if NGPIO > 0
+ struct gpiobus_attach_args gba;
+ int pin_output_caps;
+ int i;
+
+ pin_output_caps = sc->sc_variant->type == MCPGPIO_TYPE_23x18
+ ? GPIO_PIN_OPENDRAIN : GPIO_PIN_PUSHPULL;
+
+ for (i = 0; i < sc->sc_npins; i++) {
+ sc->sc_gpio_pins[i].pin_num = i;
+ sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INPUT |
+ GPIO_PIN_OUTPUT |
+ pin_output_caps |
+ GPIO_PIN_PULLUP |
+ GPIO_PIN_INVIN;
+
+ /* read initial state */
+ sc->sc_gpio_pins[i].pin_state =
+ mcpgpio_gpio_pin_read(sc, i);
+ }
+
+ /* create controller tag */
+ sc->sc_gpio_gc.gp_cookie = sc;
+ sc->sc_gpio_gc.gp_pin_read = mcpgpio_gpio_pin_read;
+ sc->sc_gpio_gc.gp_pin_write = mcpgpio_gpio_pin_write;
+ sc->sc_gpio_gc.gp_pin_ctl = mcpgpio_gpio_pin_ctl;
+
+ gba.gba_gc = &sc->sc_gpio_gc;
+ gba.gba_pins = sc->sc_gpio_pins;
+ gba.gba_npins = sc->sc_npins;
+
+ config_found(sc->sc_dev, &gba, gpiobus_print, CFARGS_NONE);
+#else
+ aprint_normal_dev(sc->sc_dev, "no GPIO configured in kernel");
+#endif
+}
diff --git a/sys/dev/ic/mcp23xxxgpioreg.h b/sys/dev/ic/mcp23xxxgpioreg.h
new file mode 100644
index 00000000000..7de46a327e3
--- /dev/null
+++ b/sys/dev/ic/mcp23xxxgpioreg.h
@@ -0,0 +1,118 @@
+/* $NetBSD: mcp23xxxgpioreg.h,v 1.1 2022/01/17 16:31:23 thorpej Exp $ */
+
+/*-
+ * Copyright (c) 2014, 2022 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Frank Kardel, and by Jason R. Thorpe.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _DEV_IC_MCP23xxxGPIOREG_H_
+#define _DEV_IC_MCP23xxxGPIOREG_H_
+
+/*
+ * Microchip serial I/O expanders:
+ *
+ * MCP23008 8-bit, I2C interface
+ * MCP23S08 8-bit, SPI interface
+ * MCP23017 16-bit, I2C interface
+ * MCP23S17 16-bit, SPI interface
+ * MCP23018 16-bit (open-drain outputs), I2C interface
+ * MCP23S18 16-bit (open-drain outputs), SPI interface
+ *
+ * Data sheet:
+ *
+ * https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf
+ */
+
+/* resources */
+#define MCPGPIO_PINS_PER_BANK 8
+
+#define MCP23x08_GPIO_NBANKS 1
+#define MCP23x08_GPIO_NPINS (MCPGPIO_PINS_PER_BANK * MCP23x08_GPIO_NBANKS)
+
+#define MCP23x17_GPIO_NBANKS 2
+#define MCP23x17_GPIO_NPINS (MCPGPIO_PINS_PER_BANK * MCP23x17_GPIO_NBANKS)
+
+/*
+ * The MCP23x17 has two addressing schemes, depending on the setting
+ * of IOCON.BANK:
+ *
+ * IOCON.BANK=1 IOCON.BANK=0 Register
+ * -----------------------------------------------------
+ * 0x00 0x00 IODIRA
+ * 0x10 0x01 IODIRB
+ * 0x01 0x02 IPOLA
+ * 0x11 0x03 IPOLB
+ * 0x02 0x04 GPINTENA
+ * 0x12 0x05 GPINTENB
+ * 0x03 0x06 DEFVALA
+ * 0x13 0x07 DEFVALB
+ * 0x04 0x08 INTCONA
+ * 0x14 0x09 INTCONB
+ * 0x05 0x0a IOCON
+ * 0x15 0x0b IOCON (yes, it's an alias)
+ * 0x06 0x0c GPPUA
+ * 0x16 0x0d GPPUB
+ * 0x07 0x0e INTFA
+ * 0x17 0x0f INTFB
+ * 0x08 0x10 INTCAPA
+ * 0x18 0x11 INTCAPB
+ * 0x09 0x12 GPIOA
+ * 0x19 0x13 GPIOB
+ * 0x0a 0x14 OLATA
+ * 0x1a 0x15 OLATB
+ *
+ * The MCP23x08, of course, only has a single bank of 8 GPIOs, and it
+ * has an addressing schme that operates like IOCON.BANK=1
+ */
+#define REG_IODIR 0x00
+#define REG_IPOL 0x01
+#define REG_GPINTEN 0x02
+#define REG_DEFVAL 0x03
+#define REG_INTCON 0x04
+#define REG_IOCON 0x05
+#define REG_GPPU 0x06
+#define REG_INTF 0x07
+#define REG_INTCAP 0x08
+#define REG_GPIO 0x09
+#define REG_OLAT 0x0a
+
+/* IOCON.BANK=1 */
+#define REGADDR_BANK1(bank, reg) (((bank) << 4) | (reg))
+
+/* IOCON.BANK=0 */
+#define REGADDR_BANK0(bank, reg) (((reg) << 1) | (bank))
+
+/* bits */
+#define IOCON_BANK __BIT(7) /* select address layout (23x1x only) */
+#define IOCON_MIRROR __BIT(6) /* mirror INTA/INTB outputs (23x1x only) */
+#define IOCON_SEQOP __BIT(5) /* sequential address operation */
+#define IOCON_DISLW __BIT(4) /* slew rate SDA output */
+#define IOCON_HAEN __BIT(3) /* hardware address enable bit (SPI only) */
+#define IOCON_ODR __BIT(2) /* configure INT pin as open drain */
+#define IOCON_INTPOL __BIT(1) /* INT pin polarity (unless ODR is set) */
+
+#endif /* _DEV_IC_MCP23xxxGPIOREG_H_ */
diff --git a/sys/dev/ic/mcp23xxxgpiovar.h b/sys/dev/ic/mcp23xxxgpiovar.h
new file mode 100644
index 00000000000..78942a77785
--- /dev/null
+++ b/sys/dev/ic/mcp23xxxgpiovar.h
@@ -0,0 +1,90 @@
+/* $NetBSD: mcp23xxxgpiovar.h,v 1.1 2022/01/17 16:31:23 thorpej Exp $ */
+
+/*-
+ * Copyright (c) 2014, 2022 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Frank Kardel, and by Jason R. Thorpe.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _DEV_IC_MCP23xxxGPIOVAR_H_
+#define _DEV_IC_MCP23xxxGPIOVAR_H_
+
+/*
+ * Driver for Microchip serial I/O expansers:
+ *
+ * MCP23008 8-bit, I2C interface
+ * MCP23S08 8-bit, SPI interface
+ * MCP23017 16-bit, I2C interface
+ * MCP23S17 16-bit, SPI interface
+ * MCP23018 16-bit (open-drain outputs), I2C interface
+ * MCP23S18 16-bit (open-drain outputs), SPI interface
+ *
+ * Data sheet:
+ *
+ * https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf
+ */
+
+#include <sys/gpio.h>
+#include <dev/gpio/gpiovar.h>
+
+struct mcpgpio_softc;
+
+typedef enum {
+ MCPGPIO_TYPE_23x08 = 0,
+ MCPGPIO_TYPE_23x17 = 1,
+ MCPGPIO_TYPE_23x18 = 2,
+} mcpgpio_type;
+
+struct mcpgpio_variant {
+ const char *name;
+ mcpgpio_type type;
+};
+
+struct mcpgpio_accessops {
+ int (*lock)(struct mcpgpio_softc *);
+ void (*unlock)(struct mcpgpio_softc *);
+ int (*read)(struct mcpgpio_softc *,
+ unsigned int, uint8_t, uint8_t *);
+ int (*write)(struct mcpgpio_softc *,
+ unsigned int, uint8_t, uint8_t);
+};
+
+struct mcpgpio_softc {
+ device_t sc_dev;
+ const struct mcpgpio_variant *sc_variant;
+ struct gpio_chipset_tag sc_gpio_gc;
+ gpio_pin_t *sc_gpio_pins;
+ unsigned int sc_npins;
+ int sc_phandle;
+ uint8_t sc_iocon; /* I/O configuration */
+
+ /* Bus-specific access functions. */
+ const struct mcpgpio_accessops *sc_accessops;
+};
+
+void mcpgpio_attach(struct mcpgpio_softc *);
+
+#endif /* _DEV_IC_MCP23xxxGPIOVAR_H_ */
diff --git a/sys/dev/spi/files.spi b/sys/dev/spi/files.spi
index d8dcb13592d..e73dde62112 100644
--- a/sys/dev/spi/files.spi
+++ b/sys/dev/spi/files.spi
@@ -1,4 +1,4 @@
-# $NetBSD: files.spi,v 1.8 2021/12/07 17:39:54 brad Exp $
+# $NetBSD: files.spi,v 1.9 2022/01/17 16:31:23 thorpej Exp $
define spibus { }
@@ -32,9 +32,8 @@ attach mcp48x1dac at spi
file dev/spi/mcp48x1.c mcp48x1dac
# MCP23S17 16-bit GPIO
-device mcp23s17gpio: gpiobus
-attach mcp23s17gpio at spi
-file dev/spi/mcp23s17.c mcp23s17gpio
+attach mcpgpio at spi with mcpgpio_spi
+file dev/spi/mcp23xxxgpio_spi.c mcpgpio_spi
# Solomon Systech SSD13xx PLED/OLED display
attach ssdfb at spi with ssdfb_spi
@@ -47,4 +46,4 @@ file dev/spi/mcp3k.c mcp3kadc
# Sparkfun Serial motor controller
attach scmd at spi with scmdspi
-file dev/spi/scmdspi.c scmdspi
+file dev/spi/scmdspi.c scmdspi
diff --git a/sys/dev/spi/mcp23s17.c b/sys/dev/spi/mcp23s17.c
deleted file mode 100644
index 37ccf708e2f..00000000000
--- a/sys/dev/spi/mcp23s17.c
+++ /dev/null
@@ -1,301 +0,0 @@
-/* $NetBSD: mcp23s17.c,v 1.4 2021/08/07 16:19:16 thorpej Exp $ */
-
-/*-
- * Copyright (c) 2014 The NetBSD Foundation, Inc.
- * All rights reserved.
- *
- * This code is derived from software contributed to The NetBSD Foundation
- * by Frank Kardel.
- *
- * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
- */
-
-#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: mcp23s17.c,v 1.4 2021/08/07 16:19:16 thorpej Exp $");
-
-/*
- * Driver for Microchip MCP23S17 GPIO
- *
- * see: http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf
- */
-
-#include "gpio.h"
-
-#include <sys/param.h>
-#include <sys/systm.h>
-#include <sys/device.h>
-#include <sys/kernel.h>
-#include <sys/types.h>
-#include <sys/gpio.h>
-#include <sys/sysctl.h>
-
-#include <dev/gpio/gpiovar.h>
-
-#include <dev/spi/spivar.h>
-
-#include <dev/spi/mcp23s17.h>
-
-/* #define MCP23S17_DEBUG */
-#ifdef MCP23S17_DEBUG
-int mcp23S17debug = 3;
-#define DPRINTF(l, x) do { if (l <= mcp23S17debug) { printf x; } } while (0)
-#else
-#define DPRINTF(l, x)
-#endif
-
-struct mcp23s17gpio_softc {
- device_t sc_dev;
- struct spi_handle *sc_sh;
- uint8_t sc_ha; /* hardware address */
- uint8_t sc_bank; /* addressing scheme */
- struct gpio_chipset_tag sc_gpio_gc;
- gpio_pin_t sc_gpio_pins[MCP23x17_GPIO_NPINS];
-};
-
-static int mcp23s17gpio_match(device_t, cfdata_t, void *);
-static void mcp23s17gpio_attach(device_t, device_t, void *);
-
-static void mcp23s17gpio_write(struct mcp23s17gpio_softc *, uint8_t, uint8_t);
-
-#if NGPIO > 0
-static uint8_t mcp23s17gpio_read(struct mcp23s17gpio_softc *, uint8_t);
-
-static int mcp23s17gpio_gpio_pin_read(void *, int);
-static void mcp23s17gpio_gpio_pin_write(void *, int, int);
-static void mcp23s17gpio_gpio_pin_ctl(void *, int, int);
-#endif
-
-CFATTACH_DECL_NEW(mcp23s17gpio, sizeof(struct mcp23s17gpio_softc),
- mcp23s17gpio_match, mcp23s17gpio_attach, NULL, NULL);
-
-static int
-mcp23s17gpio_match(device_t parent, cfdata_t cf, void *aux)
-{
- struct spi_attach_args *sa = aux;
-
- /* MCP23S17 has no way to detect it! */
-
- /* run at 10MHz */
- if (spi_configure(sa->sa_handle, SPI_MODE_0, 10000000))
- return 0;
-
- return 1;
-}
-
-static void
-mcp23s17gpio_attach(device_t parent, device_t self, void *aux)
-{
- struct mcp23s17gpio_softc *sc;
- struct spi_attach_args *sa;
-#if NGPIO > 0
- int i;
- struct gpiobus_attach_args gba;
-#endif
-
- sa = aux;
- sc = device_private(self);
- sc->sc_dev = self;
- sc->sc_sh = sa->sa_handle;
- sc->sc_bank = 0;
- sc->sc_ha = device_cfdata(sc->sc_dev)->cf_flags & 0x7;
-
- aprint_naive(": GPIO\n");
- aprint_normal(": MCP23S17 GPIO (ha=%d)\n", sc->sc_ha);
-
- DPRINTF(1, ("%s: initialize (HAEN|SEQOP)\n", device_xname(sc->sc_dev)));
-
- /* basic setup */
- mcp23s17gpio_write(sc, MCP23x17_IOCONA(sc->sc_bank), MCP23x17_IOCON_HAEN|MCP23x17_IOCON_SEQOP);
-
-#if NGPIO > 0
- for (i = 0; i < MCP23x17_GPIO_NPINS; i++) {
- sc->sc_gpio_pins[i].pin_num = i;
- sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INPUT |
- GPIO_PIN_OUTPUT |
- GPIO_PIN_PUSHPULL | GPIO_PIN_TRISTATE |
- GPIO_PIN_PULLUP |
- GPIO_PIN_INVIN;
-
- /* read initial state */
- sc->sc_gpio_pins[i].pin_state =
- mcp23s17gpio_gpio_pin_read(sc, i);
- }
-
- /* create controller tag */
- sc->sc_gpio_gc.gp_cookie = sc;
- sc->sc_gpio_gc.gp_pin_read = mcp23s17gpio_gpio_pin_read;
- sc->sc_gpio_gc.gp_pin_write = mcp23s17gpio_gpio_pin_write;
- sc->sc_gpio_gc.gp_pin_ctl = mcp23s17gpio_gpio_pin_ctl;
-
- gba.gba_gc = &sc->sc_gpio_gc;
- gba.gba_pins = sc->sc_gpio_pins;
- gba.gba_npins = MCP23x17_GPIO_NPINS;
-
- config_found(self, &gba, gpiobus_print, CFARGS_NONE);
-#else
- aprint_normal_dev(sc->sc_dev, "no GPIO configured in kernel");
-#endif
-}
-
-#if NGPIO > 0
-static uint8_t
-mcp23s17gpio_read(struct mcp23s17gpio_softc *sc, uint8_t addr)
-{
- uint8_t buf[2];
- uint8_t val = 0;
- int rc;
-
- buf[0] = MCP23x17_OP_READ(sc->sc_ha);
- buf[1] = addr;
-
- rc = spi_send_recv(sc->sc_sh, 2, buf, 1, &val);
- if (rc != 0)
- {
- aprint_normal_dev(sc->sc_dev, "SPI send_recv failed rc=%d\n", rc);
- }
-
- DPRINTF(3, ("%s: read(0x%02x) @0x%02x->0x%02x\n", device_xname(sc->sc_dev), buf[0], addr, val));
- return val;
-}
-#endif
-
-static void
-mcp23s17gpio_write(struct mcp23s17gpio_softc *sc, uint8_t addr, uint8_t val)
-{
- uint8_t buf[3];
- int rc;
-
- buf[0] = MCP23x17_OP_WRITE(sc->sc_ha);
- buf[1] = addr;
- buf[2] = val;
-
- rc = spi_send(sc->sc_sh, 3, buf);
- if (rc != 0)
- {
- aprint_normal_dev(sc->sc_dev, "SPI send failed rc=%d\n", rc);
- }
- DPRINTF(3, ("%s: write(0x%02x) @0x%02x<-0x%02x\n", device_xname(sc->sc_dev), buf[0], addr, val));
-}
-
-#if NGPIO > 0
-/* GPIO support functions */
-static int
-mcp23s17gpio_gpio_pin_read(void *arg, int pin)
-{
- struct mcp23s17gpio_softc *sc = arg;
- int epin = pin & MCP23x17_GPIO_NPINS_MASK;
- uint8_t data;
- uint8_t addr;
- int val;
-
- if (epin < 8) {
- addr = MCP23x17_GPIOA(sc->sc_bank);
- } else {
- addr = MCP23x17_GPIOB(sc->sc_bank);
- epin = pin - 8;
- }
-
- data = mcp23s17gpio_read(sc, addr);
-
- val = data & (1 << epin) ? GPIO_PIN_HIGH : GPIO_PIN_LOW;
-
- DPRINTF(2, ("%s: gpio_read pin %d->%d\n", device_xname(sc->sc_dev), pin, (val == GPIO_PIN_HIGH)));
-
- return val;
-}
-
-static void
-mcp23s17gpio_gpio_pin_write(void *arg, int pin, int value)
-{
- struct mcp23s17gpio_softc *sc = arg;
- int epin = pin & MCP23x17_GPIO_NPINS_MASK;
- uint8_t data;
- uint8_t addr;
-
- if (epin < 8) {
- addr = MCP23x17_OLATA(sc->sc_bank);
- } else {
- addr = MCP23x17_OLATB(sc->sc_bank);
- epin = pin - 8;
- }
-
- data = mcp23s17gpio_read(sc, addr);
-
- if (value == GPIO_PIN_HIGH) {
- data |= 1 << epin;
- } else {
- data &= ~(1 << epin);
- }
-
- mcp23s17gpio_write(sc, addr, data);
-
- DPRINTF(2, ("%s: gpio_write pin %d<-%d\n", device_xname(sc->sc_dev), pin, (value == GPIO_PIN_HIGH)));
-}
-
-static void
-mcp23s17gpio_gpio_pin_ctl(void *arg, int pin, int flags)
-{
- struct mcp23s17gpio_softc *sc = arg;
- int epin = pin & MCP23x17_GPIO_NPINS_MASK;
- uint8_t bit;
- uint8_t port;
- uint8_t data;
-
- if (epin < 8) {
- port = 0;
- } else {
- port = 1;
- epin = pin - 8;
- }
-
- bit = 1 << epin;
-
- DPRINTF(2, ("%s: gpio_ctl pin %d flags 0x%x\n", device_xname(sc->sc_dev), pin, flags));
-
- if (flags & (GPIO_PIN_OUTPUT|GPIO_PIN_INPUT)) {
- data = mcp23s17gpio_read(sc, MCP23x17_IODIR(sc->sc_bank, port));
- if ((flags & GPIO_PIN_INPUT) || !(flags & GPIO_PIN_OUTPUT)) {
- /* for safety INPUT will override output */
- data |= bit;
- } else {
- data &= ~bit;
- }
- mcp23s17gpio_write(sc, MCP23x17_IODIR(sc->sc_bank, port), data);
- }
-
- data = mcp23s17gpio_read(sc, MCP23x17_IPOL(sc->sc_bank, port));
- if (flags & GPIO_PIN_INVIN) {
- data |= bit;
- } else {
- data &= ~bit;
- }
- mcp23s17gpio_write(sc, MCP23x17_IPOL(sc->sc_bank, port), data);
-
- data = mcp23s17gpio_read(sc, MCP23x17_GPPU(sc->sc_bank, port));
- if (flags & GPIO_PIN_PULLUP) {
- data |= bit;
- } else {
- data &= ~bit;
- }
- mcp23s17gpio_write(sc, MCP23x17_GPPU(sc->sc_bank, port), data);
-}
-#endif /* NGPIO > 0 */
diff --git a/sys/dev/spi/mcp23s17.h b/sys/dev/spi/mcp23s17.h
deleted file mode 100644
index 5cad997965e..00000000000
--- a/sys/dev/spi/mcp23s17.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/* $NetBSD: mcp23s17.h,v 1.1 2014/04/06 17:59:39 kardel Exp $ */
-
-/*-
- * Copyright (c) 2014 The NetBSD Foundation, Inc.
- * All rights reserved.
- *
- * This code is derived from software contributed to The NetBSD Foundation
- * by Frank Kardel.
- *
- * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
- */
-#ifndef MCP23S17_H
-#define MCP23S17_H
-
-/*
- * SPI connected 16-bit general purpose parallel I/O
- *
- * see http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf
- */
-
-/* resources */
-#define MCP23x17_GPIO_NPINS 16
-#define MCP23x17_GPIO_NPINS_MASK (MCP23x17_GPIO_NPINS-1)
-
-/* address layout */
-#define MCP23x17_BANKADDR(BANK, ADDR, PORT) (((BANK) == 1) ? ((PORT) << 4 | (ADDR)) : ((ADDR)<<1 | (PORT)))
-
-#define MCP23x17_IODIR(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x0, PORT)
-#define MCP23x17_IODIRA(BANK) MCP23x17_IODIR(BANK, 0)
-#define MCP23x17_IODIRB(BANK) MCP23x17_IODIR(BANK, 1)
-#define MCP23x17_IPOL(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x1, PORT)
-#define MCP23x17_IPOLA(BANK) MCP23x17_IPOL(BANK, 0)
-#define MCP23x17_IPOLB(BANK) MCP23x17_IPOL(BANK, 1)
-#define MCP23x17_GPINTEN(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x2, PORT)
-#define MCP23x17_GPINTENA(BANK) MCP23x17_GPINTEN(BANK, 0)
-#define MCP23x17_GPINTENB(BANK) MCP23x17_GPINTEN(BANK, 1)
-#define MCP23x17_DEFVAL(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x3, PORT)
-#define MCP23x17_DEFVALA(BANK) MCP23x17_DEFVAL(BANK, 0)
-#define MCP23x17_DEFVALB(BANK) MCP23x17_DEFVAL(BANK, 1)
-#define MCP23x17_INTCON(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x4, PORT)
-#define MCP23x17_INTCONA(BANK) MCP23x17_INTCON(BANK, 0)
-#define MCP23x17_INTCONB(BANK) MCP23x17_INTCON(BANK, 0)
-#define MCP23x17_IOCON(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x5, PORT)
-#define MCP23x17_IOCONA(BANK) MCP23x17_IOCON(BANK, 0)
-#define MCP23x17_IOCONB(BANK) MCP23x17_IOCON(BANK, 1)
-#define MCP23x17_GPPU(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x6, PORT)
-#define MCP23x17_GPPUA(BANK) MCP23x17_GPPU(BANK, 0)
-#define MCP23x17_GPPUB(BANK) MCP23x17_GPPU(BANK, 1)
-#define MCP23x17_INTF(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x7, PORT)
-#define MCP23x17_INTFA(BANK) MCP23x17_INTF(BANK, 0)
-#define MCP23x17_INTFB(BANK) MCP23x17_INTF(BANK, 1)
-#define MCP23x17_INTCAP(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x8, PORT)
-#define MCP23x17_INTCAPA(BANK) MCP23x17_INTCAP(BANK, 0)
-#define MCP23x17_INTCAPB(BANK) MCP23x17_INTCAP(BANK, 1)
-#define MCP23x17_GPIO(BANK, PORT) MCP23x17_BANKADDR(BANK, 0x9, PORT)
-#define MCP23x17_GPIOA(BANK) MCP23x17_GPIO(BANK, 0)
-#define MCP23x17_GPIOB(BANK) MCP23x17_GPIO(BANK, 1)
-#define MCP23x17_OLAT(BANK, PORT) MCP23x17_BANKADDR(BANK, 0xA, PORT)
-#define MCP23x17_OLATA(BANK) MCP23x17_OLAT(BANK, 0)
-#define MCP23x17_OLATB(BANK) MCP23x17_OLAT(BANK, 1)
-
-/* commands */
-#define MCP23x17_OP_BASE_WR 0x40 /* write register */
-#define MCP23x17_OP_BASE_RD 0x41 /* read register */
-#define MCP23x17_OP_READ(HA) (MCP23x17_OP_BASE_RD|(HA)<<1)
-#define MCP23x17_OP_WRITE(HA) (MCP23x17_OP_BASE_WR|(HA)<<1)
-
-/* bits */
-#define MCP23x17_IOCON_BANK __BIT(7) /* select address layout */
-#define MCP23x17_IOCON_MIRROR __BIT(6) /* mirror INTA/INTB interrupt outputs */
-#define MCP23x17_IOCON_SEQOP __BIT(5) /* sequential address operation */
-#define MCP23x17_IOCON_DISLW __BIT(4) /* slew rate SDA output */
-#define MCP23x17_IOCON_HAEN __BIT(3) /* hardware address enable bit (only MCP23S17) */
-#define MCP23x17_IOCON_ODR __BIT(2) /* configure INT pin as open drain */
-#define MCP23x17_IOCON_INTPOL __BIT(1) /* INT pin polarity (unless ODR is set) */
-
-#endif
diff --git a/sys/dev/spi/mcp23xxxgpio_spi.c b/sys/dev/spi/mcp23xxxgpio_spi.c
new file mode 100644
index 00000000000..33c312ca2a4
--- /dev/null
+++ b/sys/dev/spi/mcp23xxxgpio_spi.c
@@ -0,0 +1,298 @@
+/* $NetBSD: mcp23xxxgpio_spi.c,v 1.1 2022/01/17 16:31:23 thorpej Exp $ */
+
+/*-
+ * Copyright (c) 2014, 2022 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Frank Kardel, and by Jason R. Thorpe.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: mcp23xxxgpio_spi.c,v 1.1 2022/01/17 16:31:23 thorpej Exp $");
+
+/*
+ * Driver for Microchip serial I/O expanders:
+ *
+ * MCP23S08 8-bit, SPI interface
+ * MCP23S17 16-bit, SPI interface
+ * MCP23S18 16-bit (open-drain outputs), SPI interface
+ *
+ * Data sheet:
+ *
+ * https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf
+ */
+
+#include <sys/types.h>
+#include <sys/bitops.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/mutex.h>
+
+#include <dev/ic/mcp23xxxgpioreg.h>
+#include <dev/ic/mcp23xxxgpiovar.h>
+
+#include <dev/spi/spivar.h>
+
+/*
+ * Multi-chip-on-select configurations appear to the upper layers like
+ * additional GPIO banks; mixing different chip types on the same chip
+ * select is not allowed.
+ *
+ * Some chips have 2 banks per chip, and we have up to 8 chips per chip
+ * select, it's a total of 16 banks per chip select / driver instance.
+ */
+#define MCPGPIO_SPI_MAXBANKS 16
+
+struct mcpgpio_spi_softc {
+ struct mcpgpio_softc sc_mcpgpio;
+
+ kmutex_t sc_mutex;
+ struct spi_handle *sc_sh;
+ uint8_t sc_ha[MCPGPIO_SPI_MAXBANKS];
+};
+
+/*
+ * SPI-specific commands (the serial interface on the I2C flavor of
+ * the chip uses the I2C protocol to infer this information). Careful
+ * readers will note that this ends up being exactly the same bits
+ * on the serial interface that the I2C flavor of the chip uses.
+ *
+ * The SPI version can have up to 4 (or 8) chips per chip-select, demuxed
+ * using the hardware address (selected by tying the 2 or 3 HA pins high/low
+ * as desired).
+ */
+#define OP_READ(ha) (0x41 | ((ha) << 1))
+#define OP_WRITE(ha) (0x40 | ((ha) << 1))
+
+#define MCPGPIO_TO_SPI(sc) \
+ container_of((sc), struct mcpgpio_spi_softc, sc_mcpgpio)
+
+#if 0
+static const struct mcpgpio_variant mcp23s08 = {
+ .name = "MCP23S08",
+ .type = MCPGPIO_TYPE_23x08,
+};
+#endif
+
+static const struct mcpgpio_variant mcp23s17 = {
+ .name = "MCP23S17",
+ .type = MCPGPIO_TYPE_23x17,
+};
+
+#if 0
+static const struct mcpgpio_variant mcp23s18 = {
+ .name = "MCP23S18",
+ .type = MCPGPIO_TYPE_23x18,
+};
+#endif
+
+#if 0
+static const struct device_compatible_entry compat_data[] = {
+ { .compat = "microchip,mcp23s08", .data = &mcp23s08 },
+ { .compat = "microchip,mcp23s17", .data = &mcp23s17 },
+ { .compat = "microchip,mcp23s18", .data = &mcp23s18 },
+ DEVICE_COMPAT_EOL
+};
+#endif
+
+static int
+mcpgpio_spi_lock(struct mcpgpio_softc *sc)
+{
+ struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc);
+
+ mutex_enter(&ssc->sc_mutex);
+ return 0;
+}
+
+static void
+mcpgpio_spi_unlock(struct mcpgpio_softc *sc)
+{
+ struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc);
+
+ mutex_exit(&ssc->sc_mutex);
+}
+
+static int
+mcpgpio_spi_read(struct mcpgpio_softc *sc, unsigned int bank,
+ uint8_t reg, uint8_t *valp)
+{
+ struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc);
+ uint8_t buf[2];
+
+ KASSERT(bank < (sc->sc_npins >> 3));
+
+ buf[0] = OP_READ(ssc->sc_ha[bank]);
+ buf[1] = reg;
+
+ return spi_send_recv(ssc->sc_sh, 2, buf, 1, valp);
+}
+
+static int
+mcpgpio_spi_write(struct mcpgpio_softc *sc, unsigned int bank,
+ uint8_t reg, uint8_t val)
+{
+ struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc);
+ uint8_t buf[3];
+
+ KASSERT(bank < (sc->sc_npins >> 3));
+
+ buf[0] = OP_WRITE(ssc->sc_ha[bank]);
+ buf[1] = reg;
+ buf[2] = val;
+
+ return spi_send(ssc->sc_sh, 3, buf);
+}
+
+static const struct mcpgpio_accessops mcpgpio_spi_accessops = {
+ .lock = mcpgpio_spi_lock,
+ .unlock = mcpgpio_spi_unlock,
+ .read = mcpgpio_spi_read,
+ .write = mcpgpio_spi_write,
+};
+
+static int
+mcpgpio_spi_match(device_t parent, cfdata_t cf, void *aux)
+{
+ struct spi_attach_args *sa = aux;
+
+ /* MCP23S17 has no way to detect it! */
+
+ /* run at 10MHz */
+ if (spi_configure(sa->sa_handle, SPI_MODE_0, 10000000))
+ return 0;
+
+ return 1;
+}
+
+static void
+mcpgpio_spi_attach(device_t parent, device_t self, void *aux)
+{
+ struct mcpgpio_spi_softc *ssc = device_private(self);
+ struct mcpgpio_softc *sc = &ssc->sc_mcpgpio;
+ struct spi_attach_args *sa = aux;
+ uint32_t spi_present_mask;
+ int bank, nchips, error, ha;
+
+ mutex_init(&ssc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
+ ssc->sc_sh = sa->sa_handle;
+
+ sc->sc_dev = self;
+ sc->sc_variant = &mcp23s17; /* XXX */
+ sc->sc_iocon = IOCON_HAEN | IOCON_SEQOP;
+ sc->sc_npins = MCP23x17_GPIO_NPINS;
+ sc->sc_phandle = -1;
+ sc->sc_accessops = &mcpgpio_spi_accessops;
+
+ aprint_naive("\n");
+ aprint_normal(": %s I/O Expander\n", sc->sc_variant->name);
+
+ /*
+ * Before we decode the topology information, ensure each
+ * chip has IOCON.HAEN set so that it will actually decode
+ * the address bits.
+ *
+ * XXX Going on blind faith that IOCON.BANK is already 0.
+ */
+ if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) {
+ error = mcpgpio_spi_write(sc, 0, REG_IOCON, sc->sc_iocon);
+ } else {
+ error = mcpgpio_spi_write(sc, 0, REGADDR_BANK0(0, REG_IOCON),
+ sc->sc_iocon);
+ if (error == 0) {
+ error = mcpgpio_spi_write(sc, 1,
+ REGADDR_BANK0(1, REG_IOCON), sc->sc_iocon);
+ }
+ }
+ if (error) {
+ aprint_error_dev(self,
+ "unable to initialize IOCON, error=%d\n", error);
+ return;
+ }
+
+#if 0
+ /*
+ * The number of devices sharing this chip select, along
+ * with their assigned addresses, is encoded in the
+ * "microchip,spi-present-mask" property. Note that this
+ * device tree binding means that we will just have a
+ * single driver instance for however many chips are on
+ * this chip select. We treat them logically as banks.
+ */
+ if (of_getprop_uint32(phandle, "microchip,spi-present-mask",
+ &spi_present_mask) != 0 ||
+ of_getprop_uint32(phandle, "mcp,spi-present-mask",
+ &spi_present_mask) != 0) {
+ aprint_error_dev(self,
+ "missing \"microchip,spi-present-mask\" property\n");
+ return false;
+ }
+#else
+ /*
+ * XXX Until we support decoding the DT properties that
+ * XXX give us the topology information.
+ */
+ spi_present_mask = __BIT(device_cfdata(self)->cf_flags & 0x7);
+#endif
+
+ /*
+ * The 23S08 has 2 address pins (4 devices per chip select),
+ * and the others have 3 (8 devices per chip select).
+ */
+ if (spi_present_mask == 0 ||
+ (sc->sc_variant->type == MCPGPIO_TYPE_23x08 &&
+ spi_present_mask >= __BIT(4)) ||
+ (sc->sc_variant->type != MCPGPIO_TYPE_23x08 &&
+ spi_present_mask >= __BIT(8))) {
+ aprint_error_dev(self,
+ "invalid \"microchip,spi-present-mask\" value: 0x%08x\n",
+ spi_present_mask);
+ return;
+ }
+ nchips = popcount32(spi_present_mask);
+ sc->sc_npins = nchips *
+ (sc->sc_variant->type == MCPGPIO_TYPE_23x08 ? MCP23x08_GPIO_NPINS
+ : MCP23x17_GPIO_NPINS);
+
+ /* Record the hardware addresses for each logical bank of 8 pins. */
+ for (bank = 0; spi_present_mask != 0; spi_present_mask &= ~__BIT(ha)) {
+ int ha_first, ha_last;
+
+ ha = ffs32(spi_present_mask) - 1;
+ ha_first = bank * MCPGPIO_PINS_PER_BANK;
+ ssc->sc_ha[bank++] = ha;
+ if (sc->sc_variant->type != MCPGPIO_TYPE_23x08) {
+ ssc->sc_ha[bank++] = ha;
+ }
+ ha_last = (bank * MCPGPIO_PINS_PER_BANK) - 1;
+ aprint_verbose_dev(self, "pins %d..%d at HA %d\n",
+ ha_first, ha_last, ha);
+ }
+ KASSERT((bank * MCPGPIO_PINS_PER_BANK) == sc->sc_npins);
+
+ mcpgpio_attach(sc);
+}
+
+CFATTACH_DECL_NEW(mcpgpio_spi, sizeof(struct mcpgpio_spi_softc),
+ mcpgpio_spi_match, mcpgpio_spi_attach, NULL, NULL);