Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: input: Implement driver for ADC keys #68446

Merged
merged 2 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/input/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ zephyr_library()
zephyr_library_property(ALLOW_EMPTY TRUE)

# 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_SETTINGS input_analog_axis_settings.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_CAP1203 input_cap1203.c)
Expand Down
1 change: 1 addition & 0 deletions drivers/input/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ if INPUT
menu "Input drivers"

# zephyr-keep-sorted-start
source "drivers/input/Kconfig.adc_keys"
source "drivers/input/Kconfig.analog_axis"
source "drivers/input/Kconfig.cap1203"
source "drivers/input/Kconfig.cst816s"
Expand Down
10 changes: 10 additions & 0 deletions drivers/input/Kconfig.adc_keys
Original file line number Diff line number Diff line change
@@ -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.
232 changes: 232 additions & 0 deletions drivers/input/input_adc_keys.c
Original file line number Diff line number Diff line change
@@ -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>
fabiobaltieri marked this conversation as resolved.
Show resolved Hide resolved
#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;
fabiobaltieri marked this conversation as resolved.
Show resolved Hide resolved
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;
fabiobaltieri marked this conversation as resolved.
Show resolved Hide resolved
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)
fabiobaltieri marked this conversation as resolved.
Show resolved Hide resolved
{
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 \
bbilas marked this conversation as resolved.
Show resolved Hide resolved
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, \
fabiobaltieri marked this conversation as resolved.
Show resolved Hide resolved
.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)
70 changes: 70 additions & 0 deletions dts/bindings/input/adc-keys.yaml
Original file line number Diff line number Diff line change
@@ -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.
fabiobaltieri marked this conversation as resolved.
Show resolved Hide resolved

zephyr,code:
type: int
required: true
description: Key code to emit.
14 changes: 14 additions & 0 deletions tests/drivers/build_all/input/app.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@
status = "okay";
};

adc-keys {
compatible = "adc-keys";
io-channels = <&test_adc 0>;
keyup-threshold-mv = <0>;
button_0 {
press-thresholds-mv = <1500>, <1750>;
zephyr,code = <0>;
};
button_1 {
press-thresholds-mv = <2500>, <1750>;
zephyr,code = <1>;
};
};

gpio-keys {
compatible = "gpio-keys";
debounce-interval-ms = <30>;
Expand Down
4 changes: 4 additions & 0 deletions tests/drivers/build_all/input/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ tests:
extra_configs:
- CONFIG_ADC=y
- CONFIG_SETTINGS=y

drivers.input.adc_keys:
extra_configs:
- CONFIG_ADC=y