drivers: input: Implement driver for ADC keys

This commit introduces a driver for ADC keys, a common circuit design where
keys are connected to an ADC input via a resistor ladder.

Signed-off-by: Chen Xingyu <hi@xingrz.me>
This commit is contained in:
Chen Xingyu 2024-02-02 11:59:08 +08:00 committed by Fabio Baltieri
parent d79b99a979
commit d181607f4e
5 changed files with 314 additions and 0 deletions

View file

@ -4,6 +4,7 @@ zephyr_library()
zephyr_library_property(ALLOW_EMPTY TRUE) zephyr_library_property(ALLOW_EMPTY TRUE)
# zephyr-keep-sorted-start # zephyr-keep-sorted-start
zephyr_library_sources_ifdef(CONFIG_INPUT_ADC_KEYS input_adc_keys.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_ANALOG_AXIS input_analog_axis.c) zephyr_library_sources_ifdef(CONFIG_INPUT_ANALOG_AXIS input_analog_axis.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_ANALOG_AXIS_SETTINGS input_analog_axis_settings.c) zephyr_library_sources_ifdef(CONFIG_INPUT_ANALOG_AXIS_SETTINGS input_analog_axis_settings.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_CAP1203 input_cap1203.c) zephyr_library_sources_ifdef(CONFIG_INPUT_CAP1203 input_cap1203.c)

View file

@ -6,6 +6,7 @@ if INPUT
menu "Input drivers" menu "Input drivers"
# zephyr-keep-sorted-start # zephyr-keep-sorted-start
source "drivers/input/Kconfig.adc_keys"
source "drivers/input/Kconfig.analog_axis" source "drivers/input/Kconfig.analog_axis"
source "drivers/input/Kconfig.cap1203" source "drivers/input/Kconfig.cap1203"
source "drivers/input/Kconfig.cst816s" source "drivers/input/Kconfig.cst816s"

View file

@ -0,0 +1,10 @@
# Copyright (c) 2024 Chen Xingyu <hi@xingrz.me>
# SPDX-License-Identifier: Apache-2.0
config INPUT_ADC_KEYS
bool "ADC attached resistor ladder buttons"
default y
depends on DT_HAS_ADC_KEYS_ENABLED
depends on ADC
help
Enable support for ADC attached resistor ladder buttons.

View file

