refactor: Move drivers into properly module.

* Align our driver module layout to properly match Zephyr conventions,
  allowing proper CMake setup to amend the library for each type of driver.
This commit is contained in:
Peter Johanson
2023-09-02 20:07:31 -07:00
committed by Pete Johanson
parent eaeea4bdfa
commit 690bc1bb44
55 changed files with 53 additions and 39 deletions

View File

@@ -0,0 +1,3 @@
zephyr_include_directories(include)
add_subdirectory(drivers)

2
app/module/Kconfig Normal file
View File

@@ -0,0 +1,2 @@
rsource "drivers/Kconfig"

View File

@@ -0,0 +1,7 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
add_subdirectory_ifdef(CONFIG_GPIO gpio)
add_subdirectory_ifdef(CONFIG_KSCAN kscan)
add_subdirectory_ifdef(CONFIG_SENSOR sensor)
add_subdirectory_ifdef(CONFIG_DISPLAY display)

View File

@@ -0,0 +1,7 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
rsource "gpio/Kconfig"
rsource "kscan/Kconfig"
rsource "sensor/Kconfig"
rsource "display/Kconfig"

View File

@@ -0,0 +1,6 @@
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_library_amend()
zephyr_library_sources_ifdef(CONFIG_IL0323 il0323.c)

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
if DISPLAY
rsource "Kconfig.il0323"
endif # DISPLAY

View File

@@ -0,0 +1,11 @@
# Copyright (c) 2020 Phytec Messtechnik GmbH, Peter Johanson
# SPDX-License-Identifier: Apache-2.0
# IL0323 display controller configuration options
config IL0323
bool "IL0323 compatible display controller driver"
depends on SPI
depends on HEAP_MEM_POOL_SIZE != 0
help
Enable driver for IL0323 compatible controller.

View File

@@ -0,0 +1,394 @@
/*
* Copyright (c) 2020 PHYTEC Messtechnik GmbHH, Peter Johanson
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT gooddisplay_il0323
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/sys/byteorder.h>
#include "il0323_regs.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(il0323, CONFIG_DISPLAY_LOG_LEVEL);
/**
* IL0323 compatible EPD controller driver.
*
*/
#define EPD_PANEL_WIDTH DT_INST_PROP(0, width)
#define EPD_PANEL_HEIGHT DT_INST_PROP(0, height)
#define IL0323_PIXELS_PER_BYTE 8U
/* Horizontally aligned page! */
#define IL0323_NUMOF_PAGES (EPD_PANEL_WIDTH / IL0323_PIXELS_PER_BYTE)
#define IL0323_PANEL_FIRST_GATE 0U
#define IL0323_PANEL_LAST_GATE (EPD_PANEL_HEIGHT - 1)
#define IL0323_PANEL_FIRST_PAGE 0U
#define IL0323_PANEL_LAST_PAGE (IL0323_NUMOF_PAGES - 1)
#define IL0323_BUFFER_SIZE 1280
struct il0323_cfg {
struct gpio_dt_spec reset;
struct gpio_dt_spec dc;
struct gpio_dt_spec busy;
struct spi_dt_spec spi;
};
static uint8_t il0323_pwr[] = DT_INST_PROP(0, pwr);
static uint8_t last_buffer[IL0323_BUFFER_SIZE];
static bool blanking_on = true;
static bool init_clear_done = false;
static inline int il0323_write_cmd(const struct il0323_cfg *cfg, uint8_t cmd, uint8_t *data,
size_t len) {
struct spi_buf buf = {.buf = &cmd, .len = sizeof(cmd)};
struct spi_buf_set buf_set = {.buffers = &buf, .count = 1};
gpio_pin_set_dt(&cfg->dc, 1);
if (spi_write_dt(&cfg->spi, &buf_set)) {
return -EIO;
}
if (data != NULL) {
buf.buf = data;
buf.len = len;
gpio_pin_set_dt(&cfg->dc, 0);
if (spi_write_dt(&cfg->spi, &buf_set)) {
return -EIO;
}
}
return 0;
}
static inline void il0323_busy_wait(const struct il0323_cfg *cfg) {
int pin = gpio_pin_get_dt(&cfg->busy);
while (pin > 0) {
__ASSERT(pin >= 0, "Failed to get pin level");
// LOG_DBG("wait %u", pin);
k_msleep(IL0323_BUSY_DELAY);
pin = gpio_pin_get_dt(&cfg->busy);
}
}
static int il0323_update_display(const struct device *dev) {
const struct il0323_cfg *cfg = dev->config;
LOG_DBG("Trigger update sequence");
if (il0323_write_cmd(cfg, IL0323_CMD_DRF, NULL, 0)) {
return -EIO;
}
k_msleep(IL0323_BUSY_DELAY);
return 0;
}
static int il0323_write(const struct device *dev, const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc, const void *buf) {
const struct il0323_cfg *cfg = dev->config;
uint16_t x_end_idx = x + desc->width - 1;
uint16_t y_end_idx = y + desc->height - 1;
uint8_t ptl[IL0323_PTL_REG_LENGTH] = {0};
size_t buf_len;
LOG_DBG("x %u, y %u, height %u, width %u, pitch %u", x, y, desc->height, desc->width,
desc->pitch);
buf_len = MIN(desc->buf_size, desc->height * desc->width / IL0323_PIXELS_PER_BYTE);
__ASSERT(desc->width <= desc->pitch, "Pitch is smaller then width");
__ASSERT(buf != NULL, "Buffer is not available");
__ASSERT(buf_len != 0U, "Buffer of length zero");
__ASSERT(!(desc->width % IL0323_PIXELS_PER_BYTE), "Buffer width not multiple of %d",
IL0323_PIXELS_PER_BYTE);
LOG_DBG("buf_len %d", buf_len);
if ((y_end_idx > (EPD_PANEL_HEIGHT - 1)) || (x_end_idx > (EPD_PANEL_WIDTH - 1))) {
LOG_ERR("Position out of bounds");
return -EINVAL;
}
/* Setup Partial Window and enable Partial Mode */
ptl[IL0323_PTL_HRST_IDX] = x;
ptl[IL0323_PTL_HRED_IDX] = x_end_idx;
ptl[IL0323_PTL_VRST_IDX] = y;
ptl[IL0323_PTL_VRED_IDX] = y_end_idx;
ptl[sizeof(ptl) - 1] = IL0323_PTL_PT_SCAN;
LOG_HEXDUMP_DBG(ptl, sizeof(ptl), "ptl");
il0323_busy_wait(cfg);
if (il0323_write_cmd(cfg, IL0323_CMD_PIN, NULL, 0)) {
return -EIO;
}
if (il0323_write_cmd(cfg, IL0323_CMD_PTL, ptl, sizeof(ptl))) {
return -EIO;
}
if (il0323_write_cmd(cfg, IL0323_CMD_DTM1, last_buffer, IL0323_BUFFER_SIZE)) {
return -EIO;
}
if (il0323_write_cmd(cfg, IL0323_CMD_DTM2, (uint8_t *)buf, buf_len)) {
return -EIO;
}
memcpy(last_buffer, (uint8_t *)buf, IL0323_BUFFER_SIZE);
/* Update partial window and disable Partial Mode */
if (blanking_on == false) {
if (il0323_update_display(dev)) {
return -EIO;
}
}
if (il0323_write_cmd(cfg, IL0323_CMD_POUT, NULL, 0)) {
return -EIO;
}
return 0;
}
static int il0323_read(const struct device *dev, const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc, void *buf) {
LOG_ERR("not supported");
return -ENOTSUP;
}
static int il0323_clear_and_write_buffer(const struct device *dev, uint8_t pattern, bool update) {
struct display_buffer_descriptor desc = {
.buf_size = IL0323_NUMOF_PAGES,
.width = EPD_PANEL_WIDTH,
.height = 1,
.pitch = EPD_PANEL_WIDTH,
};
uint8_t *line;
line = k_malloc(IL0323_NUMOF_PAGES);
if (line == NULL) {
return -ENOMEM;
}
memset(line, pattern, IL0323_NUMOF_PAGES);
for (int i = 0; i < EPD_PANEL_HEIGHT; i++) {
il0323_write(dev, 0, i, &desc, line);
}
k_free(line);
if (update == true) {
if (il0323_update_display(dev)) {
return -EIO;
}
}
return 0;
}
static int il0323_blanking_off(const struct device *dev) {
const struct il0323_cfg *cfg = dev->config;
if (!init_clear_done) {
/* Update EPD panel in normal mode */
il0323_busy_wait(cfg);
if (il0323_clear_and_write_buffer(dev, 0xff, true)) {
return -EIO;
}
init_clear_done = true;
}
blanking_on = false;
if (il0323_update_display(dev)) {
return -EIO;
}
return 0;
}
static int il0323_blanking_on(const struct device *dev) {
blanking_on = true;
return 0;
}
static void *il0323_get_framebuffer(const struct device *dev) {
LOG_ERR("not supported");
return NULL;
}
static int il0323_set_brightness(const struct device *dev, const uint8_t brightness) {
LOG_WRN("not supported");
return -ENOTSUP;
}
static int il0323_set_contrast(const struct device *dev, uint8_t contrast) {
LOG_WRN("not supported");
return -ENOTSUP;
}
static void il0323_get_capabilities(const struct device *dev, struct display_capabilities *caps) {
memset(caps, 0, sizeof(struct display_capabilities));
caps->x_resolution = EPD_PANEL_WIDTH;
caps->y_resolution = EPD_PANEL_HEIGHT;
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
caps->current_pixel_format = PIXEL_FORMAT_MONO10;
caps->screen_info = SCREEN_INFO_MONO_MSB_FIRST | SCREEN_INFO_EPD;
}
static int il0323_set_orientation(const struct device *dev,
const enum display_orientation orientation) {
LOG_ERR("Unsupported");
return -ENOTSUP;
}
static int il0323_set_pixel_format(const struct device *dev, const enum display_pixel_format pf) {
if (pf == PIXEL_FORMAT_MONO10) {
return 0;
}
LOG_ERR("not supported");
return -ENOTSUP;
}
static int il0323_controller_init(const struct device *dev) {
const struct il0323_cfg *cfg = dev->config;
uint8_t tmp[IL0323_TRES_REG_LENGTH];
LOG_DBG("");
gpio_pin_set_dt(&cfg->reset, 1);
k_msleep(IL0323_RESET_DELAY);
gpio_pin_set_dt(&cfg->reset, 0);
k_msleep(IL0323_RESET_DELAY);
il0323_busy_wait(cfg);
LOG_DBG("Initialize IL0323 controller");
if (il0323_write_cmd(cfg, IL0323_CMD_PWR, il0323_pwr, sizeof(il0323_pwr))) {
return -EIO;
}
/* Turn on: booster, controller, regulators, and sensor. */
if (il0323_write_cmd(cfg, IL0323_CMD_PON, NULL, 0)) {
return -EIO;
}
k_msleep(IL0323_PON_DELAY);
il0323_busy_wait(cfg);
/* Pannel settings, KW mode */
tmp[0] = IL0323_PSR_UD | IL0323_PSR_SHL | IL0323_PSR_SHD | IL0323_PSR_RST;
#if EPD_PANEL_WIDTH == 80
#if EPD_PANEL_HEIGHT == 128
tmp[0] |= IL0323_PSR_RES_HEIGHT;
#endif /* panel height */
#else
tmp[0] |= IL0323_PSR_RES_WIDTH;
#if EPD_PANEL_HEIGHT == 96
tmp[0] |= IL0323_PSR_RES_HEIGHT;
#else
#endif /* panel height */
#endif /* panel width */
LOG_HEXDUMP_DBG(tmp, 1, "PSR");
if (il0323_write_cmd(cfg, IL0323_CMD_PSR, tmp, 1)) {
return -EIO;
}
/* Set panel resolution */
tmp[IL0323_TRES_HRES_IDX] = EPD_PANEL_WIDTH;
tmp[IL0323_TRES_VRES_IDX] = EPD_PANEL_HEIGHT;
LOG_HEXDUMP_DBG(tmp, IL0323_TRES_REG_LENGTH, "TRES");
if (il0323_write_cmd(cfg, IL0323_CMD_TRES, tmp, IL0323_TRES_REG_LENGTH)) {
return -EIO;
}
tmp[IL0323_CDI_CDI_IDX] = DT_INST_PROP(0, cdi);
LOG_HEXDUMP_DBG(tmp, IL0323_CDI_REG_LENGTH, "CDI");
if (il0323_write_cmd(cfg, IL0323_CMD_CDI, tmp, IL0323_CDI_REG_LENGTH)) {
return -EIO;
}
tmp[0] = DT_INST_PROP(0, tcon);
if (il0323_write_cmd(cfg, IL0323_CMD_TCON, tmp, 1)) {
return -EIO;
}
/* Enable Auto Sequence */
tmp[0] = IL0323_AUTO_PON_DRF_POF;
if (il0323_write_cmd(cfg, IL0323_CMD_AUTO, tmp, 1)) {
return -EIO;
}
return 0;
}
static int il0323_init(const struct device *dev) {
const struct il0323_cfg *cfg = dev->config;
if (!spi_is_ready(&cfg->spi)) {
LOG_ERR("SPI device not ready for IL0323");
return -EIO;
}
if (!device_is_ready(cfg->reset.port)) {
LOG_ERR("Could not get GPIO port for IL0323 reset");
return -EIO;
}
gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_INACTIVE);
if (!device_is_ready(cfg->dc.port)) {
LOG_ERR("Could not get GPIO port for IL0323 DC signal");
return -EIO;
}
gpio_pin_configure_dt(&cfg->dc, GPIO_OUTPUT_INACTIVE);
if (!device_is_ready(cfg->busy.port)) {
LOG_ERR("Could not get GPIO port for IL0323 busy signal");
return -EIO;
}
gpio_pin_configure_dt(&cfg->busy, GPIO_INPUT);
return il0323_controller_init(dev);
}
static struct il0323_cfg il0323_config = {
.spi = SPI_DT_SPEC_INST_GET(0, SPI_OP_MODE_MASTER | SPI_WORD_SET(8), 0),
.reset = GPIO_DT_SPEC_INST_GET(0, reset_gpios),
.busy = GPIO_DT_SPEC_INST_GET(0, busy_gpios),
.dc = GPIO_DT_SPEC_INST_GET(0, dc_gpios),
};
static struct display_driver_api il0323_driver_api = {
.blanking_on = il0323_blanking_on,
.blanking_off = il0323_blanking_off,
.write = il0323_write,
.read = il0323_read,
.get_framebuffer = il0323_get_framebuffer,
.set_brightness = il0323_set_brightness,
.set_contrast = il0323_set_contrast,
.get_capabilities = il0323_get_capabilities,
.set_pixel_format = il0323_set_pixel_format,
.set_orientation = il0323_set_orientation,
};
DEVICE_DT_INST_DEFINE(0, il0323_init, NULL, NULL, &il0323_config, POST_KERNEL,
CONFIG_APPLICATION_INIT_PRIORITY, &il0323_driver_api);

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2020 PHYTEC Messtechnik GmbH, Peter Johanson
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_DISPLAY_IL0323_REGS_H_
#define ZEPHYR_DRIVERS_DISPLAY_IL0323_REGS_H_
#define IL0323_CMD_PSR 0x00
#define IL0323_CMD_PWR 0x01
#define IL0323_CMD_POF 0x02
#define IL0323_CMD_PFS 0x03
#define IL0323_CMD_PON 0x04
#define IL0323_CMD_PMES 0x05
#define IL0323_CMD_CPSET 0x06
#define IL0323_CMD_DSLP 0x07
#define IL0323_CMD_DTM1 0x10
#define IL0323_CMD_DSP 0x11
#define IL0323_CMD_DRF 0x12
#define IL0323_CMD_DTM2 0x13
#define IL0323_CMD_AUTO 0x17
#define IL0323_CMD_LUTOPT 0x2A
#define IL0323_CMD_PLL 0x30
#define IL0323_CMD_TSC 0x40
#define IL0323_CMD_TSE 0x41
#define IL0323_CMD_PBC 0x44
#define IL0323_CMD_CDI 0x50
#define IL0323_CMD_LPD 0x51
#define IL0323_CMD_TCON 0x60
#define IL0323_CMD_TRES 0x61
#define IL0323_CMD_GSST 0x65
#define IL0323_CMD_REV 0x70
#define IL0323_CMD_FLG 0x71
#define IL0323_CMD_CRC 0x72
#define IL0323_CMD_AMV 0x80
#define IL0323_CMD_VV 0x81
#define IL0323_CMD_VDCS 0x82
#define IL0323_CMD_PTL 0x90
#define IL0323_CMD_PIN 0x91
#define IL0323_CMD_POUT 0x92
#define IL0323_CMD_PGM 0xA0
#define IL0323_CMD_APG 0xA1
#define IL0323_CMD_ROTP 0xA2
#define IL0323_CMD_CCSET 0xE0
#define IL0323_CMD_PWS 0xE3
#define IL0323_CMD_LVSEL 0xE4
#define IL0323_CMD_TSSET 0xE5
#define IL0323_PSR_RES_WIDTH BIT(7)
#define IL0323_PSR_RES_HEIGHT BIT(6)
#define IL0323_PSR_LUT_REG BIT(5)
#define IL0323_PSR_LUT_OTP BIT(4)
#define IL0323_PSR_UD BIT(3)
#define IL0323_PSR_SHL BIT(2)
#define IL0323_PSR_SHD BIT(1)
#define IL0323_PSR_RST BIT(0)
#define IL0323_AUTO_PON_DRF_POF 0xA5
#define IL0323_AUTO_PON_DRF_POF_DSLP 0xA7
#define IL0323_CDI_REG_LENGTH 1U
#define IL0323_CDI_CDI_IDX 0
#define IL0323_TRES_REG_LENGTH 2U
#define IL0323_TRES_HRES_IDX 0
#define IL0323_TRES_VRES_IDX 1
#define IL0323_PTL_REG_LENGTH 5U
#define IL0323_PTL_HRST_IDX 0
#define IL0323_PTL_HRED_IDX 1
#define IL0323_PTL_VRST_IDX 2
#define IL0323_PTL_VRED_IDX 3
#define IL0323_PTL_PT_SCAN BIT(0)
/* Time constants in ms */
#define IL0323_RESET_DELAY 10U
#define IL0323_PON_DELAY 100U
#define IL0323_BUSY_DELAY 1U
#endif /* ZEPHYR_DRIVERS_DISPLAY_IL0323_REGS_H_ */

