From 835c535e1854f71dcc849dd33df653c7f2064782 Mon Sep 17 00:00:00 2001 From: Zik Saleeba Date: Fri, 29 Aug 2025 13:01:34 +1000 Subject: [PATCH] drivers/ioexpander/aw9523b: New driver for AW9523B i/o expander This is an I2C driver for the Awinic AW9523B I/O expander. As well as the supporting usual digital I/Os, this device features the ability to drive LEDs directly, and can control the drive current to each LED individually. The driver was derived from the PCA9555 driver, provides all the standard interfaces of an i/o expander driver. It also features support for the special features of the AW9523B through pin direction IOEXPANDER_DIRECTION_OUT_LED and IOEXPANDER_OPTION_NONGENERIC with AW9523B_OPTION_* options. The driver has a number of configurable features including interrupt handling and a shadow register mode based on the PCA9555 driver, plus new LED/dimming support. Testing was done using an ESP32S3 host and I2C connection to the Adafruit AW9523 breakout board. Signed-off-by: Zik Saleeba --- drivers/ioexpander/CMakeLists.txt | 4 + drivers/ioexpander/Kconfig | 57 ++ drivers/ioexpander/Make.defs | 4 + drivers/ioexpander/aw9523b.c | 1529 ++++++++++++++++++++++++++++ drivers/ioexpander/aw9523b.h | 188 ++++ include/nuttx/ioexpander/aw9523b.h | 177 ++++ 6 files changed, 1959 insertions(+) create mode 100644 drivers/ioexpander/aw9523b.c create mode 100644 drivers/ioexpander/aw9523b.h create mode 100644 include/nuttx/ioexpander/aw9523b.h diff --git a/drivers/ioexpander/CMakeLists.txt b/drivers/ioexpander/CMakeLists.txt index 76f56102bd..23e712e1d3 100644 --- a/drivers/ioexpander/CMakeLists.txt +++ b/drivers/ioexpander/CMakeLists.txt @@ -82,6 +82,10 @@ if(CONFIG_IOEXPANDER) if(CONFIG_IOEXPANDER_SX1509) list(APPEND SRCS sx1509.c) endif() + + if(CONFIG_IOEXPANDER_AW9523B) + list(APPEND SRCS aw9523b.c) + endif() endif() # GPIO test device driver (independent of IOEXPANDERS) diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig index 81b7340799..95133b576d 100644 --- a/drivers/ioexpander/Kconfig +++ b/drivers/ioexpander/Kconfig @@ -495,6 +495,63 @@ config SX1509_RETRY endif # IOEXPANDER_SX1509 +config IOEXPANDER_AW9523B + bool "AW9523B I2C IO expander" + default n + depends on I2C + ---help--- + Enable support for the Awinic AW9523B IO Expander + +if IOEXPANDER_AW9523B + +config AW9523B_MULTIPLE + bool "Multiple AW9523B Devices" + default n + ---help--- + Can be defined to support multiple AW9523B devices on board. + +config AW9523B_LED_ENABLE + bool "Enable AW9523B LED Support" + default y + ---help--- + Enable driver LED functionality + +config AW9523B_INT_ENABLE + bool "Enable AW9523B Interrupt Support" + default n + select IOEXPANDER_INT_ENABLE + ---help--- + Enable driver interrupt functionality + +config AW9523B_INT_NCALLBACKS + int "Max number of interrupt callbacks" + default 4 + depends on AW9523B_INT_ENABLE + ---help--- + This is the maximum number of interrupt callbacks supported + +config AW9523B_SHADOW_MODE + bool "Use Shadow Mode instead of Read-Modify-Write Operations" + default n + ---help--- + This setting enables a mode where the output and pin + configuration registers are held in RAM. + With this for example we do not need to read back the + output-register every time we want to change one pin. + We do instead change the bit in the internal register + and then just write this register to the IO-Expander. + This reduces bus traffic and eliminates the problem of + EMC-caused toggling of output pins. + +config AW9523B_RETRY + bool "Retry to send commands and data at I2C communication errors" + default n + ---help--- + Retry to send commands and data if a I2C-communication + error occurs (eg. caused by EMC). + +endif # IOEXPANDER_AW9523B + config IOEXPANDER_INT_ENABLE bool default n diff --git a/drivers/ioexpander/Make.defs b/drivers/ioexpander/Make.defs index 8e44fb4c08..96cdc9cb45 100644 --- a/drivers/ioexpander/Make.defs +++ b/drivers/ioexpander/Make.defs @@ -82,6 +82,10 @@ ifeq ($(CONFIG_IOEXPANDER_SX1509),y) CSRCS += sx1509.c endif +ifeq ($(CONFIG_IOEXPANDER_AW9523B),y) + CSRCS += aw9523b.c +endif + endif # CONFIG_IOEXPANDER # GPIO test device driver (independent of IOEXPANDERS) diff --git a/drivers/ioexpander/aw9523b.c b/drivers/ioexpander/aw9523b.c new file mode 100644 index 0000000000..b061e67107 --- /dev/null +++ b/drivers/ioexpander/aw9523b.c @@ -0,0 +1,1529 @@ +/**************************************************************************** + * drivers/ioexpander/aw9523b.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. + * + ****************************************************************************/ + +/* The AW9523B is a 16-channel I/O expander with an I2C interface. + * It provides GPIO and LED control functionality, including adjustable LED + * dimming. + * + * It also has some quirks - pins 0-7 default to open-drain rather than + * push-pull and the default direction and state of pins is determined by + * the hardware-set I2C device address. + * + * For more details check the comment at aw9523b_initialize(). + * + * References: + * "16 Multi-function LED Driver and GPIO Controller with I2C Interface", + * May 2016, v1.1.1, Shanghai Awinic Technology Co., Ltd. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "aw9523b.h" + +#if defined(CONFIG_IOEXPANDER_AW9523B) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef CONFIG_I2C +# warning I2C support is required (CONFIG_I2C) +#endif + +/* Pins and ports on the AW9523B. */ +#define AW9523B_PINS_PER_PORT_BITS 3 +#define AW9523B_PINS_PER_PORT (1 << AW9523B_PINS_PER_PORT_BITS) +#define AW9523B_PORT_PIN_MASK (AW9523B_PINS_PER_PORT - 1) +#define AW9523B_NUM_PORTS 2 + +/**************************************************************************** + * Macros to handle pinsets + ****************************************************************************/ + +#define GET_BIT(pinset, pin) \ + (((pinset) & (1 << (pin))) != 0) +#define SET_BIT(pinset, pin, value) \ + ((value) ? ((pinset) |= (1 << (pin))) : ((pinset) &= ~(1 << (pin)))) + +#define AW9523B_GET_INVERT_PIN(aw, pin) \ + (GET_BIT((aw)->invert_pin, pin)) +#define AW9523B_SET_INVERT_PIN(aw, pin, v) \ + (SET_BIT((aw)->invert_pin, pin, v)) + +#ifdef CONFIG_AW9523B_LED_ENABLE +#define AW9523B_GET_IS_LED(aw, b) \ + (GET_BIT((aw)->is_led_bitset, b)) +#define AW9523B_SET_IS_LED(aw, b, v) \ + (SET_BIT((aw)->is_led_bitset, b, v)) +#define AW9523B_GET_OUTPUT_IS_ON(aw, b) \ + (GET_BIT((aw)->output_is_on_bitset, b)) +#define AW9523B_SET_OUTPUT_IS_ON(aw, b, v) \ + (SET_BIT((aw)->output_is_on_bitset, b, v)) +#endif + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static inline int aw9523b_write(FAR struct aw9523b_dev_s *aw, + FAR const uint8_t *wbuffer, int wbuflen); +static inline int aw9523b_writeread(FAR struct aw9523b_dev_s *aw, + FAR const uint8_t *wbuffer, int wbuflen, FAR uint8_t *rbuffer, + int rbuflen); +static int aw9523b_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int dir); +static int aw9523b_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int opt, void *val); +static int aw9523b_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + bool value); +static int aw9523b_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value); +static int aw9523b_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value); +#ifdef CONFIG_IOEXPANDER_MULTIPIN +static int aw9523b_multiwritepin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, FAR const bool *values, int count); +static int aw9523b_multireadpin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, FAR bool *values, int count); +static int aw9523b_multireadbuf(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, FAR bool *values, int count); +#endif +#ifdef CONFIG_AW9523B_INT_ENABLE +static FAR void *aw9523b_attach(FAR struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, ioe_callback_t callback, FAR void *arg); +static int aw9523b_detach(FAR struct ioexpander_dev_s *dev, + FAR void *handle); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifndef CONFIG_AW9523B_MULTIPLE +/* If only a single AW9523B device is supported, then the driver state + * structure may as well be pre-allocated. + */ + +static struct aw9523b_dev_s g_aw9523b; + +/* Otherwise, we will need to maintain allocated driver instances in a + * list. + */ + +#else +static struct aw9523b_dev_s *g_aw9523blist; +#endif + +/* I/O expander vtable. */ + +static const struct ioexpander_ops_s g_aw9523b_ops = +{ + aw9523b_direction, + aw9523b_option, + aw9523b_writepin, + aw9523b_readpin, + aw9523b_readbuf +#ifdef CONFIG_IOEXPANDER_MULTIPIN + , aw9523b_multiwritepin + , aw9523b_multireadpin + , aw9523b_multireadbuf +#endif +#ifdef CONFIG_IOEXPANDER_INT_ENABLE +#ifdef CONFIG_AW9523B_INT_ENABLE + , aw9523b_attach + , aw9523b_detach +#else + , NULL + , NULL +#endif +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: aw9523b_lock + * + * Description: + * Get exclusive access to the AW9523B + * + ****************************************************************************/ + +static int aw9523b_lock(FAR struct aw9523b_dev_s *aw) +{ + return nxsem_wait_uninterruptible(&aw->exclsem); +} + +#define aw9523b_unlock(p) nxsem_post(&(p)->exclsem) + +/**************************************************************************** + * Name: aw9523b_port_from_pin + * + * Description: + * Get the port offset and port pin index for a given pin. + * + ****************************************************************************/ + +static inline int aw9523b_port_from_pin(uint8_t pin, + FAR int *port, + FAR int *port_pin) +{ + if (pin >= AW9523B_GPIO_NPINS) + { + return -EINVAL; + } + + /* Get the register address and port pin index for the given pin. */ + + *port = (pin >> AW9523B_PINS_PER_PORT_BITS); + *port_pin = (pin & AW9523B_PORT_PIN_MASK); + + return OK; +} + +/**************************************************************************** + * Name: aw9523b_dimming_reg_from_pin + * + * Description: + * Get the dimming register address for a given pin. + * + ****************************************************************************/ + +static inline int aw9523b_dimming_reg_from_pin(uint8_t pin) +{ + if (pin >= AW9523B_GPIO_NPINS) + { + return -EINVAL; + } + + /* Get the register address and port pin index for the given pin. */ + + if (pin < 8) + { + return AW9523B_REG_DIM_P0_0 + pin; + } + else if (pin < 12) + { + return AW9523B_REG_DIM_P1_0 + (pin - 8); + } + else + { + return AW9523B_REG_DIM_P1_4 + (pin - 12); + } +} + +/**************************************************************************** + * Name: aw9523b_write + * + * Description: + * Write to the I2C device. + * + ****************************************************************************/ + +static inline int aw9523b_write(FAR struct aw9523b_dev_s *aw, + FAR const uint8_t *wbuffer, int wbuflen) +{ + struct i2c_msg_s msg; + int ret; + + /* Setup for the transfer. */ + + msg.frequency = aw->config->frequency; + msg.addr = AW9523B_I2C_ADDR_BASE + aw->config->sub_address; + msg.flags = 0; + msg.buffer = (FAR uint8_t *)wbuffer; /* Override const. */ + msg.length = wbuflen; + + /* Then perform the transfer. */ + + ret = I2C_TRANSFER(aw->i2c, &msg, 1); + return (ret >= 0) ? OK : ret; +} + +/**************************************************************************** + * Name: aw9523b_writeread + * + * Description: + * Write to then read from the I2C device. + * + ****************************************************************************/ + +static inline int aw9523b_writeread(FAR struct aw9523b_dev_s *aw, + 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 = aw->config->frequency; + config.address = AW9523B_I2C_ADDR_BASE + aw->config->sub_address; + config.addrlen = 7; + + return i2c_writeread(aw->i2c, &config, wbuffer, wbuflen, + rbuffer, rbuflen); +} + +/**************************************************************************** + * Name: aw9523b_setbit + * + * Description: + * Write a bit in a register pair + * + ****************************************************************************/ + +static int aw9523b_setbit(FAR struct aw9523b_dev_s *aw, uint8_t addr, + uint8_t pin, bool bitval) +{ + uint8_t buf[2]; + int ret; + int port; + int port_pin; + uint8_t reg16addr; + + ret = aw9523b_port_from_pin(pin, &port, &port_pin); + if (ret < 0) + { + return ret; + } + + reg16addr = addr + port; + buf[0] = reg16addr; + +#ifdef CONFIG_AW9523B_SHADOW_MODE + /* Get the shadowed register value. */ + + buf[1] = aw->sreg[reg16addr]; + +#else + /* Get the register value from the IO-Expander. */ + + ret = aw9523b_writeread(aw, &buf[0], 1, &buf[1], 1); + if (ret < 0) + { + return ret; + } +#endif + + SET_BIT(buf[1], port_pin, bitval); + + ret = aw9523b_write(aw, buf, 2); + +#ifdef CONFIG_AW9523B_RETRY + if (ret != OK) + { + /* Try again (only once). */ + + ret = aw9523b_write(aw, buf, 2); + } +#endif + +#ifdef CONFIG_AW9523B_SHADOW_MODE + if (ret == OK && addr < AW9523B_NUM_SHADOW_REGS) + { + /* Save the new register value in the shadow register. */ + + aw->sreg[reg16addr] = buf[1]; + } +#endif + + return ret; +} + +/**************************************************************************** + * Name: aw9523b_getbit + * + * Description: + * Get a bit from a register pair + * + ****************************************************************************/ + +static int aw9523b_getbit(FAR struct aw9523b_dev_s *aw, uint8_t addr, + uint8_t pin, FAR bool *val) +{ + uint8_t buf[2]; + int ret; + int port; + int port_pin; + + ret = aw9523b_port_from_pin(pin, &port, &port_pin); + if (ret < 0) + { + return ret; + } + + buf[0] = addr + port; + + ret = aw9523b_writeread(aw, &buf[0], 1, &buf[1], 1); + if (ret < 0) + { + return ret; + } + +#ifdef CONFIG_AW9523B_SHADOW_MODE + if (addr < AW9523B_NUM_SHADOW_REGS) + { + /* Save the new register value in the shadow register. */ + + aw->sreg[addr] = buf[1]; + } +#endif + + *val = (buf[1] >> port_pin) & 0x1; + return OK; +} + +/**************************************************************************** + * Name: aw9523b_setbitsmasked + * + * Description: + * Write masked bits in a single register + * Doesn't use shadow mode. + * + ****************************************************************************/ + +static int aw9523b_setbitsmasked(FAR struct aw9523b_dev_s *aw, uint8_t addr, + uint8_t mask, uint8_t value) +{ + uint8_t buf[2]; + int ret; + + buf[0] = addr; + + /* Get the register value from the IO-Expander. */ + + ret = aw9523b_writeread(aw, &buf[0], 1, &buf[1], 1); + if (ret < 0) + { + return ret; + } + + /* Apply the mask and value. */ + + buf[1] = (buf[1] & ~mask) | (value & mask); + + ret = aw9523b_write(aw, buf, 2); + +#ifdef CONFIG_AW9523B_RETRY + if (ret != OK) + { + /* Try again (only once). */ + + ret = aw9523b_write(aw, buf, 2); + } +#endif + + return ret; +} + +/**************************************************************************** + * Name: aw9523b_write_reg + * + * Description: + * Write to the I2C device. + * + ****************************************************************************/ + +static inline int aw9523b_write_reg(FAR struct aw9523b_dev_s *aw, + int reg, uint8_t value) +{ + uint8_t buf[2] = { + reg, + value + }; + + struct i2c_msg_s msg; + int ret; + + /* Setup for the transfer. */ + + msg.frequency = aw->config->frequency; + msg.addr = AW9523B_I2C_ADDR_BASE + aw->config->sub_address; + msg.flags = 0; + msg.buffer = &buf[0]; /* Override const. */ + msg.length = sizeof(buf); + + /* Then perform the transfer. */ + + ret = I2C_TRANSFER(aw->i2c, &msg, 1); + return (ret >= 0) ? OK : ret; +} + +/**************************************************************************** + * Name: aw9523b_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 aw9523b_direction(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int direction) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + int ret; +#ifdef CONFIG_AW9523B_LED_ENABLE + bool is_led = false; +#endif + + if (direction != IOEXPANDER_DIRECTION_IN && + direction != IOEXPANDER_DIRECTION_OUT && + direction != IOEXPANDER_DIRECTION_OUT_LED) + { + return -EINVAL; + } + +#ifndef CONFIG_AW9523B_LED_ENABLE + if (direction == IOEXPANDER_DIRECTION_OUT_LED) + { + return -EINVAL; + } +#endif + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + +#ifdef CONFIG_AW9523B_LED_ENABLE + /* Set the LED mode first. */ + + if (direction == IOEXPANDER_DIRECTION_OUT_LED) + { + is_led = true; + } + + AW9523B_SET_IS_LED(aw, pin, is_led); + + ret = aw9523b_setbit(aw, AW9523B_REG_LEDMODE0, pin, !is_led); + if (ret < 0) + { + goto errout; + } + + if (is_led) + { + /* Set the LED current. */ + + uint8_t dim_reg = aw9523b_dimming_reg_from_pin(pin); + uint8_t led_current = AW9523B_GET_OUTPUT_IS_ON(aw, pin) ? + aw->led_current[pin] : 0; + ret = aw9523b_write_reg(aw, dim_reg, led_current); + if (ret < 0) + { + goto errout; + } + } +#endif + + /* Set the pin's GPIO direction. */ + + ret = aw9523b_setbit(aw, AW9523B_REG_CONFIG0, pin, + (direction == IOEXPANDER_DIRECTION_IN)); + if (ret < 0) + { + goto errout; + } + +errout: + aw9523b_unlock(aw); + return ret; +} + +/**************************************************************************** + * Name: aw9523b_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 aw9523b_option(FAR struct ioexpander_dev_s *dev, uint8_t pin, + int opt, FAR void *value) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + int ret = -EINVAL; + + if (pin >= AW9523B_GPIO_NPINS) + { + return -EINVAL; + } + + if (opt == IOEXPANDER_OPTION_INVERT) + { + /* Set the pin as inverted or not. */ + + if (value == NULL) + { + return -EINVAL; + } + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + + /* Update the invert pin set. */ + + AW9523B_SET_INVERT_PIN(aw, pin, + (uintptr_t)value == IOEXPANDER_VAL_INVERT); + + aw9523b_unlock(aw); + ret = OK; + } + + else if (opt == IOEXPANDER_OPTION_NONGENERIC) + { + struct aw9523b_nongeneric_option_s *optval = + (struct aw9523b_nongeneric_option_s *)value; + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + + switch (optval->command) + { + case AW9523B_OPTION_SOFTWARE_RESET: + + /* Reset the device by writing zero to the reset register. */ + + ret = aw9523b_write_reg(aw, AW9523B_REG_RESET, 0); + break; + + case AW9523B_OPTION_PORT0_PUSH_PULL: + /* Set port 0 as pull-pull or open-drain. + * This is a global setting, not per-pin. + * Sets bit 4 of the AW9523B_REG_GCR register. + */ + + ret = aw9523b_setbit(aw, AW9523B_REG_GCR, 4, + optval->value.port0_push_pull != + AW9523B_OPTION_P0_PP_OPEN_DRAIN); + break; + + case AW9523B_OPTION_READ_ID: + { + uint8_t reg = AW9523B_REG_ID; + ret = aw9523b_writeread(aw, ®, 1, &optval->value.id, 1); + break; + } + +#ifdef CONFIG_AW9523B_LED_ENABLE + case AW9523B_OPTION_DIMMING: + /* Set the dimming range for LEDs. 0-3. + * 0 = max. 1 = 0.75x. 2 = 0.5x. 3 = 0.25x. + * This is a global setting, not per-pin. + * Sets bits 0-1 of the AW9523B_REG_GCR register. + */ + + if (optval->value.dimming > AW9523B_OPTION_DIMMING_25) + { + ret = -EINVAL; + break; + } + + ret = aw9523b_setbitsmasked(aw, AW9523B_REG_GCR, + AW9523B_REG_GCR_DIMMING_MASK, + optval->value.dimming); + break; + + case AW9523B_OPTION_LED_CURRENT: + { + uint8_t dim_reg = aw9523b_dimming_reg_from_pin(pin); + uint8_t led_current = AW9523B_GET_OUTPUT_IS_ON(aw, pin) ? + optval->value.led_current : 0; + + if (led_current > AW9523B_OPTION_LED_CURRENT_MAX) + { + ret = -EINVAL; + break; + } + + ret = aw9523b_write_reg(aw, dim_reg, led_current); + break; + } +#endif + + default: + ret = -ENOSYS; + break; + } + + aw9523b_unlock(aw); + } +#ifdef CONFIG_AW9523B_INT_ENABLE + else if (opt == IOEXPANDER_OPTION_INTCFG) + { + uintptr_t v = (uintptr_t)value; + if (v != IOEXPANDER_VAL_BOTH && + v != IOEXPANDER_VAL_DISABLE) + { + return -EINVAL; + } + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + + /* Turn on the interrupt. */ + + ret = aw9523b_setbit(aw, AW9523B_REG_INT0, pin, + v == IOEXPANDER_VAL_BOTH); + aw9523b_unlock(aw); + } +#endif + + return ret; +} + +/**************************************************************************** + * Name: aw9523b_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 aw9523b_writepin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + bool value) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + int ret; + + if (pin >= AW9523B_GPIO_NPINS) + { + return -EINVAL; + } + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + + /* If the pin is inverted, invert the value. */ + + if (AW9523B_GET_INVERT_PIN(aw, pin)) + { + value = !value; + } + + /* Set the pin level. */ + + ret = aw9523b_setbit(aw, AW9523B_REG_OUTPUT0, pin, value); + if (ret < 0) + { + goto errout; + } + + /* If the pin is an LED, set the LED current. */ +#ifdef CONFIG_AW9523B_LED_ENABLE + if (AW9523B_GET_IS_LED(aw, pin)) + { + /* Set the LED current. */ + + uint8_t dim_reg = aw9523b_dimming_reg_from_pin(pin); + uint8_t led_current = value ? aw->led_current[pin] : 0; + + ret = aw9523b_write_reg(aw, dim_reg, led_current); + if (ret < 0) + { + goto errout; + } + + AW9523B_SET_OUTPUT_IS_ON(aw, pin, value); + } +#endif + +errout: + aw9523b_unlock(aw); + return ret; +} + +/**************************************************************************** + * Name: aw9523b_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 aw9523b_readpin(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + int ret; + bool pin_value = false; + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + + ret = aw9523b_getbit(aw, AW9523B_REG_INPUT0, pin, &pin_value); + aw9523b_unlock(aw); + + /* If the pin is inverted, invert the value. */ + + if (AW9523B_GET_INVERT_PIN(aw, pin)) + { + pin_value = !pin_value; + } + + *value = pin_value; + return ret; +} + +/**************************************************************************** + * Name: aw9523b_readbuf + * + * Description: + * Read the buffered pin level. + * This can be different from the actual pin state. Required. + * NOTE: Does not read a buffer full of pins, reads a single buffered pin. + * + * 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 aw9523b_readbuf(FAR struct ioexpander_dev_s *dev, uint8_t pin, + FAR bool *value) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + int ret; + int port; + int port_pin; + bool pin_value; + + ret = aw9523b_port_from_pin(pin, &port, &port_pin); + if (ret < 0) + { + return ret; + } + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + +#ifdef CONFIG_AW9523B_SHADOW_MODE + /* Get the value from a shadow register if it's in use. */ + + pin_value = (aw->sreg[AW9523B_REG_INPUT0 + port] >> port_pin) & 0x1; +#else + /* Read the register value from the IO-Expander. */ + + ret = aw9523b_getbit(aw, AW9523B_REG_INPUT0, pin, &pin_value); + *value = pin_value; +#endif + + aw9523b_unlock(aw); + + /* If the pin is inverted, invert the value. */ + + if (AW9523B_GET_INVERT_PIN(aw, pin)) + { + pin_value = !pin_value; + } + + *value = pin_value; + return ret; +} + +#ifdef CONFIG_IOEXPANDER_MULTIPIN + +/**************************************************************************** + * Name: aw9523b_getmultibits + * + * Description: + * Read multiple bits from AW9523B registers. + * + ****************************************************************************/ + +static int aw9523b_getmultibits(FAR struct aw9523b_dev_s *aw, uint8_t addr, + FAR const uint8_t *pins, FAR bool *values, + int count) +{ + uint8_t buf[2]; + int ret = OK; + int i; + int pin; + int port; + int port_pin; + + ret = aw9523b_writeread(aw, &addr, 1, buf, 2); + if (ret < 0) + { + return ret; + } + +#ifdef CONFIG_AW9523B_SHADOW_MODE + /* Save the new register value in the shadow register. */ + + aw->sreg[addr] = buf[0]; + aw->sreg[addr + 1] = buf[1]; +#endif + + /* Read the requested bits. */ + + for (i = 0; i < count; i++) + { + pin = pins[i]; + + ret = aw9523b_port_from_pin(pin, &port, &port_pin); + if (ret < 0) + { + break; + } + + values[i] = (buf[port] >> port_pin) & 1; + + if (AW9523B_GET_INVERT_PIN(aw, pin)) + { + /* If the pin is inverted, invert the value. */ + + values[i] = !values[i]; + } + } + + return ret; +} + +/**************************************************************************** + * Name: aw9523b_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 aw9523b_multiwritepin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, + FAR const bool *values, + int count) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + uint8_t addr = AW9523B_REG_OUTPUT0; + uint8_t buf[AW9523B_NUM_PORTS + 1]; + int ret; + int i; + int pin; + int port; + int port_pin; + bool pin_value; + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + 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_AW9523B_SHADOW_MODE + ret = aw9523b_writeread(aw, &addr, 1, &buf[1], 2); + if (ret < 0) + { + aw9523b_unlock(aw); + return ret; + } +#else + /* In Shadow-Mode we "read" the pin status from the shadow registers. */ + + buf[1] = aw->sreg[addr]; + buf[2] = aw->sreg[addr + 1]; +#endif + + /* Apply the user defined changes. */ + + for (i = 0; i < count; i++) + { + pin = pins[i]; + + ret = aw9523b_port_from_pin(pin, &port, &port_pin); + if (ret < 0) + { + aw9523b_unlock(aw); + return ret; + } + + pin_value = values[i]; + if (AW9523B_GET_INVERT_PIN(aw, pin)) + { + /* If the pin is inverted, invert the value. */ + + pin_value = !pin_value; + } + + SET_BIT(buf[port + 1], port_pin, pin_value); + } + + /* Now write back the new pins states. */ + + buf[0] = addr; + ret = aw9523b_write(aw, buf, sizeof(buf)); + if (ret < 0) + { + goto errout; + } + +#ifdef CONFIG_AW9523B_SHADOW_MODE + /* Save the new register values in the shadow register. */ + + aw->sreg[addr] = buf[1]; + aw->sreg[addr + 1] = buf[2]; +#endif + +#ifdef CONFIG_AW9523B_LED_ENABLE + /* If the pin is an LED, set the LED current. */ + + for (i = 0; i < count; i++) + { + pin = pins[i]; + + if (AW9523B_GET_IS_LED(aw, pin)) + { + /* Set the LED current. */ + + uint8_t dim_reg = aw9523b_dimming_reg_from_pin(pin); + uint8_t led_current = values[i] ? aw->led_current[pin] : 0; + + ret = aw9523b_write_reg(aw, dim_reg, led_current); + if (ret < 0) + { + goto errout; + } + + AW9523B_SET_OUTPUT_IS_ON(aw, pin, values[i]); + } + } +#endif + +errout: + aw9523b_unlock(aw); + return ret; +} + +/**************************************************************************** + * Name: aw9523b_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 aw9523b_multireadpin(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, + FAR bool *values, + int count) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + int ret; + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + + ret = aw9523b_getmultibits(aw, AW9523B_REG_INPUT0, + pins, values, count); + aw9523b_unlock(aw); + return ret; +} + +/**************************************************************************** + * Name: aw9523b_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 aw9523b_multireadbuf(FAR struct ioexpander_dev_s *dev, + FAR const uint8_t *pins, + FAR bool *values, + int count) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + int ret; + + /* Get exclusive access to the AW9523B */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + return ret; + } + + ret = aw9523b_getmultibits(aw, AW9523B_REG_INPUT0, + pins, values, count); + aw9523b_unlock(aw); + return ret; +} + +#endif + +#ifdef CONFIG_AW9523B_INT_ENABLE + +/**************************************************************************** + * Name: aw9523b_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 *aw9523b_attach(FAR struct ioexpander_dev_s *dev, + ioe_pinset_t pinset, ioe_callback_t callback, + FAR void *arg) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + FAR void *handle = NULL; + int i; + int ret; + + /* Get exclusive access to the AW9523B. */ + + ret = aw9523b_lock(aw); + if (ret < 0) + { + set_errno(-ret); + return NULL; + } + + /* Find an available in entry in the callback table. */ + + for (i = 0; i < CONFIG_AW9523B_INT_NCALLBACKS; i++) + { + /* Is this entry available (i.e., no callback attached). */ + + if (aw->cb[i].cbfunc == NULL) + { + /* Yes.. use this entry. */ + + aw->cb[i].pinset = pinset; + aw->cb[i].cbfunc = callback; + aw->cb[i].cbarg = arg; + handle = &aw->cb[i]; + break; + } + } + + aw9523b_unlock(aw); + + if (i >= CONFIG_AW9523B_INT_NCALLBACKS) + { + /* No available entry found. */ + + set_errno(ENOSPC); + return NULL; + } + + return handle; +} + +/**************************************************************************** + * Name: aw9523b_detach + * + * Description: + * Detach and disable a pin interrupt callback function. + * + * Input Parameters: + * dev - Device-specific state data + * handle - The non-NULL opaque value returned by aw9523b_attach() + * + * Returned Value: + * 0 on success, else a negative error code + * + ****************************************************************************/ + +static int aw9523b_detach(FAR struct ioexpander_dev_s *dev, FAR void *handle) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)dev; + FAR struct aw9523b_callback_s *cb = + (FAR struct aw9523b_callback_s *)handle; + + DEBUGASSERT(aw != NULL && cb != NULL); + DEBUGASSERT((uintptr_t)cb >= (uintptr_t)&aw->cb[0] && + (uintptr_t)cb <= + (uintptr_t)&aw->cb[CONFIG_AW9523B_INT_NCALLBACKS - 1]); + UNUSED(aw); + + cb->pinset = 0; + cb->cbfunc = NULL; + cb->cbarg = NULL; + return OK; +} + +/**************************************************************************** + * Name: aw9523b_irqworker + * + * Description: + * Handle GPIO interrupt events (this function actually executes in the + * context of the worker thread). + * + ****************************************************************************/ + +static void aw9523b_irqworker(void *arg) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_dev_s *)arg; + uint8_t addr = AW9523B_REG_INPUT0; + uint8_t buf[2]; + ioe_pinset_t pinset; + int ret; + int i; + + /* Read inputs. */ + + ret = aw9523b_writeread(aw, &addr, 1, buf, 2); + if (ret == OK) + { +#ifdef CONFIG_AW9523B_SHADOW_MODE + /* Don't forget to update the shadow registers at this point. */ + + aw->sreg[addr] = buf[0]; + aw->sreg[addr + 1] = buf[1]; +#endif + /* Create a 16-bit pinset */ + + pinset = ((unsigned int)buf[1] << 8) | buf[0]; + + /* Perform pin interrupt callbacks */ + + for (i = 0; i < CONFIG_AW9523B_INT_NCALLBACKS; i++) + { + /* Is this entry valid (i.e., callback attached)? If so, did + * any of the requested pin interrupts occur? + */ + + if (aw->cb[i].cbfunc != NULL) + { + /* Did any of the requested pin interrupts occur? */ + + ioe_pinset_t match = pinset & aw->cb[i].pinset; + if (match != 0) + { + /* Yes.. perform the callback. */ + + aw->cb[i].cbfunc(&aw->dev, match, + aw->cb[i].cbarg); + } + } + } + } + + /* Re-enable interrupts. */ + + aw->config->enable(aw->config, TRUE); +} + +/**************************************************************************** + * Name: aw9523b_interrupt + * + * Description: + * Handle GPIO interrupt events (this function executes in the + * context of the interrupt). + * + ****************************************************************************/ + +static int aw9523b_interrupt(int irq, FAR void *context, FAR void *arg) +{ + FAR struct aw9523b_dev_s *aw = (FAR struct aw9523b_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 aw9523b_irqworker() when the work is + * completed. + */ + + if (work_available(&aw->work)) + { + aw->config->enable(aw->config, FALSE); + work_queue(HPWORK, &aw->work, aw9523b_irqworker, + (FAR void *)aw, 0); + } + + return OK; +} + +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: aw9523b_initialize + * + * Description: + * Initialize a AW9523B I2C device. + * + * Note that the AW9523B has hardware gotchas that need to be understood + * when you design around it. + * + * 1) Port 0 (pins 0-7) defaults to open drain on reset, so if you want + * to use those pins as push-pull outputs, you need to set the option + * IOEXPANDER_OPTION_NONGENERIC with AW9523B_OPTION_PORT0_PUSH_PULL=1 + * + * 2) Port 1 (pins 8-15) is push-pull only, if configured as outputs. + * + * 3) The default direction and state of pins on reset is dependent on the + * hardware sub-address, according to the following table: + * + * +-----------------------------------------------------------------+ + * | AD1 | AD0 | subaddr | I2C addr | P0-3 | P4-7 | P8-11 | P12-15 | + * +-----------------------------------------------------------------+ + * | 0 | 0 | 0 | 0x58 | out 0 | out 0 | out 0 | out 0 | + * | 0 | 1 | 1 | 0x59 | in | out 0 | out 1 | out 0 | + * | 1 | 0 | 2 | 0x5a | out 0 | in | out 0 | out 1 | + * | 1 | 1 | 3 | 0x5b | in | in | out 1 | out 1 | + * +-----------------------------------------------------------------+ + * + * This annoying default can't be turned off, and your hardware has + * to be designed around it. + * + ****************************************************************************/ + +FAR struct ioexpander_dev_s *aw9523b_initialize( + FAR struct i2c_master_s *i2cdev, + FAR struct aw9523b_config_s *config) +{ + FAR struct aw9523b_dev_s *aw; + int i; + + DEBUGASSERT(i2cdev != NULL && config != NULL); + +#ifdef CONFIG_AW9523B_MULTIPLE + /* Allocate the device state structure. */ + + aw = (FAR struct aw9523b_dev_s *) + kmm_zalloc(sizeof(struct aw9523b_dev_s)); + if (!aw) + { + return NULL; + } + + /* And save the device structure in the list of AW9523B so that we can + * find it later. + */ + + aw->flink = g_aw9523blist; + g_aw9523blist = aw; + +#else + /* Use the one-and-only AW9523B driver instance. */ + + aw = &g_aw9523b; +#endif + + /* Initialize the device state structure. */ + + aw->i2c = i2cdev; + aw->dev.ops = &g_aw9523b_ops; + aw->config = config; + +#ifdef CONFIG_AW9523B_INT_ENABLE + aw->config->attach(aw->config, aw9523b_interrupt, aw); + aw->config->enable(aw->config, TRUE); +#endif + +#ifdef CONFIG_AW9523B_SHADOW_MODE + /* Initialize the shadow registers. */ + + for (i = 0; i < AW9523B_NUM_SHADOW_REGS; i++) + { + aw->sreg[i] = 0; + } + + /* All pins are GPIOs by default, not LEDs. */ + + aw->sreg[AW9523B_REG_LEDMODE0] = AW9523B_LEDMODE_ALL_GPIO; + aw->sreg[AW9523B_REG_LEDMODE1] = AW9523B_LEDMODE_ALL_GPIO; +#endif + +#ifdef CONFIG_AW9523B_LED_ENABLE + /* LED output configuration. */ + + { + static uint16_t default_outputs[4] = + { + AW9523B_DEFAULT_OUT_0, + AW9523B_DEFAULT_OUT_1, + AW9523B_DEFAULT_OUT_2, + AW9523B_DEFAULT_OUT_3 + }; + + if (aw->config->sub_address > 3) + { + aw->config->sub_address = 0; + } + + /* Initialize the LED dimming values. */ + + for (i = 0; i < AW9523B_GPIO_NPINS; i++) + { + aw->led_current[i] = AW9523B_LED_DEFAULT_DIMMING; + } + + aw->is_led_bitset = 0; + aw->output_is_on_bitset = default_outputs[aw->config->sub_address]; + } +#endif + + nxsem_init(&aw->exclsem, 0, 1); + return &aw->dev; +} + +#endif /* CONFIG_IOEXPANDER_AW9523B */ diff --git a/drivers/ioexpander/aw9523b.h b/drivers/ioexpander/aw9523b.h new file mode 100644 index 0000000000..b614e88288 --- /dev/null +++ b/drivers/ioexpander/aw9523b.h @@ -0,0 +1,188 @@ +/**************************************************************************** + * drivers/ioexpander/aw9523b.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. + * + ****************************************************************************/ + +/* References: + * "16 Multi-function LED Driver and GPIO Controller with I2C Interface", + * May 2016, v1.1.1, Shanghai Awinic Technology Co., Ltd. + * Derived from the PCA9555 driver. + */ + +#ifndef __DRIVERS_IOEXPANDER_AW9523B_H +#define __DRIVERS_IOEXPANDER_AW9523B_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(CONFIG_IOEXPANDER) && defined(CONFIG_IOEXPANDER_AW9523B) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Prerequisites: + * CONFIG_I2C + * I2C support is required + * CONFIG_IOEXPANDER + * Enables support for the AW9523B I/O expander + * + * CONFIG_IOEXPANDER_AW9523B + * Enables support for the AW9523B driver (Needs CONFIG_INPUT) + * CONFIG_AW9523B_MULTIPLE + * Can be defined to support multiple AW9523B devices on board. + * CONFIG_AW9523B_INT_ENABLE + * Enables support for pin on-change interrupts. + * CONFIG_AW9523B_INT_NCALLBACKS + * Maximum number of supported pin interrupt callbacks. + */ + +#ifdef CONFIG_AW9523B_INT_ENABLE +# ifndef CONFIG_AW9523B_INT_NCALLBACKS +# define CONFIG_AW9523B_INT_NCALLBACKS 4 +# endif +# ifndef CONFIG_SCHED_WORKQUEUE +# error Work queue support required. CONFIG_SCHED_WORKQUEUE must be selected. +# endif +#endif + +#undef CONFIG_AW9523B_REFCNT + +/* Driver support ***********************************************************/ + +/* This format is used to construct the /dev/input[n] device driver path. + * It defined here + * so that it will be used consistently in all places. + */ + +/* AW9523B Resources ********************************************************/ + +#ifndef CONFIG_I2C +#error "CONFIG_I2C is required by AW9523B" +#endif + +#define AW9523B_MAXDEVS 4 + +/* AW9523B Registers ********************************************************/ + +#define AW9523B_I2C_ADDR_BASE 0x58 /* 7-bit base address for AW9523B */ + +/* AW9523B register addresses */ + +#define AW9523B_REG_INPUT0 0x00 +#define AW9523B_REG_INPUT1 0x01 +#define AW9523B_REG_OUTPUT0 0x02 +#define AW9523B_REG_OUTPUT1 0x03 +#define AW9523B_REG_CONFIG0 0x04 +#define AW9523B_REG_CONFIG1 0x05 +#define AW9523B_REG_INT0 0x06 +#define AW9523B_REG_INT1 0x07 +#define AW9523B_REG_ID 0x10 +#define AW9523B_REG_GCR 0x11 +#define AW9523B_REG_LEDMODE0 0x12 +#define AW9523B_REG_LEDMODE1 0x13 +#define AW9523B_REG_DIM_P1_0 0x20 +#define AW9523B_REG_DIM_P0_0 0x24 +#define AW9523B_REG_DIM_P1_4 0x2c +#define AW9523B_REG_RESET 0x7f + +#define AW9523B_REG_GCR_DIMMING_MASK 0x03 + +/* The number of registers to shadow if we're using shadow mode */ + +#define AW9523B_NUM_SHADOW_REGS 0x14 + +/* LED default current = 5mA. 5mA / 37mA * 255 = 34 */ + +#define AW9523B_LED_DEFAULT_DIMMING 34 + +/* Default output values for each of the subaddresses. */ + +#define AW9523B_DEFAULT_OUT_0 0x0000 +#define AW9523B_DEFAULT_OUT_1 0x0f00 +#define AW9523B_DEFAULT_OUT_2 0xf000 +#define AW9523B_DEFAULT_OUT_3 0xff00 + +/* All pins are GPIOs by default, not LEDs. */ + +#define AW9523B_LEDMODE_ALL_GPIO 0xff + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +#ifdef CONFIG_AW9523B_INT_ENABLE +/* This type represents on registered pin interrupt callback */ + +struct aw9523b_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 AW9523B driver */ + +struct aw9523b_dev_s +{ + struct ioexpander_dev_s dev; /* Nested structure to allow casting as public gpio + * expander. */ +#ifdef CONFIG_AW9523B_SHADOW_MODE + uint8_t sreg[AW9523B_NUM_SHADOW_REGS]; /* Shadowed registers of the AW9523B */ +#endif +#ifdef CONFIG_AW9523B_MULTIPLE + FAR struct aw9523b_dev_s *flink; /* Supports a singly linked list of drivers */ +#endif + FAR struct aw9523b_config_s *config; /* Board configuration data */ + FAR struct i2c_master_s *i2c; /* Saved I2C driver instance */ + sem_t exclsem; /* Mutual exclusion */ + +#ifdef CONFIG_AW9523B_INT_ENABLE + struct work_s work; /* Supports the interrupt handling "bottom half" */ + + /* Saved callback information for each I/O expander client */ + + struct aw9523b_callback_s cb[CONFIG_AW9523B_INT_NCALLBACKS]; +#endif +#ifdef CONFIG_AW9523B_LED_ENABLE + ioe_pinset_t output_is_on_bitset; /* Indicates if each output is active */ + ioe_pinset_t is_led_bitset; /* Indicates if each pin is a LED */ + uint8_t led_current[AW9523B_GPIO_NPINS]; /* LED current for each pin */ +#endif + ioe_pinset_t invert_pin; /* Pins that are inverted (either input or output) */ +}; + +#endif /* CONFIG_IOEXPANDER && CONFIG_IOEXPANDER_AW9523B */ +#endif /* __DRIVERS_IOEXPANDER_AW9523B_H */ diff --git a/include/nuttx/ioexpander/aw9523b.h b/include/nuttx/ioexpander/aw9523b.h new file mode 100644 index 0000000000..8441c5c51a --- /dev/null +++ b/include/nuttx/ioexpander/aw9523b.h @@ -0,0 +1,177 @@ +/**************************************************************************** + * include/nuttx/ioexpander/aw9523b.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + * + ****************************************************************************/ + +/* References: + * "16-bit I2C-bus and SMBus I/O port with interrupt product datasheet", + * Rev. 08 - 22 October 2009, NXP + */ + +#ifndef __INCLUDE_NUTTX_IOEXPANDER_AW9523B_H +#define __INCLUDE_NUTTX_IOEXPANDER_AW9523B_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define AW9523B_GPIO_NPINS 16 /* All pins can be used as GPIOs */ +#define AW9523B_ID 0x23 /* Device ID */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* A reference to a structure of this type must be passed to the AW9523B + * driver when the driver is instantiated. This structure provides + * information about the configuration of the AW9523B and provides some + * board-specific hooks. + * + * Memory for this structure is provided by the caller. It is not copied by + * the driver and is presumed to persist while the driver is active. The + * memory must be writeable because, under certain circumstances, the driver + * may modify the frequency. + */ + +struct aw9523b_config_s +{ + /* Device characterization */ + + uint8_t sub_address; /* configured via the ad1 and ad0 pins. 0-3 */ + uint32_t frequency; /* I2C or SPI frequency */ + +#ifdef CONFIG_AW9523B_INT_ENABLE + uint8_t irq; /* IRQ number for the device */ + + /* If multiple AW9523B devices are supported, then an IRQ number must + * be provided for each so that their interrupts can be distinguished. + */ + + /* IRQ/GPIO access callbacks. These operations all hidden behind + * callbacks to isolate the AW9523B driver from differences in GPIO + * interrupt handling by varying boards and MCUs. + * + * attach - Attach the AW9523B interrupt handler to the GPIO interrupt + * enable - Enable or disable the GPIO interrupt + */ + + CODE int (*attach)(FAR struct aw9523b_config_s *state, xcpt_t isr, + FAR void *arg); + CODE void (*enable)(FAR struct aw9523b_config_s *state, bool enable); +#endif +}; + +/* AW9523B-specific options */ + +enum aw9523b_option_e +{ + AW9523B_OPTION_SOFTWARE_RESET = 1, /* Software reset the device */ + AW9523B_OPTION_PORT0_PUSH_PULL, /* Set port 0 as push-pull */ + AW9523B_OPTION_READ_ID, /* Read the ID register */ +#ifdef CONFIG_AW9523B_LED_ENABLE + AW9523B_OPTION_DIMMING, /* Set the dimming range for LEDs */ + AW9523B_OPTION_LED_CURRENT, /* Set the LED current for a pin */ +#endif +}; +typedef enum aw9523b_option_e aw9523b_option_t; + +/* Values for AW9523B_OPTION_* */ + +#define AW9523B_OPTION_DIMMING_100 0 +#define AW9523B_OPTION_DIMMING_75 1 +#define AW9523B_OPTION_DIMMING_50 2 +#define AW9523B_OPTION_DIMMING_25 3 + +#define AW9523B_OPTION_P0_PP_OPEN_DRAIN 0 +#define AW9523B_OPTION_P0_PP_PUSH_PULL 1 + +#define AW9523B_OPTION_LED_CURRENT_MAX 255 /* Max. LED current setting */ + +/* aw9523b-specific option structure + * + * Options: + * port0_push_pull - Set port 0 as push-pull vs open-drain. + * 1 = push-pull, 0 = open-drain. + * id - Returned by AW9523B_OPTION_READ_ID. + * dimming - Dimming range for LEDs. 0-3. + * 0 = max. 1 = 0.75x. 2 = 0.5x. 3 = 0.25x. + * led_current - LED current 0-255. + * Total current = 37mA * dimming * led_current/255. + */ + +struct aw9523b_nongeneric_option_s +{ + aw9523b_option_t command; /* Option command */ + union + { + uint8_t port0_push_pull; + uint8_t id; +#ifdef CONFIG_AW9523B_LED_ENABLE + uint8_t dimming; + uint8_t led_current; +#endif + } value; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: aw9523b_initialize + * + * Description: + * Instantiate and configure the AW9523B device driver to use the provided + * I2C device + * instance. + * + * Input Parameters: + * dev - An I2C driver instance + * minor - The device i2c address + * config - Persistent board configuration data + * + * Returned Value: + * an ioexpander_dev_s instance on success, NULL on failure. + * + ****************************************************************************/ + +FAR struct ioexpander_dev_s *aw9523b_initialize(FAR struct i2c_master_s *dev, + FAR struct aw9523b_config_s *config); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_IOEXPANDER_AW9523B_H */