diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -321,6 +321,7 @@ mac_test.4 \ malo.4 \ max44009.4 \ + mcp23xxx.4 \ md.4 \ mdio.4 \ me.4 \ diff --git a/share/man/man4/mcp23xxx.4 b/share/man/man4/mcp23xxx.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/mcp23xxx.4 @@ -0,0 +1,89 @@ +.Dd August 25, 2025 +.Dt MCP23XXX 4 +.Sh NAME +.Nm mcp23xxx +.Nd driver for mcp23008/mcp23017/mcp23s08/mcp23s17 +IO expanders +.Sh SYNOPSIS +.Cd "device mcp23017" +.Cd "device mcp23s17" +.Cd "device gpio" +.Cd "device spibus" +.Cd "device iicbus" +.Pp +In +.Xr rc.conf 5 : +.Cd mcp23017_load="YES" +.Cd mcp23s17_load="YES" +.Sh DESCRIPTION +The +.Nm +driver provides +.Xr gpiobus 4 +control over the expander. +Two variants of the driver exist, mcp23s17 for SPI, and mcp23017 for I2C. +The driver supports the following GPIO flags: +.Pp +.Bl -tag -width "GPIO_PIN_PULLDOWN" -compact +.It Dv GPIO_PIN_OUTPUT +.It Dv GPIO_PIN_INPUT +.It Dv GPIO_PIN_INVIN +.It Dv GPIO_PIN_PULLUP +.El +.Pp +Interrupts are not supported. +.Sh HARDWARE +The following properties are required for nodes that describe +.Nm +devices: +.Bl -tag -width "compatible" -compact +.It Va compatible +Must be one of: +.Bl -tag -compact -width "microchip,mcp23008" +.It microchip,mcp23008 +.It microchip,mcp23017 +.It microchip,mcp23s08 +.It microchip,mcp23s17 +.El +.It Va reg +For I2C devices, this specifies the I2C address. +For SPI devices, this specifies the chip select number. +.El +.Pp +The following properties are optional: +.Bl -tag -width "compatible" -compact +.It Va spi-addr +Used to set the address bits according to +the state of the hardware address pins (A0–A2) (SPI variants only). +For example, if A0 is tied high, this value should include +0x1. +.El +.Sh EXAMPLES +The following example shows an I2C variant (MCP23017) +attached to an I2C bus: +.Bd -literal +mcp23017@20 { + compatible = "microchip,mcp23017"; + reg = <0x20>; +}; +.Ed +.Pp +The following example shows an SPI variant (MCP23S17) +attached to an SPI bus, with address pin A0 pulled high: +.Bd -literal +mcp23s17@0 { + compatible = "microchip,mcp23s17"; + reg = <0>; + spi-addr = <0x1>; +}; +.Ed +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 15.0 . +.Sh AUTHORS +The +.Nm +device driver and manpage was written by +.An Evgenii Ivanov Aq Mt devivanov@proton.me . diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -1733,6 +1733,9 @@ dev/gpio/gpiobus_if.m optional gpio dev/gpio/gpiopps.c optional gpiopps fdt dev/gpio/ofw_gpiobus.c optional fdt gpio +dev/gpio/mcp23xxx.c optional mcp23017 | mcp23s17 +dev/gpio/mcp23s17.c optional mcp23s17 fdt gpio spibus +dev/gpio/mcp23017.c optional mcp23017 fdt gpio iicbus dev/hid/bcm5974.c optional bcm5974 dev/hid/hconf.c optional hconf dev/hid/hcons.c optional hcons diff --git a/sys/dev/gpio/mcp23017.c b/sys/dev/gpio/mcp23017.c new file mode 100644 --- /dev/null +++ b/sys/dev/gpio/mcp23017.c @@ -0,0 +1,174 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Evgenii Ivanov + * + * 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 AUTHORS 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 AUTHORS 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 +#include +#include + +#include +#include +#include + +#include +#include + +#include "gpio_if.h" +#include "mcp23xxx.h" + +static struct ofw_compat_data compat_data[] = { + { "microchip,mcp23017", MCP23017 }, + { "microchip,mcp23008", MCP23008 }, + { NULL, 0 } +}; + +static int +mcp23017_read(struct mcp23xxx_softc *sc, uint8_t reg, uint16_t *val) +{ + int error; + uint8_t buf[2] = {0}; + + struct iic_msg msgs[2] = { + { 0, IIC_M_WR, 1, ® }, + { 0, IIC_M_RD, 1, buf }, + }; + + msgs[0].slave = sc->addr; + msgs[1].slave = sc->addr; + + if (sc->npin == 16) + msgs[1].len = 2; + + error = iicbus_transfer_excl(sc->dev, msgs, 2, IIC_WAIT); + *val = (uint16_t)buf[1] << 8 | buf[0]; + return (iic2errno(error)); +} + +static int +mcp23017_write(struct mcp23xxx_softc *sc, uint8_t reg, uint16_t val) +{ + int error; + struct iic_msg msg; + uint8_t buf[] = { reg, val, val >> 8 }; + + msg.slave = sc->addr; + msg.flags = IIC_M_WR; + msg.buf = buf; + msg.len = sc->npin == 16 ? 3 : 2; + + error = iicbus_transfer_excl(sc->dev, &msg, 1, IIC_WAIT); + return (iic2errno(error)); +} + +static int +mcp23017_probe(device_t dev) +{ + int chip; + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + chip = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + if (chip == 0) + return (ENXIO); + + if (chip == MCP23017) + device_set_desc(dev, "MCP23017 GPIO controller"); + else + device_set_desc(dev, "MCP23008 GPIO controller"); + + return (BUS_PROBE_DEFAULT); +} + +static int +mcp23017_attach(device_t dev) +{ + int chip; + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + sc->addr = iicbus_get_addr(dev); + + chip = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + + sc->dev = dev; + sc->write = mcp23017_write; + sc->read = mcp23017_read; + sc->npin = chip == MCP23017 ? 16 : 8; + + return (mcp23xxx_attach(sc)); +} + +static int +mcp23017_detach(device_t dev) +{ + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + + return (mcp23xxx_detach(sc)); +} + +static phandle_t +mcp23017_gpio_get_node(device_t bus, device_t dev) +{ + return (ofw_bus_get_node(bus)); +} + +static device_method_t mcp23017_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mcp23017_probe), + DEVMETHOD(device_attach, mcp23017_attach), + DEVMETHOD(device_detach, mcp23017_detach), + + /* GPIO protocol */ + DEVMETHOD(gpio_get_bus, mcp23xxx_get_bus), + DEVMETHOD(gpio_pin_max, mcp23xxx_pin_max), + DEVMETHOD(gpio_pin_getcaps, mcp23xxx_pin_getcaps), + DEVMETHOD(gpio_pin_getflags, mcp23xxx_pin_getflags), + DEVMETHOD(gpio_pin_setflags, mcp23xxx_pin_setflags), + DEVMETHOD(gpio_pin_getname, mcp23xxx_pin_getname), + DEVMETHOD(gpio_pin_get, mcp23xxx_pin_get), + DEVMETHOD(gpio_pin_set, mcp23xxx_pin_set), + DEVMETHOD(gpio_pin_toggle, mcp23xxx_pin_toggle), + + /* ofw_bus interface */ + DEVMETHOD(ofw_bus_get_node, mcp23017_gpio_get_node), + + DEVMETHOD_END +}; + +static driver_t mcp23017_driver = { + "gpio", + mcp23017_methods, + sizeof(struct mcp23xxx_softc) +}; + +DRIVER_MODULE(mcp23017, iicbus, mcp23017_driver, 0, 0); +MODULE_DEPEND(mcp23017, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); +MODULE_DEPEND(mcp23017, gpiobus, 1, 1, 1); +MODULE_VERSION(mcp23017, 1); +IICBUS_FDT_PNP_INFO(compat_data); diff --git a/sys/dev/gpio/mcp23s17.c b/sys/dev/gpio/mcp23s17.c new file mode 100644 --- /dev/null +++ b/sys/dev/gpio/mcp23s17.c @@ -0,0 +1,187 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Evgenii Ivanov + * + * 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 AUTHORS 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 AUTHORS 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 +#include +#include + +#include +#include +#include +#include +#include + +#include "gpio_if.h" +#include "mcp23xxx.h" +#include "spibus_if.h" + +#define MCP_WRITE 0 +#define MCP_READ 1 + +static struct ofw_compat_data compat_data[] = { + { "microchip,mcp23s17", MCP23S17 }, + { "microchip,mcp23s08", MCP23S08 }, + { NULL, 0 } +}; + +static int +mcp23s17_read(struct mcp23xxx_softc *sc, uint8_t reg, uint16_t *val) +{ + int error; + struct spi_command spi_command = SPI_COMMAND_INITIALIZER; + uint8_t buf[] = { + sc->addr | MCP_READ, /* address, r/w */ + reg + }; + + spi_command.tx_cmd = spi_command.rx_cmd = buf; + spi_command.tx_cmd_sz = spi_command.rx_cmd_sz = sizeof(buf); + spi_command.tx_data = spi_command.rx_data = buf; + if (sc->npin == 16) + spi_command.tx_data_sz = spi_command.rx_data_sz = 2; + else + spi_command.tx_data_sz = spi_command.rx_data_sz = 1; + + error = SPIBUS_TRANSFER(sc->parent, sc->dev, &spi_command); + *val = (uint16_t)buf[1] << 8 | buf[0]; + return (error); +} + +static int +mcp23s17_write(struct mcp23xxx_softc *sc, uint8_t reg, uint16_t val) +{ + struct spi_command spi_command = SPI_COMMAND_INITIALIZER; + uint8_t buf[] = { + sc->addr | MCP_WRITE, /* address, r/w */ + reg, + val, val >> 8, + }; + + spi_command.tx_cmd = spi_command.rx_cmd = buf; + if (sc->npin == 16) + spi_command.tx_cmd_sz = spi_command.rx_cmd_sz = 4; + else + spi_command.tx_cmd_sz = spi_command.rx_cmd_sz = 3; + + return (SPIBUS_TRANSFER(sc->parent, sc->dev, &spi_command)); +} + +static int +mcp23s17_probe(device_t dev) +{ + int chip; + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + chip = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + if (chip == 0) + return (ENXIO); + + if (chip == MCP23S17) + device_set_desc(dev, "MCP23S17 GPIO controller"); + else + device_set_desc(dev, "MCP23S08 GPIO controller"); + + return (BUS_PROBE_DEFAULT); +} + +static int +mcp23s17_attach(device_t dev) +{ + int chip; + pcell_t addr; + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + sc->parent = device_get_parent(dev); + + chip = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + + sc->addr = 0x20; + sc->dev = dev; + sc->write = mcp23s17_write; + sc->read = mcp23s17_read; + sc->npin = chip == MCP23S17 ? 16 : 8; + + if (OF_getencprop(ofw_bus_get_node(dev), "spi-addr", &addr, + sizeof(addr)) != 0) + sc->addr |= addr; + + sc->addr <<= 1; + + return (mcp23xxx_attach(sc)); +} + +static int +mcp23s17_detach(device_t dev) +{ + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + + return (mcp23xxx_detach(sc)); +} + +static phandle_t +mcp23s17_gpio_get_node(device_t bus, device_t dev) +{ + return (ofw_bus_get_node(bus)); +} + +static device_method_t mcp23s17_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mcp23s17_probe), + DEVMETHOD(device_attach, mcp23s17_attach), + DEVMETHOD(device_detach, mcp23s17_detach), + + /* GPIO protocol */ + DEVMETHOD(gpio_get_bus, mcp23xxx_get_bus), + DEVMETHOD(gpio_pin_max, mcp23xxx_pin_max), + DEVMETHOD(gpio_pin_getcaps, mcp23xxx_pin_getcaps), + DEVMETHOD(gpio_pin_getflags, mcp23xxx_pin_getflags), + DEVMETHOD(gpio_pin_setflags, mcp23xxx_pin_setflags), + DEVMETHOD(gpio_pin_getname, mcp23xxx_pin_getname), + DEVMETHOD(gpio_pin_get, mcp23xxx_pin_get), + DEVMETHOD(gpio_pin_set, mcp23xxx_pin_set), + DEVMETHOD(gpio_pin_toggle, mcp23xxx_pin_toggle), + + /* ofw_bus interface */ + DEVMETHOD(ofw_bus_get_node, mcp23s17_gpio_get_node), + + DEVMETHOD_END +}; + +static driver_t mcp23s17_driver = { + "gpio", + mcp23s17_methods, + sizeof(struct mcp23xxx_softc) +}; + +DRIVER_MODULE(mcp23s17, spibus, mcp23s17_driver, 0, 0); +MODULE_DEPEND(mcp23s17, spibus, 1, 1, 1); +MODULE_DEPEND(mcp23s17, gpiobus, 1, 1, 1); diff --git a/sys/dev/gpio/mcp23xxx.h b/sys/dev/gpio/mcp23xxx.h new file mode 100644 --- /dev/null +++ b/sys/dev/gpio/mcp23xxx.h @@ -0,0 +1,69 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Evgenii Ivanov + * + * 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 AUTHORS 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 AUTHORS 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 __MCP23XXX_H__ +#define __MCP23XXX_H__ + +enum { + MCP23017 = 1, + MCP23S17, + MCP23008, + MCP23S08, +}; + +struct mcp23xxx_softc { + device_t dev; + device_t busdev; + struct sx lock; + uint8_t npin; + + int (*write)(struct mcp23xxx_softc *, uint8_t, uint16_t); + int (*read)(struct mcp23xxx_softc *, uint8_t, uint16_t *); + + uint16_t gpio; + uint16_t dir; + uint16_t ipol; + uint16_t gppu; + + uint8_t addr; + device_t parent; /* only used in spi */ +}; + +int mcp23xxx_pin_get(device_t dev, uint32_t pin, unsigned int *on); +int mcp23xxx_pin_set(device_t dev, uint32_t pin, unsigned int on); +int mcp23xxx_pin_toggle(device_t dev, uint32_t pin); +int mcp23xxx_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags); +int mcp23xxx_pin_setflags(device_t dev, uint32_t pin, uint32_t flags); +int mcp23xxx_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps); +int mcp23xxx_pin_getname(device_t dev, uint32_t pin, char *name); +int mcp23xxx_pin_max(device_t dev, int *maxpin); +device_t mcp23xxx_get_bus(device_t dev); + +int mcp23xxx_attach(struct mcp23xxx_softc *sc); +int mcp23xxx_detach(struct mcp23xxx_softc *sc); + +#endif diff --git a/sys/dev/gpio/mcp23xxx.c b/sys/dev/gpio/mcp23xxx.c new file mode 100644 --- /dev/null +++ b/sys/dev/gpio/mcp23xxx.c @@ -0,0 +1,337 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Evgenii Ivanov + * + * 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 AUTHORS 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 AUTHORS 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 +#include +#include +#include +#include + +#include + +#include "mcp23xxx.h" + +#define PIN_CAPS \ + (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT | GPIO_PIN_INVIN | GPIO_PIN_PULLUP) + +#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 +#define MAXREG 0x0B + +#define MCP23XXX_IODIR_IN 0xFFFF + +struct regdefault { + uint32_t reg; + uint32_t defval; +}; + +static const struct regdefault mcp_regdefault[] = { + { REG_IODIR, 0xFFFF }, + { REG_IPOL, 0x0000 }, + { REG_GPINTEN, 0x0000 }, + { REG_DEFVAL, 0x0000 }, + { REG_INTCON, 0x0000 }, + { REG_IOCON, 0x0000 }, + { REG_GPPU, 0x0000 }, + { REG_INTF, 0x0000 }, + { REG_INTCAP, 0x0000 }, + { REG_GPIO, 0x0000 }, + { REG_OLAT, 0x0000 }, +}; + +static int +mcp_write(struct mcp23xxx_softc *sc, uint8_t reg, uint16_t pin) +{ + int error; + + if (sc->npin == 16) + reg = reg << 1; + + error = sc->write(sc, reg, pin); + if (error) { + device_printf(sc->dev, + "failed to write to register %02x. error: %d\n", + reg, error); + } + + return (error); +} + +static int +mcp_read(struct mcp23xxx_softc *sc, uint8_t reg, uint16_t *pin) +{ + int error; + + if (sc->npin == 16) + reg = reg << 1; + + error = sc->read(sc, reg, pin); + if (error) { + device_printf(sc->dev, + "failed to read from register %02x. error: %d\n", + reg, error); + } + + return (error); +} + +int +mcp23xxx_pin_get(device_t dev, uint32_t pin, unsigned int *on) +{ + int error; + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->npin) + return (EINVAL); + + error = 0; + + /* convert pin number to bitmask */ + pin = 1 << pin; + + sx_xlock(&sc->lock); + if ((sc->dir & pin) & MCP23XXX_IODIR_IN) + error = mcp_read(sc, REG_GPIO, &sc->gpio); + + *on = (sc->gpio & pin) ? GPIO_PIN_HIGH : GPIO_PIN_LOW; + sx_unlock(&sc->lock); + return (error); +} + +int +mcp23xxx_pin_set(device_t dev, uint32_t pin, unsigned int on) +{ + int error; + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->npin) + return (EINVAL); + + /* convert pin number to bitmask */ + pin = 1 << pin; + + sx_xlock(&sc->lock); + if ((sc->dir & pin) & MCP23XXX_IODIR_IN) { + sx_unlock(&sc->lock); + return (EINVAL); + } + + sc->gpio = (sc->gpio & ~((uint16_t)pin)) | (pin * on); + error = mcp_write(sc, REG_GPIO, sc->gpio); + sx_xunlock(&sc->lock); + return (error); +} + +int +mcp23xxx_pin_toggle(device_t dev, uint32_t pin) +{ + int error; + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->npin) + return (EINVAL); + + /* convert pin number to bitmask */ + pin = 1 << pin; + + sx_xlock(&sc->lock); + if ((sc->dir & pin) & MCP23XXX_IODIR_IN) { + sx_unlock(&sc->lock); + return (EINVAL); + } + + sc->gpio = sc->gpio ^ (pin); + error = mcp_write(sc, REG_GPIO, sc->gpio); + sx_unlock(&sc->lock); + return (error); +} + +int +mcp23xxx_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) +{ + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->npin) + return (EINVAL); + + /* convert pin number to bitmask */ + pin = 1 << pin; + + sx_xlock(&sc->lock); + *pflags = (sc->dir & pin) ? GPIO_PIN_INPUT : GPIO_PIN_OUTPUT; + *pflags |= (sc->ipol & pin) ? GPIO_PIN_INVIN : 0; + *pflags |= (sc->gppu & pin) ? GPIO_PIN_PULLUP : 0; + sx_xunlock(&sc->lock); + + return (0); +} + +int +mcp23xxx_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) +{ + int error; + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->npin) + return (EINVAL); + if ((flags & ~PIN_CAPS) != 0) + return (EINVAL); + + error = 0; + + /* convert pin number to bitmask */ + pin = 1 << pin; + + sx_xlock(&sc->lock); + if (flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) { + if (flags & GPIO_PIN_OUTPUT) + sc->dir &= ~(pin); + else + sc->dir |= pin; + + error = mcp_write(sc, REG_IODIR, sc->dir); + if (error) + goto end; + } + + if (flags & GPIO_PIN_INVIN) { + sc->gppu |= pin; + error = mcp_write(sc, REG_GPPU, sc->gppu); + } else if (sc->gppu & pin) { + sc->gppu &= ~(pin); + error = mcp_write(sc, REG_GPPU, sc->gppu); + } + + if (flags & GPIO_PIN_INVIN) { + sc->ipol |= pin; + error = mcp_write(sc, REG_IPOL, sc->ipol); + } else if (sc->ipol & pin) { + sc->ipol &= ~(pin); + error = mcp_write(sc, REG_IPOL, sc->ipol); + } + +end: + sx_unlock(&sc->lock); + return (error); +} + +int +mcp23xxx_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) +{ + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->npin) + return (EINVAL); + + *caps = PIN_CAPS; + return (0); +} + +int +mcp23xxx_pin_getname(device_t dev, uint32_t pin, char *name) +{ + int bank; + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + if (pin >= sc->npin) + return (EINVAL); + + bank = pin / 8; + pin -= bank * 8; + + snprintf(name, GPIOMAXNAME, "P%c%d", 'A' + bank, pin); + return (0); +} + +int +mcp23xxx_pin_max(device_t dev, int *maxpin) +{ + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + *maxpin = sc->npin - 1; + return (0); +} + +device_t +mcp23xxx_get_bus(device_t dev) +{ + struct mcp23xxx_softc *sc; + + sc = device_get_softc(dev); + return (sc->busdev); +} + +int +mcp23xxx_attach(struct mcp23xxx_softc *sc) +{ + int i, error; + + for (i = 0; i < MAXREG; i++) { + error = mcp_write(sc, mcp_regdefault[i].reg, + mcp_regdefault[i].defval); + if (error) + return (error); + } + + sc->busdev = gpiobus_add_bus(sc->dev); + if (sc->busdev == NULL) + return (ENXIO); + + sc->gpio = mcp_regdefault[REG_GPIO].defval; + sc->dir = mcp_regdefault[REG_IODIR].defval; + sc->ipol = mcp_regdefault[REG_IPOL].defval; + sc->gppu = mcp_regdefault[REG_GPPU].defval; + + sx_init(&sc->lock, "mcp23xxx lock"); + bus_attach_children(sc->dev); + return (0); +} + +int +mcp23xxx_detach(struct mcp23xxx_softc *sc) +{ + gpiobus_detach_bus(sc->dev); + sx_destroy(&sc->lock); + return (0); +} diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile --- a/sys/modules/i2c/Makefile +++ b/sys/modules/i2c/Makefile @@ -31,7 +31,8 @@ rv3032 \ rx8803 \ tca64xx \ - tmp461 + tmp461 \ + mcp23017 .endif .if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \ diff --git a/sys/modules/i2c/mcp23017/Makefile b/sys/modules/i2c/mcp23017/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/i2c/mcp23017/Makefile @@ -0,0 +1,8 @@ +.PATH: ${SRCTOP}/sys/dev/gpio/ + +KMOD= mcp23017 +SRCS= mcp23xxx.c mcp23017.c +SRCS+= device_if.h bus_if.h ofw_bus_if.h opt_platform.h +SRCS+= gpio_if.h + +.include diff --git a/sys/modules/spi/Makefile b/sys/modules/spi/Makefile --- a/sys/modules/spi/Makefile +++ b/sys/modules/spi/Makefile @@ -3,7 +3,11 @@ at45d \ ${_atopcase} \ mx25l \ - spibus \ + spibus + +.if !empty(OPT_FDT) +SUBDIR += mcp23s17 +.endif .if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64" _atopcase=atopcase diff --git a/sys/modules/spi/mcp23s17/Makefile b/sys/modules/spi/mcp23s17/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/spi/mcp23s17/Makefile @@ -0,0 +1,8 @@ +.PATH: ${SRCTOP}/sys/dev/gpio/ + +KMOD= mcp23s17 +SRCS= mcp23xxx.c mcp23s17.c +SRCS+= device_if.h bus_if.h ofw_bus_if.h opt_platform.h +SRCS+= gpio_if.h spibus_if.h + +.include