View File

@@ -0,0 +1,7 @@
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_library_amend()
zephyr_library_sources_ifdef(CONFIG_GPIO_595 gpio_595.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_MAX7318 gpio_max7318.c)

View File

@@ -0,0 +1,7 @@
if GPIO
rsource "Kconfig.max7318"
rsource "Kconfig.595"
endif # GPIO

View File

@@ -0,0 +1,24 @@
# 595 GPIO configuration options
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
DT_COMPAT_ZMK_GPIO_595 := zmk,gpio-595
menuconfig GPIO_595
bool "595 Shift Register SPI driver"
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_GPIO_595))
depends on SPI
select HAS_DTS_GPIO
help
Enable driver for 595 shift register chip using SPI.
if GPIO_595
config GPIO_595_INIT_PRIORITY
int "Init priority"
default 75
help
Device driver initialization priority.
endif #GPIO_595

View File

@@ -0,0 +1,24 @@
# MAX7318 GPIO configuration options
# Copyright (c) 2022 The ZMK Contributors
# SPDX-License-Identifier: MIT
DT_COMPAT_MAXIM_MAX7318 := maxim,max7318
menuconfig GPIO_MAX7318
bool "MAX7318 I2C-based GPIO chip"
default $(dt_compat_enabled,$(DT_COMPAT_MAXIM_MAX7318))
depends on I2C
select HAS_DTS_GPIO
help
Enable driver for MAX7318 I2C-based GPIO chip.
if GPIO_MAX7318
config GPIO_MAX7318_INIT_PRIORITY
int "Init priority"
default 75
help
Device driver initialization priority.
endif #GPIO_MAX7318

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_gpio_595
/**
* @file Driver for 595 SPI-based GPIO driver.
*/
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(gpio_595);
/** Configuration data */
struct reg_595_config {
/* gpio_driver_data needs to be first */
struct gpio_driver_config common;
struct spi_dt_spec bus;
uint8_t ngpios;
};
/** Runtime driver data */
struct reg_595_drv_data {
/* gpio_driver_data needs to be first */
struct gpio_driver_config data;
struct k_sem lock;
uint32_t gpio_cache;
};
static int reg_595_write_registers(const struct device *dev, uint32_t value) {
const struct reg_595_config *config = dev->config;
struct reg_595_drv_data *const drv_data = (struct reg_595_drv_data *const)dev->data;
int ret = 0;
uint8_t nwrite = config->ngpios / 8;
uint32_t reg_data = sys_cpu_to_be32(value);
/* Allow a sequence of 1-4 registers in sequence, lowest byte is for the first in the chain */
const struct spi_buf tx_buf[1] = {{
.buf = ((uint8_t *)&reg_data) + (4 - nwrite),
.len = nwrite,
}};
const struct spi_buf_set tx = {
.buffers = tx_buf,
.count = ARRAY_SIZE(tx_buf),
};
ret = spi_write_dt(&config->bus, &tx);
if (ret < 0) {
LOG_ERR("spi_write FAIL %d\n", ret);
return ret;
}
drv_data->gpio_cache = value;
return 0;
}
/**
* @brief Setup the pin direction (input or output)
*
* @param dev Device struct of the 595
* @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_OUTPUT) == 0U) {
return -ENOTSUP;
}
return 0;
}
static int reg_595_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("595: error setting pin direction (%d)", ret);
}
return ret;
}
static int reg_595_port_get_raw(const struct device *dev, uint32_t *value) { return -ENOTSUP; }
static int reg_595_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) {
struct reg_595_drv_data *const drv_data = (struct reg_595_drv_data *const)dev->data;
uint32_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->gpio_cache;
buf = (buf & ~mask) | (mask & value);
ret = reg_595_write_registers(dev, buf);
k_sem_give(&drv_data->lock);
return ret;
}
static int reg_595_port_set_bits_raw(const struct device *dev, uint32_t mask) {
return reg_595_port_set_masked_raw(dev, mask, mask);
}
static int reg_595_port_clear_bits_raw(const struct device *dev, uint32_t mask) {
return reg_595_port_set_masked_raw(dev, mask, 0);
}
static int reg_595_port_toggle_bits(const struct device *dev, uint32_t mask) {
struct reg_595_drv_data *const drv_data = (struct reg_595_drv_data *const)dev->data;
uint32_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->gpio_cache;
buf ^= mask;
ret = reg_595_write_registers(dev, buf);
k_sem_give(&drv_data->lock);
return ret;
}
static const struct gpio_driver_api api_table = {
.pin_configure = reg_595_pin_config,
.port_get_raw = reg_595_port_get_raw,
.port_set_masked_raw = reg_595_port_set_masked_raw,
.port_set_bits_raw = reg_595_port_set_bits_raw,
.port_clear_bits_raw = reg_595_port_clear_bits_raw,
.port_toggle_bits = reg_595_port_toggle_bits,
};
/**
* @brief Initialization function of 595
*
* @param dev Device struct
* @return 0 if successful, failed otherwise.
*/
static int reg_595_init(const struct device *dev) {
const struct reg_595_config *const config = dev->config;
struct reg_595_drv_data *const drv_data = (struct reg_595_drv_data *const)dev->data;
if (!device_is_ready(config->bus.bus)) {
LOG_ERR("Unable to get SPI bus device");
return -ENODEV;
}
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_595_INIT(n) \
static struct reg_595_config reg_595_##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), \
}; \
\
static struct reg_595_drv_data reg_595_##n##_drvdata = {}; \
\
/* This has to init after SPI master */ \
DEVICE_DT_INST_DEFINE(n, reg_595_init, NULL, &reg_595_##n##_drvdata, &reg_595_##n##_config, \
POST_KERNEL, CONFIG_GPIO_595_INIT_PRIORITY, &api_table);
DT_INST_FOREACH_STATUS_OKAY(REG_595_INIT)

View File

