drivers: input: add an analog-axis driver

Add an input driver to read data from an analog device, such as a
thumbstick, connected to an ADC channel, and report it as an input
device.

Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
This commit is contained in:
Fabio Baltieri 2023-12-16 21:13:57 +00:00 committed by Carles Cufí
commit bd8cee8683
11 changed files with 688 additions and 0 deletions

View file

@ -4,6 +4,8 @@ zephyr_library()
zephyr_library_property(ALLOW_EMPTY TRUE)
# zephyr-keep-sorted-start
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)
zephyr_library_sources_ifdef(CONFIG_INPUT_CST816S input_cst816s.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_ESP32_TOUCH_SENSOR input_esp32_touch_sensor.c)

View file

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

View file

@ -0,0 +1,43 @@
# Copyright 2023 Google LLC
# SPDX-License-Identifier: Apache-2.0
config INPUT_ANALOG_AXIS
bool "ADC based analog axis input driver"
default y
depends on DT_HAS_ANALOG_AXIS_ENABLED
depends on ADC
depends on MULTITHREADING
help
ADC based analog axis input driver
if INPUT_ANALOG_AXIS
config INPUT_ANALOG_AXIS_THREAD_STACK_SIZE
int "Stack size for the analog axis thread"
default 762
help
Size of the stack used for the analog axis thread.
config INPUT_ANALOG_AXIS_THREAD_PRIORITY
int "Priority for the analog axis thread"
default 0
help
Priority level of the analog axis thread.
config INPUT_ANALOG_AXIS_SETTINGS
bool "Analog axis settings support"
default y
depends on SETTINGS
help
Settings support for the analog axis driver, exposes a
analog_axis_calibration_save() function to save the calibration into
settings and load them automatically on startup.
config INPUT_ANALOG_AXIS_SETTINGS_MAX_AXES
int "Maximum number of axes supported in the settings."
default 8
help
Maximum number of axes that can have calibration value saved in
settings.
endif

View file

