From 61ac77006c743d5174fe0e943ccf206dcefd462e Mon Sep 17 00:00:00 2001 From: Alan Carvalho de Assis Date: Sat, 2 Sep 2023 09:59:20 -0300 Subject: [PATCH] mcp23008 Initial support --- drivers/ioexpander/Kconfig | 44 ++ drivers/ioexpander/Make.defs | 4 + drivers/ioexpander/mcp23x08.c | 1007 +++++++++++++++++++++++++++++++++ drivers/ioexpander/mcp23x08.h | 166 ++++++ 4 files changed, 1221 insertions(+) create mode 100644 drivers/ioexpander/mcp23x08.c create mode 100644 drivers/ioexpander/mcp23x08.h diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig index 2094ff94a2a97..6f5928e0f05e4 100644 --- a/drivers/ioexpander/Kconfig +++ b/drivers/ioexpander/Kconfig @@ -90,6 +90,50 @@ config ISO1I813T_MULTIPLE endif # IOEXPANDER_ISO1I813T +config IOEXPANDER_MCP23X08 + bool "MCP23008/MCP23S08 I2C/SPI IO expander" + default n + depends on I2C + ---help--- + Enable support for the MCP23008/MCP23S08 IO Expander + +if IOEXPANDER_MCP23X08 + +config MCP23X08_MULTIPLE + bool "Multiple MCP23x08 Devices" + default n + ---help--- + Can be defined to support multiple MCP23x08 devices on board. + +config MCP23X08_INT_ENABLE + bool "Enable MCP23x08 Interrupt Support" + default n + select IOEXPANDER_INT_ENABLE + ---help--- + Enable driver interrupt functionality + +config MCP23X08_INT_NCALLBACKS + int "Max number of interrupt callbacks" + default 4 + depends on MCP23X08_INT_ENABLE + ---help--- + This is the maximum number of interrupt callbacks supported + +config MCP23X08_INT_POLL + bool "Enable interrupt poll" + default n + ---help--- + Enable polling for missed interrupts. + +config MCP23X08_INT_POLLDELAY + int "Interrupt poll delay (used)" + default 500000 + depends on MCP23X08_INT_POLL + ---help--- + This microsecond delay defines the polling rate for missed interrupts. + +endif # IOEXPANDER_MCP23X08 + config IOEXPANDER_MCP23X17 bool "MCP23017/MCP23S17 I2C/SPI IO expander" default n diff --git a/drivers/ioexpander/Make.defs b/drivers/ioexpander/Make.defs index dbb9a5ac831a2..2777af4415b49 100644 --- a/drivers/ioexpander/Make.defs +++ b/drivers/ioexpander/Make.defs @@ -60,6 +60,10 @@ ifeq ($(CONFIG_IOEXPANDER_PCF8575),y) CSRCS += pcf8575.c endif +ifeq ($(CONFIG_IOEXPANDER_MCP23X08),y) + CSRCS += mcp23x08.c +endif + ifeq ($(CONFIG_IOEXPANDER_MCP23X17),y) CSRCS += mcp23x17.c endif diff --git a/drivers/ioexpander/mcp23x08.c b/drivers/ioexpander/mcp23x08.c new file mode 100644 index 0000000000000..f300e719dad15 --- /dev/null +++ b/drivers/ioexpander/mcp23x08.c @@ -0,0 +1,1007 @@ +/**************************************************************************** + * drivers/ioexpander/mcp23x08.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "mcp23x08.h" + +#if defined(CONFIG_IOEXPANDER_MCP23X08) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_I2C +# warning I2C support is required (CONFIG_I2C) +#endif + +#if (!defined CONFIG_SCHED_WORKQUEUE) && (defined CONFIG_MCP23X08_INT_ENABLE) +# error Work queue support is required (CONFIG_SCHED_WORKQUEUE) +#endif + +#if (!defined CONFIG_SCHED_HPWORK) && (defined CONFIG_MCP23X08_INT_ENABLE) +# error High-Priority Work support is required (CONFIG_SCHED_HPWORK) +#endif + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static inline int mcp23x08_write(FAR struct mcp23x08_dev_s *priv, + FAR const uint8_t *wbuffer, int wbuflen); +static inline int mcp23x08_writeread(FAR struct mcp23x08_dev_s *priv, + FAR const uint8_t *wbuffer, int wbuflen, FAR uint8_t *rbuffer, + int rbuflen); +static int mcp23x08_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int dir); +static int mcp23x08_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int opt, void *val); +static int mcp23x08_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + bool value); +static int mcp23x08_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value); +static int mcp23x08_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value); +#ifdef CONFIG_IOEXPANDER_MULTIPIN +static int mcp23x08_multiwritepin(FAR struct ioexpander_dev_s *dev, + FAR uint8_t *pins, FAR bool *values, int count); +static int mcp23x08_multireadpin(FAR struct ioexpander_dev_s *dev, + FAR uint8_t *pins, FAR bool *values, int count); +static int mcp23x08_multireadbuf(FAR struct ioexpander_dev_s *dev, + FAR uint8_t *pins, FAR bool *values, int count); +#endif +#ifdef CONFIG_IOEXPANDER_INT_ENABLE +static FAR void *mcp23x08_attach(FAR struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg); +static int mcp23x08_detach(FAR struct ioexpander_dev_s *dev, + FAR void *handle); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifndef CONFIG_MCP23X08_MULTIPLE +/* If only a single MCP23X08 device is supported, then the driver state + * structure may as well be pre-allocated. + */ + +static struct mcp23x08_dev_s g_mcp23x08; + +/* Otherwise, we will need to maintain allocated driver instances in a list */ + +#else +static struct mcp23x08_dev_s *g_mcp23x08list; +#endif + +/* I/O expander vtable */ + +static const struct ioexpander_ops_s g_mcp23x08_ops = +{ + mcp23x08_direction, + mcp23x08_option, + mcp23x08_writepin, + mcp23x08_readpin, + mcp23x08_readbuf +#ifdef CONFIG_IOEXPANDER_MULTIPIN + , mcp23x08_multiwritepin + , mcp23x08_multireadpin + , mcp23x08_multireadbuf +#endif +#ifdef CONFIG_IOEXPANDER_INT_ENABLE + , mcp23x08_attach + , mcp23x08_detach +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mcp23x08_write + * + * Description: + * Write to the I2C device. + * + ****************************************************************************/ + +static inline int mcp23x08_write(FAR struct mcp23x08_dev_s *priv, + FAR const uint8_t *wbuffer, int wbuflen) +{ + struct i2c_msg_s msg; + int ret; + + /* Setup for the transfer */ + + msg.frequency = priv->config->frequency; + msg.addr = priv->config->address; + msg.flags = 0; + msg.buffer = (FAR uint8_t *)wbuffer; /* Override const */ + msg.length = wbuflen; + + /* Then perform the transfer. */ + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + return (ret >= 0) ? OK : ret; +} + +/**************************************************************************** + * Name: mcp23x08_writeread + * + * Description: + * Write to then read from the I2C device. + * + ****************************************************************************/ + +static inline int mcp23x08_writeread(FAR struct mcp23x08_dev_s *priv, + FAR const uint8_t *wbuffer, int wbuflen, + FAR uint8_t *rbuffer, int rbuflen) +{ + struct i2c_config_s config; + + /* Set up the configuration and perform the write-read operation */ + + config.frequency = priv->config->frequency; + config.address = priv->config->address; + config.addrlen = 7; + + return i2c_writeread(priv->i2c, &config, wbuffer, wbuflen, + rbuffer, rbuflen); +} + +/**************************************************************************** + * Name: mcp23x08_setbit + * + * Description: + * Write a bit in a register pair + * + ****************************************************************************/ + +static int mcp23x08_setbit(FAR struct mcp23x08_dev_s *priv, uint8_t addr, + uint8_t pin, bool bitval) +{ + uint8_t buf[2]; + int ret; + + if (pin > 7) + { + return -ENXIO; + } + + buf[0] = addr; + +#ifdef CONFIG_MCP23X08_SHADOW_MODE + /* Get the shadowed register value */ + + buf[1] = priv->sreg[addr]; + +#else + /* Get the register value from the IO-Expander */ + + ret = mcp23x08_writeread(priv, &buf[0], 1, &buf[1], 1); + if (ret < 0) + { + return ret; + } +#endif + + if (bitval) + { + buf[1] |= (1 << pin); + } + else + { + buf[1] &= ~(1 << pin); + } + +#ifdef CONFIG_MCP23X08_SHADOW_MODE + /* Save the new register value in the shadow register */ + + priv->sreg[addr] = buf[1]; +#endif + + ret = mcp23x08_write(priv, buf, 2); +#ifdef CONFIG_MCP23X08_RETRY + if (ret != OK) + { + /* Try again (only once) */ + + ret = mcp23x08_write(priv, buf, 2); + } +#endif + + return ret; +} + +/**************************************************************************** + * Name: mcp23x08_getbit + * + * Description: + * Get a bit from a register pair + * + ****************************************************************************/ + +static int mcp23x08_getbit(FAR struct mcp23x08_dev_s *priv, uint8_t addr, + uint8_t pin, FAR bool *val) +{ + uint8_t buf; + int ret; + + if (pin > 7) + { + return -ENXIO; + } + + ret = mcp23x08_writeread(priv, &addr, 1, &buf, 1); + if (ret < 0) + { + return ret; + } + +#ifdef CONFIG_MCP23X08_SHADOW_MODE + /* Save the new register value in the shadow register */ + + priv->sreg[addr] = buf; +#endif + + *val = (buf >> pin) & 1; + return OK; +} + +/**************************************************************************** + * Name: mcp23x08_direction + * + * Description: + * Set the direction of an ioexpander pin. Required. + * + * Input Parameters: + * dev - Device-specific state data + * pin - The index of the pin to alter in this call + * dir - One of the IOEXPANDER_DIRECTION_ macros + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int direction) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + int ret; + + if (direction != IOEXPANDER_DIRECTION_IN && + direction != IOEXPANDER_DIRECTION_IN_PULLUP && + direction != IOEXPANDER_DIRECTION_OUT) + { + return -EINVAL; + } + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = mcp23x08_setbit(priv, MCP23X08_IODIR, pin, + (direction == IOEXPANDER_DIRECTION_IN) || + (direction == IOEXPANDER_DIRECTION_IN_PULLUP)); + if (ret < 0) + { + nxmutex_unlock(&priv->lock); + return ret; + } + + ret = mcp23x08_setbit(priv, MCP23X08_GPPU, pin, + (direction == IOEXPANDER_DIRECTION_IN_PULLUP)); + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: mcp23x08_option + * + * Description: + * Set pin options. Required. + * Since all IO expanders have various pin options, this API allows setting + * pin options in a flexible way. + * + * Input Parameters: + * dev - Device-specific state data + * pin - The index of the pin to alter in this call + * opt - One of the IOEXPANDER_OPTION_ macros + * val - The option's value + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int opt, FAR void *value) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + int ret = -EINVAL; + + if (opt == IOEXPANDER_OPTION_INVERT) + { + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = mcp23x08_setbit(priv, MCP23X08_IPOL, pin, + ((uintptr_t)value == IOEXPANDER_VAL_INVERT)); + nxmutex_unlock(&priv->lock); + } + + return ret; +} + +/**************************************************************************** + * Name: mcp23x08_writepin + * + * Description: + * Set the pin level. Required. + * + * Input Parameters: + * dev - Device-specific state data + * pin - The index of the pin to alter in this call + * val - The pin level. Usually TRUE will set the pin high, + * except if OPTION_INVERT has been set on this pin. + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + bool value) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + int ret; + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = mcp23x08_setbit(priv, MCP23X08_GPIO, pin, value); + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: mcp23x08_readpin + * + * Description: + * Read the actual PIN level. This can be different from the last value + * written to this pin. Required. + * + * Input Parameters: + * dev - Device-specific state data + * pin - The index of the pin + * valptr - Pointer to a buffer where the pin level is stored. Usually TRUE + * if the pin is high, except if OPTION_INVERT has been set on + * this pin. + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + int ret; + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = mcp23x08_getbit(priv, MCP23X08_GPIO, pin, value); + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: mcp23x08_readbuf + * + * Description: + * Read the buffered pin level. + * This can be different from the actual pin state. Required. + * + * Input Parameters: + * dev - Device-specific state data + * pin - The index of the pin + * valptr - Pointer to a buffer where the level is stored. + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + int ret; + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = mcp23x08_getbit(priv, MCP23X08_GPIO, pin, value); + nxmutex_unlock(&priv->lock); + return ret; +} + +#ifdef CONFIG_IOEXPANDER_MULTIPIN + +/**************************************************************************** + * Name: mcp23x08_getmultibits + * + * Description: + * Read multiple bits from MCP23X08 registers. + * + ****************************************************************************/ + +static int mcp23x08_getmultibits(FAR struct mcp23x08_dev_s *priv, + uint8_t addr, + FAR uint8_t *pins, + FAR bool *values, + int count) +{ + uint8_t buf[2]; + int ret = OK; + int i; + int index; + int pin; + + ret = mcp23x08_writeread(priv, &addr, 1, buf, 2); + if (ret < 0) + { + return ret; + } + +#ifdef CONFIG_MCP23X08_SHADOW_MODE + /* Save the new register value in the shadow register */ + + priv->sreg[addr] = buf[0]; + priv->sreg[addr + 1] = buf[1]; +#endif + + /* Read the requested bits */ + + for (i = 0; i < count; i++) + { + index = 0; + pin = pins[i]; + if (pin > 15) + { + return -ENXIO; + } + else if (pin > 7) + { + index = 1; + pin -= 8; + } + + values[i] = (buf[index] >> pin) & 1; + } + + return OK; +} + +/**************************************************************************** + * Name: mcp23x08_multiwritepin + * + * Description: + * Set the pin level for multiple pins. This routine may be faster than + * individual pin accesses. Optional. + * + * Input Parameters: + * dev - Device-specific state data + * pins - The list of pin indexes to alter in this call + * val - The list of pin levels. + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_multiwritepin(FAR struct ioexpander_dev_s *dev, + FAR uint8_t *pins, FAR bool *values, + int count) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + uint8_t addr = MCP23X08_GPIOA; + uint8_t buf[3]; + int ret; + int i; + int index; + int pin; + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Start by reading both registers, whatever the pins to change. We could + * attempt to read one port only if all pins were on the same port, but + * this would not save much. + */ + +#ifndef CONFIG_MCP23X08_SHADOW_MODE + ret = mcp23x08_writeread(priv, &addr, 1, &buf[1], 2); + if (ret < 0) + { + nxmutex_unlock(&priv->lock); + return ret; + } +#else + /* In Shadow-Mode we "read" the pin status from the shadow registers */ + + buf[1] = priv->sreg[addr]; + buf[2] = priv->sreg[addr + 1]; +#endif + + /* Apply the user defined changes */ + + for (i = 0; i < count; i++) + { + index = 1; + pin = pins[i]; + if (pin > 15) + { + nxmutex_unlock(&priv->lock); + return -ENXIO; + } + else if (pin > 7) + { + index = 2; + pin -= 8; + } + + if (values[i]) + { + buf[index] |= (1 << pin); + } + else + { + buf[index] &= ~(1 << pin); + } + } + + /* Now write back the new pins states */ + + buf[0] = addr; +#ifdef CONFIG_MCP23X08_SHADOW_MODE + /* Save the new register values in the shadow register */ + + priv->sreg[addr] = buf[1]; + priv->sreg[addr + 1] = buf[2]; +#endif + ret = mcp23x08_write(priv, buf, 3); + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: mcp23x08_multireadpin + * + * Description: + * Read the actual level for multiple pins. This routine may be faster than + * individual pin accesses. Optional. + * + * Input Parameters: + * dev - Device-specific state data + * pin - The list of pin indexes to read + * valptr - Pointer to a buffer where the pin levels are stored. + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_multireadpin(FAR struct ioexpander_dev_s *dev, + FAR uint8_t *pins, FAR bool *values, + int count) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + int ret; + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = mcp23x08_getmultibits(priv, MCP23X08_GPIO, + pins, values, count); + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Name: mcp23x08_multireadbuf + * + * Description: + * Read the buffered level of multiple pins. This routine may be faster + * than individual pin accesses. Optional. + * + * Input Parameters: + * dev - Device-specific state data + * pin - The index of the pin + * valptr - Pointer to a buffer where the buffered levels are stored. + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_multireadbuf(FAR struct ioexpander_dev_s *dev, + FAR uint8_t *pins, FAR bool *values, + int count) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + int ret; + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + ret = mcp23x08_getmultibits(priv, MCP23X08_GPIO, + pins, values, count); + nxmutex_unlock(&priv->lock); + return ret; +} + +#endif + +#ifdef CONFIG_MCP23X08_INT_ENABLE + +/**************************************************************************** + * Name: mcp23x08_attach + * + * Description: + * Attach and enable a pin interrupt callback function. + * + * Input Parameters: + * dev - Device-specific state data + * pinset - The set of pin events that will generate the callback + * callback - The pointer to callback function. NULL will detach the + * callback. + * arg - User-provided callback argument + * + * Returned Value: + * A non-NULL handle value is returned on success. This handle may be + * used later to detach and disable the pin interrupt. + * + ****************************************************************************/ + +static FAR void *mcp23x08_attach(FAR struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, + ioe_callback_t callback, + FAR void *arg) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + FAR void *handle = NULL; + uint8_t addr = MCP23X08_GPINTENA; + uint8_t buf[2]; + int i; + int ret; + + /* Get exclusive access to the MCP23X08 */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return NULL; + } + + ret = mcp23x08_writeread(priv, &addr, 1, &buf[1], 1); + if (ret < 0) + { + nxmutex_unlock(&priv->lock); + return NULL; + } + + buf[0] = addr; + buf[1] |= pinset; + + ret = mcp23x08_write(priv, buf, 2); + if (ret < 0) + { + nxmutex_unlock(&priv->lock); + return NULL; + } + + /* Find and available in entry in the callback table */ + + for (i = 0; i < CONFIG_MCP23X08_INT_NCALLBACKS; i++) + { + /* Is this entry available (i.e., no callback attached) */ + + if (priv->cb[i].cbfunc == NULL) + { + /* Yes.. use this entry */ + + priv->cb[i].pinset = pinset; + priv->cb[i].cbfunc = callback; + priv->cb[i].cbarg = arg; + handle = &priv->cb[i]; + break; + } + } + + /* Add this callback to the table */ + + nxmutex_unlock(&priv->lock); + return handle; +} + +/**************************************************************************** + * Name: mcp23x08_detach + * + * Description: + * Detach and disable a pin interrupt callback function. + * + * Input Parameters: + * dev - Device-specific state data + * handle - The non-NULL opaque value return by mcp23x08_attch() + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int mcp23x08_detach(FAR struct ioexpander_dev_s *dev, + FAR void *handle) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)dev; + FAR struct mcp23x08_callback_s *cb = + (FAR struct mcp23x08_callback_s *)handle; + + DEBUGASSERT(priv != NULL && cb != NULL); + DEBUGASSERT((uintptr_t)cb >= (uintptr_t)&priv->cb[0] && + (uintptr_t)cb <= + (uintptr_t)&priv->cb[CONFIG_MCP23X08_INT_NCALLBACKS - 1]); + UNUSED(priv); + + cb->pinset = 0; + cb->cbfunc = NULL; + cb->cbarg = NULL; + return OK; +} + +/**************************************************************************** + * Name: mcp23x08_irqworker + * + * Description: + * Handle GPIO interrupt events (this function actually executes in the + * context of the worker thread). + * + ****************************************************************************/ + +static void mcp23x08_irqworker(void *arg) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)arg; + uint8_t addr = MCP23X08_INTFA; + uint8_t buf[2]; + ioe_pinset_t pinset; + int ret; + int i; + + /* Read interrupt flags */ + + ret = mcp23x08_writeread(priv, &addr, 1, buf, 1); + if (ret == OK) + { + /* Create a 16-bit pinset */ + + pinset = ((unsigned int)buf[1] << 8) | buf[0]; + + /* Perform pin interrupt callbacks */ + + for (i = 0; i < CONFIG_MCP23X08_INT_NCALLBACKS; i++) + { + /* Is this entry valid (i.e., callback attached)? If so, did + * any of the requested pin interrupts occur? + */ + + if (priv->cb[i].cbfunc != NULL) + { + /* Did any of the requested pin interrupts occur? */ + + ioe_pinset_t match = pinset & priv->cb[i].pinset; + if (match != 0) + { + /* Yes.. perform the callback */ + + priv->cb[i].cbfunc(&priv->dev, match, + priv->cb[i].cbarg); + } + } + } + + /* Read GPIOs to clear interrupt condition */ + + addr = MCP23X08_INTCAP; + + mcp23x08_writeread(priv, &addr, 1, buf, 1); + +#ifdef CONFIG_MCP23X08_SHADOW_MODE + /* Don't forget to update the shadow registers at this point */ + + priv->sreg[addr] = buf[0]; +#endif + } + + /* Re-enable interrupts */ + + priv->config->enable(priv->config, TRUE); +} + +/**************************************************************************** + * Name: mcp23x08_interrupt + * + * Description: + * Handle GPIO interrupt events (this function executes in the + * context of the interrupt). + * + ****************************************************************************/ + +static int mcp23x08_interrupt(int irq, FAR void *context, FAR void *arg) +{ + FAR struct mcp23x08_dev_s *priv = (FAR struct mcp23x08_dev_s *)arg; + + /* In complex environments, we cannot do I2C transfers from the interrupt + * handler because semaphores are probably used to lock the I2C bus. In + * this case, we will defer processing to the worker thread. This is also + * much kinder in the use of system resources and is, therefore, probably + * a good thing to do in any event. + */ + + /* Notice that further GPIO interrupts are disabled until the work is + * actually performed. This is to prevent overrun of the worker thread. + * Interrupts are re-enabled in mcp23x08_irqworker() when the work is + * completed. + */ + + if (work_available(&priv->work)) + { + priv->config->enable(priv->config, FALSE); + work_queue(HPWORK, &priv->work, mcp23x08_irqworker, + (FAR void *)priv, 0); + } + + return OK; +} + +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mcp23x08_initialize + * + * Description: + * Initialize a MCP23X08 I2C device. + * + * TODO: Add support for more than one device. + * + ****************************************************************************/ + +FAR struct ioexpander_dev_s *mcp23x08_initialize( + FAR struct i2c_master_s *i2cdev, + FAR struct mcp23x08_config_s *config) +{ + FAR struct mcp23x08_dev_s *priv; +#ifdef CONFIG_MCP23X08_INT_MIRROR + uint8_t buf[3]; +#endif + + DEBUGASSERT(i2cdev != NULL && config != NULL); + +#ifdef CONFIG_MCP23X08_MULTIPLE + /* Allocate the device state structure */ + + priv = (FAR struct mcp23x08_dev_s *) + kmm_zalloc(sizeof(struct mcp23x08_dev_s)); + if (!priv) + { + return NULL; + } + + /* And save the device structure in the list of MCP23X08 so that we can + * find it later. + */ + + priv->flink = g_mcp23x08list; + g_mcp23x08list = priv; + +#else + /* Use the global MCP23X08 driver instance */ + + priv = &g_mcp23x08; +#endif + + /* Initialize the device state structure */ + + priv->i2c = i2cdev; + priv->dev.ops = &g_mcp23x08_ops; + priv->config = config; + +#ifdef CONFIG_MCP23X08_INT_ENABLE + priv->config->attach(priv->config, mcp23x08_interrupt, priv); + priv->config->enable(priv->config, TRUE); +#endif + + nxmutex_init(&priv->lock); + return &priv->dev; +} + +#endif /* CONFIG_IOEXPANDER_MCP23X08 */ diff --git a/drivers/ioexpander/mcp23x08.h b/drivers/ioexpander/mcp23x08.h new file mode 100644 index 0000000000000..cf47a7d9b11cd --- /dev/null +++ b/drivers/ioexpander/mcp23x08.h @@ -0,0 +1,166 @@ +/**************************************************************************** + * drivers/ioexpander/mcp23x08.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __DRIVERS_IOEXPANDER_MCP23X08_H +#define __DRIVERS_IOEXPANDER_MCP23X08_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(CONFIG_IOEXPANDER) && defined(CONFIG_IOEXPANDER_MCP23X08) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Prerequisites: + * CONFIG_I2C + * I2C support is required + * CONFIG_IOEXPANDER + * Enables I/O expander support + * + * CONFIG_IOEXPANDER_MCP23X08 + * Enables support for the MCP23X08 driver (Needs CONFIG_INPUT) + * CONFIG_MCP23X08_MULTIPLE + * Can be defined to support multiple MCP23X08 devices on board. + * CONFIG_MCP23X08_INT_NCALLBACKS + * Maximum number of supported pin interrupt callbacks. + * CONFIG_MCP23X08_INT_POLL + * Enables a poll for missed interrupts + * CONFIG_MCP23X08_INT_POLLDELAY + * If CONFIG_MCP23X08_INT_POLL=y, then this is the delay in microseconds + * between polls for missed interrupts. + */ + +#ifndef CONFIG_I2C +# error "CONFIG_I2C is required by MCP23X08" +#endif + +#ifdef CONFIG_IOEXPANDER_INT_ENABLE +# ifndef CONFIG_MCP23X08_INT_NCALLBACKS +# define CONFIG_MCP23X08_INT_NCALLBACKS 4 +# endif +#endif + +#ifdef CONFIG_IOEXPANDER_INT_ENABLE +# ifndef CONFIG_SCHED_WORKQUEUE +# error Work queue support required. CONFIG_SCHED_WORKQUEUE must be selected. +# endif +#endif + +#ifndef CONFIG_MCP23X08_INT_POLLDELAY +# define CONFIG_MCP23X08_INT_POLLDELAY 500000 +#endif + +/* MCP23X08 Definitions *****************************************************/ + +/* I2C frequency */ + +#define MCP23X08_I2C_MAXFREQUENCY 400000 /* 400KHz */ + +/* MCP23X08 *****************************************************************/ + +/* If IOCON.BANK = 0 Addressing Mode */ + +#define MCP23X08_IODIR 0x00 +#define MCP23X08_IPOL 0x01 +#define MCP23X08_GPINTEN 0x02 +#define MCP23X08_DEFVAL 0x03 +#define MCP23X08_INTCON 0x04 +#define MCP23X08_IOCON 0x05 +#define MCP23X08_GPPU 0x06 +#define MCP23X08_INTF 0x07 +#define MCP23X08_INTCAP 0x08 +#define MCP23X08_GPIO 0x09 +#define MCP23X08_OLAT 0x0a + +#define MCP23X08_IOCON_INTPOL (1 << 1) /* Polarity of INT output pin */ +#define MCP23X08_IOCON_ODR (1 << 2) /* Config INT pin as open-drain */ +#define MCP23X08_IOCON_HAEN (1 << 3) /* HW Address enable bit */ +#define MCP23X08_IOCON_DISSLW (1 << 4) /* Disable Slew Rate for SDA output */ +#define MCP23X08_IOCON_SEQOP (1 << 5) /* Disable Sequential Operation */ + +#define MCP23X08_NR_GPIO_MAX 8 + +#define MCP23X08_POLLDELAY (CONFIG_MCP23X08_INT_POLLDELAY / USEC_PER_TICK) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +#ifdef CONFIG_IOEXPANDER_INT_ENABLE +/* This type represents on registered pin interrupt callback */ + +struct mcp23x08_callback_s +{ + ioe_pinset_t pinset; /* Set of pin interrupts that will generate + * the callback. */ + ioe_callback_t cbfunc; /* The saved callback function pointer */ + FAR void *cbarg; /* Callback argument */ +}; +#endif + +/* This structure represents the state of the MCP23X08 driver */ + +struct mcp23x08_dev_s +{ + struct ioexpander_dev_s dev; /* Nested structure to allow casting + * as public gpio expander. + */ +#ifdef CONFIG_MCP23X08_MULTIPLE + FAR struct mcp23x08_dev_s *flink; /* Supports a singly linked list of drivers */ +#endif + + FAR struct mcp23x08_config_s *config; /* Board configuration data */ + FAR struct i2c_master_s *i2c; /* Saved I2C driver instance */ + mutex_t lock; /* Mutual exclusion */ + +#ifdef CONFIG_IOEXPANDER_INT_ENABLE +#ifdef CONFIG_MCP23X08_INT_POLL + struct wdog_s wdog; /* Timer used to poll for missed interrupts */ +#endif + + ioe_pinset_t input; /* Last input registers */ + ioe_pinset_t intstat; /* Pending interrupts */ + struct work_s work; /* Supports the interrupt handling "bottom half" */ + + /* Saved callback information for each I/O expander client */ + + struct mcp23x08_callback_s cb[CONFIG_MCP23X08_INT_NCALLBACKS]; +#endif +}; + +#endif /* CONFIG_IOEXPANDER && CONFIG_IOEXPANDER_MCP23X08 */ +#endif /* __DRIVERS_IOEXPANDER_MCP23X08_H */