@@ -0,0 +1,341 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT maxim_max7318
/**
* @file Driver for MAX7318 I2C-based GPIO driver.
*/
#include <errno.h>
#include <kernel.h>
#include <device.h>
#include <init.h>
#include <sys/byteorder.h>
#include <drivers/gpio.h>
#include <drivers/i2c.h>
#include <drivers/ext_power.h>
#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(gpio_max7318);
// Register definitions
#define REG_INPUT_PORTA 0x00
#define REG_INPUT_PORTB 0x01
#define REG_OUTPUT_PORTA 0x02
#define REG_OUTPUT_PORTB 0x03
#define REG_IPOL_PORTA 0x04
#define REG_IPOL_PORTB 0x05
#define REG_CONFIG_PORTA 0x06
#define REG_CONFIG_PORTB 0x07
// Configuration data
struct max7318_config {
struct gpio_driver_config common;
struct i2c_dt_spec i2c_bus;
uint8_t ngpios;
};
// Runtime driver data
struct max7318_drv_data {
// gpio_driver_data needs to be first
struct gpio_driver_config data;
struct k_sem lock;
struct {
uint16_t ipol;
uint16_t config;
uint16_t output;
} reg_cache;
};
/**
* @brief Read the value of two consecutive registers
*
* Read two consecutive bytes from the register at address `reg` and `reg + 1`,
* typically reading from registers for port 0 and 1 simultaneously.
*
* @param dev The max7318 device.
* @param reg Register to read (the PORT0 of the pair of registers).
* @param buf Buffer to read data into.
*
* @return 0 if successful, failed otherwise.
*/
static int read_registers(const struct device *dev, uint8_t reg, uint16_t *buf) {
const struct max7318_config *config = dev->config;
uint8_t data[2] = {0};
int ret = i2c_burst_read_dt(&config->i2c_bus, reg, &data[0], sizeof(data));
if (ret) {
LOG_DBG("i2c_burst_read FAIL %d\n", ret);
return ret;
}
// the first register is data[0], the second one is data[1]
// since we only ever read the PORTA registers here, it's effectively little endian.
*buf = sys_get_le16(data);
LOG_DBG("max7318: read: reg[0x%X] = 0x%X, reg[0x%X] = 0x%X", reg, data[0], (reg + 1), data[1]);
return 0;
}
/**
* @brief Write the value of two consecutive registers
*
* Write two consecutive bytes from the register at address `reg` and `reg + 1`,
* typically to registers for port 0 and 1 simultaneously.
*
* @param dev The max7318 device.
* @param reg Register to write (usually the register for PORT0).
* @param value The value to write
*
* @return 0 if successful, failed otherwise.
*/
static int write_registers(const struct device *dev, uint8_t reg, uint16_t value) {
const struct max7318_config *config = dev->config;
LOG_DBG("max7318: write: reg[0x%X] = 0x%X, reg[0x%X] = 0x%X", reg, (value & 0xFF), (reg + 1),
(value >> 8));
uint8_t data[2] = {0};
// bits 0..7 are port A, 8..15 are port B, so we should write bits 0..7 first
// -- ie. this is little endian also.
sys_put_le16(value, &data[0]);
return i2c_burst_write_dt(&config->i2c_bus, reg, &data[0], sizeof(data));
}
/**
* @brief Setup the pin direction (input or output)
*
* @param dev The max7318 device.
* @param pin The pin number.
* @param flags Flags of pin or port.
*
* @return 0 if successful, failed otherwise
*/
static int set_pin_direction(const struct device *dev, uint32_t pin, int flags) {
struct max7318_drv_data *const drv_data = (struct max7318_drv_data *const)dev->data;
uint16_t *dir = &drv_data->reg_cache.config;
uint16_t *output = &drv_data->reg_cache.output;
/*
The output register is 1=high, 0=low; the direction (config) register
is 1=input, 0=output.
*/
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);
}
int ret = write_registers(dev, REG_OUTPUT_PORTA, *output);
if (ret != 0) {
return ret;
}
return write_registers(dev, REG_CONFIG_PORTA, *dir);
}
/**
* @brief Setup the pin pull up/pull down status. This function doesn't actually set any
* registers, since the max7318 only supports a pullup, and it can't be controlled.
*
* @param dev The max7318 device.
* @param pin The pin number
* @param flags Flags of pin or port
*
* @return 0 if successful, failed otherwise
*/
static int set_pin_pull_direction(const struct device *dev, uint32_t pin, int flags) {
// actually, this chip only supports pull-up, and it can't be disabled.
// so, if we try to set anything else, return enotsup; we don't actually
// need to set any registers.
if ((flags & GPIO_PULL_DOWN) != 0U) {
return -ENOTSUP;
}
return 0;
}
static int max7318_config(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags) {
struct max7318_drv_data *const drv_data = (struct max7318_drv_data *const)dev->data;
/* Can't do I2C bus operations from an ISR */
if (k_is_in_isr()) {
return -EWOULDBLOCK;
}
k_sem_take(&drv_data->lock, K_FOREVER);
int ret = 0;
if ((flags & GPIO_OPEN_DRAIN) != 0U) {
ret = -ENOTSUP;
goto done;
};
ret = set_pin_direction(dev, pin, flags);
if (ret != 0) {
LOG_ERR("error setting pin direction (%d)", ret);
goto done;
}
ret = set_pin_pull_direction(dev, pin, flags);
if (ret != 0) {
LOG_ERR("error setting pin pull up/down (%d)", ret);
goto done;
}
done:
k_sem_give(&drv_data->lock);
return ret;
}
static int max7318_port_get_raw(const struct device *dev, uint32_t *value) {
struct max7318_drv_data *const drv_data = (struct max7318_drv_data *const)dev->data;
/* Can't do I2C bus operations from an ISR */
if (k_is_in_isr()) {
return -EWOULDBLOCK;
}
k_sem_take(&drv_data->lock, K_FOREVER);
uint16_t buf = 0;
int ret = read_registers(dev, REG_INPUT_PORTA, &buf);
if (ret != 0) {
goto done;
}
*value = buf;
done:
k_sem_give(&drv_data->lock);
return ret;
}
static int max7318_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value) {
struct max7318_drv_data *const drv_data = (struct max7318_drv_data *const)dev->data;
/* Can't do I2C bus operations from an ISR */
if (k_is_in_isr()) {
return -EWOULDBLOCK;
}
k_sem_take(&drv_data->lock, K_FOREVER);
uint16_t buf = drv_data->reg_cache.output;
buf = (buf & ~mask) | (mask & value);
int ret = write_registers(dev, REG_OUTPUT_PORTA, buf);
if (ret == 0) {
drv_data->reg_cache.output = buf;
}
k_sem_give(&drv_data->lock);
return ret;
}
static int max7318_port_set_bits_raw(const struct device *dev, uint32_t mask) {
return max7318_port_set_masked_raw(dev, mask, mask);
}
static int max7318_port_clear_bits_raw(const struct device *dev, uint32_t mask) {
return max7318_port_set_masked_raw(dev, mask, 0);
}
static int max7318_port_toggle_bits(const struct device *dev, uint32_t mask) {
struct max7318_drv_data *const drv_data = (struct max7318_drv_data *const)dev->data;
/* Can't do I2C bus operations from an ISR */
if (k_is_in_isr()) {
return -EWOULDBLOCK;
}
k_sem_take(&drv_data->lock, K_FOREVER);
uint16_t buf = drv_data->reg_cache.output;
buf ^= mask;
int ret = write_registers(dev, REG_OUTPUT_PORTA, buf);
if (ret == 0) {
drv_data->reg_cache.output = buf;
}
k_sem_give(&drv_data->lock);
return ret;
}
static int max7318_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 = max7318_config,
.port_get_raw = max7318_port_get_raw,
.port_set_masked_raw = max7318_port_set_masked_raw,
.port_set_bits_raw = max7318_port_set_bits_raw,
.port_clear_bits_raw = max7318_port_clear_bits_raw,
.port_toggle_bits = max7318_port_toggle_bits,
.pin_interrupt_configure = max7318_pin_interrupt_configure,
};
/**
* @brief Initialisation function of MAX7318
*
* @param dev Device struct
* @return 0 if successful, failed otherwise.
*/
static int max7318_init(const struct device *dev) {
const struct max7318_config *const config = dev->config;
struct max7318_drv_data *const drv_data = (struct max7318_drv_data *const)dev->data;
if (!device_is_ready(config->i2c_bus.bus)) {
LOG_WRN("i2c bus not ready!");
return -EINVAL;
}
LOG_INF("device initialised at 0x%x", config->i2c_bus.addr);
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 MAX7318_INIT(inst) \
static struct max7318_config max7318_##inst##_config = { \
.common = {.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst)}, \
.i2c_bus = I2C_DT_SPEC_INST_GET(inst)}; \
\
static struct max7318_drv_data max7318_##inst##_drvdata = { \
/* Default for registers according to datasheet */ \
.reg_cache.ipol = 0x0, \
.reg_cache.config = 0xFFFF, \
.reg_cache.output = 0xFFFF, \
}; \
\
DEVICE_DT_INST_DEFINE(inst, max7318_init, NULL, &max7318_##inst##_drvdata, \
&max7318_##inst##_config, POST_KERNEL, \
CONFIG_GPIO_MAX7318_INIT_PRIORITY, &api_table);
DT_INST_FOREACH_STATUS_OKAY(MAX7318_INIT)

View File

@@ -0,0 +1,12 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_library_amend()
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER debounce.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MATRIX kscan_gpio_matrix.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c)

View File

@@ -0,0 +1,98 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite
DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux
DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct
DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix
DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock
if KSCAN
config ZMK_KSCAN_COMPOSITE_DRIVER
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_COMPOSITE))
config ZMK_KSCAN_GPIO_DRIVER
bool
select GPIO
config ZMK_KSCAN_GPIO_DEMUX
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX))
select ZMK_KSCAN_GPIO_DRIVER
config ZMK_KSCAN_GPIO_DIRECT
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT))
select ZMK_KSCAN_GPIO_DRIVER
config ZMK_KSCAN_GPIO_MATRIX
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX))
select ZMK_KSCAN_GPIO_DRIVER
if ZMK_KSCAN_GPIO_MATRIX
config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS
int "Ticks to wait before reading inputs after an output set active"
default 0
help
When iterating over each output to drive it active, read inputs, then set
inactive again, some boards may take time for output to propagate to the
inputs. In that scenario, set this value to a positive value to configure
the number of ticks to wait after setting an output active before reading
the inputs for their active state.
config ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS
int "Ticks to wait between each output when scanning"
default 1 if SOC_RP2040
default 0
help
When iterating over each output to drive it active, read inputs, then set
inactive again, some boards may take time for the previous output to
"settle" before reading inputs for the next active output column. In that
scenario, set this value to a positive value to configure the number of
ticks to wait after reading each column of keys.
endif # ZMK_KSCAN_GPIO_MATRIX
config ZMK_KSCAN_MOCK_DRIVER
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_MOCK))
if ZMK_KSCAN_GPIO_DRIVER
config ZMK_KSCAN_MATRIX_POLLING
bool "Poll for key event triggers instead of using interrupts on matrix boards."
config ZMK_KSCAN_DIRECT_POLLING
bool "Poll for key event triggers instead of using interrupts on direct wired boards."
config ZMK_KSCAN_DEBOUNCE_PRESS_MS
int "Debounce time for key press in milliseconds."
default -1
help
Global debounce time for key press in milliseconds.
If this is -1, the debounce time is controlled by the debounce-press-ms
Devicetree property, which defaults to 5 ms. Otherwise this overrides the
debounce time for all key scan drivers to the chosen value.
config ZMK_KSCAN_DEBOUNCE_RELEASE_MS
int "Debounce time for key release in milliseconds."
default -1
help
Global debounce time for key release in milliseconds.
If this is -1, the debounce time is controlled by the debounce-release-ms
Devicetree property, which defaults to 5 ms. Otherwise this overrides the
debounce time for all key scan drivers to the chosen value.
endif
# config ZMK_KSCAN_INIT_PRIORITY
# int "Keyboard scan driver init priority"
# default 40
# help
# Keyboard scan device driver initialization priority.
endif # KSCAN

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include "debounce.h"
static uint32_t get_threshold(const struct debounce_state *state,
const struct debounce_config *config) {
return state->pressed ? config->debounce_release_ms : config->debounce_press_ms;
}
static void increment_counter(struct debounce_state *state, const int elapsed_ms) {
if (state->counter + elapsed_ms > DEBOUNCE_COUNTER_MAX) {
state->counter = DEBOUNCE_COUNTER_MAX;
} else {
state->counter += elapsed_ms;
}
}
static void decrement_counter(struct debounce_state *state, const int elapsed_ms) {
if (state->counter < elapsed_ms) {
state->counter = 0;
} else {
state->counter -= elapsed_ms;
}
}
void debounce_update(struct debounce_state *state, const bool active, const int elapsed_ms,
const struct debounce_config *config) {
// This uses a variation of the integrator debouncing described at
// https://www.kennethkuhn.com/electronics/debounce.c
// Every update where "active" does not match the current state, we increment
// a counter, otherwise we decrement it. When the counter reaches a
// threshold, the state flips and we reset the counter.
state->changed = false;
if (active == state->pressed) {
decrement_counter(state, elapsed_ms);
return;
}
const uint32_t flip_threshold = get_threshold(state, config);
if (state->counter < flip_threshold) {
increment_counter(state, elapsed_ms);
return;
}
state->pressed = !state->pressed;
state->counter = 0;
state->changed = true;
}
bool debounce_is_active(const struct debounce_state *state) {
return state->pressed || state->counter > 0;
}
bool debounce_is_pressed(const struct debounce_state *state) { return state->pressed; }
bool debounce_get_changed(const struct debounce_state *state) { return state->changed; }

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/sys/util.h>
#define DEBOUNCE_COUNTER_BITS 14
#define DEBOUNCE_COUNTER_MAX BIT_MASK(DEBOUNCE_COUNTER_BITS)
struct debounce_state {
bool pressed : 1;
bool changed : 1;
uint16_t counter : DEBOUNCE_COUNTER_BITS;
};
struct debounce_config {
/** Duration a switch must be pressed to latch as pressed. */
uint32_t debounce_press_ms;
/** Duration a switch must be released to latch as released. */
uint32_t debounce_release_ms;
};
/**
* Debounces one switch.
*
* @param state The state for the switch to debounce.
* @param active Is the switch currently pressed?
* @param elapsed_ms Time elapsed since the previous update in milliseconds.
* @param config Debounce settings.
*/
void debounce_update(struct debounce_state *state, const bool active, const int elapsed_ms,
const struct debounce_config *config);
/**
* @returns whether the switch is either latched as pressed or it is potentially
* pressed but the debouncer has not yet made a decision. If this returns true,
* the kscan driver should continue to poll quickly.
*/
bool debounce_is_active(const struct debounce_state *state);
/**
* @returns whether the switch is latched as pressed.
*/
bool debounce_is_pressed(const struct debounce_state *state);
/**
* @returns whether the pressed state of the switch changed in the last call to
* debounce_update.
*/
bool debounce_get_changed(const struct debounce_state *state);

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_kscan_composite
#include <zephyr/device.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define MATRIX_NODE_ID DT_DRV_INST(0)
#define MATRIX_ROWS DT_PROP(MATRIX_NODE_ID, rows)
#define MATRIX_COLS DT_PROP(MATRIX_NODE_ID, columns)
struct kscan_composite_child_config {
const struct device *child;
uint8_t row_offset;
uint8_t column_offset;
};
#define CHILD_CONFIG(inst) \
{.child = DEVICE_DT_GET(DT_PHANDLE(inst, kscan)), \
.row_offset = DT_PROP(inst, row_offset), \
.column_offset = DT_PROP(inst, column_offset)},
const struct kscan_composite_child_config kscan_composite_children[] = {
DT_FOREACH_CHILD(MATRIX_NODE_ID, CHILD_CONFIG)};
struct kscan_composite_config {};
struct kscan_composite_data {
kscan_callback_t callback;
const struct device *dev;
};
static int kscan_composite_enable_callback(const struct device *dev) {
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
kscan_enable_callback(cfg->child);
}
return 0;
}
static int kscan_composite_disable_callback(const struct device *dev) {
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
kscan_disable_callback(cfg->child);
}
return 0;
}
static void kscan_composite_child_callback(const struct device *child_dev, uint32_t row,
uint32_t column, bool pressed) {
// TODO: Ideally we can get this passed into our callback!
const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(0));
struct kscan_composite_data *data = dev->data;
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
if (cfg->child != child_dev) {
continue;
}
data->callback(dev, row + cfg->row_offset, column + cfg->column_offset, pressed);
}
}
static int kscan_composite_configure(const struct device *dev, kscan_callback_t callback) {
struct kscan_composite_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
kscan_config(cfg->child, &kscan_composite_child_callback);
}
data->callback = callback;
return 0;
}
static int kscan_composite_init(const struct device *dev) {
struct kscan_composite_data *data = dev->data;
data->dev = dev;
return 0;
}
static const struct kscan_driver_api mock_driver_api = {
.config = kscan_composite_configure,
.enable_callback = kscan_composite_enable_callback,
.disable_callback = kscan_composite_disable_callback,
};
static const struct kscan_composite_config kscan_composite_config = {};
static struct kscan_composite_data kscan_composite_data;
DEVICE_DT_INST_DEFINE(0, kscan_composite_init, NULL, &kscan_composite_data, &kscan_composite_config,
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &mock_driver_api);

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include "kscan_gpio.h"
#include <stdlib.h>
static int compare_ports(const void *a, const void *b) {
const struct kscan_gpio *gpio_a = a;
const struct kscan_gpio *gpio_b = b;
return gpio_a->spec.port - gpio_b->spec.port;
}
void kscan_gpio_list_sort_by_port(struct kscan_gpio_list *list) {
qsort(list->gpios, list->len, sizeof(list->gpios[0]), compare_ports);
}
int kscan_gpio_pin_get(const struct kscan_gpio *gpio, struct kscan_gpio_port_state *state) {
if (gpio->spec.port != state->port) {
state->port = gpio->spec.port;
const int err = gpio_port_get(state->port, &state->value);
if (err) {
return err;
}
}
return (state->value & BIT(gpio->spec.pin)) != 0;
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/sys/util.h>
struct kscan_gpio {
struct gpio_dt_spec spec;
/** The index of the GPIO in the devicetree *-gpios array. */
size_t index;
};
/** GPIO_DT_SPEC_GET_BY_IDX(), but for a struct kscan_gpio. */
#define KSCAN_GPIO_GET_BY_IDX(node_id, prop, idx) \
((struct kscan_gpio){.spec = GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx), .index = idx})
struct kscan_gpio_list {
struct kscan_gpio *gpios;
size_t len;
};
/** Define a kscan_gpio_list from a compile-time GPIO array. */
#define KSCAN_GPIO_LIST(gpio_array) \
((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)})
struct kscan_gpio_port_state {
const struct device *port;
gpio_port_value_t value;
};
/**
* Sorts a GPIO list by port so it can be used with kscan_gpio_pin_get().
*/
void kscan_gpio_list_sort_by_port(struct kscan_gpio_list *list);
/**
* Get logical level of an input pin.
*
* This is equivalent to gpio_pin_get() except that, when iterating through the
* pins in a list which is sorted by kscan_gpio_list_sort_by_port(), it only
* performs one read per port instead of one read per pin.
*
* @param gpio The input pin to read.
* @param state An object to track state between reads. Must be zero-initialized before the first
* use.
*
* @retval 1 If pin logical value is 1 / active.
* @retval 0 If pin logical value is 0 / inactive.
* @retval -EIO I/O error when accessing an external GPIO chip.
* @retval -EWOULDBLOCK if operation would block.
*/
int kscan_gpio_pin_get(const struct kscan_gpio *gpio, struct kscan_gpio_port_state *state);

