mirror of
https://github.com/zmkfirmware/zmk.git
synced 2026-03-20 04:55:20 -05:00
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:
committed by
Pete Johanson
parent
eaeea4bdfa
commit
690bc1bb44
3
app/module/CMakeLists.txt
Normal file
3
app/module/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
zephyr_include_directories(include)
|
||||
|
||||
add_subdirectory(drivers)
|
||||
2
app/module/Kconfig
Normal file
2
app/module/Kconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
rsource "drivers/Kconfig"
|
||||
7
app/module/drivers/CMakeLists.txt
Normal file
7
app/module/drivers/CMakeLists.txt
Normal 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)
|
||||
7
app/module/drivers/Kconfig
Normal file
7
app/module/drivers/Kconfig
Normal 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"
|
||||
6
app/module/drivers/display/CMakeLists.txt
Normal file
6
app/module/drivers/display/CMakeLists.txt
Normal 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)
|
||||
8
app/module/drivers/display/Kconfig
Normal file
8
app/module/drivers/display/Kconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2021 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if DISPLAY
|
||||
|
||||
rsource "Kconfig.il0323"
|
||||
|
||||
endif # DISPLAY
|
||||
11
app/module/drivers/display/Kconfig.il0323
Normal file
11
app/module/drivers/display/Kconfig.il0323
Normal 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.
|
||||
394
app/module/drivers/display/il0323.c
Normal file
394
app/module/drivers/display/il0323.c
Normal 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);
|
||||
81
app/module/drivers/display/il0323_regs.h
Normal file
81
app/module/drivers/display/il0323_regs.h
Normal 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_ */
|
||||
7
app/module/drivers/gpio/CMakeLists.txt
Normal file
7
app/module/drivers/gpio/CMakeLists.txt
Normal 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)
|
||||
7
app/module/drivers/gpio/Kconfig
Normal file
7
app/module/drivers/gpio/Kconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
if GPIO
|
||||
|
||||
rsource "Kconfig.max7318"
|
||||
rsource "Kconfig.595"
|
||||
|
||||
endif # GPIO
|
||||
24
app/module/drivers/gpio/Kconfig.595
Normal file
24
app/module/drivers/gpio/Kconfig.595
Normal 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
|
||||
24
app/module/drivers/gpio/Kconfig.max7318
Normal file
24
app/module/drivers/gpio/Kconfig.max7318
Normal 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
|
||||
215
app/module/drivers/gpio/gpio_595.c
Normal file
215
app/module/drivers/gpio/gpio_595.c
Normal 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 *)®_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, ®_595_##n##_drvdata, ®_595_##n##_config, \
|
||||
POST_KERNEL, CONFIG_GPIO_595_INIT_PRIORITY, &api_table);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(REG_595_INIT)
|
||||
341
app/module/drivers/gpio/gpio_max7318.c
Normal file
341
app/module/drivers/gpio/gpio_max7318.c
Normal 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)
|
||||
12
app/module/drivers/kscan/CMakeLists.txt
Normal file
12
app/module/drivers/kscan/CMakeLists.txt
Normal 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)
|
||||
98
app/module/drivers/kscan/Kconfig
Normal file
98
app/module/drivers/kscan/Kconfig
Normal 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
|
||||
62
app/module/drivers/kscan/debounce.c
Normal file
62
app/module/drivers/kscan/debounce.c
Normal 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; }
|
||||
56
app/module/drivers/kscan/debounce.h
Normal file
56
app/module/drivers/kscan/debounce.h
Normal 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);
|
||||
112
app/module/drivers/kscan/kscan_composite.c
Normal file
112
app/module/drivers/kscan/kscan_composite.c
Normal 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);
|
||||
33
app/module/drivers/kscan/kscan_gpio.c
Normal file
33
app/module/drivers/kscan/kscan_gpio.c
Normal 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;
|
||||
}
|
||||
61
app/module/drivers/kscan/kscan_gpio.h
Normal file
61
app/module/drivers/kscan/kscan_gpio.h
Normal 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);
|
||||
207
app/module/drivers/kscan/kscan_gpio_demux.c
Normal file
207
app/module/drivers/kscan/kscan_gpio_demux.c
Normal 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)
|
||||
360
app/module/drivers/kscan/kscan_gpio_direct.c
Normal file
360
app/module/drivers/kscan/kscan_gpio_direct.c
Normal 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);
|
||||
471
app/module/drivers/kscan/kscan_gpio_matrix.c
Normal file
471
app/module/drivers/kscan/kscan_gpio_matrix.c
Normal 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);
|
||||
95
app/module/drivers/kscan/kscan_mock.c
Normal file
95
app/module/drivers/kscan/kscan_mock.c
Normal 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)
|
||||
5
app/module/drivers/sensor/CMakeLists.txt
Normal file
5
app/module/drivers/sensor/CMakeLists.txt
Normal 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)
|
||||
9
app/module/drivers/sensor/Kconfig
Normal file
9
app/module/drivers/sensor/Kconfig
Normal 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
|
||||
10
app/module/drivers/sensor/battery/CMakeLists.txt
Normal file
10
app/module/drivers/sensor/battery/CMakeLists.txt
Normal 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)
|
||||
28
app/module/drivers/sensor/battery/Kconfig
Normal file
28
app/module/drivers/sensor/battery/Kconfig
Normal 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.
|
||||
43
app/module/drivers/sensor/battery/battery_common.c
Normal file
43
app/module/drivers/sensor/battery/battery_common.c
Normal 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;
|
||||
}
|
||||
21
app/module/drivers/sensor/battery/battery_common.h
Normal file
21
app/module/drivers/sensor/battery/battery_common.h
Normal 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);
|
||||
116
app/module/drivers/sensor/battery/battery_nrf_vddh.c
Normal file
116
app/module/drivers/sensor/battery/battery_nrf_vddh.c
Normal 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);
|
||||
175
app/module/drivers/sensor/battery/battery_voltage_divider.c
Normal file
175
app/module/drivers/sensor/battery/battery_voltage_divider.c
Normal 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);
|
||||
9
app/module/drivers/sensor/ec11/CMakeLists.txt
Normal file
9
app/module/drivers/sensor/ec11/CMakeLists.txt
Normal 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)
|
||||
52
app/module/drivers/sensor/ec11/Kconfig
Normal file
52
app/module/drivers/sensor/ec11/Kconfig
Normal 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
|
||||
161
app/module/drivers/sensor/ec11/ec11.c
Normal file
161
app/module/drivers/sensor/ec11/ec11.c
Normal 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)
|
||||
52
app/module/drivers/sensor/ec11/ec11.h
Normal file
52
app/module/drivers/sensor/ec11/ec11.h
Normal 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
|
||||
146
app/module/drivers/sensor/ec11/ec11_trigger.c
Normal file
146
app/module/drivers/sensor/ec11/ec11_trigger.c
Normal 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;
|
||||
}
|
||||
61
app/module/dts/bindings/display/gooddisplay,il0323.yaml
Normal file
61
app/module/dts/bindings/display/gooddisplay,il0323.yaml
Normal 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
|
||||
29
app/module/dts/bindings/gpio/maxim,max7318.yaml
Normal file
29
app/module/dts/bindings/gpio/maxim,max7318.yaml
Normal 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
|
||||
30
app/module/dts/bindings/gpio/zmk,gpio-595.yaml
Normal file
30
app/module/dts/bindings/gpio/zmk,gpio-595.yaml
Normal 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
|
||||
22
app/module/dts/bindings/kscan/zmk,kscan-gpio-demux.yaml
Normal file
22
app/module/dts/bindings/kscan/zmk,kscan-gpio-demux.yaml
Normal 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
|
||||
37
app/module/dts/bindings/kscan/zmk,kscan-gpio-direct.yaml
Normal file
37
app/module/dts/bindings/kscan/zmk,kscan-gpio-direct.yaml
Normal 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.
|
||||
43
app/module/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml
Normal file
43
app/module/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml
Normal 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
|
||||
26
app/module/dts/bindings/sensor/alps,ec11.yaml
Normal file
26
app/module/dts/bindings/sensor/alps,ec11.yaml
Normal 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
|
||||
6
app/module/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml
Normal file
6
app/module/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml
Normal 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"
|
||||
@@ -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
|
||||
14
app/module/include/dt-bindings/zmk/kscan_mock.h
Normal file
14
app/module/include/dt-bindings/zmk/kscan_mock.h
Normal 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)
|
||||
5
app/module/zephyr/module.yml
Normal file
5
app/module/zephyr/module.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
build:
|
||||
cmake: .
|
||||
kconfig: Kconfig
|
||||
settings:
|
||||
dts_root: .
|
||||
Reference in New Issue
Block a user