diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index bf0ee29000b..92f90b98946 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_GPIO_ESP32 gpio_esp32.c) zephyr_library_sources_ifdef(CONFIG_GPIO_SIFIVE gpio_sifive.c) zephyr_library_sources_ifdef(CONFIG_GPIO_GECKO gpio_gecko.c) zephyr_library_sources_ifdef(CONFIG_GPIO_IMX gpio_imx.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_MCP23S17 gpio_mcp23s17.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MCUX gpio_mcux.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MCUX_IGPIO gpio_mcux_igpio.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MCUX_LPC gpio_mcux_lpc.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 826b6c44277..41afa00cc23 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -24,6 +24,8 @@ source "drivers/gpio/Kconfig.dw" source "drivers/gpio/Kconfig.pca95xx" +source "drivers/gpio/Kconfig.mcp23s17" + source "drivers/gpio/Kconfig.mcux" source "drivers/gpio/Kconfig.mcux_igpio" diff --git a/drivers/gpio/Kconfig.mcp23s17 b/drivers/gpio/Kconfig.mcp23s17 new file mode 100644 index 00000000000..81523ff81e4 --- /dev/null +++ b/drivers/gpio/Kconfig.mcp23s17 @@ -0,0 +1,21 @@ +# MCP23S17 GPIO configuration options + +# Copyright (c) 2020 Geanix ApS +# SPDX-License-Identifier: Apache-2.0 + +menuconfig GPIO_MCP23S17 + bool "MCP23S17 SPI-based GPIO chip" + depends on SPI + select HAS_DTS_GPIO + help + Enable driver for MCP23S17 SPI-based GPIO chip. + +if GPIO_MCP23S17 + +config GPIO_MCP23S17_INIT_PRIORITY + int "Init priority" + default 75 + help + Device driver initialization priority. + +endif #GPIO_MCP23S17 diff --git a/drivers/gpio/gpio_mcp23s17.c b/drivers/gpio/gpio_mcp23s17.c new file mode 100644 index 00000000000..0a9745168d5 --- /dev/null +++ b/drivers/gpio/gpio_mcp23s17.c @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2020 Geanix ApS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT microchip_mcp23s17 + +/** + * @file Driver for MCP23S17 SPI-based GPIO driver. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "gpio_utils.h" +#include "gpio_mcp23s17.h" + +#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL +#include +LOG_MODULE_REGISTER(gpio_mcp23s17); + +/** + * @brief Read both port 0 and port 1 registers of certain register function. + * + * Given the register in reg, read the pair of port 0 and port 1. + * + * @param dev Device struct of the MCP23S17. + * @param reg Register to read (the PORTA of the pair of registers). + * @param buf Buffer to read data into. + * + * @return 0 if successful, failed otherwise. + */ +static int read_port_regs(struct device *dev, u8_t reg, u16_t *buf) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + int ret; + u16_t port_data; + + u8_t addr = MCP23S17_ADDR | MCP23S17_READBIT; + u8_t buffer_tx[4] = { addr, reg, 0, 0 }; + + const struct spi_buf tx_buf = { + .buf = buffer_tx, + .len = 4, + }; + const struct spi_buf_set tx = { + .buffers = &tx_buf, + .count = 1, + }; + const struct spi_buf rx_buf[2] = { + { + .buf = NULL, + .len = 2 + }, + { + .buf = (u8_t *)&port_data, + .len = 2 + } + }; + const struct spi_buf_set rx = { + .buffers = rx_buf, + .count = ARRAY_SIZE(rx_buf), + }; + + ret = spi_transceive(drv_data->spi, &drv_data->spi_cfg, &tx, &rx); + if (ret) { + LOG_DBG("spi_transceive FAIL %d\n", ret); + return ret; + } + + *buf = sys_le16_to_cpu(port_data); + + LOG_DBG("MCP23S17: Read: REG[0x%X] = 0x%X, REG[0x%X] = 0x%X", + reg, (*buf & 0xFF), (reg + 1), (*buf >> 8)); + + return 0; +} + +/** + * @brief Write both port 0 and port 1 registers of certain register function. + * + * Given the register in reg, write the pair of port 0 and port 1. + * + * @param dev Device struct of the MCP23S17. + * @param reg Register to write into (the PORTA of the pair of registers). + * @param buf Buffer to write data from. + * + * @return 0 if successful, failed otherwise. + */ +static int write_port_regs(struct device *dev, u8_t reg, u16_t value) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + int ret; + u16_t port_data; + + LOG_DBG("MCP23S17: Write: REG[0x%X] = 0x%X, REG[0x%X] = 0x%X", + reg, (value & 0xFF), (reg + 1), (value >> 8)); + + port_data = sys_cpu_to_le16(value); + + u8_t addr = MCP23S17_ADDR; + u8_t buffer_tx[2] = { addr, reg }; + + const struct spi_buf tx_buf[2] = { + { + .buf = buffer_tx, + .len = 2, + }, + { + .buf = (u8_t *)&port_data, + .len = 2, + } + }; + const struct spi_buf_set tx = { + .buffers = tx_buf, + .count = ARRAY_SIZE(tx_buf), + }; + + ret = spi_write(drv_data->spi, &drv_data->spi_cfg, &tx); + if (ret) { + LOG_DBG("spi_write FAIL %d\n", ret); + return ret; + } + + return 0; +} + +/** + * @brief Setup the pin direction (input or output) + * + * @param dev Device struct of the MCP23S17 + * @param pin The pin number + * @param flags Flags of pin or port + * + * @return 0 if successful, failed otherwise + */ +static int setup_pin_dir(struct device *dev, u32_t pin, int flags) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + u16_t *dir = &drv_data->reg_cache.iodir; + u16_t *output = &drv_data->reg_cache.gpio; + int ret; + + if ((flags & GPIO_OUTPUT) != 0U) { + if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0U) { + *output |= BIT(pin); + } else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0U) { + *output &= ~BIT(pin); + } + *dir &= ~BIT(pin); + } else { + *dir |= BIT(pin); + } + + ret = write_port_regs(dev, REG_GPIO_PORTA, *output); + if (ret != 0) { + return ret; + } + + ret = write_port_regs(dev, REG_IODIR_PORTA, *dir); + + return ret; +} + +/** + * @brief Setup the pin pull up/pull down status + * + * @param dev Device struct of the MCP23S17 + * @param pin The pin number + * @param flags Flags of pin or port + * + * @return 0 if successful, failed otherwise + */ +static int setup_pin_pullupdown(struct device *dev, u32_t pin, int flags) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + u16_t port; + int ret; + + /* Setup pin pull up or pull down */ + port = drv_data->reg_cache.gppu; + + /* pull down == 0, pull up == 1 */ + if ((flags & GPIO_PULL_DOWN) != 0U) { + return -ENOTSUP; + } + + WRITE_BIT(port, pin, (flags & GPIO_PULL_UP) != 0U); + + ret = write_port_regs(dev, REG_GPPU_PORTA, port); + if (ret == 0) { + drv_data->reg_cache.gppu = port; + } + + return ret; +} + +static int mcp23s17_config(struct device *dev, + gpio_pin_t pin, gpio_flags_t flags) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + int ret; + + /* Can't do SPI bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drv_data->lock, K_FOREVER); + + if ((flags & GPIO_OPEN_DRAIN) != 0U) { + ret = -ENOTSUP; + goto done; + }; + + ret = setup_pin_dir(dev, pin, flags); + if (ret) { + LOG_ERR("MCP23S17: error setting pin direction (%d)", ret); + goto done; + } + + ret = setup_pin_pullupdown(dev, pin, flags); + if (ret) { + LOG_ERR("MCP23S17: error setting pin pull up/down (%d)", ret); + goto done; + } + +done: + k_sem_give(&drv_data->lock); + return ret; +} + +static int mcp23s17_port_get_raw(struct device *dev, u32_t *value) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + u16_t buf; + int ret; + + /* Can't do SPI bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drv_data->lock, K_FOREVER); + + ret = read_port_regs(dev, REG_GPIO_PORTA, &buf); + if (ret != 0) { + goto done; + } + + *value = buf; + +done: + k_sem_give(&drv_data->lock); + return ret; +} + +static int mcp23s17_port_set_masked_raw(struct device *dev, + u32_t mask, u32_t value) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + u16_t buf; + int ret; + + /* Can't do SPI bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drv_data->lock, K_FOREVER); + + buf = drv_data->reg_cache.gpio; + buf = (buf & ~mask) | (mask & value); + + ret = write_port_regs(dev, REG_GPIO_PORTA, buf); + if (ret == 0) { + drv_data->reg_cache.gpio = buf; + } + + k_sem_give(&drv_data->lock); + + return ret; +} + +static int mcp23s17_port_set_bits_raw(struct device *dev, u32_t mask) +{ + return mcp23s17_port_set_masked_raw(dev, mask, mask); +} + +static int mcp23s17_port_clear_bits_raw(struct device *dev, u32_t mask) +{ + return mcp23s17_port_set_masked_raw(dev, mask, 0); +} + +static int mcp23s17_port_toggle_bits(struct device *dev, u32_t mask) +{ + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + u16_t buf; + int ret; + + /* Can't do SPI bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + k_sem_take(&drv_data->lock, K_FOREVER); + + buf = drv_data->reg_cache.gpio; + buf ^= mask; + + ret = write_port_regs(dev, REG_GPIO_PORTA, buf); + if (ret == 0) { + drv_data->reg_cache.gpio = buf; + } + + k_sem_give(&drv_data->lock); + + return ret; +} + +static int mcp23s17_pin_interrupt_configure(struct device *dev, + gpio_pin_t pin, + enum gpio_int_mode mode, + enum gpio_int_trig trig) +{ + return -ENOTSUP; +} + +static const struct gpio_driver_api api_table = { + .pin_configure = mcp23s17_config, + .port_get_raw = mcp23s17_port_get_raw, + .port_set_masked_raw = mcp23s17_port_set_masked_raw, + .port_set_bits_raw = mcp23s17_port_set_bits_raw, + .port_clear_bits_raw = mcp23s17_port_clear_bits_raw, + .port_toggle_bits = mcp23s17_port_toggle_bits, + .pin_interrupt_configure = mcp23s17_pin_interrupt_configure, +}; + +/** + * @brief Initialization function of MCP23S17 + * + * @param dev Device struct + * @return 0 if successful, failed otherwise. + */ +static int mcp23s17_init(struct device *dev) +{ + const struct mcp23s17_config *const config = + dev->config->config_info; + struct mcp23s17_drv_data *const drv_data = + (struct mcp23s17_drv_data *const)dev->driver_data; + + drv_data->spi = device_get_binding((char *)config->spi_dev_name); + if (!drv_data->spi) { + LOG_DBG("Unable to get SPI device"); + return -ENODEV; + } + + if (config->cs_dev) { + /* handle SPI CS thru GPIO if it is the case */ + drv_data->mcp23s17_cs_ctrl.gpio_dev = + device_get_binding(config->cs_dev); + if (!drv_data->mcp23s17_cs_ctrl.gpio_dev) { + LOG_ERR("Unable to get GPIO SPI CS device"); + return -ENODEV; + } + + drv_data->mcp23s17_cs_ctrl.gpio_pin = config->cs_pin; + drv_data->mcp23s17_cs_ctrl.delay = 0; + + drv_data->spi_cfg.cs = &drv_data->mcp23s17_cs_ctrl; + + LOG_DBG("SPI GPIO CS configured on %s:%u", + config->cs_dev, config->cs_pin); + } + + drv_data->spi_cfg.frequency = config->freq; + drv_data->spi_cfg.operation = (SPI_OP_MODE_MASTER | SPI_MODE_CPOL | + SPI_MODE_CPHA | SPI_WORD_SET(8) | + SPI_LINES_SINGLE); + drv_data->spi_cfg.slave = config->slave; + + k_sem_init(&drv_data->lock, 1, 1); + + return 0; +} + +/* Initialization for MCP23S17_0 */ +#if DT_HAS_DRV_INST(0) +static struct mcp23s17_config mcp23s17_0_config = { + .common = { + .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(0), + }, + .spi_dev_name = DT_INST_BUS_LABEL(0), + .slave = DT_INST_REG_ADDR(0), + .freq = DT_INST_PROP(0, spi_max_frequency), +#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0) + .cs_dev = DT_INST_SPI_DEV_CS_GPIOS_LABEL(0), +#endif +#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0) + .cs_pin = DT_INST_SPI_DEV_CS_GPIOS_PIN(0), +#endif +}; + +static struct mcp23s17_drv_data mcp23s17_0_drvdata = { + /* Default for registers according to datasheet */ + .reg_cache.iodir = 0xFFFF, + .reg_cache.ipol = 0x0, + .reg_cache.gpinten = 0x0, + .reg_cache.defval = 0x0, + .reg_cache.intcon = 0x0, + .reg_cache.iocon = 0x0, + .reg_cache.gppu = 0x0, + .reg_cache.intf = 0x0, + .reg_cache.intcap = 0x0, + .reg_cache.gpio = 0x0, + .reg_cache.olat = 0x0, +}; + +/* This has to init after SPI master */ +DEVICE_AND_API_INIT(mcp23s17_0, DT_INST_LABEL(0), + mcp23s17_init, &mcp23s17_0_drvdata, &mcp23s17_0_config, + POST_KERNEL, CONFIG_GPIO_MCP23S17_INIT_PRIORITY, + &api_table); +#endif /* DT_HAS_DRV_INST(0) */ + +/* Initialization for MCP23S17_1 */ +#if DT_HAS_DRV_INST(1) +static struct mcp23s17_config mcp23s17_1_config = { + .common = { + .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(1), + }, + .spi_dev_name = DT_INST_BUS_LABEL(1), + .slave = DT_INST_REG_ADDR(1), + .freq = DT_INST_PROP(1, spi_max_frequency), +#if DT_INST_SPI_DEV_HAS_CS_GPIOS(1) + .cs_dev = DT_INST_SPI_DEV_CS_GPIOS_LABEL(1), +#endif +#if DT_INST_SPI_DEV_HAS_CS_GPIOS(1) + .cs_pin = DT_INST_SPI_DEV_CS_GPIOS_PIN(1), +#endif +}; + +static struct mcp23s17_drv_data mcp23s17_1_drvdata = { + /* Default for registers according to datasheet */ + .reg_cache.iodir = 0xFFFF, + .reg_cache.ipol = 0x0, + .reg_cache.gpinten = 0x0, + .reg_cache.defval = 0x0, + .reg_cache.intcon = 0x0, + .reg_cache.iocon = 0x0, + .reg_cache.gppu = 0x0, + .reg_cache.intf = 0x0, + .reg_cache.intcap = 0x0, + .reg_cache.gpio = 0x0, + .reg_cache.olat = 0x0, +}; + +/* This has to init after SPI master */ +DEVICE_AND_API_INIT(mcp23s17_1, DT_INST_LABEL(1), + mcp23s17_init, &mcp23s17_1_drvdata, &mcp23s17_1_config, + POST_KERNEL, CONFIG_GPIO_MCP23S17_INIT_PRIORITY, + &api_table); +#endif /* DT_HAS_DRV_INST(0) */ diff --git a/drivers/gpio/gpio_mcp23s17.h b/drivers/gpio/gpio_mcp23s17.h new file mode 100644 index 00000000000..0ce9de1d502 --- /dev/null +++ b/drivers/gpio/gpio_mcp23s17.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 Geanix ApS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file Header file for the MCP23S17 driver. + */ + +#ifndef ZEPHYR_DRIVERS_GPIO_GPIO_MCP23S17_H_ +#define ZEPHYR_DRIVERS_GPIO_GPIO_MCP23S17_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Register definitions */ +#define REG_IODIR_PORTA 0x00 +#define REG_IODIR_PORTB 0x01 +#define REG_IPOL_PORTA 0x02 +#define REG_IPOL_PORTB 0x03 +#define REG_GPINTEN_PORTA 0x04 +#define REG_GPINTEN_PORTB 0x05 +#define REG_DEFVAL_PORTA 0x06 +#define REG_DEFVAL_PORTB 0x07 +#define REG_INTCON_PORTA 0x08 +#define REG_INTCON_PORTB 0x09 +#define REG_GPPU_PORTA 0x0C +#define REG_GPPU_PORTB 0x0D +#define REG_INTF_PORTA 0x0E +#define REG_INTF_PORTB 0x0F +#define REG_INTCAP_PORTA 0x10 +#define REG_INTCAP_PORTB 0x11 +#define REG_GPIO_PORTA 0x12 +#define REG_GPIO_PORTB 0x13 +#define REG_OLAT_PORTA 0x14 +#define REG_OLAT_PORTB 0x15 + +#define MCP23S17_ADDR 0x40 +#define MCP23S17_READBIT 0x01 + +/** Configuration data */ +struct mcp23s17_config { + /* gpio_driver_data needs to be first */ + struct gpio_driver_config common; + + const char *const spi_dev_name; + const u16_t slave; + const u32_t freq; + const char *const cs_dev; + const u32_t cs_pin; +}; + +/** Runtime driver data */ +struct mcp23s17_drv_data { + /* gpio_driver_data needs to be first */ + struct gpio_driver_config data; + + /** Master SPI device */ + struct device *spi; + struct spi_config spi_cfg; + struct spi_cs_control mcp23s17_cs_ctrl; + + struct k_sem lock; + + struct { + u16_t iodir; + u16_t ipol; + u16_t gpinten; + u16_t defval; + u16_t intcon; + u16_t iocon; + u16_t gppu; + u16_t intf; + u16_t intcap; + u16_t gpio; + u16_t olat; + } reg_cache; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_GPIO_GPIO_MCP23S17_H_ */ diff --git a/dts/bindings/gpio/microchip,mcp23s17.yaml b/dts/bindings/gpio/microchip,mcp23s17.yaml new file mode 100644 index 00000000000..cdcd8f10a7d --- /dev/null +++ b/dts/bindings/gpio/microchip,mcp23s17.yaml @@ -0,0 +1,29 @@ +# +# Copyright (c) 2020 Geanix ApS +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: > + This is a representation of the Microchip MCP23S17 SPI Gpio Expander. + +compatible: "microchip,mcp23s17" + +include: [gpio-controller.yaml, spi-device.yaml] + +properties: + label: + required: true + + "#gpio-cells": + const: 2 + + ngpios: + type: int + required: true + const: 16 + description: Number of gpios supported + +gpio-cells: + - pin + - flags