View File

@@ -0,0 +1,207 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_kscan_gpio_demux
#include <zephyr/device.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
// Helper macro
#define PWR_TWO(x) (1 << (x))
// Define row and col cfg
#define _KSCAN_GPIO_CFG_INIT(n, prop, idx) GPIO_DT_SPEC_GET_BY_IDX(n, prop, idx),
// Check debounce config
#define CHECK_DEBOUNCE_CFG(n, a, b) COND_CODE_0(DT_INST_PROP(n, debounce_period), a, b)
// Define the row and column lengths
#define INST_MATRIX_INPUTS(n) DT_INST_PROP_LEN(n, input_gpios)
#define INST_DEMUX_GPIOS(n) DT_INST_PROP_LEN(n, output_gpios)
#define INST_MATRIX_OUTPUTS(n) PWR_TWO(INST_DEMUX_GPIOS(n))
#define POLL_INTERVAL(n) DT_INST_PROP(n, polling_interval_msec)
#define GPIO_INST_INIT(n) \
struct kscan_gpio_irq_callback_##n { \
struct CHECK_DEBOUNCE_CFG(n, (k_work), (k_work_delayable)) * work; \
struct gpio_callback callback; \
const struct device *dev; \
}; \
\
struct kscan_gpio_config_##n { \
const struct gpio_dt_spec rows[INST_MATRIX_INPUTS(n)]; \
const struct gpio_dt_spec cols[INST_DEMUX_GPIOS(n)]; \
}; \
\
struct kscan_gpio_data_##n { \
kscan_callback_t callback; \
struct k_timer poll_timer; \
struct CHECK_DEBOUNCE_CFG(n, (k_work), (k_work_delayable)) work; \
bool matrix_state[INST_MATRIX_INPUTS(n)][INST_MATRIX_OUTPUTS(n)]; \
const struct device *dev; \
}; \
/* IO/GPIO SETUP */ \
static const struct gpio_dt_spec *kscan_gpio_input_specs_##n(const struct device *dev) { \
const struct kscan_gpio_config_##n *cfg = dev->config; \
return cfg->rows; \
} \
\
static const struct gpio_dt_spec *kscan_gpio_output_specs_##n(const struct device *dev) { \
const struct kscan_gpio_config_##n *cfg = dev->config; \
return cfg->cols; \
} \
/* POLLING SETUP */ \
static void kscan_gpio_timer_handler(struct k_timer *timer) { \
struct kscan_gpio_data_##n *data = \
CONTAINER_OF(timer, struct kscan_gpio_data_##n, poll_timer); \
k_work_submit(&data->work.work); \
} \
\
/* Read the state of the input GPIOs */ \
/* This is the core matrix_scan func */ \
static int kscan_gpio_read_##n(const struct device *dev) { \
bool submit_follow_up_read = false; \
struct kscan_gpio_data_##n *data = dev->data; \
static bool read_state[INST_MATRIX_INPUTS(n)][INST_MATRIX_OUTPUTS(n)]; \
for (int o = 0; o < INST_MATRIX_OUTPUTS(n); o++) { \
/* Iterate over bits and set GPIOs accordingly */ \
for (uint8_t bit = 0; bit < INST_DEMUX_GPIOS(n); bit++) { \
uint8_t state = (o & (0b1 << bit)) >> bit; \
const struct gpio_dt_spec *out_spec = &kscan_gpio_output_specs_##n(dev)[bit]; \
gpio_pin_set_dt(out_spec, state); \
} \
/* Let the col settle before reading the rows */ \
k_usleep(1); \
\
for (int i = 0; i < INST_MATRIX_INPUTS(n); i++) { \
/* Get the input spec */ \
const struct gpio_dt_spec *in_spec = &kscan_gpio_input_specs_##n(dev)[i]; \
read_state[i][o] = gpio_pin_get_dt(in_spec) > 0; \
} \
} \
for (int r = 0; r < INST_MATRIX_INPUTS(n); r++) { \
for (int c = 0; c < INST_MATRIX_OUTPUTS(n); c++) { \
bool pressed = read_state[r][c]; \
submit_follow_up_read = (submit_follow_up_read || pressed); \
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); \
} \
} \
} \
if (submit_follow_up_read) { \
CHECK_DEBOUNCE_CFG(n, ({ k_work_submit(&data->work); }), \
({ k_work_reschedule(&data->work, K_MSEC(5)); })) \
} \
return 0; \
} \
\
static void kscan_gpio_work_handler_##n(struct k_work *work) { \
struct kscan_gpio_data_##n *data = CONTAINER_OF(work, struct kscan_gpio_data_##n, work); \
kscan_gpio_read_##n(data->dev); \
} \
\
static struct kscan_gpio_data_##n kscan_gpio_data_##n = {}; \
\
/* KSCAN API configure function */ \
static int kscan_gpio_configure_##n(const struct device *dev, kscan_callback_t callback) { \
LOG_DBG("KSCAN API configure"); \
struct kscan_gpio_data_##n *data = dev->data; \
if (!callback) { \
return -EINVAL; \
} \
data->callback = callback; \
LOG_DBG("Configured GPIO %d", n); \
return 0; \
}; \
\
/* KSCAN API enable function */ \
static int kscan_gpio_enable_##n(const struct device *dev) { \
LOG_DBG("KSCAN API enable"); \
struct kscan_gpio_data_##n *data = dev->data; \
/* TODO: we might want a follow up to hook into the sleep state hooks in Zephyr, */ \
/* and disable this timer when we enter a sleep state */ \
k_timer_start(&data->poll_timer, K_MSEC(POLL_INTERVAL(n)), K_MSEC(POLL_INTERVAL(n))); \
return 0; \
}; \
\
/* KSCAN API disable function */ \
static int kscan_gpio_disable_##n(const struct device *dev) { \
LOG_DBG("KSCAN API disable"); \
struct kscan_gpio_data_##n *data = dev->data; \
k_timer_stop(&data->poll_timer); \
return 0; \
}; \
\
/* GPIO init function*/ \
static int kscan_gpio_init_##n(const struct device *dev) { \
LOG_DBG("KSCAN GPIO init"); \
struct kscan_gpio_data_##n *data = dev->data; \
int err; \
/* configure input devices*/ \
for (int i = 0; i < INST_MATRIX_INPUTS(n); i++) { \
const struct gpio_dt_spec *in_spec = &kscan_gpio_input_specs_##n(dev)[i]; \
if (!device_is_ready(in_spec->port)) { \
LOG_ERR("Unable to find input GPIO device"); \
return -EINVAL; \
} \
err = gpio_pin_configure_dt(in_spec, GPIO_INPUT); \
if (err) { \
LOG_ERR("Unable to configure pin %d for input", in_spec->pin); \
return err; \
} else { \
LOG_DBG("Configured pin %d for input", in_spec->pin); \
} \
if (err) { \
LOG_ERR("Error adding the callback to the column device"); \
return err; \
} \
} \
/* configure output devices*/ \
for (int o = 0; o < INST_DEMUX_GPIOS(n); o++) { \
const struct gpio_dt_spec *out_spec = &kscan_gpio_output_specs_##n(dev)[o]; \
if (!device_is_ready(out_spec->port)) { \
LOG_ERR("Unable to find output GPIO device"); \
return -EINVAL; \
} \
err = gpio_pin_configure_dt(out_spec, GPIO_OUTPUT_ACTIVE); \
if (err) { \
LOG_ERR("Unable to configure pin %d for output", out_spec->pin); \
return err; \
} else { \
LOG_DBG("Configured pin %d for output", out_spec->pin); \
} \
} \
data->dev = dev; \
\
k_timer_init(&data->poll_timer, kscan_gpio_timer_handler, NULL); \
\
(CHECK_DEBOUNCE_CFG(n, (k_work_init), (k_work_init_delayable)))( \
&data->work, kscan_gpio_work_handler_##n); \
return 0; \
} \
\
static const struct kscan_driver_api gpio_driver_api_##n = { \
.config = kscan_gpio_configure_##n, \
.enable_callback = kscan_gpio_enable_##n, \
.disable_callback = kscan_gpio_disable_##n, \
}; \
\
static const struct kscan_gpio_config_##n kscan_gpio_config_##n = { \
.rows = {DT_FOREACH_PROP_ELEM(DT_DRV_INST(n), input_gpios, _KSCAN_GPIO_CFG_INIT)}, \
.cols = {DT_FOREACH_PROP_ELEM(DT_DRV_INST(n), output_gpios, _KSCAN_GPIO_CFG_INIT)}, \
}; \
\
DEVICE_DT_INST_DEFINE(n, kscan_gpio_init_##n, NULL, &kscan_gpio_data_##n, \
&kscan_gpio_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
&gpio_driver_api_##n);
DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)

View File

