diff --git a/CMakeLists.txt b/CMakeLists.txt index e81b0dcb..5a4bec04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,21 @@ cmake_minimum_required(VERSION 3.13.1) list(APPEND BOARD_ROOT ${CMAKE_SOURCE_DIR}) list(APPEND DTS_ROOT ${CMAKE_SOURCE_DIR}) +# Add our custom Zephyr module for drivers w/ syscalls, etc. +list(APPEND DTS_ROOT ${CMAKE_SOURCE_DIR}/drivers/zephyr) + +list(APPEND ZEPHYR_EXTRA_MODULES + ${CMAKE_CURRENT_SOURCE_DIR}/drivers +) +list(APPEND SYSCALL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/drivers/zephyr) + include(cmake/keymap.cmake) find_package(Zephyr) project(zmk) + if(EXISTS ${KEYMAP_DIR}/keymap.c) - message(STATUS "ADDING THE KEYMAP SOURCE") target_sources(app PRIVATE ${KEYMAP_DIR}/keymap.c) endif() diff --git a/boards/native_posix.conf b/boards/native_posix.conf index acccd99f..4efd71d9 100644 --- a/boards/native_posix.conf +++ b/boards/native_posix.conf @@ -1,6 +1,6 @@ -CONFIG_ZMK_KSCAN_MOCK_DRIVER=y CONFIG_KSCAN=n -CONFIG_KSCAN_GPIO=n +CONFIG_ZMK_KSCAN_MOCK_DRIVER=y +CONFIG_ZMK_KSCAN_GPIO_DRIVER=n CONFIG_GPIO=n CONFIG_ZMK_BLE=n CONFIG_LOG=y diff --git a/drivers/zephyr/CMakeLists.txt b/drivers/zephyr/CMakeLists.txt new file mode 100644 index 00000000..03a97c6e --- /dev/null +++ b/drivers/zephyr/CMakeLists.txt @@ -0,0 +1,9 @@ +if(CONFIG_ZMK_KSCAN_GPIO_DRIVER) + # Add hello_world_driver.h to the set of global include paths. + zephyr_include_directories(.) + + zephyr_library() + zephyr_library_sources( + kscan_gpio.c + ) +endif() diff --git a/drivers/zephyr/Kconfig b/drivers/zephyr/Kconfig new file mode 100644 index 00000000..2dcb3a82 --- /dev/null +++ b/drivers/zephyr/Kconfig @@ -0,0 +1,11 @@ +config ZMK_KSCAN_GPIO_DRIVER + bool "Enable GPIO kscan driver to simulate key presses" + default y + select GPIO + +config ZMK_KSCAN_INIT_PRIORITY + int "Keyboard scan driver init priority" + default 40 + help + Keyboard scan device driver initialization priority. + diff --git a/drivers/zephyr/dts/bindings/gpio-kscan.yaml b/drivers/zephyr/dts/bindings/gpio-kscan.yaml new file mode 100644 index 00000000..0698c3ef --- /dev/null +++ b/drivers/zephyr/dts/bindings/gpio-kscan.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2020, Pete Johanson +# SPDX-License-Identifier: Apache-2.0 + +description: GPIO keyboard matrix controller + +compatible: "gpio-kscan" + +include: kscan.yaml + +properties: + row-gpios: + type: phandle-array + required: true + col-gpios: + type: phandle-array + required: true + debounce-period: + type: int + default: 5 + diode-direction: + type: string + default: row2col + enum: + - row2col + - col2row diff --git a/drivers/zephyr/kscan_gpio.c b/drivers/zephyr/kscan_gpio.c new file mode 100644 index 00000000..71ea68b5 --- /dev/null +++ b/drivers/zephyr/kscan_gpio.c @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2020 Peter Johanson + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT gpio_kscan + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define MATRIX_NODE_ID DT_DRV_INST(0) +#define MATRIX_ROWS DT_PROP_LEN(MATRIX_NODE_ID, row_gpios) +#define MATRIX_COLS DT_PROP_LEN(MATRIX_NODE_ID, col_gpios) + +#if DT_ENUM_IDX(MATRIX_NODE_ID, diode_direction) == 0 +#define SCAN_ROW_TO_COLUMN +#else +#define SCAN_COL_TO_ROW +#endif + +struct kscan_gpio_item_config +{ + char *label; + gpio_pin_t pin; + gpio_flags_t flags; +}; + +struct kscan_gpio_config +{ + u8_t debounce_period; + struct kscan_gpio_item_config rows[MATRIX_ROWS]; + struct kscan_gpio_item_config cols[MATRIX_COLS]; +}; + +struct kscan_gpio_data +{ + kscan_callback_t callback; +#if DT_PROP(MATRIX_NODE_ID, debounce_id) == 0 + struct k_work work; +#else + struct k_delayed_work work; +#endif + bool matrix_state[MATRIX_ROWS][MATRIX_COLS]; + struct device *rows[MATRIX_ROWS]; + struct device *cols[MATRIX_COLS]; + struct device *dev; +}; + +struct kscan_gpio_irq_callback +{ +#if DT_PROP(MATRIX_NODE_ID, debounce_id) == 0 + struct k_work *work; +#else + struct k_delayed_work *work; +#endif + struct gpio_callback callback; +}; + +static int kscan_gpio_config_interrupts(struct device **devices, + const struct kscan_gpio_item_config *configs, + size_t len, gpio_flags_t flags) +{ + for (int i = 0; i < len; i++) + { + struct device *dev = devices[i]; + const struct kscan_gpio_item_config *cfg = &configs[i]; + + int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags); + + if (err) + { + LOG_ERR("Unable to enable matrix GPIO interrupt"); + return err; + } + } + + return 0; +} + +#ifdef SCAN_ROW_TO_COLUMN + +static struct kscan_gpio_irq_callback irq_callbacks[MATRIX_COLS]; + +static int kscan_gpio_enable_interrupts(struct device *dev) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + return kscan_gpio_config_interrupts( + data->cols, cfg->cols, MATRIX_COLS, + GPIO_INT_DEBOUNCE | GPIO_INT_EDGE_BOTH); +} + +static int kscan_gpio_disable_interrupts(struct device *dev) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + return kscan_gpio_config_interrupts( + data->cols, cfg->cols, MATRIX_COLS, + GPIO_INT_DISABLE); +} + +static void kscan_gpio_set_row_state(struct device *dev, int value) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + for (int r = 0; r < MATRIX_ROWS; r++) + { + struct device *row = data->rows[r]; + const struct kscan_gpio_item_config *row_cfg = &cfg->rows[r]; + + gpio_pin_set(row, row_cfg->pin, value); + } +} + +static int kscan_gpio_read(struct device *dev) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + static bool read_state[MATRIX_ROWS][MATRIX_COLS]; + + LOG_DBG("Scanning the matrix for updated state"); + + /* Disable our interrupts temporarily while we scan, to avoid */ + /* re-entry while we iterate columns and set them active one by one */ + /* to get pressed state for each matrix cell. */ + kscan_gpio_disable_interrupts(dev); + kscan_gpio_set_row_state(dev, 0); + + for (int r = 0; r < MATRIX_ROWS; r++) + { + struct device *row = data->rows[r]; + const struct kscan_gpio_item_config *row_cfg = &cfg->rows[r]; + + gpio_pin_set(row, row_cfg->pin, 1); + for (int c = 0; c < MATRIX_COLS; c++) + { + struct device *col = data->cols[c]; + const struct kscan_gpio_item_config *col_cfg = &cfg->cols[c]; + + read_state[r][c] = + gpio_pin_get(col, col_cfg->pin) > 0; + } + + gpio_pin_set(row, row_cfg->pin, 0); + } + + /* Set all our outputs as active again, then re-enable interrupts, */ + /* so we can trigger interrupts again for future press/release */ + kscan_gpio_set_row_state(dev, 1); + kscan_gpio_enable_interrupts(dev); + + for (int r = 0; r < MATRIX_ROWS; r++) + { + for (int c = 0; c < MATRIX_COLS; c++) + { + bool pressed = read_state[r][c]; + + if (pressed != data->matrix_state[r][c]) + { + LOG_DBG("Sending event at %d,%d state %s", + r, c, (pressed ? "on" : "off")); + data->matrix_state[r][c] = pressed; + data->callback(dev, r, c, pressed); + } + } + } + + return 0; +} + +#else + +static struct kscan_gpio_irq_callback irq_callbacks[MATRIX_ROWS]; + +static int kscan_gpio_enable_interrupts(struct device *dev) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + return kscan_gpio_config_interrupts(data->rows, cfg->rows, MATRIX_ROWS, + GPIO_INT_DEBOUNCE | GPIO_INT_EDGE_BOTH); +} + +static int kscan_gpio_disable_interrupts(struct device *dev) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + return kscan_gpio_config_interrupts(data->rows, cfg->rows, MATRIX_ROWS, + GPIO_INT_DISABLE); +} + +static void kscan_gpio_set_column_state(struct device *dev, int value) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + for (int c = 0; c < MATRIX_COLS; c++) + { + struct device *col = data->cols[c]; + const struct kscan_gpio_item_config *col_cfg = &cfg->cols[c]; + + gpio_pin_set(col, col_cfg->pin, value); + } +} + +static int kscan_gpio_read(struct device *dev) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + + static bool read_state[MATRIX_ROWS][MATRIX_COLS]; + + LOG_DBG("Scanning the matrix for updated state"); + + /* Disable our interrupts temporarily while we scan, to avoid */ + /* re-entry while we iterate columns and set them active one by one */ + /* to get pressed state for each matrix cell. */ + kscan_gpio_disable_interrupts(dev); + kscan_gpio_set_column_state(dev, 0); + + for (int c = 0; c < MATRIX_COLS; c++) + { + struct device *col = data->cols[c]; + const struct kscan_gpio_item_config *col_cfg = &cfg->cols[c]; + + gpio_pin_set(col, col_cfg->pin, 1); + + for (int r = 0; r < MATRIX_ROWS; r++) + { + struct device *row = data->rows[r]; + const struct kscan_gpio_item_config *row_cfg = &cfg->rows[r]; + + read_state[r][c] = + gpio_pin_get(row, row_cfg->pin) > 0; + } + + gpio_pin_set(col, col_cfg->pin, 0); + } + + /* Set all our outputs as active again, then re-enable interrupts, */ + /* so we can trigger interrupts again for future press/release */ + kscan_gpio_set_column_state(dev, 1); + kscan_gpio_enable_interrupts(dev); + + for (int r = 0; r < MATRIX_ROWS; r++) + { + for (int c = 0; c < MATRIX_COLS; c++) + { + bool pressed = read_state[r][c]; + + if (pressed != data->matrix_state[r][c]) + { + LOG_DBG("Sending event at %d,%d state %s", + r, c, (pressed ? "on" : "off")); + data->matrix_state[r][c] = pressed; + data->callback(dev, r, c, pressed); + } + } + } + + return 0; +} + +#endif + +static void kscan_gpio_irq_callback_handler(struct device *dev, + struct gpio_callback *cb, gpio_port_pins_t pin) +{ + struct kscan_gpio_irq_callback *data = + CONTAINER_OF(cb, struct kscan_gpio_irq_callback, callback); + +#if DT_PROP(MATRIX_NODE_ID, debounce_id) == 0 + k_work_submit(data->work); +#else + k_delayed_work_cancel(data->work); + k_delayed_work_submit(data->work, + K_MSEC(DT_PROP(MATRIX_NODE_ID, debounce_id))); +#endif +} + +static void kscan_gpio_work_handler(struct k_work *work) +{ + struct kscan_gpio_data *data = + CONTAINER_OF(work, struct kscan_gpio_data, work); + + kscan_gpio_read(data->dev); +} + +static int kscan_gpio_configure(struct device *dev, kscan_callback_t callback) +{ + struct kscan_gpio_data *data = dev->driver_data; + + if (!callback) + { + return -EINVAL; + } + + data->callback = callback; + + return 0; +} + +static int kscan_gpio_init(struct device *dev) +{ + struct kscan_gpio_data *data = dev->driver_data; + const struct kscan_gpio_config *cfg = dev->config_info; + int err; + +#ifdef SCAN_ROW_TO_COLUMN + gpio_flags_t col_gpio_dir = GPIO_INPUT; + gpio_flags_t row_gpio_dir = GPIO_OUTPUT_ACTIVE; +#else + gpio_flags_t col_gpio_dir = GPIO_OUTPUT_ACTIVE; + gpio_flags_t row_gpio_dir = GPIO_INPUT; +#endif + + for (int c = 0; c < MATRIX_COLS; c++) + { + const struct kscan_gpio_item_config *item_cfg = &cfg->cols[c]; + + data->cols[c] = device_get_binding(item_cfg->label); + if (data->cols[c] == NULL) + { + LOG_ERR("Unable to find column GPIO device\n"); + return -EINVAL; + } + + err = gpio_pin_configure(data->cols[c], + item_cfg->pin, + col_gpio_dir | item_cfg->flags); + if (err) + { + LOG_ERR("Unable to configure column GPIO pin"); + return -EINVAL; + } + +#ifdef SCAN_ROW_TO_COLUMN + irq_callbacks[c].work = &data->work; + gpio_init_callback(&irq_callbacks[c].callback, + kscan_gpio_irq_callback_handler, + BIT(item_cfg->pin)); + if (gpio_add_callback(data->cols[c], &irq_callbacks[c].callback)) + { + LOG_ERR("Error adding the callback to the column device"); + } +#endif + } + + for (int r = 0; r < MATRIX_ROWS; r++) + { + const struct kscan_gpio_item_config *item_cfg = &cfg->rows[r]; + + data->rows[r] = device_get_binding(item_cfg->label); + if (data->rows[r] == NULL) + { + LOG_ERR("Unable to find row GPIO device\n"); + return -EINVAL; + } + + int err = gpio_pin_configure(data->rows[r], + item_cfg->pin, + row_gpio_dir | item_cfg->flags); + if (err) + { + LOG_ERR("Unable to configure row GPIO pin"); + return -EINVAL; + } + +#ifdef SCAN_COL_TO_ROW + irq_callbacks[r].work = &data->work; + gpio_init_callback(&irq_callbacks[r].callback, + kscan_gpio_irq_callback_handler, + BIT(item_cfg->pin)); + gpio_add_callback(data->rows[r], &irq_callbacks[r].callback); +#endif + } + + data->dev = dev; + +#if DT_PROP(MATRIX_NODE_ID, debounce_id) == 0 + k_work_init(&data->work, kscan_gpio_work_handler); +#else + k_delayed_work_init(&data->work, kscan_gpio_work_handler); +#endif + + return 0; +} + +static const struct kscan_driver_api gpio_driver_api = { + .config = kscan_gpio_configure, + .enable_callback = kscan_gpio_enable_interrupts, + .disable_callback = kscan_gpio_disable_interrupts, +}; + +#define _KSCAN_GPIO_ITEM_CFG_INIT(prop, idx) \ + { \ + .label = DT_GPIO_LABEL_BY_IDX(MATRIX_NODE_ID, prop, idx), \ + .pin = DT_GPIO_PIN_BY_IDX(MATRIX_NODE_ID, prop, idx), \ + .flags = DT_GPIO_FLAGS_BY_IDX(MATRIX_NODE_ID, prop, idx), \ + } + +static const struct kscan_gpio_config kscan_gpio_config = { + .rows = { +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 0) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 0), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 1) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 1), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 2) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 2), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 3) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 3), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 4) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 4), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 5) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 5), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 6) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 6), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 7) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 7), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 8) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 8), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 9) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 9), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, row_gpios, 10) + _KSCAN_GPIO_ITEM_CFG_INIT(row_gpios, 10), +#endif + }, + .cols = { +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 0) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 0), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 1) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 1), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 2) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 2), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 3) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 3), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 4) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 4), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 5) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 5), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 6) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 6), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 7) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 7), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 8) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 8), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 9) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 9), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 10) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 10), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 11) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 11), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 12) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 12), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 13) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 13), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 14) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 14), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 15) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 15), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 16) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 16), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 17) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 17), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 18) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 18), +#endif +#if DT_PROP_HAS_IDX(MATRIX_NODE_ID, col_gpios, 19) + _KSCAN_GPIO_ITEM_CFG_INIT(col_gpios, 19), +#endif + }}; + +static struct kscan_gpio_data kscan_gpio_data; + +DEVICE_AND_API_INIT(kscan_gpio, DT_INST_LABEL(0), kscan_gpio_init, + &kscan_gpio_data, &kscan_gpio_config, + POST_KERNEL, CONFIG_ZMK_KSCAN_INIT_PRIORITY, + &gpio_driver_api); diff --git a/drivers/zephyr/module.yaml b/drivers/zephyr/module.yaml new file mode 100644 index 00000000..cbff6a1a --- /dev/null +++ b/drivers/zephyr/module.yaml @@ -0,0 +1,3 @@ +build: + cmake: zephyr + kconfig: zephyr/Kconfig diff --git a/prj.conf b/prj.conf index 863e5024..4c11c885 100644 --- a/prj.conf +++ b/prj.conf @@ -1,5 +1,3 @@ -CONFIG_KSCAN=y -CONFIG_KSCAN_GPIO=y CONFIG_USB=y CONFIG_USB_DEVICE_STACK=y CONFIG_USB_DEVICE_HID=y @@ -11,6 +9,7 @@ CONFIG_BT_GATT_NOTIFY_MULTIPLE=n CONFIG_BT_DEVICE_NAME="ZMK Keyboard" CONFIG_BT_DEVICE_APPEARANCE=961 # CONFIG_LOG=y +# CONFIG_ZMK_LOG_LEVEL_DBG=y # CONFIG_KSCAN_LOG_LEVEL_DBG=y # Incresed stack due to settings API usage diff --git a/west.yml b/west.yml index 8b76eb8b..0ea6d103 100644 --- a/west.yml +++ b/west.yml @@ -6,8 +6,8 @@ manifest: url-base: https://github.com/petejohanson projects: - name: zephyr - remote: petejohanson - revision: driver-kscan-gpio-matrix-first-pass + remote: zephyrproject-rtos + revision: master import: true self: path: zmk