From ee3f50fdeb16b9f1f0f7c124eb73659545b9a061 Mon Sep 17 00:00:00 2001 From: Imre Lagas Date: Thu, 15 Jul 2021 19:51:36 +0200 Subject: [PATCH] drivers: gpio: Add fxl6408 driver Add a driver for the fxl6408 gpio-expander using gpio api. Origin: original Signed-off-by: Imre Lagas --- drivers/gpio/CMakeLists.txt | 1 + drivers/gpio/Kconfig | 2 + drivers/gpio/Kconfig.fxl6408 | 19 + drivers/gpio/gpio_fxl6408.c | 447 +++++++++++++++++++++++ dts/bindings/gpio/fcs,fxl6408.yaml | 19 + tests/drivers/build_all/gpio/app.overlay | 9 + tests/drivers/build_all/gpio/prj.conf | 1 + 7 files changed, 498 insertions(+) create mode 100644 drivers/gpio/Kconfig.fxl6408 create mode 100644 drivers/gpio/gpio_fxl6408.c create mode 100644 dts/bindings/gpio/fcs,fxl6408.yaml diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index 32112957005..fe94c1ef79c 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -43,6 +43,7 @@ zephyr_library_sources_ifdef(CONFIG_GPIO_SNPS_CREG gpio_creg_gpio.c) zephyr_library_sources_ifdef(CONFIG_GPIO_STMPE1600 gpio_stmpe1600.c) zephyr_library_sources_ifdef(CONFIG_GPIO_XEC_V2 gpio_mchp_xec_v2.c) zephyr_library_sources_ifdef(CONFIG_GPIO_PCA953X gpio_pca953x.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_FXL6408 gpio_fxl6408.c) zephyr_library_sources_ifdef(CONFIG_GPIO_SHELL gpio_shell.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 5f0a68e220a..20237d4c196 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -99,4 +99,6 @@ source "drivers/gpio/Kconfig.stmpe1600" source "drivers/gpio/Kconfig.pca953x" +source "drivers/gpio/Kconfig.fxl6408" + endif # GPIO diff --git a/drivers/gpio/Kconfig.fxl6408 b/drivers/gpio/Kconfig.fxl6408 new file mode 100644 index 00000000000..f8ef5d64c05 --- /dev/null +++ b/drivers/gpio/Kconfig.fxl6408 @@ -0,0 +1,19 @@ +# Copyright (c) 2021 Abel Sensors +# SPDX-License-Identifier: Apache-2.0 + +menuconfig GPIO_FXL6408 + bool "FXL6408 I2C-based GPIO chip" + depends on I2C + help + Enable driver for FXL6408 I2C-based GPIO chip. + +config GPIO_FXL6408_INIT_PRIORITY + int "Init priority" + default 80 + depends on GPIO_FXL6408 + help + Device driver initialization priority. + +module = FXL6408 +module-str = fxl6408 +source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/gpio/gpio_fxl6408.c b/drivers/gpio/gpio_fxl6408.c new file mode 100644 index 00000000000..2e395bae932 --- /dev/null +++ b/drivers/gpio/gpio_fxl6408.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2021 Abel Sensors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "gpio_utils.h" +#include + +LOG_MODULE_REGISTER(fxl6408, CONFIG_FXL6408_LOG_LEVEL); +#define DT_DRV_COMPAT fcs_fxl6408 + +/* Register definitions */ +#define REG_DEVICE_ID_CTRL 0x01 +#define REG_DIRECTION 0x03 +#define REG_OUTPUT 0x05 +#define REG_OUTPUT_HIGH_Z 0x07 +#define REG_INPUT_DEFAULT_STATE 0x09 +#define REG_PUD_EN 0x0B +#define REG_PUD_SEL 0x0D +#define REG_INPUT_VALUE 0x0F +#define REG_INT_MASK 0x11 +#define REG_INT_STATUS 0x13 + +#define SUPPORTED_FLAGS (GPIO_INPUT | GPIO_OUTPUT | GPIO_OUTPUT_INIT_LOW |\ + GPIO_OUTPUT_INIT_HIGH | GPIO_PULL_DOWN | GPIO_PULL_UP |\ + GPIO_ACTIVE_HIGH | GPIO_ACTIVE_LOW | GPIO_INT_DEBOUNCE) + +/** Configuration data*/ +struct gpio_fxl6408_config { + /* gpio_driver_config needs to be first */ + struct gpio_driver_config common; + + /** Master I2C device */ + const struct device *i2c_master; + + /** The slave address of the chip */ + uint16_t i2c_slave_addr; +}; + +/** Runtime driver data */ +struct gpio_fxl6408_drv_data { + /* gpio_driver_data needs to be first */ + struct gpio_driver_data common; + + struct { + uint8_t input; + uint8_t output; + uint8_t dir; + uint8_t high_z; + uint8_t pud_en; + uint8_t pud_sel; + } reg_cache; + + struct k_sem lock; +}; + +/** + * @brief Read the port of certain register function. + * + * @param dev Device struct of the FXL6408. + * @param reg Register to read. + * @param cache Pointer to the cache to be updated after successful read. + * + * @return 0 if successful, failed otherwise. + */ +static int read_port_regs(const struct device *dev, uint8_t reg, uint8_t *cache) +{ + const struct gpio_fxl6408_config *const config = dev->config; + const struct device *i2c_master = config->i2c_master; + uint16_t i2c_addr = config->i2c_slave_addr; + uint8_t port_data; + int ret; + + ret = i2c_reg_read_byte(i2c_master, i2c_addr, reg, &port_data); + if (ret != 0) { + LOG_ERR("Error reading register 0x%X (%d)", reg, ret); + return ret; + } + *cache = port_data; + LOG_DBG("Read: REG[0x%X] = 0x%X", reg, *cache); + + return ret; +} + +/** + * @brief Write to the port registers of certain register function. + * + * @param dev Device struct of the FXL6408. + * @param reg Register to write into. Possible values: REG_DEVICE_ID_CTRL, + * REG_OUTPUT, REG_DIRECTION, REG_PUD_SEL, REG_PUD_EN, REG_OUTPUT_HIGH_Z and + * REG_INPUT_DEFAULT. + * @param cache Pointer to the cache to be updated after successful write. + * @param value New value to set. + * + * @return 0 if successful, failed otherwise. + */ +static int write_port_regs(const struct device *dev, uint8_t reg, + uint8_t *cache, uint8_t value) +{ + const struct gpio_fxl6408_config *const config = dev->config; + const struct device *i2c_master = config->i2c_master; + int ret = 0; + + if (*cache != value) { + ret = i2c_reg_write_byte(i2c_master, config->i2c_slave_addr, + reg, value); + if (ret != 0) { + LOG_ERR("error writing to register 0x%X (%d)", + reg, ret); + return ret; + } + *cache = value; + LOG_DBG("Write: REG[0x%X] = 0x%X", reg, *cache); + } + + return ret; +} + +static inline int update_input_regs(const struct device *dev, uint8_t *buf) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + int ret = read_port_regs(dev, REG_INPUT_VALUE, + &drv_data->reg_cache.input); + *buf = drv_data->reg_cache.input; + + return ret; +} + +static inline int update_output_regs(const struct device *dev, uint8_t value) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + + return write_port_regs(dev, REG_OUTPUT, + &drv_data->reg_cache.output, value); +} + +static inline int update_high_z_regs(const struct device *dev, uint8_t value) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + + return write_port_regs(dev, REG_OUTPUT_HIGH_Z, + &drv_data->reg_cache.high_z, value); +} + +static inline int update_direction_regs(const struct device *dev, uint8_t value) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + + return write_port_regs(dev, REG_DIRECTION, + &drv_data->reg_cache.dir, value); +} + +static inline int update_pul_sel_regs(const struct device *dev, uint8_t value) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + + return write_port_regs(dev, REG_PUD_SEL, + &drv_data->reg_cache.pud_sel, value); +} + +static inline int update_pul_en_regs(const struct device *dev, uint8_t value) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + + return write_port_regs(dev, REG_PUD_EN, + &drv_data->reg_cache.pud_en, value); +} + +static int setup_pin_dir(const struct device *dev, uint32_t pin, int flags) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + uint8_t reg_dir = drv_data->reg_cache.dir; + uint8_t reg_out = drv_data->reg_cache.output; + uint8_t reg_high_z = drv_data->reg_cache.high_z; + int ret; + + if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT) != 0)) { + return -ENOTSUP; + } + + /* Update the driver data to the actual situation of the FXL6408 */ + if (flags & GPIO_OUTPUT) { + if ((flags & GPIO_OUTPUT_INIT_HIGH)) { + reg_out |= BIT(pin); + } else if ((flags & GPIO_OUTPUT_INIT_LOW)) { + reg_out &= ~BIT(pin); + } + reg_dir |= BIT(pin); + reg_high_z &= ~BIT(pin); + } else if (flags & GPIO_INPUT) { + reg_dir &= ~BIT(pin); + reg_high_z &= ~BIT(pin); + } else { + reg_high_z |= BIT(pin); + reg_dir |= BIT(pin); + } + + ret = update_output_regs(dev, reg_out); + if (ret != 0) { + return ret; + } + + ret = update_high_z_regs(dev, reg_high_z); + if (ret != 0) { + return ret; + } + + ret = update_direction_regs(dev, reg_dir); + return ret; +} + +/** + * @brief Setup the pin pull up/pull down status + * + * @param dev Device struct of the FXL6408 + * @param pin The pin number + * @param flags Flags of pin or port + * + * @return 0 if successful, failed otherwise + */ +static int setup_pin_pullupdown(const struct device *dev, uint32_t pin, + int flags) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + uint8_t reg_pud; + int ret; + + /* If disabling pull up/down, there is no need to set the selection + * register. Just go straight to disabling. + */ + if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0U) { + /* Setup pin pull up or pull down */ + reg_pud = drv_data->reg_cache.pud_sel; + + /* Pull down == 0, pull up == 1 */ + WRITE_BIT(reg_pud, pin, (flags & GPIO_PULL_UP) != 0U); + + ret = update_pul_sel_regs(dev, reg_pud); + if (ret != 0) { + return ret; + } + } + + /* Enable/disable pull up/down */ + reg_pud = drv_data->reg_cache.pud_en; + + WRITE_BIT(reg_pud, pin, + (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0U); + + ret = update_pul_en_regs(dev, reg_pud); + return ret; +} + +static int gpio_fxl6408_pin_config(const struct device *dev, gpio_pin_t pin, + gpio_flags_t flags) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + int ret; + + /* Check if supported flag is set */ + if ((flags & ~SUPPORTED_FLAGS) != 0) { + return -ENOTSUP; + } + + /* Can't do I2C bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + k_sem_take(&drv_data->lock, K_FOREVER); + + ret = setup_pin_dir(dev, pin, flags); + if (ret != 0) { + LOG_ERR("error setting pin direction (%d)", ret); + goto done; + } + + ret = setup_pin_pullupdown(dev, pin, flags); + if (ret) { + LOG_ERR("error setting pin pull up/down (%d)", ret); + goto done; + } + +done: + k_sem_give(&drv_data->lock); + return ret; +} + +static int gpio_fxl6408_port_get_raw(const struct device *dev, uint32_t *value) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + uint8_t buf = 0; + int ret = 0; + + /* Can't do I2C bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drv_data->lock, K_FOREVER); + + ret = update_input_regs(dev, &buf); + if (ret != 0) { + goto done; + } + *value = buf; + +done: + k_sem_give(&drv_data->lock); + return ret; +} + +static int gpio_fxl6408_port_set_masked_raw(const struct device *dev, + uint32_t mask, uint32_t value) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + uint8_t reg_out; + int ret; + + /* Can't do I2C bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drv_data->lock, K_FOREVER); + + reg_out = drv_data->reg_cache.output; + reg_out = (reg_out & ~mask) | (mask & value); + + ret = update_output_regs(dev, reg_out); + + k_sem_give(&drv_data->lock); + + return ret; +} + +static int gpio_fxl6408_port_set_bits_raw(const struct device *dev, + uint32_t mask) +{ + return gpio_fxl6408_port_set_masked_raw(dev, mask, mask); +} + +static int gpio_fxl6408_port_clear_bits_raw(const struct device *dev, + uint32_t mask) +{ + return gpio_fxl6408_port_set_masked_raw(dev, mask, 0); +} + +static int gpio_fxl6408_port_toggle_bits(const struct device *dev, + uint32_t mask) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + uint8_t reg_out; + int ret; + + /* Can't do I2C bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drv_data->lock, K_FOREVER); + + reg_out = drv_data->reg_cache.output; + reg_out ^= mask; + ret = update_output_regs(dev, reg_out); + + k_sem_give(&drv_data->lock); + + return ret; +} + +static int gpio_fxl6408_pin_interrupt_configure(const struct device *port, + gpio_pin_t pin, + enum gpio_int_mode mode, + enum gpio_int_trig trig) +{ + LOG_DBG("Pin interrupts not supported."); + return -ENOTSUP; +} + +int gpio_fxl6408_init(const struct device *dev) +{ + struct gpio_fxl6408_drv_data *const drv_data = + (struct gpio_fxl6408_drv_data *const)dev->data; + const struct gpio_fxl6408_config *const config = dev->config; + + if (!device_is_ready(config->i2c_master)) { + LOG_ERR("%s is not ready", config->i2c_master->name); + return -ENODEV; + } + + k_sem_init(&drv_data->lock, 1, 1); + + return 0; +} + +static const struct gpio_driver_api gpio_fxl_driver = { + .pin_configure = gpio_fxl6408_pin_config, + .port_get_raw = gpio_fxl6408_port_get_raw, + .port_set_masked_raw = gpio_fxl6408_port_set_masked_raw, + .port_set_bits_raw = gpio_fxl6408_port_set_bits_raw, + .port_clear_bits_raw = gpio_fxl6408_port_clear_bits_raw, + .port_toggle_bits = gpio_fxl6408_port_toggle_bits, + .pin_interrupt_configure = gpio_fxl6408_pin_interrupt_configure +}; + +#define GPIO_FXL6408_DEVICE_INSTANCE(inst) \ + static const struct gpio_fxl6408_config gpio_fxl6408_##inst##_cfg = { \ + .common = { \ + .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst),\ + }, \ + .i2c_slave_addr = DT_INST_REG_ADDR(inst), \ + .i2c_master = DEVICE_DT_GET(DT_INST_BUS(inst)) \ + }; \ +\ + static struct gpio_fxl6408_drv_data gpio_fxl6408_##inst##_drvdata = { \ + .reg_cache = { \ + .input = 0x0, \ + .output = 0x00, \ + .dir = 0x0, \ + .high_z = 0xFF, \ + .pud_en = 0xFF, \ + .pud_sel = 0x0 \ + } \ + }; \ +\ + DEVICE_DT_INST_DEFINE(inst, gpio_fxl6408_init, NULL, \ + &gpio_fxl6408_##inst##_drvdata, \ + &gpio_fxl6408_##inst##_cfg, POST_KERNEL, \ + CONFIG_GPIO_FXL6408_INIT_PRIORITY, \ + &gpio_fxl_driver); + +DT_INST_FOREACH_STATUS_OKAY(GPIO_FXL6408_DEVICE_INSTANCE) diff --git a/dts/bindings/gpio/fcs,fxl6408.yaml b/dts/bindings/gpio/fcs,fxl6408.yaml new file mode 100644 index 00000000000..72cf18a15f1 --- /dev/null +++ b/dts/bindings/gpio/fcs,fxl6408.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2021 Abel Sensors +# SPDX-License-Identifier: Apache-2.0 + +description: FXL6408 I2C-based GPIO expander + +compatible: "fcs,fxl6408" + +include: [gpio-controller.yaml, i2c-device.yaml] + +properties: + label: + required: true + + "#gpio-cells": + const: 2 + +gpio-cells: + - pin + - flags diff --git a/tests/drivers/build_all/gpio/app.overlay b/tests/drivers/build_all/gpio/app.overlay index 32f4670ab29..d67def8a49c 100644 --- a/tests/drivers/build_all/gpio/app.overlay +++ b/tests/drivers/build_all/gpio/app.overlay @@ -71,6 +71,15 @@ ngpios = <8>; nint-gpios = <&test_gpio 0 0>; }; + test_i2c_fxl6408: fxl6408@43 { + status = "okay"; + compatible = "fcs,fxl6408"; + label = "FXL6408"; + reg = <0x43>; + ngpios = <8>; + #gpio-cells = <2>; + gpio-controller; + }; }; test_spi: spi@33334444 { diff --git a/tests/drivers/build_all/gpio/prj.conf b/tests/drivers/build_all/gpio/prj.conf index c0530d07384..65e6a50e054 100644 --- a/tests/drivers/build_all/gpio/prj.conf +++ b/tests/drivers/build_all/gpio/prj.conf @@ -5,5 +5,6 @@ CONFIG_I2C=y CONFIG_GPIO_SX1509B=y CONFIG_GPIO_PCAL6408A=y CONFIG_GPIO_PCA953X=y +CONFIG_GPIO_FXL6408=y CONFIG_SPI=y CONFIG_GPIO_MCP23S17=y