| /* |
| * 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 = 0; |
| 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) |