@ -0,0 +1,271 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT analog_axis
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/input/input.h>
#include <zephyr/input/input_analog_axis.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
LOG_MODULE_REGISTER(analog_axis, CONFIG_INPUT_LOG_LEVEL);
struct analog_axis_channel_config {
struct adc_dt_spec adc;
int16_t out_min;
int16_t out_max;
uint16_t axis;
bool invert;
};
struct analog_axis_channel_data {
int last_out;
};
struct analog_axis_config {
uint32_t poll_period_ms;
const struct analog_axis_channel_config *channel_cfg;
struct analog_axis_channel_data *channel_data;
struct analog_axis_calibration *calibration;
const uint8_t num_channels;
};
struct analog_axis_data {
struct k_mutex cal_lock;
analog_axis_raw_data_t raw_data_cb;
struct k_timer timer;
struct k_thread thread;
K_KERNEL_STACK_MEMBER(thread_stack,
CONFIG_INPUT_ANALOG_AXIS_THREAD_STACK_SIZE);
};
int analog_axis_num_axes(const struct device *dev)
{
const struct analog_axis_config *cfg = dev->config;
return cfg->num_channels;
}
int analog_axis_calibration_get(const struct device *dev,
int channel,
struct analog_axis_calibration *out_cal)
{
const struct analog_axis_config *cfg = dev->config;
struct analog_axis_data *data = dev->data;
struct analog_axis_calibration *cal = &cfg->calibration[channel];
if (channel >= cfg->num_channels) {
return -EINVAL;
}
k_mutex_lock(&data->cal_lock, K_FOREVER);
memcpy(out_cal, cal, sizeof(struct analog_axis_calibration));
k_mutex_unlock(&data->cal_lock);
return 0;
}
void analog_axis_set_raw_data_cb(const struct device *dev, analog_axis_raw_data_t cb)
{
struct analog_axis_data *data = dev->data;
k_mutex_lock(&data->cal_lock, K_FOREVER);
data->raw_data_cb = cb;
k_mutex_unlock(&data->cal_lock);
}
int analog_axis_calibration_set(const struct device *dev,
int channel,
struct analog_axis_calibration *new_cal)
{
const struct analog_axis_config *cfg = dev->config;
struct analog_axis_data *data = dev->data;
struct analog_axis_calibration *cal = &cfg->calibration[channel];
if (channel >= cfg->num_channels) {
return -EINVAL;
}
k_mutex_lock(&data->cal_lock, K_FOREVER);
memcpy(cal, new_cal, sizeof(struct analog_axis_calibration));
k_mutex_unlock(&data->cal_lock);
return 0;
}
static void analog_axis_loop(const struct device *dev)
{
const struct analog_axis_config *cfg = dev->config;
struct analog_axis_data *data = dev->data;
int16_t bufs[cfg->num_channels];
int32_t out;
struct adc_sequence sequence = {
.buffer = bufs,
.buffer_size = sizeof(bufs),
};
const struct analog_axis_channel_config *axis_cfg_0 = &cfg->channel_cfg[0];
int err;
int i;
adc_sequence_init_dt(&axis_cfg_0->adc, &sequence);
for (i = 0; i < cfg->num_channels; i++) {
const struct analog_axis_channel_config *axis_cfg = &cfg->channel_cfg[i];
sequence.channels |= BIT(axis_cfg->adc.channel_id);
}
err = adc_read(axis_cfg_0->adc.dev, &sequence);
if (err < 0) {
LOG_ERR("Could not read (%d)", err);
return;
}
k_mutex_lock(&data->cal_lock, K_FOREVER);
for (i = 0; i < cfg->num_channels; i++) {
const struct analog_axis_channel_config *axis_cfg = &cfg->channel_cfg[i];
struct analog_axis_channel_data *axis_data = &cfg->channel_data[i];
struct analog_axis_calibration *cal = &cfg->calibration[i];
int16_t in_range = cal->in_max - cal->in_min;
int16_t out_range = axis_cfg->out_max - axis_cfg->out_min;
int32_t raw_val = bufs[i];
if (axis_cfg->invert) {
raw_val *= -1;
}
if (data->raw_data_cb != NULL) {
data->raw_data_cb(dev, i, raw_val);
}
LOG_DBG("%s: ch %d: raw_val: %d", dev->name, i, raw_val);
out = CLAMP((raw_val - cal->in_min) * out_range / in_range + axis_cfg->out_min,
axis_cfg->out_min, axis_cfg->out_max);
if (cal->out_deadzone > 0) {
int16_t center = DIV_ROUND_CLOSEST(
axis_cfg->out_max + axis_cfg->out_min, 2);
if (abs(out - center) < cal->out_deadzone) {
out = center;
}
}
if (axis_data->last_out != out) {
input_report_abs(dev, axis_cfg->axis, out, true, K_FOREVER);
}
axis_data->last_out = out;
}
k_mutex_unlock(&data->cal_lock);
}
static void analog_axis_thread(void *arg1, void *arg2, void *arg3)
{
const struct device *dev = arg1;
const struct analog_axis_config *cfg = dev->config;
struct analog_axis_data *data = dev->data;
int err;
int i;
for (i = 0; i < cfg->num_channels; i++) {
const struct analog_axis_channel_config *axis_cfg = &cfg->channel_cfg[i];
if (!adc_is_ready_dt(&axis_cfg->adc)) {
LOG_ERR("ADC controller device not ready");
return;
}
err = adc_channel_setup_dt(&axis_cfg->adc);
if (err < 0) {
LOG_ERR("Could not setup channel #%d (%d)", i, err);
return;
}
}
k_timer_init(&data->timer, NULL, NULL);
k_timer_start(&data->timer,
K_MSEC(cfg->poll_period_ms), K_MSEC(cfg->poll_period_ms));
while (true) {
analog_axis_loop(dev);
k_timer_status_sync(&data->timer);
}
}
static int analog_axis_init(const struct device *dev)
{
struct analog_axis_data *data = dev->data;
k_tid_t tid;
k_mutex_init(&data->cal_lock);
tid = k_thread_create(&data->thread, data->thread_stack,
K_KERNEL_STACK_SIZEOF(data->thread_stack),
analog_axis_thread, (void *)dev, NULL, NULL,
CONFIG_INPUT_ANALOG_AXIS_THREAD_PRIORITY,
0, K_NO_WAIT);
if (!tid) {
LOG_ERR("thread creation failed");
return -ENODEV;
}
k_thread_name_set(&data->thread, dev->name);
return 0;
}
#define ANALOG_AXIS_CHANNEL_CFG_DEF(node_id) \
{ \
.adc = ADC_DT_SPEC_GET(node_id), \
.out_min = (int16_t)DT_PROP(node_id, out_min), \
.out_max = (int16_t)DT_PROP(node_id, out_max), \
.axis = DT_PROP(node_id, zephyr_axis), \
.invert = DT_PROP(node_id, invert), \
}
#define ANALOG_AXIS_CHANNEL_CAL_DEF(node_id) \
{ \
.in_min = (int16_t)DT_PROP(node_id, in_min), \
.in_max = (int16_t)DT_PROP(node_id, in_max), \
.out_deadzone = DT_PROP(node_id, out_deadzone), \
}
#define ANALOG_AXIS_INIT(inst) \
static const struct analog_axis_channel_config analog_axis_channel_cfg_##inst[] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(inst, ANALOG_AXIS_CHANNEL_CFG_DEF, (,)) \
}; \
\
static struct analog_axis_channel_data \
analog_axis_channel_data_##inst[ARRAY_SIZE(analog_axis_channel_cfg_##inst)]; \
\
static struct analog_axis_calibration \
analog_axis_calibration##inst[ARRAY_SIZE(analog_axis_channel_cfg_##inst)] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP( \
inst, ANALOG_AXIS_CHANNEL_CAL_DEF, (,)) \
}; \
\
static const struct analog_axis_config analog_axis_cfg_##inst = { \
.poll_period_ms = DT_INST_PROP(inst, poll_period_ms), \
.channel_cfg = analog_axis_channel_cfg_##inst, \
.channel_data = analog_axis_channel_data_##inst, \
.calibration = analog_axis_calibration##inst, \
.num_channels = ARRAY_SIZE(analog_axis_channel_cfg_##inst), \
}; \
\
static struct analog_axis_data analog_axis_data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, analog_axis_init, NULL, \
&analog_axis_data_##inst, &analog_axis_cfg_##inst, \
POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(ANALOG_AXIS_INIT)