@ -0,0 +1,232 @@
/*
* Copyright (c) 2024 Chen Xingyu <hi@xingrz.me>
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT adc_keys
#include <stdlib.h>
#include <stdbool.h>
#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/input/input.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
LOG_MODULE_REGISTER(adc_keys, CONFIG_INPUT_LOG_LEVEL);
struct adc_keys_code_config {
int32_t press_mv;
uint8_t key_index;
};
struct adc_keys_key_state {
bool last_state;
bool curr_state;
};
struct adc_keys_config {
struct adc_dt_spec channel;
uint32_t sample_period_ms;
int32_t keyup_mv;
const struct adc_keys_code_config *code_cfg;
const uint16_t *key_code;
struct adc_keys_key_state *key_state;
uint8_t code_cnt;
uint8_t key_cnt;
};
struct adc_keys_data {
const struct device *self;
struct k_work_delayable dwork;
struct adc_sequence seq;
};
static inline int32_t adc_keys_read(const struct device *dev)
{
const struct adc_keys_config *cfg = dev->config;
struct adc_keys_data *data = dev->data;
uint16_t sample_raw;
int32_t sample_mv;
int ret;
data->seq.buffer = &sample_raw;
data->seq.buffer_size = sizeof(sample_raw);
ret = adc_read(cfg->channel.dev, &data->seq);
if (ret) {
LOG_ERR("ADC read failed %d", ret);
return cfg->keyup_mv;
}
sample_mv = (int32_t)sample_raw;
adc_raw_to_millivolts_dt(&cfg->channel, &sample_mv);
return sample_mv;
}
static inline void adc_keys_process(const struct device *dev)
{
const struct adc_keys_config *cfg = dev->config;
int32_t sample_mv, closest_mv;
uint32_t diff, closest_diff = UINT32_MAX;
const struct adc_keys_code_config *code_cfg;
struct adc_keys_key_state *key_state;
uint16_t key_code;
sample_mv = adc_keys_read(dev);
/*
* Find the closest key press threshold to the sample value.
*/
for (uint8_t i = 0; i < cfg->code_cnt; i++) {
diff = abs(sample_mv - cfg->code_cfg[i].press_mv);
if (diff < closest_diff) {
closest_diff = diff;
closest_mv = cfg->code_cfg[i].press_mv;
}
}
diff = abs(sample_mv - cfg->keyup_mv);
if (diff < closest_diff) {
closest_diff = diff;
closest_mv = cfg->keyup_mv;
}
LOG_DBG("sample=%d mV, closest=%d mV, diff=%d mV", sample_mv, closest_mv, closest_diff);
/*
* Update cached key states according to the closest key press threshold.
*
* Note that multiple keys may have the same press threshold, which is
* the mixed voltage that these keys are simultaneously pressed.
*/
for (uint8_t i = 0; i < cfg->code_cnt; i++) {
code_cfg = &cfg->code_cfg[i];
key_state = &cfg->key_state[code_cfg->key_index];
/*
* Only update curr_state if the key is pressed to prevent
* being overwritten by another threshold configuration.
*/
if (closest_mv == code_cfg->press_mv) {
key_state->curr_state = true;
}
}
/*
* Report the key event if the key state has changed.
*/
for (uint8_t i = 0; i < cfg->key_cnt; i++) {
key_state = &cfg->key_state[i];
key_code = cfg->key_code[i];
if (key_state->last_state != key_state->curr_state) {
LOG_DBG("Report event %s %d, code=%d", dev->name, key_state->curr_state,
key_code);
input_report_key(dev, key_code, key_state->curr_state, true, K_FOREVER);
key_state->last_state = key_state->curr_state;
}
/*
* Reset the state so that it can be updated in the next
* iteration.
*/
key_state->curr_state = false;
}
}
static void adc_keys_work_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct adc_keys_data *data = CONTAINER_OF(dwork, struct adc_keys_data, dwork);
const struct device *dev = data->self;
const struct adc_keys_config *cfg = dev->config;
adc_keys_process(dev);
k_work_schedule(&data->dwork, K_MSEC(cfg->sample_period_ms));
}
static int adc_keys_init(const struct device *dev)
{
const struct adc_keys_config *cfg = dev->config;
struct adc_keys_data *data = dev->data;
int ret;
if (!adc_is_ready_dt(&cfg->channel)) {
LOG_ERR("ADC controller device %s not ready", cfg->channel.dev->name);
return -ENODEV;
}
ret = adc_channel_setup_dt(&cfg->channel);
if (ret) {
LOG_ERR("ADC channel setup failed %d", ret);
return ret;
}
ret = adc_sequence_init_dt(&cfg->channel, &data->seq);
if (ret) {
LOG_ERR("ADC sequence init failed %d", ret);
return ret;
}
data->self = dev;
k_work_init_delayable(&data->dwork, adc_keys_work_handler);
if (IS_ENABLED(CONFIG_INPUT_LOG_LEVEL_DBG)) {
for (uint8_t i = 0; i < cfg->code_cnt; i++) {
LOG_DBG("* code %d: key_index=%d threshold=%d mV code=%d", i,
cfg->code_cfg[i].key_index, cfg->code_cfg[i].press_mv,
cfg->key_code[cfg->code_cfg[i].key_index]);
}
}
k_work_schedule(&data->dwork, K_MSEC(cfg->sample_period_ms));
return 0;
}
#define ADC_KEYS_CODE_CFG_ITEM(node_id, prop, idx) \
{ \
.key_index = DT_NODE_CHILD_IDX(node_id) /* include disabled nodes */, \
.press_mv = DT_PROP_BY_IDX(node_id, prop, idx), \
}
#define ADC_KEYS_CODE_CFG(node_id) \
DT_FOREACH_PROP_ELEM_SEP(node_id, press_thresholds_mv, ADC_KEYS_CODE_CFG_ITEM, (,))
#define ADC_KEYS_KEY_CODE(node_id) DT_PROP(node_id, zephyr_code)
#define ADC_KEYS_INST(n) \
static struct adc_keys_data adc_keys_data_##n; \
\
static const struct adc_keys_code_config adc_keys_code_cfg_##n[] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, ADC_KEYS_CODE_CFG, (,))}; \
\
static const uint16_t adc_keys_key_code_##n[] = { \
DT_INST_FOREACH_CHILD_SEP(n, ADC_KEYS_KEY_CODE, (,))}; \
\
static struct adc_keys_key_state \
adc_keys_key_state_##n[ARRAY_SIZE(adc_keys_key_code_##n)]; \
\
static const struct adc_keys_config adc_keys_cfg_##n = { \
.channel = ADC_DT_SPEC_INST_GET(n), \
.sample_period_ms = DT_INST_PROP(n, sample_period_ms), \
.keyup_mv = DT_INST_PROP(n, keyup_threshold_mv), \
.code_cfg = adc_keys_code_cfg_##n, \
.key_code = adc_keys_key_code_##n, \
.key_state = adc_keys_key_state_##n, \
.code_cnt = ARRAY_SIZE(adc_keys_code_cfg_##n), \
.key_cnt = ARRAY_SIZE(adc_keys_key_code_##n), \
}; \
\
DEVICE_DT_INST_DEFINE(n, adc_keys_init, NULL, &adc_keys_data_##n, &adc_keys_cfg_##n, \
POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(ADC_KEYS_INST)

View file

@ -0,0 +1,70 @@
# Copyright (c) 2024 Chen Xingyu <hi@xingrz.me>
# SPDX-License-Identifier: Apache-2.0
description: |
Input driver for ADC attached resistor ladder buttons.
The driver itself does not calculate each possible combination of resistor
values. Instead, users are required to specify the voltage for each single
key press or for combinations of key presses.
Example:
#include <dt-bindings/input/input-event-codes.h>
/ {
buttons {
compatible = "adc-keys";
io-channels = <&adc 2>;
keyup-threshold-mv = <0>;
key_0 {
press-thresholds-mv = <1650>, /* KEY0 */
<2536>; /* KEY0 + KEY1 */
zephyr,code = <INPUT_KEY_0>;
};
key_1 {
press-thresholds-mv = <2300>, /* KEY1 */
<2536>; /* KEY0 + KEY1 */
zephyr,code = <INPUT_KEY_1>;
};
};
};
compatible: "adc-keys"
include: base.yaml
properties:
io-channels:
type: phandle-array
required: true
description: Phandle to an ADC channel.
sample-period-ms:
type: int
default: 20
description: |
Sample period in milliseconds.
If not specified defaults to 20.
keyup-threshold-mv:
type: int
required: true
description: |
Millivolt value to which all the keys are considered up.
child-binding:
description: ADC KEYS child node.
properties:
press-thresholds-mv:
type: array
required: true
description: |
Array of millivolt values to consider a key pressed.
zephyr,code:
type: int
required: true
description: Key code to emit.