diff options
| author | thorpej <thorpej@NetBSD.org> | 2022-01-17 16:31:23 +0000 |
|---|---|---|
| committer | thorpej <thorpej@NetBSD.org> | 2022-01-17 16:31:23 +0000 |
| commit | 4ee342adb1e668d202bdaabb31a9aad6d7a37ba1 (patch) | |
| tree | 9d5be055c08b9195be1e4b8d34cce36882f6e042 /sys/dev | |
| parent | d7984d8dc37a31541f1d7268f514428d86b8e807 (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.i2c | 6 | ||||
| -rw-r--r-- | sys/dev/i2c/mcp23xxxgpio_i2c.c | 176 | ||||
| -rw-r--r-- | sys/dev/ic/mcp23xxxgpio.c | 355 | ||||
| -rw-r--r-- | sys/dev/ic/mcp23xxxgpioreg.h | 118 | ||||
| -rw-r--r-- | sys/dev/ic/mcp23xxxgpiovar.h | 90 | ||||
| -rw-r--r-- | sys/dev/spi/files.spi | 9 | ||||
| -rw-r--r-- | sys/dev/spi/mcp23s17.c | 301 | ||||
| -rw-r--r-- | sys/dev/spi/mcp23s17.h | 96 | ||||
| -rw-r--r-- | sys/dev/spi/mcp23xxxgpio_spi.c | 298 |
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, + ®, 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, + ®, 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); |
