diff --git a/app/drivers/CMakeLists.txt b/app/drivers/CMakeLists.txt index c1625f2f..c58ada48 100644 --- a/app/drivers/CMakeLists.txt +++ b/app/drivers/CMakeLists.txt @@ -1,6 +1,7 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +add_subdirectory(gpio) add_subdirectory(kscan) add_subdirectory(sensor) -add_subdirectory(display) \ No newline at end of file +add_subdirectory(display) diff --git a/app/drivers/Kconfig b/app/drivers/Kconfig index 5bcc522d..c57ed334 100644 --- a/app/drivers/Kconfig +++ b/app/drivers/Kconfig @@ -1,6 +1,7 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT +rsource "gpio/Kconfig" rsource "kscan/Kconfig" rsource "sensor/Kconfig" -rsource "display/Kconfig" \ No newline at end of file +rsource "display/Kconfig" diff --git a/app/drivers/gpio/CMakeLists.txt b/app/drivers/gpio/CMakeLists.txt new file mode 100644 index 00000000..b879b238 --- /dev/null +++ b/app/drivers/gpio/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_library_named(zmk__drivers__gpio) +zephyr_library_include_directories(${CMAKE_SOURCE_DIR}/include) + +zephyr_library_sources_ifdef(CONFIG_GPIO_MCP23017 gpio_mcp23017.c) +zephyr_library_sources_ifndef(CONFIG_GPIO_MCP23017 ${ZEPHYR_BASE}/misc/empty_file.c) diff --git a/app/drivers/gpio/Kconfig b/app/drivers/gpio/Kconfig new file mode 100644 index 00000000..09f9609f --- /dev/null +++ b/app/drivers/gpio/Kconfig @@ -0,0 +1 @@ +rsource "Kconfig.mcp23017" diff --git a/app/drivers/gpio/Kconfig.mcp23017 b/app/drivers/gpio/Kconfig.mcp23017 new file mode 100644 index 00000000..4953b924 --- /dev/null +++ b/app/drivers/gpio/Kconfig.mcp23017 @@ -0,0 +1,21 @@ +# MCP23017 GPIO configuration options + +# Copyright (c) 2021 Pete Johanson +# SPDX-License-Identifier: Apache-2.0 + +menuconfig GPIO_MCP23017 + bool "MCP23017 I2C-based GPIO chip" + depends on I2C + select HAS_DTS_GPIO + help + Enable driver for MCP23017 I2C-based GPIO chip. + +if GPIO_MCP23017 + +config GPIO_MCP23017_INIT_PRIORITY + int "Init priority" + default 75 + help + Device driver initialization priority. + +endif #GPIO_MCP23017 diff --git a/app/drivers/gpio/gpio_mcp23017.c b/app/drivers/gpio/gpio_mcp23017.c new file mode 100644 index 00000000..eb38ce50 --- /dev/null +++ b/app/drivers/gpio/gpio_mcp23017.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2020 Geanix ApS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT microchip_mcp23017 + +/** + * @file Driver for MCP23017 SPI-based GPIO driver. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "gpio_mcp23017.h" + +#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL +#include +LOG_MODULE_REGISTER(gpio_mcp23017); + +/** + * @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 MCP23017. + * @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(const struct device *dev, uint8_t reg, uint16_t *buf) { + const struct mcp23017_config *const config = dev->config; + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + int ret; + uint16_t port_data; + + uint8_t addr = config->slave; + + ret = i2c_burst_read(drv_data->i2c, addr, reg, (uint8_t *)&port_data, sizeof(port_data)); + if (ret) { + LOG_DBG("i2c_write_read FAIL %d\n", ret); + return ret; + } + + *buf = sys_le16_to_cpu(port_data); + + LOG_DBG("MCP23017: 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 MCP23017. + * @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(const struct device *dev, uint8_t reg, uint16_t value) { + const struct mcp23017_config *const config = dev->config; + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + int ret; + uint16_t port_data; + + LOG_DBG("MCP23017: 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); + + ret = i2c_burst_write(drv_data->i2c, config->slave, reg, (uint8_t *)&port_data, + sizeof(port_data)); + if (ret) { + LOG_DBG("i2c_write FAIL %d\n", ret); + return ret; + } + + return 0; +} + +/** + * @brief Setup the pin direction (input or output) + * + * @param dev Device struct of the MCP23017 + * @param pin The pin number + * @param flags Flags of pin or port + * + * @return 0 if successful, failed otherwise + */ +static int setup_pin_dir(const struct device *dev, uint32_t pin, int flags) { + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + uint16_t *dir = &drv_data->reg_cache.iodir; + uint16_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 MCP23017 + * @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 mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + uint16_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 mcp23017_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->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("MCP23017: error setting pin direction (%d)", ret); + goto done; + } + + ret = setup_pin_pullupdown(dev, pin, flags); + if (ret) { + LOG_ERR("MCP23017: error setting pin pull up/down (%d)", ret); + goto done; + } + +done: + k_sem_give(&drv_data->lock); + return ret; +} + +static int mcp23017_port_get_raw(const struct device *dev, uint32_t *value) { + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + uint16_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 mcp23017_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) { + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + uint16_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 mcp23017_port_set_bits_raw(const struct device *dev, uint32_t mask) { + return mcp23017_port_set_masked_raw(dev, mask, mask); +} + +static int mcp23017_port_clear_bits_raw(const struct device *dev, uint32_t mask) { + return mcp23017_port_set_masked_raw(dev, mask, 0); +} + +static int mcp23017_port_toggle_bits(const struct device *dev, uint32_t mask) { + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + uint16_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 mcp23017_pin_interrupt_configure(const 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 = mcp23017_config, + .port_get_raw = mcp23017_port_get_raw, + .port_set_masked_raw = mcp23017_port_set_masked_raw, + .port_set_bits_raw = mcp23017_port_set_bits_raw, + .port_clear_bits_raw = mcp23017_port_clear_bits_raw, + .port_toggle_bits = mcp23017_port_toggle_bits, + .pin_interrupt_configure = mcp23017_pin_interrupt_configure, +}; + +/** + * @brief Initialization function of MCP23017 + * + * @param dev Device struct + * @return 0 if successful, failed otherwise. + */ +static int mcp23017_init(const struct device *dev) { + const struct mcp23017_config *const config = dev->config; + struct mcp23017_drv_data *const drv_data = (struct mcp23017_drv_data *const)dev->data; + + drv_data->i2c = device_get_binding((char *)config->i2c_dev_name); + if (!drv_data->i2c) { + LOG_DBG("Unable to get i2c device"); + return -ENODEV; + } + + k_sem_init(&drv_data->lock, 1, 1); + + return 0; +} + +#define MCP23017_INIT(inst) \ + static struct mcp23017_config mcp23017_##inst##_config = { \ + .i2c_dev_name = DT_INST_BUS_LABEL(inst), \ + .slave = DT_INST_REG_ADDR(inst), \ + \ + }; \ + \ + static struct mcp23017_drv_data mcp23017_##inst##_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_DT_INST_DEFINE(inst, mcp23017_init, device_pm_control_nop, &mcp23017_##inst##_drvdata, \ + &mcp23017_##inst##_config, POST_KERNEL, \ + CONFIG_GPIO_MCP23017_INIT_PRIORITY, &api_table); + +DT_INST_FOREACH_STATUS_OKAY(MCP23017_INIT) diff --git a/app/drivers/gpio/gpio_mcp23017.h b/app/drivers/gpio/gpio_mcp23017.h new file mode 100644 index 00000000..026565cd --- /dev/null +++ b/app/drivers/gpio/gpio_mcp23017.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 Geanix ApS, Pete Johanson + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file Header file for the MCP23017 driver. + */ + +#ifndef ZEPHYR_DRIVERS_GPIO_GPIO_MCP23017_H_ +#define ZEPHYR_DRIVERS_GPIO_GPIO_MCP23017_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 MCP23017_ADDR 0x40 +#define MCP23017_READBIT 0x01 + +/** Configuration data */ +struct mcp23017_config { + /* gpio_driver_data needs to be first */ + struct gpio_driver_config common; + + const char *const i2c_dev_name; + const uint16_t slave; +}; + +/** Runtime driver data */ +struct mcp23017_drv_data { + /* gpio_driver_data needs to be first */ + struct gpio_driver_config data; + + /** Master SPI device */ + const struct device *i2c; + + struct k_sem lock; + + struct { + uint16_t iodir; + uint16_t ipol; + uint16_t gpinten; + uint16_t defval; + uint16_t intcon; + uint16_t iocon; + uint16_t gppu; + uint16_t intf; + uint16_t intcap; + uint16_t gpio; + uint16_t olat; + } reg_cache; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_GPIO_GPIO_MCP23017_H_ */ diff --git a/app/drivers/zephyr/dts/bindings/gpio/microchip,mcp23017.yaml b/app/drivers/zephyr/dts/bindings/gpio/microchip,mcp23017.yaml new file mode 100644 index 00000000..75e19c49 --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/gpio/microchip,mcp23017.yaml @@ -0,0 +1,29 @@ +# +# Copyright (c) 2020 Geanix ApS +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: > + This is a representation of the Microchip MCP23017 I2C Gpio Expander. + +compatible: "microchip,mcp23017" + +include: [gpio-controller.yaml, i2c-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