From 2403ecb046123a7adf88281e5c7d4819e3d366d6 Mon Sep 17 00:00:00 2001 From: Purdea Andrei Date: Sun, 22 Jan 2023 08:26:58 +0200 Subject: [PATCH] implement gpio_165 driver This is an input-only gpio driver for use with 74x165 style parallel-input shift registers, (using SPI peripheral) The user can choose to either use cs-gpios of spi or load-gpios for connecting to the SH/LD pin of the shift register. The difference is that cs-gpios will keep the input latch enabled while idle, and load-gpios will instead pulse the SH/LD signal before the register content is shifted. In both cases the gpio should be configured GPIO_ACTIVE_HIGH. --- app/drivers/gpio/CMakeLists.txt | 1 + app/drivers/gpio/Kconfig | 1 + app/drivers/gpio/Kconfig.165 | 25 ++ app/drivers/gpio/gpio_165.c | 235 ++++++++++++++++++ .../dts/bindings/gpio/zmk,gpio-165.yaml | 34 +++ 5 files changed, 296 insertions(+) create mode 100644 app/drivers/gpio/Kconfig.165 create mode 100644 app/drivers/gpio/gpio_165.c create mode 100644 app/drivers/zephyr/dts/bindings/gpio/zmk,gpio-165.yaml diff --git a/app/drivers/gpio/CMakeLists.txt b/app/drivers/gpio/CMakeLists.txt index 0756ed38..80028377 100644 --- a/app/drivers/gpio/CMakeLists.txt +++ b/app/drivers/gpio/CMakeLists.txt @@ -4,5 +4,6 @@ zephyr_library_named(zmk__drivers__gpio) zephyr_library_include_directories(${CMAKE_SOURCE_DIR}/include) +zephyr_library_sources_ifdef(CONFIG_GPIO_165 gpio_165.c) zephyr_library_sources_ifdef(CONFIG_GPIO_595 gpio_595.c) zephyr_library_sources_ifdef(CONFIG_GPIO_MAX7318 gpio_max7318.c) diff --git a/app/drivers/gpio/Kconfig b/app/drivers/gpio/Kconfig index 54b30590..f6844d4c 100644 --- a/app/drivers/gpio/Kconfig +++ b/app/drivers/gpio/Kconfig @@ -3,3 +3,4 @@ menuconfig ZMK_DRIVERS_GPIO rsource "Kconfig.max7318" rsource "Kconfig.595" +rsource "Kconfig.165" diff --git a/app/drivers/gpio/Kconfig.165 b/app/drivers/gpio/Kconfig.165 new file mode 100644 index 00000000..881bc05d --- /dev/null +++ b/app/drivers/gpio/Kconfig.165 @@ -0,0 +1,25 @@ +# 165 GPIO configuration options + +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +DT_COMPAT_ZMK_GPIO_165 := zmk,gpio-165 + +menuconfig GPIO_165 + bool "165 Parallel Input Shift Register SPI driver" + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_GPIO_165)) + depends on SPI + select HAS_DTS_GPIO + select ZMK_DRIVERS_GPIO + help + Enable driver for 165 shift register chip using SPI. + +if GPIO_165 + +config GPIO_165_INIT_PRIORITY + int "Init priority" + default 75 + help + Device driver initialization priority. + +endif #GPIO_165 diff --git a/app/drivers/gpio/gpio_165.c b/app/drivers/gpio/gpio_165.c new file mode 100644 index 00000000..6b625157 --- /dev/null +++ b/app/drivers/gpio/gpio_165.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_gpio_165 + +/** + * @file Driver for 165 parallel-input shift register, SPI-based GPIO driver. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL + +#define LOAD_GPIOS_LEN(n) DT_INST_PROP_LEN(n, load_gpios) +#define LOAD_GPIO_CFG_INIT(idx, inst_idx) \ + GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst_idx), load_gpios, idx) + +#include +LOG_MODULE_REGISTER(gpio_165); + +/** Configuration data */ +struct reg_165_config { + /* gpio_driver_data needs to be first */ + struct gpio_driver_config common; + + struct spi_dt_spec bus; + + uint8_t ngpios; + + const struct gpio_dt_spec *load_gpios; + size_t load_gpios_len; +}; + +/** Runtime driver data */ +struct reg_165_drv_data { + /* gpio_driver_data needs to be first */ + struct gpio_driver_config data; + + struct k_sem lock; +}; + +static int reg_165_read_registers(const struct device *dev, uint32_t *value) { + const struct reg_165_config *config = dev->config; + int ret = 0; + + uint8_t nread = config->ngpios / 8; + uint32_t reg_data = 0; + + /* Allow a sequence of 1-4 registers in sequence, lowest byte is for the first in the chain */ + const struct spi_buf rx_buf[1] = {{ + .buf = ((uint8_t *)®_data) + (4 - nread), + .len = nread, + }}; + + const struct spi_buf_set rx = { + .buffers = rx_buf, + .count = ARRAY_SIZE(rx_buf), + }; + + for (int i = 0; i < config->load_gpios_len; i++) { + int err = gpio_pin_set_dt(&config->load_gpios[i], 0); + if (err) { + LOG_ERR("Unable to set pin %u on %s to idle value '0'", config->load_gpios[i].pin, + config->load_gpios[i].port->name); + return err; + } + } + + for (int i = 0; i < config->load_gpios_len; i++) { + int err = gpio_pin_set_dt(&config->load_gpios[i], 1); + if (err) { + LOG_ERR("Unable to set pin %u on %s to idle value '1'", config->load_gpios[i].pin, + config->load_gpios[i].port->name); + return err; + } + } + + ret = spi_read_dt(&config->bus, &rx); + + *value = sys_be32_to_cpu(reg_data); + + if (ret < 0) { + LOG_ERR("spi_read FAIL %d\n", ret); + return ret; + } + + return 0; +} + +/** + * @brief Setup the pin direction (input or output) + * + * @param dev Device struct of the 165 + * @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) { + if ((flags & GPIO_INPUT) == 0U) { + return -ENOTSUP; + } + + return 0; +} + +static int reg_165_pin_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) { + int ret; + + /* Can't do SPI bus operations from an ISR */ + if (k_is_in_isr()) { + return -EWOULDBLOCK; + } + + if ((flags & GPIO_OPEN_DRAIN) != 0U) { + return -ENOTSUP; + }; + + ret = setup_pin_dir(dev, pin, flags); + if (ret) { + LOG_ERR("165: error setting pin direction (%d)", ret); + } + + return ret; +} + +static int reg_165_port_get_raw(const struct device *dev, uint32_t *value) { + struct reg_165_drv_data *const drv_data = (struct reg_165_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); + + ret = reg_165_read_registers(dev, value); + + k_sem_give(&drv_data->lock); + return ret; +} + +static int reg_165_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) { + return -ENOTSUP; +} + +static int reg_165_port_set_bits_raw(const struct device *dev, uint32_t mask) { return -ENOTSUP; } + +static int reg_165_port_clear_bits_raw(const struct device *dev, uint32_t mask) { return -ENOTSUP; } + +static int reg_165_port_toggle_bits(const struct device *dev, uint32_t mask) { return -ENOTSUP; } + +static const struct gpio_driver_api api_table = { + .pin_configure = reg_165_pin_config, + .port_get_raw = reg_165_port_get_raw, + .port_set_masked_raw = reg_165_port_set_masked_raw, + .port_set_bits_raw = reg_165_port_set_bits_raw, + .port_clear_bits_raw = reg_165_port_clear_bits_raw, + .port_toggle_bits = reg_165_port_toggle_bits, +}; + +/** + * @brief Initialization function of 165 + * + * @param dev Device struct + * @return 0 if successful, failed otherwise. + */ +static int reg_165_init(const struct device *dev) { + const struct reg_165_config *const config = dev->config; + struct reg_165_drv_data *const drv_data = (struct reg_165_drv_data *const)dev->data; + + if (!device_is_ready(config->bus.bus)) { + LOG_ERR("Unable to get SPI bus device"); + return -ENODEV; + } + + for (int i = 0; i < config->load_gpios_len; i++) { + int err = gpio_pin_configure_dt(&config->load_gpios[i], GPIO_OUTPUT); + if (err) { + LOG_ERR("Unable to configure pin %u on %s for output", config->load_gpios[i].pin, + config->load_gpios[i].port->name); + return err; + } + err = gpio_pin_set_dt(&config->load_gpios[i], 1); + if (err) { + LOG_ERR("Unable to set pin %u on %s to idle value '1'", config->load_gpios[i].pin, + config->load_gpios[i].port->name); + return err; + } + } + + k_sem_init(&drv_data->lock, 1, 1); + + return 0; +} + +#define GPIO_PORT_PIN_MASK_FROM_NGPIOS(ngpios) ((gpio_port_pins_t)(((uint64_t)1 << (ngpios)) - 1U)) + +#define GPIO_PORT_PIN_MASK_FROM_DT_INST(inst) \ + GPIO_PORT_PIN_MASK_FROM_NGPIOS(DT_INST_PROP(inst, ngpios)) + +#define REG_165_INIT(n) \ + static const struct gpio_dt_spec load_gpios_##n[] = { \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(n, load_gpios), \ + (LISTIFY(LOAD_GPIOS_LEN(n), LOAD_GPIO_CFG_INIT, (, ), n)), ())}; \ + static struct reg_165_config reg_165_##n##_config = { \ + .common = \ + { \ + .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n), \ + }, \ + .bus = \ + SPI_DT_SPEC_INST_GET(n, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0), \ + .ngpios = DT_INST_PROP(n, ngpios), \ + .load_gpios = load_gpios_##n, \ + .load_gpios_len = ARRAY_SIZE(load_gpios_##n), \ + }; \ + \ + static struct reg_165_drv_data reg_165_##n##_drvdata = {}; \ + \ + /* This has to init after SPI master */ \ + DEVICE_DT_INST_DEFINE(n, reg_165_init, NULL, ®_165_##n##_drvdata, ®_165_##n##_config, \ + POST_KERNEL, CONFIG_GPIO_165_INIT_PRIORITY, &api_table); + +DT_INST_FOREACH_STATUS_OKAY(REG_165_INIT) diff --git a/app/drivers/zephyr/dts/bindings/gpio/zmk,gpio-165.yaml b/app/drivers/zephyr/dts/bindings/gpio/zmk,gpio-165.yaml new file mode 100644 index 00000000..ba552f68 --- /dev/null +++ b/app/drivers/zephyr/dts/bindings/gpio/zmk,gpio-165.yaml @@ -0,0 +1,34 @@ +# +# Copyright (c) 2022 The ZMK Contributors +# +# SPDX-License-Identifier: MIT +# + +description: > + This is a representation of the 165 Parrallel-in Shift Register. + +compatible: "zmk,gpio-165" + +include: [gpio-controller.yaml, spi-device.yaml] + +properties: + "#gpio-cells": + const: 2 + + ngpios: + type: int + required: true + enum: + - 8 + - 16 + - 24 + - 32 + description: Number of gpios supported + + load-gpios: + type: phandle-array + description: GPIO to which the /PL a.k.a. SH/LD pin is connected + +gpio-cells: + - pin + - flags