@@ -0,0 +1,360 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include "debounce.h"
#include "kscan_gpio.h"
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DT_DRV_COMPAT zmk_kscan_gpio_direct
#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0
#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS
#else
#define INST_DEBOUNCE_PRESS_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms))
#endif
#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0
#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS
#else
#define INST_DEBOUNCE_RELEASE_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms))
#endif
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_DIRECT_POLLING)
#define USE_INTERRUPTS (!USE_POLLING)
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_DIRECT_POLLING, (), code)
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
COND_CODE_1(CONFIG_ZMK_KSCAN_DIRECT_POLLING, pollcode, intcode)
#define INST_INPUTS_LEN(n) DT_INST_PROP_LEN(n, input_gpios)
#define KSCAN_DIRECT_INPUT_CFG_INIT(idx, inst_idx) \
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), input_gpios, idx)
struct kscan_direct_irq_callback {
const struct device *dev;
struct gpio_callback callback;
};
struct kscan_direct_data {
const struct device *dev;
struct kscan_gpio_list inputs;
kscan_callback_t callback;
struct k_work_delayable work;
#if USE_INTERRUPTS
/** Array of length config->inputs.len */
struct kscan_direct_irq_callback *irqs;
#endif
/** Timestamp of the current or scheduled scan. */
int64_t scan_time;
/** Current state of the inputs as an array of length config->inputs.len */
struct debounce_state *pin_state;
};
struct kscan_direct_config {
struct debounce_config debounce_config;
int32_t debounce_scan_period_ms;
int32_t poll_period_ms;
bool toggle_mode;
};
#if USE_INTERRUPTS
static int kscan_direct_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
const struct kscan_direct_data *data = dev->data;
for (int i = 0; i < data->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
if (err) {
LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
return err;
}
}
return 0;
}
#endif
#if USE_INTERRUPTS
static int kscan_direct_interrupt_enable(const struct device *dev) {
return kscan_direct_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
}
#endif
#if USE_INTERRUPTS
static int kscan_direct_interrupt_disable(const struct device *dev) {
return kscan_direct_interrupt_configure(dev, GPIO_INT_DISABLE);
}
#endif
#if USE_INTERRUPTS
static void kscan_direct_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
const gpio_port_pins_t pin) {
struct kscan_direct_irq_callback *irq_data =
CONTAINER_OF(cb, struct kscan_direct_irq_callback, callback);
struct kscan_direct_data *data = irq_data->dev->data;
// Disable our interrupts temporarily to avoid re-entry while we scan.
kscan_direct_interrupt_disable(data->dev);
data->scan_time = k_uptime_get();
k_work_reschedule(&data->work, K_NO_WAIT);
}
#endif
static gpio_flags_t kscan_gpio_get_extra_flags(const struct gpio_dt_spec *gpio, bool active) {
if (!active) {
return ((BIT(0) & gpio->dt_flags) ? GPIO_PULL_UP : GPIO_PULL_DOWN);
}
return 0;
}
static int kscan_inputs_set_flags(const struct kscan_gpio_list *inputs,
const struct gpio_dt_spec *active_gpio) {
for (int i = 0; i < inputs->len; i++) {
const bool active = &inputs->gpios[i].spec == active_gpio;
const gpio_flags_t extra_flags =
GPIO_INPUT | kscan_gpio_get_extra_flags(&inputs->gpios[i].spec, active);
LOG_DBG("Extra flags equal to: %d", extra_flags);
int err = gpio_pin_configure_dt(&inputs->gpios[i].spec, extra_flags);
if (err) {
LOG_ERR("Unable to configure flags on pin %d on %s", inputs->gpios[i].spec.pin,
inputs->gpios[i].spec.port->name);
return err;
}
}
return 0;
}
static void kscan_direct_read_continue(const struct device *dev) {
const struct kscan_direct_config *config = dev->config;
struct kscan_direct_data *data = dev->data;
data->scan_time += config->debounce_scan_period_ms;
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
}
static void kscan_direct_read_end(const struct device *dev) {
#if USE_INTERRUPTS
// Return to waiting for an interrupt.
kscan_direct_interrupt_enable(dev);
#else
struct kscan_direct_data *data = dev->data;
const struct kscan_direct_config *config = dev->config;
data->scan_time += config->poll_period_ms;
// Return to polling slowly.
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
#endif
}
static int kscan_direct_read(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
const struct kscan_direct_config *config = dev->config;
// Read the inputs.
struct kscan_gpio_port_state state = {0};
for (int i = 0; i < data->inputs.len; i++) {
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
const int active = kscan_gpio_pin_get(gpio, &state);
if (active < 0) {
LOG_ERR("Failed to read port %s: %i", gpio->spec.port->name, active);
return active;
}
debounce_update(&data->pin_state[gpio->index], active, config->debounce_scan_period_ms,
&config->debounce_config);
}
// Process the new state.
bool continue_scan = false;
for (int i = 0; i < data->inputs.len; i++) {
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
struct debounce_state *state = &data->pin_state[gpio->index];
if (debounce_get_changed(state)) {
const bool pressed = debounce_is_pressed(state);
LOG_DBG("Sending event at 0,%i state %s", gpio->index, pressed ? "on" : "off");
data->callback(dev, 0, gpio->index, pressed);
if (config->toggle_mode && pressed) {
kscan_inputs_set_flags(&data->inputs, &gpio->spec);
}
}
continue_scan = continue_scan || debounce_is_active(state);
}
if (continue_scan) {
// At least one key is pressed or the debouncer has not yet decided if
// it is pressed. Poll quickly until everything is released.
kscan_direct_read_continue(dev);
} else {
// All keys are released. Return to normal.
kscan_direct_read_end(dev);
}
return 0;
}
static void kscan_direct_work_handler(struct k_work *work) {
struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
struct kscan_direct_data *data = CONTAINER_OF(dwork, struct kscan_direct_data, work);
kscan_direct_read(data->dev);
}
static int kscan_direct_configure(const struct device *dev, kscan_callback_t callback) {
struct kscan_direct_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
data->callback = callback;
return 0;
}
static int kscan_direct_enable(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
data->scan_time = k_uptime_get();
// Read will automatically start interrupts/polling once done.
return kscan_direct_read(dev);
}
static int kscan_direct_disable(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
k_work_cancel_delayable(&data->work);
#if USE_INTERRUPTS
return kscan_direct_interrupt_disable(dev);
#else
return 0;
#endif
}
static int kscan_direct_init_input_inst(const struct device *dev, const struct gpio_dt_spec *gpio,
const int index, bool toggle_mode) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure_dt(
gpio, GPIO_INPUT | (toggle_mode ? kscan_gpio_get_extra_flags(gpio, false) : 0));
if (err) {
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for input", gpio->pin, gpio->port->name);
#if USE_INTERRUPTS
struct kscan_direct_data *data = dev->data;
struct kscan_direct_irq_callback *irq = &data->irqs[index];
irq->dev = dev;
gpio_init_callback(&irq->callback, kscan_direct_irq_callback_handler, BIT(gpio->pin));
err = gpio_add_callback(gpio->port, &irq->callback);
if (err) {
LOG_ERR("Error adding the callback to the input device: %i", err);
return err;
}
#endif
return 0;
}
static int kscan_direct_init_inputs(const struct device *dev) {
const struct kscan_direct_data *data = dev->data;
const struct kscan_direct_config *config = dev->config;
for (int i = 0; i < data->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
int err = kscan_direct_init_input_inst(dev, gpio, i, config->toggle_mode);
if (err) {
return err;
}
}
return 0;
}
static int kscan_direct_init(const struct device *dev) {
struct kscan_direct_data *data = dev->data;
data->dev = dev;
// Sort inputs by port so we can read each port just once per scan.
kscan_gpio_list_sort_by_port(&data->inputs);
kscan_direct_init_inputs(dev);
k_work_init_delayable(&data->work, kscan_direct_work_handler);
return 0;
}
static const struct kscan_driver_api kscan_direct_api = {
.config = kscan_direct_configure,
.enable_callback = kscan_direct_enable,
.disable_callback = kscan_direct_disable,
};
#define KSCAN_DIRECT_INIT(n) \
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
\
static struct kscan_gpio kscan_direct_inputs_##n[] = { \
LISTIFY(INST_INPUTS_LEN(n), KSCAN_DIRECT_INPUT_CFG_INIT, (, ), n)}; \
\
static struct debounce_state kscan_direct_state_##n[INST_INPUTS_LEN(n)]; \
\
COND_INTERRUPTS( \
(static struct kscan_direct_irq_callback kscan_direct_irqs_##n[INST_INPUTS_LEN(n)];)) \
\
static struct kscan_direct_data kscan_direct_data_##n = { \
.inputs = KSCAN_GPIO_LIST(kscan_direct_inputs_##n), \
.pin_state = kscan_direct_state_##n, \
COND_INTERRUPTS((.irqs = kscan_direct_irqs_##n, ))}; \
\
static struct kscan_direct_config kscan_direct_config_##n = { \
.debounce_config = \
{ \
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \
}, \
.debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
.poll_period_ms = DT_INST_PROP(n, poll_period_ms), \
.toggle_mode = DT_INST_PROP(n, toggle_mode), \
}; \
\
DEVICE_DT_INST_DEFINE(n, &kscan_direct_init, NULL, &kscan_direct_data_##n, \
&kscan_direct_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
&kscan_direct_api);
DT_INST_FOREACH_STATUS_OKAY(KSCAN_DIRECT_INIT);

View File

@@ -0,0 +1,471 @@
/*
* Copyright (c) 2020-2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include "debounce.h"
#include "kscan_gpio.h"
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DT_DRV_COMPAT zmk_kscan_gpio_matrix
#define INST_DIODE_DIR(n) DT_ENUM_IDX(DT_DRV_INST(n), diode_direction)
#define COND_DIODE_DIR(n, row2col_code, col2row_code) \
COND_CODE_0(INST_DIODE_DIR(n), row2col_code, col2row_code)
#define INST_ROWS_LEN(n) DT_INST_PROP_LEN(n, row_gpios)
#define INST_COLS_LEN(n) DT_INST_PROP_LEN(n, col_gpios)
#define INST_MATRIX_LEN(n) (INST_ROWS_LEN(n) * INST_COLS_LEN(n))
#define INST_INPUTS_LEN(n) COND_DIODE_DIR(n, (INST_COLS_LEN(n)), (INST_ROWS_LEN(n)))
#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0
#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS
#else
#define INST_DEBOUNCE_PRESS_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms))
#endif
#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0
#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS
#else
#define INST_DEBOUNCE_RELEASE_MS(n) \
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms))
#endif
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MATRIX_POLLING)
#define USE_INTERRUPTS (!USE_POLLING)
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (), code)
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode)
#define KSCAN_GPIO_ROW_CFG_INIT(idx, inst_idx) \
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), row_gpios, idx)
#define KSCAN_GPIO_COL_CFG_INIT(idx, inst_idx) \
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), col_gpios, idx)
enum kscan_diode_direction {
KSCAN_ROW2COL,
KSCAN_COL2ROW,
};
struct kscan_matrix_irq_callback {
const struct device *dev;
struct gpio_callback callback;
};
struct kscan_matrix_data {
const struct device *dev;
struct kscan_gpio_list inputs;
kscan_callback_t callback;
struct k_work_delayable work;
#if USE_INTERRUPTS
/** Array of length config->inputs.len */
struct kscan_matrix_irq_callback *irqs;
#endif
/** Timestamp of the current or scheduled scan. */
int64_t scan_time;
/**
* Current state of the matrix as a flattened 2D array of length
* (config->rows * config->cols)
*/
struct debounce_state *matrix_state;
};
struct kscan_matrix_config {
struct kscan_gpio_list outputs;
struct debounce_config debounce_config;
size_t rows;
size_t cols;
int32_t debounce_scan_period_ms;
int32_t poll_period_ms;
enum kscan_diode_direction diode_direction;
};
/**
* Get the index into a matrix state array from a row and column.
*/
static int state_index_rc(const struct kscan_matrix_config *config, const int row, const int col) {
__ASSERT(row < config->rows, "Invalid row %i", row);
__ASSERT(col < config->cols, "Invalid column %i", col);
return (col * config->rows) + row;
}
/**
* Get the index into a matrix state array from input/output pin indices.
*/
static int state_index_io(const struct kscan_matrix_config *config, const int input_idx,
const int output_idx) {
return (config->diode_direction == KSCAN_ROW2COL)
? state_index_rc(config, output_idx, input_idx)
: state_index_rc(config, input_idx, output_idx);
}
static int kscan_matrix_set_all_outputs(const struct device *dev, const int value) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->outputs.len; i++) {
const struct gpio_dt_spec *gpio = &config->outputs.gpios[i].spec;
int err = gpio_pin_set_dt(gpio, value);
if (err) {
LOG_ERR("Failed to set output %i to %i: %i", i, value, err);
return err;
}
}
return 0;
}
#if USE_INTERRUPTS
static int kscan_matrix_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
const struct kscan_matrix_data *data = dev->data;
for (int i = 0; i < data->inputs.len; i++) {
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
int err = gpio_pin_interrupt_configure_dt(gpio, flags);
if (err) {
LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
return err;
}
}
return 0;
}
#endif
#if USE_INTERRUPTS
static int kscan_matrix_interrupt_enable(const struct device *dev) {
int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
if (err) {
return err;
}
// While interrupts are enabled, set all outputs active so a pressed key
// will trigger an interrupt.
return kscan_matrix_set_all_outputs(dev, 1);
}
#endif
#if USE_INTERRUPTS
static int kscan_matrix_interrupt_disable(const struct device *dev) {
int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_DISABLE);
if (err) {
return err;
}
// While interrupts are disabled, set all outputs inactive so
// kscan_matrix_read() can scan them one by one.
return kscan_matrix_set_all_outputs(dev, 0);
}
#endif
#if USE_INTERRUPTS
static void kscan_matrix_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
const gpio_port_pins_t pin) {
struct kscan_matrix_irq_callback *irq_data =
CONTAINER_OF(cb, struct kscan_matrix_irq_callback, callback);
struct kscan_matrix_data *data = irq_data->dev->data;
// Disable our interrupts temporarily to avoid re-entry while we scan.
kscan_matrix_interrupt_disable(data->dev);
data->scan_time = k_uptime_get();
k_work_reschedule(&data->work, K_NO_WAIT);
}
#endif
static void kscan_matrix_read_continue(const struct device *dev) {
const struct kscan_matrix_config *config = dev->config;
struct kscan_matrix_data *data = dev->data;
data->scan_time += config->debounce_scan_period_ms;
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
}
static void kscan_matrix_read_end(const struct device *dev) {
#if USE_INTERRUPTS
// Return to waiting for an interrupt.
kscan_matrix_interrupt_enable(dev);
#else
struct kscan_matrix_data *data = dev->data;
const struct kscan_matrix_config *config = dev->config;
data->scan_time += config->poll_period_ms;
// Return to polling slowly.
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
#endif
}
static int kscan_matrix_read(const struct device *dev) {
struct kscan_matrix_data *data = dev->data;
const struct kscan_matrix_config *config = dev->config;
// Scan the matrix.
for (int i = 0; i < config->outputs.len; i++) {
const struct kscan_gpio *out_gpio = &config->outputs.gpios[i];
int err = gpio_pin_set_dt(&out_gpio->spec, 1);
if (err) {
LOG_ERR("Failed to set output %i active: %i", out_gpio->index, err);
return err;
}
#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0
k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS);
#endif
struct kscan_gpio_port_state state = {0};
for (int j = 0; j < data->inputs.len; j++) {
const struct kscan_gpio *in_gpio = &data->inputs.gpios[j];
const int index = state_index_io(config, in_gpio->index, out_gpio->index);
const int active = kscan_gpio_pin_get(in_gpio, &state);
if (active < 0) {
LOG_ERR("Failed to read port %s: %i", in_gpio->spec.port->name, active);
return active;
}
debounce_update(&data->matrix_state[index], active, config->debounce_scan_period_ms,
&config->debounce_config);
}
err = gpio_pin_set_dt(&out_gpio->spec, 0);
if (err) {
LOG_ERR("Failed to set output %i inactive: %i", out_gpio->index, err);
return err;
}
#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS > 0
k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS);
#endif
}
// Process the new state.
bool continue_scan = false;
for (int r = 0; r < config->rows; r++) {
for (int c = 0; c < config->cols; c++) {
const int index = state_index_rc(config, r, c);
struct debounce_state *state = &data->matrix_state[index];
if (debounce_get_changed(state)) {
const bool pressed = debounce_is_pressed(state);
LOG_DBG("Sending event at %i,%i state %s", r, c, pressed ? "on" : "off");
data->callback(dev, r, c, pressed);
}
continue_scan = continue_scan || debounce_is_active(state);
}
}
if (continue_scan) {
// At least one key is pressed or the debouncer has not yet decided if
// it is pressed. Poll quickly until everything is released.
kscan_matrix_read_continue(dev);
} else {
// All keys are released. Return to normal.
kscan_matrix_read_end(dev);
}
return 0;
}
static void kscan_matrix_work_handler(struct k_work *work) {
struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
struct kscan_matrix_data *data = CONTAINER_OF(dwork, struct kscan_matrix_data, work);
kscan_matrix_read(data->dev);
}
static int kscan_matrix_configure(const struct device *dev, const kscan_callback_t callback) {
struct kscan_matrix_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
data->callback = callback;
return 0;
}
static int kscan_matrix_enable(const struct device *dev) {
struct kscan_matrix_data *data = dev->data;
data->scan_time = k_uptime_get();
// Read will automatically start interrupts/polling once done.
return kscan_matrix_read(dev);
}
static int kscan_matrix_disable(const struct device *dev) {
struct kscan_matrix_data *data = dev->data;
k_work_cancel_delayable(&data->work);
#if USE_INTERRUPTS
return kscan_matrix_interrupt_disable(dev);
#else
return 0;
#endif
}
static int kscan_matrix_init_input_inst(const struct device *dev, const struct kscan_gpio *gpio) {
if (!device_is_ready(gpio->spec.port)) {
LOG_ERR("GPIO is not ready: %s", gpio->spec.port->name);
return -ENODEV;
}
int err = gpio_pin_configure_dt(&gpio->spec, GPIO_INPUT);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for input", gpio->spec.pin,
gpio->spec.port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for input", gpio->spec.pin, gpio->spec.port->name);
#if USE_INTERRUPTS
struct kscan_matrix_data *data = dev->data;
struct kscan_matrix_irq_callback *irq = &data->irqs[gpio->index];
irq->dev = dev;
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->spec.pin));
err = gpio_add_callback(gpio->spec.port, &irq->callback);
if (err) {
LOG_ERR("Error adding the callback to the input device: %i", err);
return err;
}
#endif
return 0;
}
static int kscan_matrix_init_inputs(const struct device *dev) {
const struct kscan_matrix_data *data = dev->data;
for (int i = 0; i < data->inputs.len; i++) {
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
int err = kscan_matrix_init_input_inst(dev, gpio);
if (err) {
return err;
}
}
return 0;
}
static int kscan_matrix_init_output_inst(const struct device *dev,
const struct gpio_dt_spec *gpio) {
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
return -ENODEV;
}
int err = gpio_pin_configure_dt(gpio, GPIO_OUTPUT);
if (err) {
LOG_ERR("Unable to configure pin %u on %s for output", gpio->pin, gpio->port->name);
return err;
}
LOG_DBG("Configured pin %u on %s for output", gpio->pin, gpio->port->name);
return 0;
}
static int kscan_matrix_init_outputs(const struct device *dev) {
const struct kscan_matrix_config *config = dev->config;
for (int i = 0; i < config->outputs.len; i++) {
const struct gpio_dt_spec *gpio = &config->outputs.gpios[i].spec;
int err = kscan_matrix_init_output_inst(dev, gpio);
if (err) {
return err;
}
}
return 0;
}
static int kscan_matrix_init(const struct device *dev) {
struct kscan_matrix_data *data = dev->data;
data->dev = dev;
// Sort inputs by port so we can read each port just once per scan.
kscan_gpio_list_sort_by_port(&data->inputs);
kscan_matrix_init_inputs(dev);
kscan_matrix_init_outputs(dev);
kscan_matrix_set_all_outputs(dev, 0);
k_work_init_delayable(&data->work, kscan_matrix_work_handler);
return 0;
}
static const struct kscan_driver_api kscan_matrix_api = {
.config = kscan_matrix_configure,
.enable_callback = kscan_matrix_enable,
.disable_callback = kscan_matrix_disable,
};
#define KSCAN_MATRIX_INIT(n) \
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
\
static struct kscan_gpio kscan_matrix_rows_##n[] = { \
LISTIFY(INST_ROWS_LEN(n), KSCAN_GPIO_ROW_CFG_INIT, (, ), n)}; \
\
static struct kscan_gpio kscan_matrix_cols_##n[] = { \
LISTIFY(INST_COLS_LEN(n), KSCAN_GPIO_COL_CFG_INIT, (, ), n)}; \
\
static struct debounce_state kscan_matrix_state_##n[INST_MATRIX_LEN(n)]; \
\
COND_INTERRUPTS( \
(static struct kscan_matrix_irq_callback kscan_matrix_irqs_##n[INST_INPUTS_LEN(n)];)) \
\
static struct kscan_matrix_data kscan_matrix_data_##n = { \
.inputs = \
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_cols_##n), (kscan_matrix_rows_##n))), \
.matrix_state = kscan_matrix_state_##n, \
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##n, ))}; \
\
static struct kscan_matrix_config kscan_matrix_config_##n = { \
.rows = ARRAY_SIZE(kscan_matrix_rows_##n), \
.cols = ARRAY_SIZE(kscan_matrix_cols_##n), \
.outputs = \
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_rows_##n), (kscan_matrix_cols_##n))), \
.debounce_config = \
{ \
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \
}, \
.debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
.poll_period_ms = DT_INST_PROP(n, poll_period_ms), \
.diode_direction = INST_DIODE_DIR(n), \
}; \
\
DEVICE_DT_INST_DEFINE(n, &kscan_matrix_init, NULL, &kscan_matrix_data_##n, \
&kscan_matrix_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
&kscan_matrix_api);
DT_INST_FOREACH_STATUS_OKAY(KSCAN_MATRIX_INIT);

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_kscan_mock
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <dt-bindings/zmk/kscan_mock.h>
struct kscan_mock_data {
kscan_callback_t callback;
uint32_t event_index;
struct k_work_delayable work;
const struct device *dev;
};
static int kscan_mock_disable_callback(const struct device *dev) {
struct kscan_mock_data *data = dev->data;
k_work_cancel_delayable(&data->work);
return 0;
}
static int kscan_mock_configure(const struct device *dev, kscan_callback_t callback) {
struct kscan_mock_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
data->event_index = 0;
data->callback = callback;
return 0;
}
#define MOCK_INST_INIT(n) \
struct kscan_mock_config_##n { \
uint32_t events[DT_INST_PROP_LEN(n, events)]; \
bool exit_after; \
}; \
static void kscan_mock_schedule_next_event_##n(const struct device *dev) { \
struct kscan_mock_data *data = dev->data; \
const struct kscan_mock_config_##n *cfg = dev->config; \
if (data->event_index < DT_INST_PROP_LEN(n, events)) { \
uint32_t ev = cfg->events[data->event_index]; \
LOG_DBG("delaying next keypress: %d", ZMK_MOCK_MSEC(ev)); \
k_work_schedule(&data->work, K_MSEC(ZMK_MOCK_MSEC(ev))); \
} else if (cfg->exit_after) { \
LOG_DBG("Exiting"); \
exit(0); \
} \
} \
static void kscan_mock_work_handler_##n(struct k_work *work) { \
struct kscan_mock_data *data = CONTAINER_OF(work, struct kscan_mock_data, work); \
const struct kscan_mock_config_##n *cfg = data->dev->config; \
uint32_t ev = cfg->events[data->event_index]; \
LOG_DBG("ev %u row %d column %d state %d\n", ev, ZMK_MOCK_ROW(ev), ZMK_MOCK_COL(ev), \
ZMK_MOCK_IS_PRESS(ev)); \
data->callback(data->dev, ZMK_MOCK_ROW(ev), ZMK_MOCK_COL(ev), ZMK_MOCK_IS_PRESS(ev)); \
kscan_mock_schedule_next_event_##n(data->dev); \
data->event_index++; \
} \
static int kscan_mock_init_##n(const struct device *dev) { \
struct kscan_mock_data *data = dev->data; \
data->dev = dev; \
k_work_init_delayable(&data->work, kscan_mock_work_handler_##n); \
return 0; \
} \
static int kscan_mock_enable_callback_##n(const struct device *dev) { \
kscan_mock_schedule_next_event_##n(dev); \
return 0; \
} \
static const struct kscan_driver_api mock_driver_api_##n = { \
.config = kscan_mock_configure, \
.enable_callback = kscan_mock_enable_callback_##n, \
.disable_callback = kscan_mock_disable_callback, \
}; \
static struct kscan_mock_data kscan_mock_data_##n; \
static const struct kscan_mock_config_##n kscan_mock_config_##n = { \
.events = DT_INST_PROP(n, events), .exit_after = DT_INST_PROP(n, exit_after)}; \
DEVICE_DT_INST_DEFINE(n, kscan_mock_init_##n, NULL, &kscan_mock_data_##n, \
&kscan_mock_config_##n, APPLICATION, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &mock_driver_api_##n);
DT_INST_FOREACH_STATUS_OKAY(MOCK_INST_INIT)

View File

@@ -0,0 +1,5 @@
# Copyright (c) 2020-2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery)
add_subdirectory_ifdef(CONFIG_EC11 ec11)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
if SENSOR
rsource "battery/Kconfig"
rsource "ec11/Kconfig"
endif # SENSOR

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2020-2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_include_directories(.)
zephyr_library()
zephyr_library_sources(battery_common.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_BATTERY_NRF_VDDH battery_nrf_vddh.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_BATTERY_VOLTAGE_DIVIDER battery_voltage_divider.c)

View File

@@ -0,0 +1,28 @@
# Copyright (c) 2020-2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
DT_COMPAT_ZMK_BATTERY_NRF_VDDH := zmk,battery-nrf-vddh
DT_COMPAT_ZMK_BATTERY_VOLTAGE_DIVIDER := zmk,battery-voltage-divider
config ZMK_BATTERY
bool "ZMK battery monitoring"
help
Enable battery monitoring
config ZMK_BATTERY_NRF_VDDH
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_BATTERY_NRF_VDDH))
select ADC
select ZMK_BATTERY
depends on SENSOR
help
Enable ZMK nRF VDDH voltage driver for battery monitoring.
config ZMK_BATTERY_VOLTAGE_DIVIDER
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_BATTERY_VOLTAGE_DIVIDER))
select ADC
select ZMK_BATTERY
depends on SENSOR
help
Enable ZMK battery voltage divider driver for battery monitoring.

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <zephyr/drivers/sensor.h>
#include "battery_common.h"
int battery_channel_get(const struct battery_value *value, enum sensor_channel chan,
struct sensor_value *val_out) {
switch (chan) {
case SENSOR_CHAN_GAUGE_VOLTAGE:
val_out->val1 = value->millivolts / 1000;
val_out->val2 = (value->millivolts % 1000) * 1000U;
break;
case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
val_out->val1 = value->state_of_charge;
val_out->val2 = 0;
break;
default:
return -ENOTSUP;
}
return 0;
}
uint8_t lithium_ion_mv_to_pct(int16_t bat_mv) {
// Simple linear approximation of a battery based off adafruit's discharge graph:
// https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages
if (bat_mv >= 4200) {
return 100;
} else if (bat_mv <= 3450) {
return 0;
}
return bat_mv * 2 / 15 - 459;
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/drivers/sensor.h>
#include <stdint.h>
struct battery_value {
uint16_t adc_raw;
uint16_t millivolts;
uint8_t state_of_charge;
};
int battery_channel_get(const struct battery_value *value, enum sensor_channel chan,
struct sensor_value *val_out);
uint8_t lithium_ion_mv_to_pct(int16_t bat_mv);

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*
* This is a simplified version of battery_voltage_divider.c which always reads
* the VDDHDIV5 channel of the &adc node and multiplies it by 5.
*/
#define DT_DRV_COMPAT zmk_battery_nrf_vddh
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include "battery_common.h"
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define VDDHDIV (5)
static const struct device *adc = DEVICE_DT_GET(DT_NODELABEL(adc));
struct vddh_data {
struct adc_channel_cfg acc;
struct adc_sequence as;
struct battery_value value;
};
static int vddh_sample_fetch(const struct device *dev, enum sensor_channel chan) {
// Make sure selected channel is supported
if (chan != SENSOR_CHAN_GAUGE_VOLTAGE && chan != SENSOR_CHAN_GAUGE_STATE_OF_CHARGE &&
chan != SENSOR_CHAN_ALL) {
LOG_DBG("Selected channel is not supported: %d.", chan);
return -ENOTSUP;
}
struct vddh_data *drv_data = dev->data;
struct adc_sequence *as = &drv_data->as;
int rc = adc_read(adc, as);
as->calibrate = false;
if (rc != 0) {
LOG_ERR("Failed to read ADC: %d", rc);
return rc;
}
int32_t val = drv_data->value.adc_raw;
rc = adc_raw_to_millivolts(adc_ref_internal(adc), drv_data->acc.gain, as->resolution, &val);
if (rc != 0) {
LOG_ERR("Failed to convert raw ADC to mV: %d", rc);
return rc;
}
drv_data->value.millivolts = val * VDDHDIV;
drv_data->value.state_of_charge = lithium_ion_mv_to_pct(drv_data->value.millivolts);
LOG_DBG("ADC raw %d ~ %d mV => %d%%", drv_data->value.adc_raw, drv_data->value.millivolts,
drv_data->value.state_of_charge);
return rc;
}
static int vddh_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val) {
struct vddh_data const *drv_data = dev->data;
return battery_channel_get(&drv_data->value, chan, val);
}
static const struct sensor_driver_api vddh_api = {
.sample_fetch = vddh_sample_fetch,
.channel_get = vddh_channel_get,
};
static int vddh_init(const struct device *dev) {
struct vddh_data *drv_data = dev->data;
if (!device_is_ready(adc)) {
LOG_ERR("ADC device is not ready %s", adc->name);
return -ENODEV;
}
drv_data->as = (struct adc_sequence){
.channels = BIT(0),
.buffer = &drv_data->value.adc_raw,
.buffer_size = sizeof(drv_data->value.adc_raw),
.oversampling = 4,
.calibrate = true,
};
#ifdef CONFIG_ADC_NRFX_SAADC
drv_data->acc = (struct adc_channel_cfg){
.gain = ADC_GAIN_1_2,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
.input_positive = SAADC_CH_PSELN_PSELN_VDDHDIV5,
};
drv_data->as.resolution = 12;
#else
#error Unsupported ADC
#endif
const int rc = adc_channel_setup(adc, &drv_data->acc);
LOG_DBG("VDDHDIV5 setup returned %d", rc);
return rc;
}
static struct vddh_data vddh_data;
DEVICE_DT_INST_DEFINE(0, &vddh_init, NULL, &vddh_data, NULL, POST_KERNEL,
CONFIG_SENSOR_INIT_PRIORITY, &vddh_api);