View file

@ -0,0 +1,111 @@
/*
* Copyright 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/input/input_analog_axis.h>
#include <zephyr/input/input_analog_axis_settings.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/printk.h>
LOG_MODULE_REGISTER(analog_axis_settings, CONFIG_INPUT_LOG_LEVEL);
#define ANALOG_AXIS_SETTINGS_PATH_MAX 32
#define MAX_AXES CONFIG_INPUT_ANALOG_AXIS_SETTINGS_MAX_AXES
static void analog_axis_calibration_log(const struct device *dev)
{
struct analog_axis_calibration cal;
int i;
for (i = 0; i < analog_axis_num_axes(dev); i++) {
analog_axis_calibration_get(dev, i, &cal);
LOG_INF("%s: ch: %d min: %d max: %d deadzone: %d",
dev->name, i, cal.in_min, cal.in_max, cal.out_deadzone);
}
}
static int analog_axis_calibration_load(const char *key, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
const struct device *dev;
struct analog_axis_calibration cal[MAX_AXES];
int axes;
char dev_name[ANALOG_AXIS_SETTINGS_PATH_MAX];
const char *next;
int nlen;
ssize_t len;
nlen = settings_name_next(key, &next);
if (nlen + 1 > sizeof(dev_name)) {
LOG_ERR("Setting name too long: %d", nlen);
return -EINVAL;
}
memcpy(dev_name, key, nlen);
dev_name[nlen] = '\0';
dev = device_get_binding(dev_name);
if (dev == NULL) {
LOG_ERR("Cannot restore: device %s not available", dev_name);
return -ENODEV;
}
len = read_cb(cb_arg, cal, sizeof(cal));
if (len < 0) {
LOG_ERR("Data restore error: %d", len);
}
axes = analog_axis_num_axes(dev);
if (len != sizeof(struct analog_axis_calibration) * axes) {
LOG_ERR("Invalid settings data length: %d, expected %d",
len, sizeof(struct analog_axis_calibration) * axes);
return -EIO;
}
for (int i = 0; i < axes; i++) {
analog_axis_calibration_set(dev, i, &cal[i]);
}
analog_axis_calibration_log(dev);
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(analog_axis, "aa-cal", NULL,
analog_axis_calibration_load, NULL, NULL);
int analog_axis_calibration_save(const struct device *dev)
{
struct analog_axis_calibration cal[MAX_AXES];
int axes;
char path[ANALOG_AXIS_SETTINGS_PATH_MAX];
int ret;
analog_axis_calibration_log(dev);
ret = snprintk(path, sizeof(path), "aa-cal/%s", dev->name);
if (ret < 0) {
return -EINVAL;
}
axes = analog_axis_num_axes(dev);
for (int i = 0; i < axes; i++) {
analog_axis_calibration_get(dev, i, &cal[i]);
}
ret = settings_save_one(path, &cal[0],
sizeof(struct analog_axis_calibration) * axes);
if (ret < 0) {
LOG_ERR("Settings save errord: %d", ret);
return ret;
}
return 0;
}