/* * Copyright (c) 2024 Chen Xingyu * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT adc_keys #include #include #include #include #include #include #include #include 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)