View File

@@ -0,0 +1,175 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_battery_voltage_divider
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include "battery_common.h"
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
struct io_channel_config {
uint8_t channel;
};
struct bvd_config {
struct io_channel_config io_channel;
struct gpio_dt_spec power;
uint32_t output_ohm;
uint32_t full_ohm;
};
struct bvd_data {
const struct device *adc;
struct adc_channel_cfg acc;
struct adc_sequence as;
struct battery_value value;
};
static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan) {
struct bvd_data *drv_data = dev->data;
const struct bvd_config *drv_cfg = dev->config;
struct adc_sequence *as = &drv_data->as;
// Make sure selected channel is supported
if (chan != SENSOR_CHAN_GAUGE_VOLTAGE && chan != SENSOR_CHAN_GAUGE_STATE_OF_CHARGE &&
chan != SENSOR_CHAN_ALL) {
LOG_DBG("Selected channel is not supported: %d.", chan);
return -ENOTSUP;
}
int rc = 0;
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
// Enable power before sampling
rc = gpio_pin_set_dt(&drv_cfg->power, 1);
if (rc != 0) {
LOG_DBG("Failed to enable ADC power GPIO: %d", rc);
return rc;
}
// wait for any capacitance to charge up
k_sleep(K_MSEC(10));
#endif // DT_INST_NODE_HAS_PROP(0, power_gpios)
// Read ADC
rc = adc_read(drv_data->adc, as);
as->calibrate = false;
if (rc == 0) {
int32_t val = drv_data->value.adc_raw;
adc_raw_to_millivolts(adc_ref_internal(drv_data->adc), drv_data->acc.gain, as->resolution,
&val);
uint16_t millivolts = val * (uint64_t)drv_cfg->full_ohm / drv_cfg->output_ohm;
LOG_DBG("ADC raw %d ~ %d mV => %d mV", drv_data->value.adc_raw, val, millivolts);
uint8_t percent = lithium_ion_mv_to_pct(millivolts);
LOG_DBG("Percent: %d", percent);
drv_data->value.millivolts = millivolts;
drv_data->value.state_of_charge = percent;
} else {
LOG_DBG("Failed to read ADC: %d", rc);
}
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
// Disable power GPIO if present
int rc2 = gpio_pin_set_dt(&drv_cfg->power, 0);
if (rc2 != 0) {
LOG_DBG("Failed to disable ADC power GPIO: %d", rc2);
return rc2;
}
#endif // DT_INST_NODE_HAS_PROP(0, power_gpios)
return rc;
}
static int bvd_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val) {
struct bvd_data *drv_data = dev->data;
return battery_channel_get(&drv_data->value, chan, val);
}
static const struct sensor_driver_api bvd_api = {
.sample_fetch = bvd_sample_fetch,
.channel_get = bvd_channel_get,
};
static int bvd_init(const struct device *dev) {
struct bvd_data *drv_data = dev->data;
const struct bvd_config *drv_cfg = dev->config;
if (drv_data->adc == NULL) {
LOG_ERR("ADC failed to retrieve ADC driver");
return -ENODEV;
}
int rc = 0;
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
if (!device_is_ready(drv_cfg->power.port)) {
LOG_ERR("GPIO port for power control is not ready");
return -ENODEV;
}
rc = gpio_pin_configure_dt(&drv_cfg->power, GPIO_OUTPUT_INACTIVE);
if (rc != 0) {
LOG_ERR("Failed to control feed %u: %d", drv_cfg->power.pin, rc);
return rc;
}
#endif // DT_INST_NODE_HAS_PROP(0, power_gpios)
drv_data->as = (struct adc_sequence){
.channels = BIT(0),
.buffer = &drv_data->value.adc_raw,
.buffer_size = sizeof(drv_data->value.adc_raw),
.oversampling = 4,
.calibrate = true,
};
#ifdef CONFIG_ADC_NRFX_SAADC
drv_data->acc = (struct adc_channel_cfg){
.gain = ADC_GAIN_1_6,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
.input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + drv_cfg->io_channel.channel,
};
drv_data->as.resolution = 12;
#else
#error Unsupported ADC
#endif
rc = adc_channel_setup(drv_data->adc, &drv_data->acc);
LOG_DBG("AIN%u setup returned %d", drv_cfg->io_channel.channel, rc);
return rc;
}
static struct bvd_data bvd_data = {.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(DT_DRV_INST(0)))};
static const struct bvd_config bvd_cfg = {
.io_channel =
{
DT_IO_CHANNELS_INPUT(DT_DRV_INST(0)),
},
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
.power = GPIO_DT_SPEC_INST_GET(0, power_gpios),
#endif
.output_ohm = DT_INST_PROP(0, output_ohms),
.full_ohm = DT_INST_PROP(0, full_ohms),
};
DEVICE_DT_INST_DEFINE(0, &bvd_init, NULL, &bvd_data, &bvd_cfg, POST_KERNEL,
CONFIG_SENSOR_INIT_PRIORITY, &bvd_api);

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
zephyr_include_directories(.)
zephyr_library()
zephyr_library_sources(ec11.c)
zephyr_library_sources_ifdef(CONFIG_EC11_TRIGGER ec11_trigger.c)

