diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index e9f7b7f0a8..97ab401878 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -39,6 +39,7 @@ zephyr_library_sources_ifdef(CONFIG_GPIO_EOS_S3 gpio_eos_s3.c) zephyr_library_sources_ifdef(CONFIG_GPIO_RCAR gpio_rcar.c) zephyr_library_sources_ifdef(CONFIG_GPIO_CY8C95XX gpio_cy8c95xx.c) 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_SHELL gpio_shell.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e9f81bc381..309027fc7e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -93,4 +93,6 @@ source "drivers/gpio/Kconfig.cy8c95xx" source "drivers/gpio/Kconfig.creg_gpio" +source "drivers/gpio/Kconfig.stmpe1600" + endif # GPIO diff --git a/drivers/gpio/Kconfig.stmpe1600 b/drivers/gpio/Kconfig.stmpe1600 new file mode 100644 index 0000000000..84efa48841 --- /dev/null +++ b/drivers/gpio/Kconfig.stmpe1600 @@ -0,0 +1,17 @@ +# STMPE1600 GPIO configuration options + +# Copyright (c) 2021 Titouan Christophe +# SPDX-License-Identifier: Apache-2.0 + +menuconfig GPIO_STMPE1600 + bool "STMPE1600 I2C-based GPIO chip" + depends on I2C + help + Enable driver for STMPE1600 I2C-based GPIO chip. + +config GPIO_STMPE1600_INIT_PRIORITY + int "Init priority" + default 70 + depends on GPIO_STMPE1600 + help + Device driver initialization priority. diff --git a/drivers/gpio/gpio_stmpe1600.c b/drivers/gpio/gpio_stmpe1600.c new file mode 100644 index 0000000000..6490efc016 --- /dev/null +++ b/drivers/gpio/gpio_stmpe1600.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2021 Titouan Christophe + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT st_stmpe1600 + +/** + * @file Driver for STMPE1600 I2C-based GPIO driver. + */ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "gpio_utils.h" + +#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL +#include +LOG_MODULE_REGISTER(stmpe1600); + +/* Register definitions */ +#define REG_CHIP_ID_LSB 0x00 /* const 0x00 */ +#define REG_CHIP_ID_MSB 0x01 /* const 0x16 */ +#define REG_VERSION_ID 0x02 /* Revision number (const 0x01) */ +#define REG_SYS_CTRL 0x03 /* Reset and interrupt control */ +#define REG_IEGPIOR_LSB 0x08 /* GPIO interrupt enable register */ +#define REG_IEGPIOR_MSB 0x09 +#define REG_ISGPIOR_LSB 0x0A /* GPIO interrupt status register */ +#define REG_ISGPIOR_MSB 0x0B +#define REG_GPMR_LSB 0x10 /* GPIO monitor pin state register */ +#define REG_GPMR_MSB 0x11 +#define REG_GPSR_LSB 0x12 /* GPIO set pin state register */ +#define REG_GPSR_MSB 0x13 +#define REG_GPDR_LSB 0x14 /* GPIO set pin direction register */ +#define REG_GPDR_MSB 0x15 +#define REG_GPPIR_LSB 0x16 /* GPIO polarity inversion register */ +#define REG_GPPIR_MSB 0x17 + + +/** Configuration data */ +struct stmpe1600_config { + /* gpio_driver_config needs to be first */ + struct gpio_driver_config common; + + /** Master I2C device */ + const struct device *i2c_bus; + + /** The slave address of the chip */ + uint16_t i2c_slave_addr; +}; + +/** Runtime driver data */ +struct stmpe1600_drvdata { + /* gpio_driver_data needs to be first */ + struct gpio_driver_data common; + + /** Driver lock */ + struct k_sem lock; + + /* Registers cache */ + uint16_t GPSR; + uint16_t GPDR; +}; + +static int write_reg16(const struct stmpe1600_config * const config, uint8_t reg, uint16_t value) +{ + uint16_t transfer_data = sys_cpu_to_le16(value); + int ret; + + LOG_DBG("STMPE1600[0x%02X]: write REG[0x%02X..0x%02X] = %04x", + config->i2c_slave_addr, reg, reg + 1, value); + + ret = i2c_burst_write(config->i2c_bus, config->i2c_slave_addr, reg, + (uint8_t *)&transfer_data, sizeof(transfer_data)); + + if (ret != 0) { + LOG_ERR("STMPE1600[0x%02X]: write error REG[0x%02X..0x%02X]: %d", + config->i2c_slave_addr, reg, reg + 1, ret); + } + return ret; +} + +static int read_reg16(const struct stmpe1600_config * const config, uint8_t reg, uint16_t *value) +{ + uint16_t transfer_data; + int ret; + + LOG_DBG("STMPE1600[0x%02X]: read REG[0x%02X..0x%02X]", + config->i2c_slave_addr, reg, reg + 1); + + ret = i2c_burst_read(config->i2c_bus, config->i2c_slave_addr, reg, + (uint8_t *) &transfer_data, sizeof(transfer_data)); + + if (ret != 0) { + LOG_ERR("STMPE1600[0x%02X]: read error REG[0x%02X..0x%02X]: %d", + config->i2c_slave_addr, reg, reg + 1, ret); + } else { + *value = sys_le16_to_cpu(transfer_data); + LOG_DBG("STMPE1600[0x%02X]: read REG[0x%02X..0x%02X] => %04x", + config->i2c_slave_addr, reg, reg + 1, *value); + } + return ret; +} + +static int set_pin_dir(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) +{ + struct stmpe1600_drvdata *const drvdata = (struct stmpe1600_drvdata *const) dev->data; + bool set_state = false; + uint16_t GPDR = drvdata->GPDR; + uint16_t GPSR = drvdata->GPSR; + int ret; + + if ((flags & GPIO_OUTPUT) != 0U) { + GPDR |= BIT(pin); + if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0U) { + GPSR |= BIT(pin); + set_state = true; + } else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0U) { + GPSR &= ~BIT(pin); + set_state = true; + } + } else { + GPDR &= ~BIT(pin); + } + + if (set_state) { + ret = write_reg16(dev->config, REG_GPSR_LSB, GPSR); + if (ret != 0) { + return ret; + } + drvdata->GPSR = GPSR; + } + + ret = write_reg16(dev->config, REG_GPDR_LSB, GPDR); + if (ret == 0) { + drvdata->GPDR = GPDR; + } + return ret; +} + +static int stmpe1600_configure(const struct device *dev, + gpio_pin_t pin, gpio_flags_t flags) +{ + const struct stmpe1600_config *const config = dev->config; + struct stmpe1600_drvdata *const drvdata = (struct stmpe1600_drvdata *const) dev->data; + int ret; + + /* No support for disconnected pin */ + if ((flags & (GPIO_INPUT | GPIO_OUTPUT)) == GPIO_DISCONNECTED) { + return -ENOTSUP; + } + + /* STMPE1600 does not support any of these modes */ + if ((flags & (GPIO_SINGLE_ENDED | GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0) { + return -ENOTSUP; + } + + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drvdata->lock, K_FOREVER); + + ret = set_pin_dir(dev, pin, flags); + if (ret != 0) { + LOG_ERR("STMPE1600[0x%X]: error setting pin direction (%d)", + config->i2c_slave_addr, ret); + } + + k_sem_give(&drvdata->lock); + return ret; +} + +static int stmpe1600_port_get_raw(const struct device *dev, uint32_t *value) +{ + struct stmpe1600_drvdata *const drvdata = (struct stmpe1600_drvdata *const) dev->data; + uint16_t reg_value; + int ret; + + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drvdata->lock, K_FOREVER); + ret = read_reg16(dev->config, REG_GPMR_LSB, ®_value); + k_sem_give(&drvdata->lock); + + if (ret == 0) { + *value = reg_value; + } + + return ret; +} + +static int stmpe1600_port_set_masked_raw(const struct device *dev, + uint32_t mask, uint32_t value) +{ + struct stmpe1600_drvdata *const drvdata = (struct stmpe1600_drvdata *const)dev->data; + uint16_t GPSR; + int ret; + + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drvdata->lock, K_FOREVER); + GPSR = (drvdata->GPSR & ~mask) | (mask & value); + ret = write_reg16(dev->config, REG_GPSR_LSB, GPSR); + if (ret == 0) { + drvdata->GPSR = GPSR; + } + k_sem_give(&drvdata->lock); + + return ret; +} + +static int stmpe1600_port_set_bits_raw(const struct device *dev, uint32_t mask) +{ + return stmpe1600_port_set_masked_raw(dev, mask, mask); +} + +static int stmpe1600_port_clear_bits_raw(const struct device *dev, uint32_t mask) +{ + return stmpe1600_port_set_masked_raw(dev, mask, 0); +} + +static int stmpe1600_port_toggle_bits(const struct device *dev, uint32_t mask) +{ + struct stmpe1600_drvdata *const drvdata = (struct stmpe1600_drvdata *const)dev->data; + uint16_t GPSR; + int ret; + + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + k_sem_take(&drvdata->lock, K_FOREVER); + GPSR = drvdata->GPSR ^ mask; + ret = write_reg16(dev->config, REG_GPSR_LSB, GPSR); + if (ret == 0) { + drvdata->GPSR = GPSR; + } + k_sem_give(&drvdata->lock); + return ret; +} + +static int stmpe1600_pin_interrupt_configure(const struct device *dev, + gpio_pin_t pin, + enum gpio_int_mode mode, + enum gpio_int_trig trig) +{ + return -ENOTSUP; +} + +static int stmpe1600_init(const struct device *dev) +{ + const struct stmpe1600_config *const config = dev->config; + struct stmpe1600_drvdata *const drvdata = (struct stmpe1600_drvdata *const) dev->data; + uint16_t chip_id; + int ret; + + LOG_DBG("STMPE1600[0x%02X] init", config->i2c_slave_addr); + + k_sem_init(&drvdata->lock, 1, 1); + + ret = read_reg16(dev->config, REG_CHIP_ID_LSB, &chip_id); + if (ret != 0) { + LOG_ERR("STMPE1600[0x%02X]: Unable to read Chip ID", config->i2c_slave_addr); + return ret; + } + + if (chip_id != 0x1600) { + LOG_ERR("STMPE1600[0x%02X]: Invalid Chip ID", config->i2c_slave_addr); + return -EINVAL; + } + + ret = read_reg16(dev->config, REG_GPSR_LSB, &drvdata->GPSR); + if (ret != 0) { + LOG_ERR("STMPE1600[0x%02X]: Unable to read GPSR", config->i2c_slave_addr); + return ret; + } + + ret = read_reg16(dev->config, REG_GPDR_LSB, &drvdata->GPDR); + if (ret != 0) { + LOG_ERR("STMPE1600[0x%02X]: Unable to read GPDR", config->i2c_slave_addr); + } + + return ret; +} + +static const struct gpio_driver_api stmpe1600_drv_api = { + .pin_configure = stmpe1600_configure, + .port_get_raw = stmpe1600_port_get_raw, + .port_set_masked_raw = stmpe1600_port_set_masked_raw, + .port_set_bits_raw = stmpe1600_port_set_bits_raw, + .port_clear_bits_raw = stmpe1600_port_clear_bits_raw, + .port_toggle_bits = stmpe1600_port_toggle_bits, + .pin_interrupt_configure = stmpe1600_pin_interrupt_configure, +}; + +#define STMPE1600_INIT(inst) \ + static struct stmpe1600_config stmpe1600_##inst##_config = { \ + .common = { .port_pin_mask = 0xffff }, \ + .i2c_bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .i2c_slave_addr = DT_INST_REG_ADDR(inst), \ + }; \ + \ + static struct stmpe1600_drvdata stmpe1600_##inst##_drvdata; \ + \ + DEVICE_DT_INST_DEFINE(inst, stmpe1600_init, NULL, \ + &stmpe1600_##inst##_drvdata, \ + &stmpe1600_##inst##_config, \ + POST_KERNEL, \ + CONFIG_GPIO_STMPE1600_INIT_PRIORITY, \ + &stmpe1600_drv_api); + +DT_INST_FOREACH_STATUS_OKAY(STMPE1600_INIT) diff --git a/dts/bindings/gpio/st,stmpe1600.yaml b/dts/bindings/gpio/st,stmpe1600.yaml new file mode 100644 index 0000000000..60249d2368 --- /dev/null +++ b/dts/bindings/gpio/st,stmpe1600.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2021 Titouan Christophe +# SPDX-License-Identifier: Apache-2.0 + +description: STMPE1600 I2C-based GPIO expander + +compatible: "st,stmpe1600" + +include: [gpio-controller.yaml, i2c-device.yaml] + +properties: + label: + required: true + + "#gpio-cells": + const: 2 + +gpio-cells: + - pin + - flags