View File

@@ -0,0 +1,52 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
menuconfig EC11
bool "EC11 Incremental Encoder Sensor"
default y
depends on DT_HAS_ALPS_EC11_ENABLED
depends on GPIO
help
Enable driver for EC11 incremental encoder sensors.
if EC11
choice
prompt "Trigger mode"
default EC11_TRIGGER_NONE
help
Specify the type of triggering to be used by the driver.
config EC11_TRIGGER_NONE
bool "No trigger"
config EC11_TRIGGER_GLOBAL_THREAD
bool "Use global thread"
depends on GPIO
select EC11_TRIGGER
config EC11_TRIGGER_OWN_THREAD
bool "Use own thread"
depends on GPIO
select EC11_TRIGGER
endchoice
config EC11_TRIGGER
bool
config EC11_THREAD_PRIORITY
int "Thread priority"
depends on EC11_TRIGGER_OWN_THREAD
default 10
help
Priority of thread used by the driver to handle interrupts.
config EC11_THREAD_STACK_SIZE
int "Thread stack size"
depends on EC11_TRIGGER_OWN_THREAD
default 1024
help
Stack size of thread used by the driver to handle interrupts.
endif # EC11

View File

@@ -0,0 +1,161 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT alps_ec11
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log.h>
#include "ec11.h"
#define FULL_ROTATION 360
LOG_MODULE_REGISTER(EC11, CONFIG_SENSOR_LOG_LEVEL);
static int ec11_get_ab_state(const struct device *dev) {
const struct ec11_config *drv_cfg = dev->config;
return (gpio_pin_get_dt(&drv_cfg->a) << 1) | gpio_pin_get_dt(&drv_cfg->b);
}
static int ec11_sample_fetch(const struct device *dev, enum sensor_channel chan) {
struct ec11_data *drv_data = dev->data;
const struct ec11_config *drv_cfg = dev->config;
uint8_t val;
int8_t delta;
__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL || chan == SENSOR_CHAN_ROTATION);
val = ec11_get_ab_state(dev);
LOG_DBG("prev: %d, new: %d", drv_data->ab_state, val);
switch (val | (drv_data->ab_state << 2)) {
case 0b0010:
case 0b0100:
case 0b1101:
case 0b1011:
delta = -1;
break;
case 0b0001:
case 0b0111:
case 0b1110:
case 0b1000:
delta = 1;
break;
default:
delta = 0;
break;
}
LOG_DBG("Delta: %d", delta);
drv_data->pulses += delta;
drv_data->ab_state = val;
// TODO: Temporary code for backwards compatibility to support
// the sensor channel rotation reporting *ticks* instead of delta of degrees.
// REMOVE ME
if (drv_cfg->steps == 0) {
drv_data->ticks = drv_data->pulses / drv_cfg->resolution;
drv_data->delta = delta;
drv_data->pulses %= drv_cfg->resolution;
}
return 0;
}
static int ec11_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val) {
struct ec11_data *drv_data = dev->data;
const struct ec11_config *drv_cfg = dev->config;
int32_t pulses = drv_data->pulses;
if (chan != SENSOR_CHAN_ROTATION) {
return -ENOTSUP;
}
drv_data->pulses = 0;
if (drv_cfg->steps > 0) {
val->val1 = (pulses * FULL_ROTATION) / drv_cfg->steps;
val->val2 = (pulses * FULL_ROTATION) % drv_cfg->steps;
if (val->val2 != 0) {
val->val2 *= 1000000;
val->val2 /= drv_cfg->steps;
}
} else {
val->val1 = drv_data->ticks;
val->val2 = drv_data->delta;
}
return 0;
}
static const struct sensor_driver_api ec11_driver_api = {
#ifdef CONFIG_EC11_TRIGGER
.trigger_set = ec11_trigger_set,
#endif
.sample_fetch = ec11_sample_fetch,
.channel_get = ec11_channel_get,
};
int ec11_init(const struct device *dev) {
struct ec11_data *drv_data = dev->data;
const struct ec11_config *drv_cfg = dev->config;
LOG_DBG("A: %s %d B: %s %d resolution %d", drv_cfg->a.port->name, drv_cfg->a.pin,
drv_cfg->b.port->name, drv_cfg->b.pin, drv_cfg->resolution);
if (!device_is_ready(drv_cfg->a.port)) {
LOG_ERR("A GPIO device is not ready");
return -EINVAL;
}
if (!device_is_ready(drv_cfg->b.port)) {
LOG_ERR("B GPIO device is not ready");
return -EINVAL;
}
if (gpio_pin_configure_dt(&drv_cfg->a, GPIO_INPUT)) {
LOG_DBG("Failed to configure A pin");
return -EIO;
}
if (gpio_pin_configure_dt(&drv_cfg->b, GPIO_INPUT)) {
LOG_DBG("Failed to configure B pin");
return -EIO;
}
#ifdef CONFIG_EC11_TRIGGER
if (ec11_init_interrupt(dev) < 0) {
LOG_DBG("Failed to initialize interrupt!");
return -EIO;
}
#endif
drv_data->ab_state = ec11_get_ab_state(dev);
return 0;
}
#define EC11_INST(n) \
struct ec11_data ec11_data_##n; \
const struct ec11_config ec11_cfg_##n = { \
.a = GPIO_DT_SPEC_INST_GET(n, a_gpios), \
.b = GPIO_DT_SPEC_INST_GET(n, b_gpios), \
.resolution = DT_INST_PROP_OR(n, resolution, 1), \
.steps = DT_INST_PROP_OR(n, steps, 0), \
}; \
DEVICE_DT_INST_DEFINE(n, ec11_init, NULL, &ec11_data_##n, &ec11_cfg_##n, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &ec11_driver_api);
DT_INST_FOREACH_STATUS_OKAY(EC11_INST)

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
struct ec11_config {
const struct gpio_dt_spec a;
const struct gpio_dt_spec b;
const uint16_t steps;
const uint8_t resolution;
};
struct ec11_data {
uint8_t ab_state;
int8_t pulses;
int8_t ticks;
int8_t delta;
#ifdef CONFIG_EC11_TRIGGER
struct gpio_callback a_gpio_cb;
struct gpio_callback b_gpio_cb;
const struct device *dev;
sensor_trigger_handler_t handler;
const struct sensor_trigger *trigger;
#if defined(CONFIG_EC11_TRIGGER_OWN_THREAD)
K_THREAD_STACK_MEMBER(thread_stack, CONFIG_EC11_THREAD_STACK_SIZE);
struct k_sem gpio_sem;
struct k_thread thread;
#elif defined(CONFIG_EC11_TRIGGER_GLOBAL_THREAD)
struct k_work work;
#endif
#endif /* CONFIG_EC11_TRIGGER */
};
#ifdef CONFIG_EC11_TRIGGER
int ec11_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
int ec11_init_interrupt(const struct device *dev);
#endif

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT alps_ec11
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include "ec11.h"
extern struct ec11_data ec11_driver;
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(EC11, CONFIG_SENSOR_LOG_LEVEL);
static inline void setup_int(const struct device *dev, bool enable) {
const struct ec11_config *cfg = dev->config;
LOG_DBG("enabled %s", (enable ? "true" : "false"));
if (gpio_pin_interrupt_configure_dt(&cfg->a, enable ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE)) {
LOG_WRN("Unable to set A pin GPIO interrupt");
}
if (gpio_pin_interrupt_configure_dt(&cfg->b, enable ? GPIO_INT_EDGE_BOTH : GPIO_INT_DISABLE)) {
LOG_WRN("Unable to set A pin GPIO interrupt");
}
}
static void ec11_a_gpio_callback(const struct device *dev, struct gpio_callback *cb,
uint32_t pins) {
struct ec11_data *drv_data = CONTAINER_OF(cb, struct ec11_data, a_gpio_cb);
LOG_DBG("");
setup_int(drv_data->dev, false);
#if defined(CONFIG_EC11_TRIGGER_OWN_THREAD)
k_sem_give(&drv_data->gpio_sem);
#elif defined(CONFIG_EC11_TRIGGER_GLOBAL_THREAD)
k_work_submit(&drv_data->work);
#endif
}
static void ec11_b_gpio_callback(const struct device *dev, struct gpio_callback *cb,
uint32_t pins) {
struct ec11_data *drv_data = CONTAINER_OF(cb, struct ec11_data, b_gpio_cb);
LOG_DBG("");
setup_int(drv_data->dev, false);
#if defined(CONFIG_EC11_TRIGGER_OWN_THREAD)
k_sem_give(&drv_data->gpio_sem);
#elif defined(CONFIG_EC11_TRIGGER_GLOBAL_THREAD)
k_work_submit(&drv_data->work);
#endif
}
static void ec11_thread_cb(const struct device *dev) {
struct ec11_data *drv_data = dev->data;
drv_data->handler(dev, drv_data->trigger);
setup_int(dev, true);
}
#ifdef CONFIG_EC11_TRIGGER_OWN_THREAD
static void ec11_thread(int dev_ptr, int unused) {
const struct device *dev = INT_TO_POINTER(dev_ptr);
struct ec11_data *drv_data = dev->data;
ARG_UNUSED(unused);
while (1) {
k_sem_take(&drv_data->gpio_sem, K_FOREVER);
ec11_thread_cb(dev);
}
}
#endif
#ifdef CONFIG_EC11_TRIGGER_GLOBAL_THREAD
static void ec11_work_cb(struct k_work *work) {
struct ec11_data *drv_data = CONTAINER_OF(work, struct ec11_data, work);
LOG_DBG("");
ec11_thread_cb(drv_data->dev);
}
#endif
int ec11_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler) {
struct ec11_data *drv_data = dev->data;
setup_int(dev, false);
k_msleep(5);
drv_data->trigger = trig;
drv_data->handler = handler;
setup_int(dev, true);
return 0;
}
int ec11_init_interrupt(const struct device *dev) {
struct ec11_data *drv_data = dev->data;
const struct ec11_config *drv_cfg = dev->config;
drv_data->dev = dev;
/* setup gpio interrupt */
gpio_init_callback(&drv_data->a_gpio_cb, ec11_a_gpio_callback, BIT(drv_cfg->a.pin));
if (gpio_add_callback(drv_cfg->a.port, &drv_data->a_gpio_cb) < 0) {
LOG_DBG("Failed to set A callback!");
return -EIO;
}
gpio_init_callback(&drv_data->b_gpio_cb, ec11_b_gpio_callback, BIT(drv_cfg->b.pin));
if (gpio_add_callback(drv_cfg->b.port, &drv_data->b_gpio_cb) < 0) {
LOG_DBG("Failed to set B callback!");
return -EIO;
}
#if defined(CONFIG_EC11_TRIGGER_OWN_THREAD)
k_sem_init(&drv_data->gpio_sem, 0, UINT_MAX);
k_thread_create(&drv_data->thread, drv_data->thread_stack, CONFIG_EC11_THREAD_STACK_SIZE,
(k_thread_entry_t)ec11_thread, dev, 0, NULL,
K_PRIO_COOP(CONFIG_EC11_THREAD_PRIORITY), 0, K_NO_WAIT);
#elif defined(CONFIG_EC11_TRIGGER_GLOBAL_THREAD)
k_work_init(&drv_data->work, ec11_work_cb);
#endif
return 0;
}

View File

@@ -0,0 +1,61 @@
# Copyright (c) 2020, Phytec Messtechnik GmbH, Peter Johanson
# SPDX-License-Identifier: Apache-2.0
description: IL0323 EPD display controller
compatible: "gooddisplay,il0323"
include: spi-device.yaml
properties:
height:
type: int
required: true
description: Height in pixel of the panel driven by the controller
width:
type: int
required: true
description: Width in pixel of the panel driven by the controller
reset-gpios:
type: phandle-array
required: true
description: RESET pin.
The RESET pin of GD7965 is active low.
If connected directly the MCU pin should be configured
as active low.
dc-gpios:
type: phandle-array
required: true
description: DC pin.
The DC pin of GD7965 is active low (transmission command byte).
If connected directly the MCU pin should be configured
as active low.
busy-gpios:
type: phandle-array
required: true
description: BUSY pin.
The BUSY pin of GD7965 is active low.
If connected directly the MCU pin should be configured
as active low.
pwr:
type: uint8-array
required: true
description: Power Setting (PWR) values
cdi:
type: int
required: true
description: VCOM and data interval value
tcon:
type: int
required: true
description: TCON setting value

View File

@@ -0,0 +1,29 @@
#
# Copyright (c) 2022 The ZMK Contributors
#
# SPDX-License-Identifier: MIT
#
description: >
This is a representation of the Maxim MAX7318 I2C Gpio Expander.
compatible: "maxim,max7318"
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

View File

@@ -0,0 +1,30 @@
#
# Copyright (c) 2022 The ZMK Contributors
#
# SPDX-License-Identifier: MIT
#
description: >
This is a representation of the 595 Shift Register.
compatible: "zmk,gpio-595"
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
gpio-cells:
- pin
- flags

View File

@@ -0,0 +1,22 @@
# Copyright (c) 2020, The ZMK Contributors
# SPDX-License-Identifier: MIT
description: GPIO keyboard demux controller
compatible: "zmk,kscan-gpio-demux"
include: kscan.yaml
properties:
input-gpios:
type: phandle-array
required: true
output-gpios:
type: phandle-array
required: true
debounce-period:
type: int
default: 5
polling-interval-msec:
type: int
default: 25

View File

@@ -0,0 +1,37 @@
# Copyright (c) 2020, Pete Johanson
# SPDX-License-Identifier: MIT
description: Direct GPIO keyboard KSCAN controller
compatible: "zmk,kscan-gpio-direct"
include: kscan.yaml
properties:
input-gpios:
type: phandle-array
required: true
debounce-period:
type: int
required: false
deprecated: true
description: Deprecated. Use debounce-press-ms and debounce-release-ms instead.
debounce-press-ms:
type: int
default: 5
description: Debounce time for key press in milliseconds. Use 0 for eager debouncing.
debounce-release-ms:
type: int
default: 5
description: Debounce time for key release in milliseconds.
debounce-scan-period-ms:
type: int
default: 1
description: Time between reads in milliseconds when any key is pressed.
poll-period-ms:
type: int
default: 10
description: Time between reads in milliseconds when no key is pressed and ZMK_KSCAN_DIRECT_POLLING is enabled.
toggle-mode:
type: boolean
description: Enable toggle-switch mode.

View File

@@ -0,0 +1,43 @@
# Copyright (c) 2020, Pete Johanson
# SPDX-License-Identifier: MIT
description: GPIO keyboard matrix controller
compatible: "zmk,kscan-gpio-matrix"
include: kscan.yaml
properties:
row-gpios:
type: phandle-array
required: true
col-gpios:
type: phandle-array
required: true
debounce-period:
type: int
required: false
deprecated: true
description: Deprecated. Use debounce-press-ms and debounce-release-ms instead.
debounce-press-ms:
type: int
default: 5
description: Debounce time for key press in milliseconds. Use 0 for eager debouncing.
debounce-release-ms:
type: int
default: 5
description: Debounce time for key release in milliseconds.
debounce-scan-period-ms:
type: int
default: 1
description: Time between reads in milliseconds when any key is pressed.
poll-period-ms:
type: int
default: 10
description: Time between reads in milliseconds when no key is pressed and ZMK_KSCAN_MATRIX_POLLING is enabled.
diode-direction:
type: string
default: row2col
enum:
- row2col
- col2row

View File

@@ -0,0 +1,26 @@
description: |
Sensor driver for the Alps EC11 rotary encoder
compatible: "alps,ec11"
properties:
label:
type: string
required: true
a-gpios:
type: phandle-array
required: true
description: A pin for the encoder
b-gpios:
type: phandle-array
required: true
description: A pin for the encoder
resolution:
type: int
description: Number of pulses per tick
deprecated: true
required: false
steps:
type: int
description: Number of pulses in one full rotation
required: false

View File

@@ -0,0 +1,6 @@
# Copyright (c) 2021 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Battery SoC monitoring using nRF VDDH
compatible: "zmk,battery-nrf-vddh"

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Battery SoC monitoring using voltage divider
compatible: "zmk,battery-voltage-divider"
include: voltage-divider.yaml

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#define ZMK_MOCK_IS_PRESS(v) ((v & (0x01 << 31)) != 0)
#define ZMK_MOCK_PRESS(row, col, msec) (row + (col << 8) + (msec << 16) + (0x01 << 31))
#define ZMK_MOCK_RELEASE(row, col, msec) (row + (col << 8) + (msec << 16))
#define ZMK_MOCK_ROW(v) (v & 0xFF)
#define ZMK_MOCK_COL(v) ((v >> 8) & 0xFF)
#define ZMK_MOCK_MSEC(v) ((v & ~(0x01 << 31)) >> 16)

View File

@@ -0,0 +1,5 @@
build:
cmake: .
kconfig: Kconfig
settings:
dts_root: .