diff --git a/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832-pinctrl.dtsi b/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832-pinctrl.dtsi index c859969e551b7..a1995ced7969d 100644 --- a/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832-pinctrl.dtsi +++ b/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832-pinctrl.dtsi @@ -25,15 +25,15 @@ i2c0_default: i2c0_default { group1 { - psels = , - ; + psels = , + ; }; }; i2c0_sleep: i2c0_sleep { group1 { - psels = , - ; + psels = , + ; low-power-enable; }; }; diff --git a/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832.dts b/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832.dts index f747f1b7a5665..68f757e5e2a59 100644 --- a/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832.dts +++ b/boards/arm/nrf52dk_nrf52832/nrf52dk_nrf52832.dts @@ -7,6 +7,7 @@ /dts-v1/; #include +#include #include "nrf52dk_nrf52832-pinctrl.dtsi" / { @@ -22,6 +23,26 @@ zephyr,sram = &sram0; zephyr,flash = &flash0; zephyr,code-partition = &slot0_partition; + zephyr,keyboard-scan = &kscan_adapter; + }; + + kscan_adapter: kscan-adapter { + compatible = "zephyr,kscan-adapter"; + input = <&ft5336>; + }; + + abs-to-rel { + compatible = "zephyr,input-abs-to-rel"; + input = <&qdec>; + }; + + longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-code = ; + short-code = ; + long-codes = , , ; + long-delays-ms = <500>, <1000>, <2000>; }; leds { @@ -51,23 +72,27 @@ }; }; - buttons { - compatible = "gpio-keys"; + buttons: buttons { + compatible = "zephyr,gpio-keys"; button0: button_0 { gpios = <&gpio0 13 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; label = "Push button switch 0"; + zephyr,code = ; }; button1: button_1 { gpios = <&gpio0 14 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; label = "Push button switch 1"; + zephyr,code = ; }; button2: button_2 { gpios = <&gpio0 15 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; label = "Push button switch 2"; + zephyr,code = ; }; button3: button_3 { gpios = <&gpio0 16 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; label = "Push button switch 3"; + zephyr,code = ; }; }; @@ -128,6 +153,31 @@ watchdog0 = &wdt0; }; }; +&pinctrl { + qdec_default: qdec_default { + group1 { + psels = , + ; + }; + }; + + qdec_sleep: qdec_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&qdec { + status = "okay"; + led-pre = <0>; + steps = <360>; + pinctrl-0 = <&qdec_default>; + pinctrl-1 = <&qdec_sleep>; + pinctrl-names = "default", "sleep"; +}; &adc { status = "okay"; @@ -156,6 +206,12 @@ arduino_i2c: &i2c0 { pinctrl-0 = <&i2c0_default>; pinctrl-1 = <&i2c0_sleep>; pinctrl-names = "default", "sleep"; + + ft5336: ft5336@38 { + compatible = "focaltech,ft5336"; + reg = <0x38>; + int-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>; + }; }; &i2c1 { diff --git a/doc/services/index.rst b/doc/services/index.rst index 675edf935d590..aa9f0f1159516 100644 --- a/doc/services/index.rst +++ b/doc/services/index.rst @@ -14,6 +14,7 @@ OS Services file_system/index.rst formatted_output.rst ipc/index.rst + input/index.rst logging/index.rst tracing/index.rst resource_management/index.rst diff --git a/doc/services/input/index.rst b/doc/services/input/index.rst new file mode 100644 index 0000000000000..2ae9a1e3f80e4 --- /dev/null +++ b/doc/services/input/index.rst @@ -0,0 +1,73 @@ +.. _input: + +Input +##### + +The input subsystem provides an API for dispatching input events from input +devices to the application. + +Input Events +************ + +The subsystem is built around the :c:struct:`input_event` structure. An input +event represents a change in an individual event entity, for example the state +of a single button, or a movement in a single axis. + +The :c:struct:`input_event` structure describes the specific event, and +includes a synchronization bit to indicate that the device reached a stable +state, for example when the events corresponding to multiple axes of a +multi-axis device have been reported. + +Input Drivers +************* + +An input device can report input events directly using any of the +:c:func:`input_report` functions, for example buttons or other on-off input +devices would use :c:func:`input_report_key`. + +Complex devices may use a combination of multiple events, and set the ``sync`` +bit once the output is stable. + +The :c:func:`input_report` functions take a :c:struct:`device` pointer, which +is used to indicate which device reported the event and can be used by +subscribers to only receive events from a specific device. If there's no actual +device associated with the event, it can be set to ``NULL``, in which case only +subscribers with no device filter will receive the event. + +Application API +*************** + +An application can register a callback using the +:c:func:`INPUT_LISTENER_CB_DEFINE` macro. If a device node is specified, the +callback is only invoked for events from the specific device, otherwise the +callback will receive all the events in the system. This is the only type of +filtering supported, any more complex filtering logic has to be implemented in +the callback itself. + +The subsystem can operate synchronously or by using an event queue, depending +on the :kconfig:option:`CONFIG_INPUT_THREAD` option. If the input thread is +used, all the events are added to a queue and executed in a common ``input`` +thread. If the thread is not used, the callback are invoked directly in the +input driver context. + +The synchronous mode can be used in a simple application to keep a minimal +footprint, or in a complex application with an existing event model, where the +callback is just a wrapper to pipe back the event in a more complex application +specific event system. + +Kscan Compatibility +******************* + +Input devices generating X/Y/Touch events can be used in existing applications +based on the :ref:`kscan_api` API by defining a +:dtcompatible:`zephyr,kscan-adapter` node. + +API Reference +************* + +.. doxygengroup:: input_interface + +Driver API Reference +******************** + +.. doxygengroup:: input_driver_interface diff --git a/drivers/gpio/Kconfig.zephyr b/drivers/gpio/Kconfig.zephyr index 0e584a6d66f1f..70406ce6a80fd 100644 --- a/drivers/gpio/Kconfig.zephyr +++ b/drivers/gpio/Kconfig.zephyr @@ -5,5 +5,6 @@ config GPIO_KEYS_ZEPHYR bool "Zephyr GPIO Keys" default y depends on DT_HAS_ZEPHYR_GPIO_KEYS_ENABLED + select INPUT help Enable support for Zephyr GPIO Keys. diff --git a/drivers/gpio/gpio_keys_zephyr.c b/drivers/gpio/gpio_keys_zephyr.c index 6a9ce6274f2c0..1c81cc774e222 100644 --- a/drivers/gpio/gpio_keys_zephyr.c +++ b/drivers/gpio/gpio_keys_zephyr.c @@ -5,10 +5,11 @@ */ #include -#include +#include #include #include #include +#include LOG_MODULE_REGISTER(zephyr_gpio_keys, CONFIG_GPIO_LOG_LEVEL); @@ -61,7 +62,7 @@ static void gpio_keys_change_deferred(struct k_work *work) pin_data->cb_data.pin_state = new_pressed; LOG_DBG("Calling callback %s %d, code=%d", dev->name, new_pressed, pin_cfg->zephyr_code); - data->callback(dev, &pin_data->cb_data, BIT(pin_cfg->spec.pin)); + input_report_key(dev, pin_cfg->zephyr_code, new_pressed, true, K_FOREVER); } } @@ -211,13 +212,12 @@ static int gpio_keys_init(const struct device *dev) k_work_init_delayable(&data->pin_data[i].work, gpio_keys_change_deferred); } + gpio_keys_zephyr_enable_interrupt(dev, NULL); + return 0; } -static const struct gpio_keys_api gpio_keys_zephyr_api = { - .enable_interrupt = gpio_keys_zephyr_enable_interrupt, - .disable_interrupt = gpio_keys_zephyr_disable_interrupt, - .get_pin = gpio_keys_zephyr_get_pin, +static const struct input_driver_api gpio_keys_api = { }; #define GPIO_KEYS_CFG_DEF(node_id) \ @@ -241,6 +241,6 @@ static const struct gpio_keys_api gpio_keys_zephyr_api = { }; \ DEVICE_DT_INST_DEFINE(i, &gpio_keys_init, NULL, &gpio_keys_data_##i, \ &gpio_keys_config_##i, POST_KERNEL, CONFIG_GPIO_INIT_PRIORITY, \ - &gpio_keys_zephyr_api); + &gpio_keys_api); DT_INST_FOREACH_STATUS_OKAY(GPIO_KEYS_INIT) diff --git a/drivers/kscan/CMakeLists.txt b/drivers/kscan/CMakeLists.txt index ded6489dfe9d3..37557e734da15 100644 --- a/drivers/kscan/CMakeLists.txt +++ b/drivers/kscan/CMakeLists.txt @@ -11,5 +11,6 @@ zephyr_library_sources_ifdef(CONFIG_KSCAN_HT16K33 kscan_ht16k33.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_CST816S kscan_cst816s.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_CAP1203 kscan_cap1203.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_NPCX kscan_npcx.c) +zephyr_library_sources_ifdef(CONFIG_KSCAN_ADAPTER kscan_adapter.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE kscan_handlers.c) diff --git a/drivers/kscan/Kconfig b/drivers/kscan/Kconfig index 9eac0c6957087..21d05b9a979cf 100644 --- a/drivers/kscan/Kconfig +++ b/drivers/kscan/Kconfig @@ -30,4 +30,9 @@ config KSCAN_INIT_PRIORITY help Keyboard scan device driver initialization priority. +config KSCAN_ADAPTER + bool "Input subsystem to kscan adapter driver" + default y + depends on DT_HAS_ZEPHYR_KSCAN_ADAPTER_ENABLED + endif # KSCAN diff --git a/drivers/kscan/Kconfig.ft5336 b/drivers/kscan/Kconfig.ft5336 index e642324ea2550..c4d2f985a335c 100644 --- a/drivers/kscan/Kconfig.ft5336 +++ b/drivers/kscan/Kconfig.ft5336 @@ -7,6 +7,7 @@ menuconfig KSCAN_FT5336 default y depends on DT_HAS_FOCALTECH_FT5336_ENABLED select I2C + select INPUT help Enable driver for multiple Focaltech capacitive touch panel controllers. This driver should support FT5x06, FT5606, FT5x16, diff --git a/drivers/kscan/kscan_adapter.c b/drivers/kscan/kscan_adapter.c new file mode 100644 index 0000000000000..048ca9da75cd1 --- /dev/null +++ b/drivers/kscan/kscan_adapter.c @@ -0,0 +1,112 @@ +#define DT_DRV_COMPAT zephyr_kscan_adapter + +#include +#include + +#include +LOG_MODULE_REGISTER(kscan_adapter, CONFIG_KSCAN_LOG_LEVEL); + +struct kscan_adapter_config { + const struct device *input_dev; +}; + +struct kscan_adapter_data { + bool enabled; + kscan_callback_t callback; + int row; + int col; + bool pressed; +}; + +static void kscan_adapter_cb(const struct device *dev, struct input_event *evt, + bool sync) +{ + struct kscan_adapter_data *data = dev->data; + + switch (evt->code) { + case INPUT_ABS_X: + data->col = evt->value; + break; + case INPUT_ABS_Y: + data->row = evt->value; + break; + case INPUT_BTN_TOUCH: + data->pressed = evt->value; + break; + } + + if (sync) { + LOG_DBG("input event: %3d %3d %d", data->row, data->col, data->pressed); + if (data->callback) { + data->callback(dev, data->row, data->col, data->pressed); + } + } +} + +static int kscan_adapter_configure(const struct device *dev, + kscan_callback_t callback) +{ + struct kscan_adapter_data *data = dev->data; + if (!callback) { + LOG_ERR("Invalid callback (NULL)"); + return -EINVAL; + } + + data->callback = callback; + + return 0; +} + +static int kscan_adapter_enable_callback(const struct device *dev) +{ + struct kscan_adapter_data *data = dev->data; + + data->enabled = true; + + return 0; +} + +static int kscan_adapter_disable_callback(const struct device *dev) +{ + struct kscan_adapter_data *data = dev->data; + + data->enabled = false; + + return 0; +} + +static int kscan_adapter_init(const struct device *dev) +{ + const struct kscan_adapter_config *cfg = dev->config; + + if (!device_is_ready(cfg->input_dev)) { + LOG_ERR("Input device not ready"); + return -ENODEV; + } + + return 0; +} + +static const struct kscan_driver_api kscan_adapter_driver_api = { + .config = kscan_adapter_configure, + .enable_callback = kscan_adapter_enable_callback, + .disable_callback = kscan_adapter_disable_callback, +}; + +#define KSCAN_ADAPTER_INIT(index) \ + static void kscan_adapter_cb_##index(struct input_event *evt, bool sync) \ + { \ + kscan_adapter_cb(DEVICE_DT_GET(DT_INST(index, DT_DRV_COMPAT)), evt, sync); \ + } \ + INPUT_LISTENER_CB_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(index, input)), \ + kscan_adapter_cb_##index); \ + static const struct kscan_adapter_config kscan_adapter_config_##index = { \ + .input_dev = DEVICE_DT_GET(DT_INST_PHANDLE(index, input)), \ + }; \ + static struct kscan_adapter_data kscan_adapter_data_##index; \ + DEVICE_DT_INST_DEFINE(index, kscan_adapter_init, NULL, \ + &kscan_adapter_data_##index, &kscan_adapter_config_##index, \ + POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, \ + &kscan_adapter_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(KSCAN_ADAPTER_INIT) diff --git a/drivers/kscan/kscan_ft5336.c b/drivers/kscan/kscan_ft5336.c index 00d69eddde816..0c9c472240486 100644 --- a/drivers/kscan/kscan_ft5336.c +++ b/drivers/kscan/kscan_ft5336.c @@ -9,8 +9,10 @@ #define DT_DRV_COMPAT focaltech_ft5336 #include +#include #include #include +#include #include LOG_MODULE_REGISTER(ft5336, CONFIG_KSCAN_LOG_LEVEL); @@ -60,6 +62,8 @@ struct ft5336_data { /** Timer (polling mode). */ struct k_timer timer; #endif + bool pressed_old; + }; static int ft5336_process(const struct device *dev) @@ -100,7 +104,14 @@ static int ft5336_process(const struct device *dev) LOG_DBG("event: %d, row: %d, col: %d", event, row, col); - data->callback(dev, row, col, pressed); + if (pressed) { + input_report_abs(dev, INPUT_ABS_X, row, false, K_FOREVER); + input_report_abs(dev, INPUT_ABS_Y, col, false, K_FOREVER); + input_report_key(dev, INPUT_BTN_TOUCH, 1, true, K_FOREVER); + } else if (data->pressed_old && !pressed) { + input_report_key(dev, INPUT_BTN_TOUCH, 0, true, K_FOREVER); + } + data->pressed_old = pressed; return 0; } @@ -216,13 +227,11 @@ static int ft5336_init(const struct device *dev) k_timer_init(&data->timer, ft5336_timer_handler, NULL); #endif + ft5336_enable_callback(dev); return 0; } -static const struct kscan_driver_api ft5336_driver_api = { - .config = ft5336_configure, - .enable_callback = ft5336_enable_callback, - .disable_callback = ft5336_disable_callback, +static const struct input_driver_api ft5336_api = { }; #define FT5336_INIT(index) \ @@ -235,6 +244,6 @@ static const struct kscan_driver_api ft5336_driver_api = { DEVICE_DT_INST_DEFINE(index, ft5336_init, NULL, \ &ft5336_data_##index, &ft5336_config_##index, \ POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, \ - &ft5336_driver_api); + &ft5336_api); DT_INST_FOREACH_STATUS_OKAY(FT5336_INIT) diff --git a/drivers/sensor/qdec_nrfx/qdec_nrfx.c b/drivers/sensor/qdec_nrfx/qdec_nrfx.c index 4d21333f0818b..346db9ee3d1b7 100644 --- a/drivers/sensor/qdec_nrfx/qdec_nrfx.c +++ b/drivers/sensor/qdec_nrfx/qdec_nrfx.c @@ -5,8 +5,10 @@ */ #include +#include #include #include +#include #include #include @@ -72,6 +74,9 @@ static int qdec_nrfx_sample_fetch(const struct device *dev, accumulate(data, acc); + input_report_abs(DEVICE_DT_GET(DT_INST(0, DT_DRV_COMPAT)), + INPUT_ABS_Z, data->acc, true, K_FOREVER); + return 0; } @@ -158,6 +163,7 @@ static void qdec_nrfx_event_handler(nrfx_qdec_event_t event) handler(DEVICE_DT_INST_GET(0), &trig); } + qdec_nrfx_sample_fetch(NULL, SENSOR_CHAN_ROTATION); break; default: @@ -181,8 +187,8 @@ NRF_DT_CHECK_PIN_ASSIGNMENTS(DT_DRV_INST(0), 1, a_pin, b_pin, led_pin); static int qdec_nrfx_init(const struct device *dev) { static const nrfx_qdec_config_t config = { - .reportper = NRF_QDEC_REPORTPER_40, - .sampleper = NRF_QDEC_SAMPLEPER_2048us, + .reportper = NRF_QDEC_REPORTPER_200, + .sampleper = NRF_QDEC_SAMPLEPER_128us, #ifdef CONFIG_PINCTRL .skip_gpio_cfg = true, .skip_psel_cfg = true, @@ -279,14 +285,11 @@ static int qdec_nrfx_pm_action(const struct device *dev, #endif /* CONFIG_PM_DEVICE */ -static const struct sensor_driver_api qdec_nrfx_driver_api = { - .sample_fetch = qdec_nrfx_sample_fetch, - .channel_get = qdec_nrfx_channel_get, - .trigger_set = qdec_nrfx_trigger_set, +static const struct input_driver_api qdec_nrfx_api = { }; PM_DEVICE_DT_INST_DEFINE(0, qdec_nrfx_pm_action); -SENSOR_DEVICE_DT_INST_DEFINE(0, qdec_nrfx_init, +DEVICE_DT_INST_DEFINE(0, qdec_nrfx_init, PM_DEVICE_DT_INST_GET(0), NULL, NULL, POST_KERNEL, - CONFIG_SENSOR_INIT_PRIORITY, &qdec_nrfx_driver_api); + CONFIG_SENSOR_INIT_PRIORITY, &qdec_nrfx_api); diff --git a/dts/bindings/input/zephyr,input-abs-to-rel.yaml b/dts/bindings/input/zephyr,input-abs-to-rel.yaml new file mode 100644 index 0000000000000..80560e16093ca --- /dev/null +++ b/dts/bindings/input/zephyr,input-abs-to-rel.yaml @@ -0,0 +1,8 @@ +description: Input absolute to relative + +compatible: "zephyr,input-abs-to-rel" + +properties: + input: + type: phandle + required: true diff --git a/dts/bindings/input/zephyr,input-longpress.yaml b/dts/bindings/input/zephyr,input-longpress.yaml new file mode 100644 index 0000000000000..cd457ef541dae --- /dev/null +++ b/dts/bindings/input/zephyr,input-longpress.yaml @@ -0,0 +1,24 @@ +description: Input absolute to relative + +compatible: "zephyr,input-longpress" + +properties: + input: + type: phandle + required: true + + input-code: + type: int + required: true + + short-code: + type: int + required: true + + long-codes: + type: array + required: true + + long-delays-ms: + type: array + required: true diff --git a/dts/bindings/kscan/zephyr,kscan-adapter.yaml b/dts/bindings/kscan/zephyr,kscan-adapter.yaml new file mode 100644 index 0000000000000..82a45ee2744b0 --- /dev/null +++ b/dts/bindings/kscan/zephyr,kscan-adapter.yaml @@ -0,0 +1,10 @@ +description: Input to kscan adapter + +compatible: "zephyr,kscan-adapter" + +include: kscan.yaml + +properties: + input: + type: phandle + required: true diff --git a/include/zephyr/drivers/input.h b/include/zephyr/drivers/input.h new file mode 100644 index 0000000000000..a9e50c1148ef4 --- /dev/null +++ b/include/zephyr/drivers/input.h @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_INPUT_H_ +#define ZEPHYR_INCLUDE_DRIVERS_INPUT_H_ + +/** + * @brief Input Driver Interface + * @defgroup input_driver_interface Input Driver Interface + * @ingroup io_interfaces + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Input device driver API. */ +__subsystem struct input_driver_api { +}; + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_INPUT_H_ */ diff --git a/include/zephyr/dt-bindings/input/input-event-codes.h b/include/zephyr/dt-bindings/input/input-event-codes.h new file mode 100644 index 0000000000000..5aa031a4fc153 --- /dev/null +++ b/include/zephyr/dt-bindings/input/input-event-codes.h @@ -0,0 +1,95 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_EVENT_CODES_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_EVENT_CODES_H_ + +/* Input event types. */ +#define INPUT_EV_KEY 0x01 +#define INPUT_EV_REL 0x02 +#define INPUT_EV_ABS 0x03 +#define INPUT_EV_SYN 0x80 + +/* Input event KEY codes. */ +#define INPUT_KEY_0 11 +#define INPUT_KEY_1 2 +#define INPUT_KEY_2 3 +#define INPUT_KEY_3 4 +#define INPUT_KEY_4 5 +#define INPUT_KEY_5 6 +#define INPUT_KEY_6 7 +#define INPUT_KEY_7 8 +#define INPUT_KEY_8 9 +#define INPUT_KEY_9 10 +#define INPUT_KEY_A 30 +#define INPUT_KEY_B 48 +#define INPUT_KEY_C 46 +#define INPUT_KEY_D 32 +#define INPUT_KEY_E 18 +#define INPUT_KEY_F 33 +#define INPUT_KEY_G 34 +#define INPUT_KEY_H 35 +#define INPUT_KEY_I 23 +#define INPUT_KEY_J 36 +#define INPUT_KEY_K 37 +#define INPUT_KEY_L 38 +#define INPUT_KEY_M 50 +#define INPUT_KEY_N 49 +#define INPUT_KEY_O 24 +#define INPUT_KEY_P 25 +#define INPUT_KEY_Q 16 +#define INPUT_KEY_R 19 +#define INPUT_KEY_S 31 +#define INPUT_KEY_T 20 +#define INPUT_KEY_U 22 +#define INPUT_KEY_V 47 +#define INPUT_KEY_VOLUMEDOWN 114 +#define INPUT_KEY_VOLUMEUP 115 +#define INPUT_KEY_W 17 +#define INPUT_KEY_X 45 +#define INPUT_KEY_Y 21 +#define INPUT_KEY_Z 44 + +/* Input event BTN codes. */ +#define INPUT_BTN_DPAD_DOWN 0x221 +#define INPUT_BTN_DPAD_LEFT 0x222 +#define INPUT_BTN_DPAD_RIGHT 0x223 +#define INPUT_BTN_DPAD_UP 0x220 +#define INPUT_BTN_EAST 0x131 +#define INPUT_BTN_LEFT 0x110 +#define INPUT_BTN_MIDDLE 0x112 +#define INPUT_BTN_MODE 0x13c +#define INPUT_BTN_NORTH 0x133 +#define INPUT_BTN_RIGHT 0x111 +#define INPUT_BTN_SELECT 0x13a +#define INPUT_BTN_SOUTH 0x130 +#define INPUT_BTN_START 0x13b +#define INPUT_BTN_THUMBL 0x13d +#define INPUT_BTN_THUMBR 0x13e +#define INPUT_BTN_TL 0x136 +#define INPUT_BTN_TL2 0x138 +#define INPUT_BTN_TOUCH 0x14a +#define INPUT_BTN_TR 0x137 +#define INPUT_BTN_TR2 0x139 +#define INPUT_BTN_WEST 0x134 + +/* Input event ABS codes. */ +#define INPUT_ABS_RX 0x03 +#define INPUT_ABS_RY 0x04 +#define INPUT_ABS_RZ 0x05 +#define INPUT_ABS_X 0x00 +#define INPUT_ABS_Y 0x01 +#define INPUT_ABS_Z 0x02 + +/* Input event REL codes. */ +#define INPUT_REL_RX 0x03 +#define INPUT_REL_RY 0x04 +#define INPUT_REL_RZ 0x05 +#define INPUT_REL_X 0x00 +#define INPUT_REL_Y 0x01 +#define INPUT_REL_Z 0x02 + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_EVENT_CODES_H_ */ diff --git a/include/zephyr/input/input.h b/include/zephyr/input/input.h new file mode 100644 index 0000000000000..349b872acd592 --- /dev/null +++ b/include/zephyr/input/input.h @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_INPUT_H_ +#define ZEPHYR_INCLUDE_INPUT_H_ + +/** + * @brief Input Interface + * @defgroup input_interface Input Interface + * @ingroup io_interfaces + * @{ + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Input event structure. + * + * This structure represents a single input event, for example a key or button + * press for a single button, or an absolute or relative coordinate for a + * single axis. + */ +struct input_event { + /** Device generating the event or NULL. */ + const struct device *dev; + /** Event type, one of INPUT_EV_*. */ + uint16_t type; + /** Event code, one of INPUT_{KEY,BTN,ABS,REL,...)_*. */ + uint16_t code; + /** Event value. */ + int32_t value; +}; + +/** + * @brief Report a new input event. + * + * This causes all the listeners for the specified device to be triggered, + * either synchronously or through the input thread if utilized. + * + * @param dev Device generating the event or NULL. + * @param type Event type, one of INPUT_EV_*. + * @param code Event code, one of INPUT_{KEY,BTN,ABS,REL,...)_*. + * @param value Event value. + * @param sync Set the synchronization bit for the event. + * @param timeout Timeout for reporting the event, ignored if + * `CONFIG_INPUT_THREAD=n`. + * @return 0 if the message has been processed, a negative error code if + * `CONFIG_INPUT_THREAD=y` and the message failed to be enqueued. + */ +int input_report(const struct device *dev, + uint16_t type, uint16_t code, int32_t value, bool sync, + k_timeout_t timeout); + +/** + * @brief Report a new INPUT_EV_KEY input event. + * + * See `input_report` for more details. Note that value is converted to either + * 0 or 1. + */ +static inline int input_report_key(const struct device *dev, + uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + return input_report(dev, INPUT_EV_KEY, code, !!value, sync, timeout); +} + +/** + * @brief Report a new INPUT_EV_REL input event. + * + * See `input_report` for more details. + */ +static inline int input_report_rel(const struct device *dev, + uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + return input_report(dev, INPUT_EV_REL, code, value, sync, timeout); +} + +/** + * @brief Report a new INPUT_EV_ABS input event. + * + * See `input_report` for more details. + */ +static inline int input_report_abs(const struct device *dev, + uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + return input_report(dev, INPUT_EV_ABS, code, value, sync, timeout); +} + +/** + * @brief Returns true if the input queue is empty. + * + * This can be used to batch input event processing until the whole queue has + * been emptied. Always returns true if `CONFIG_INPUT_THREAD=n`. + */ +bool input_queue_empty(void); + +/** + * @brief Input listener callback structure. + */ +struct input_listener { + /** `struct device` pointer or NULL. */ + const struct device *dev; + /** The callback function. */ + void (*callback)(struct input_event *evt, bool sync); +}; + +/** + * @brief Register a callback structure for input events. + * + * The `_dev` field can be used to only invoke callback for events generated by + * a specific device. Setting dev to NULL causes callback to be invoked for + * every event. + * + * @param _dev `struct device` pointer or NULL. + * @param _callback The callback function. + */ +#define INPUT_LISTENER_CB_DEFINE(_dev, _callback) \ + static const STRUCT_SECTION_ITERABLE(input_listener, \ + _input_listener__##_callback) = { \ + .dev = _dev, \ + .callback = _callback, \ + }; + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* ZEPHYR_INCLUDE_INPUT_H_ */ diff --git a/samples/basic/blinky/src/main.c b/samples/basic/blinky/src/main.c index 7680058f76225..9eb2023fdd4fa 100644 --- a/samples/basic/blinky/src/main.c +++ b/samples/basic/blinky/src/main.c @@ -6,9 +6,10 @@ #include #include +#include /* 1000 msec = 1 sec */ -#define SLEEP_TIME_MS 1000 +#define SLEEP_TIME_MS 200 /* The devicetree node identifier for the "led0" alias. */ #define LED0_NODE DT_ALIAS(led0) @@ -40,3 +41,14 @@ void main(void) k_msleep(SLEEP_TIME_MS); } } + +static void input_cb(struct input_event *evt, bool sync) +{ + printk("input event: %16s %c %2x %3d %d\n", + evt->dev->name, + sync ? 'S' : ' ', + evt->type, + evt->code, + evt->value); +} +INPUT_LISTENER_CB_DEFINE(NULL, input_cb); diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 1ec4d51188087..203a80bb9f479 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory_ifdef(CONFIG_DISK_ACCESS disk) add_subdirectory_ifdef(CONFIG_EMUL emul) add_subdirectory(fs) add_subdirectory(ipc) +add_subdirectory_ifdef(CONFIG_INPUT input) add_subdirectory(mgmt) add_subdirectory_ifdef(CONFIG_IMG_MANAGER dfu) add_subdirectory_ifdef(CONFIG_NET_BUF net) diff --git a/subsys/Kconfig b/subsys/Kconfig index bf718b90155b7..41d0ab107da00 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -22,6 +22,8 @@ source "subsys/fb/Kconfig" source "subsys/fs/Kconfig" +source "subsys/input/Kconfig" + source "subsys/ipc/Kconfig" source "subsys/jwt/Kconfig" diff --git a/subsys/input/CMakeLists.txt b/subsys/input/CMakeLists.txt new file mode 100644 index 0000000000000..91698a849618a --- /dev/null +++ b/subsys/input/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(input.c) + +zephyr_linker_sources(SECTIONS input.ld) + +zephyr_library_sources_ifdef(CONFIG_INPUT_ABS_TO_REL input_abs_to_rel.c) +zephyr_library_sources_ifdef(CONFIG_INPUT_LONGPRESS input_longpress.c) diff --git a/subsys/input/Kconfig b/subsys/input/Kconfig new file mode 100644 index 0000000000000..52ba4a15c5786 --- /dev/null +++ b/subsys/input/Kconfig @@ -0,0 +1,41 @@ +# Copyright 2023 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +menuconfig INPUT + bool "Input" + help + Include input subsystem and drivers in the system config + +if INPUT + +module = INPUT +module-str = input +source "subsys/logging/Kconfig.template.log_config" + +config INPUT_THREAD + bool "Use a dedicated thread to process input events" + default y + +if INPUT_THREAD + +config INPUT_QUEUE_SIZE + int "Input queue size" + default 16 + +config INPUT_THREAD_STACK_SIZE + int "Input thread stack size" + default 512 + +endif # INPUT_THREAD + +config INPUT_ABS_TO_REL + bool "Input absolute to relative adapter" + default y + depends on DT_HAS_ZEPHYR_INPUT_ABS_TO_REL_ENABLED + +config INPUT_LONGPRESS + bool "Input longpress" + default y + depends on DT_HAS_ZEPHYR_INPUT_LONGPRESS_ENABLED + +endif # INPUT diff --git a/subsys/input/input.c b/subsys/input/input.c new file mode 100644 index 0000000000000..751367aa12895 --- /dev/null +++ b/subsys/input/input.c @@ -0,0 +1,92 @@ +/* + * Copyright 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(input, CONFIG_INPUT_LOG_LEVEL); + +#define INPUT_TYPE(t) (t & 0x7f) + +#define INPUT_SYN_IS_SET(v) (v & INPUT_EV_SYN) + +#if CONFIG_INPUT_THREAD + +K_MSGQ_DEFINE(input_msgq, sizeof(struct input_event), CONFIG_INPUT_QUEUE_SIZE, 4); + +#endif + +static void input_process(struct input_event *evt) +{ + bool sync = INPUT_SYN_IS_SET(evt->type); + + evt->type = INPUT_TYPE(evt->type); + + STRUCT_SECTION_FOREACH(input_listener, listener) { + if (listener->dev == NULL || listener->dev == evt->dev) { + listener->callback(evt, sync); + } + } +} + +bool input_queue_empty(void) +{ +#if CONFIG_INPUT_THREAD + if (k_msgq_num_used_get(&input_msgq) > 0) { + return false; + } +#endif + return true; +} + +int input_report(const struct device *dev, + uint16_t type, uint16_t code, int32_t value, bool sync, + k_timeout_t timeout) +{ + struct input_event evt = { + .dev = dev, + .type = type, + .code = code, + .value = value, + }; + + if (sync) { + evt.type |= INPUT_EV_SYN; + } + +#if CONFIG_INPUT_THREAD + return k_msgq_put(&input_msgq, &evt, timeout); +#else + input_process(&evt); + return 0; +#endif +} + +#if CONFIG_INPUT_THREAD + +static void input_thread(void) +{ + struct input_event evt; + int ret; + + while (true) { + ret = k_msgq_get(&input_msgq, &evt, K_FOREVER); + if (ret) { + LOG_ERR("k_msgq_get error: %d", ret); + } + + input_process(&evt); + } +} + +K_THREAD_DEFINE(input, + CONFIG_INPUT_THREAD_STACK_SIZE, + input_thread, + NULL, NULL, NULL, + K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0); + +#endif /* CONFIG_INPUT_THREAD */ diff --git a/subsys/input/input.ld b/subsys/input/input.ld new file mode 100644 index 0000000000000..8aa4e98dc4b7a --- /dev/null +++ b/subsys/input/input.ld @@ -0,0 +1 @@ +ITERABLE_SECTION_ROM(input_listener, 4) diff --git a/subsys/input/input_abs_to_rel.c b/subsys/input/input_abs_to_rel.c new file mode 100644 index 0000000000000..1862c771a1226 --- /dev/null +++ b/subsys/input/input_abs_to_rel.c @@ -0,0 +1,72 @@ +#define DT_DRV_COMPAT zephyr_input_abs_to_rel + +#include +#include + +#include +LOG_MODULE_REGISTER(input_abs_to_rel, LOG_LEVEL_INF); + +struct input_abs_to_rel_config { + const struct device *input_dev; +}; + +struct input_abs_to_rel_data { + int32_t last_val; + int32_t acc; +}; + +static void input_abs_to_rel_cb(const struct device *dev, + struct input_event *evt, bool sync) +{ + struct input_abs_to_rel_data *data = dev->data; + int rel; + int out; + + if (evt->code != INPUT_ABS_Z) { + return; + } + + rel = evt->value - data->last_val; + data->last_val = evt->value; + + data->acc += rel; + + out = data->acc / 4; + data->acc %= 4; + + if (out) { + input_report_rel(dev, INPUT_REL_Z, out, true, K_FOREVER); + } +} + +static int input_abs_to_rel_init(const struct device *dev) +{ + const struct input_abs_to_rel_config *cfg = dev->config; + + if (!device_is_ready(cfg->input_dev)) { + LOG_ERR("Input device not ready"); + return -ENODEV; + } + + return 0; +} + +static const struct input_driver_api input_abs_to_rel_api = { +}; + +#define INPUT_ABS_TO_REL_INIT(index) \ + static void input_abs_to_rel_cb_##index(struct input_event *evt, bool sync) \ + { \ + input_abs_to_rel_cb(DEVICE_DT_GET(DT_INST(index, DT_DRV_COMPAT)), evt, sync); \ + } \ + INPUT_LISTENER_CB_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(index, input)), \ + input_abs_to_rel_cb_##index); \ + static const struct input_abs_to_rel_config input_abs_to_rel_config_##index = { \ + .input_dev = DEVICE_DT_GET(DT_INST_PHANDLE(index, input)), \ + }; \ + static struct input_abs_to_rel_data input_abs_to_rel_data_##index; \ + DEVICE_DT_INST_DEFINE(index, input_abs_to_rel_init, NULL, \ + &input_abs_to_rel_data_##index, &input_abs_to_rel_config_##index, \ + POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, &input_abs_to_rel_api); + +DT_INST_FOREACH_STATUS_OKAY(INPUT_ABS_TO_REL_INIT) diff --git a/subsys/input/input_longpress.c b/subsys/input/input_longpress.c new file mode 100644 index 0000000000000..01ae285a9345a --- /dev/null +++ b/subsys/input/input_longpress.c @@ -0,0 +1,109 @@ +#define DT_DRV_COMPAT zephyr_input_longpress + +#include +#include + +#include +LOG_MODULE_REGISTER(input_longpress, LOG_LEVEL_INF); + +struct longpress_config { + const struct device *input_dev; + uint16_t in_code; + uint8_t steps; + uint16_t short_code; + const uint16_t *long_codes; + const int *long_delays_ms; +}; + +struct longpress_data { + const struct device *dev; + struct k_work_delayable work; + uint8_t step; +}; + +static void longpress_deferred(struct k_work *work) +{ + struct longpress_data *data = CONTAINER_OF(work, struct longpress_data, work); + const struct device *dev = data->dev; + const struct longpress_config *cfg = dev->config; + int next_delay_ms; + uint16_t code; + + code = cfg->long_codes[data->step]; + input_report_key(dev, code, 1, true, K_FOREVER); + + data->step++; + + if (data->step >= cfg->steps) { + return; + } + + next_delay_ms = cfg->long_delays_ms[data->step]; + + k_work_schedule(&data->work, K_MSEC(next_delay_ms)); +} + +static void longpress_cb(const struct device *dev, struct input_event *evt, + bool sync) +{ + const struct longpress_config *cfg = dev->config; + struct longpress_data *data = dev->data; + + if (evt->code != cfg->in_code) { + return; + } + + if (evt->value) { + data->step = 0; + k_work_schedule(&data->work, K_MSEC(cfg->long_delays_ms[0])); + } else { + if (data->step == 0) { + input_report_key(dev, cfg->short_code, 1, true, K_FOREVER); + } + k_work_cancel_delayable(&data->work); + } +} + +static int longpress_init(const struct device *dev) +{ + const struct longpress_config *cfg = dev->config; + struct longpress_data *data = dev->data; + + if (!device_is_ready(cfg->input_dev)) { + LOG_ERR("Input device not ready"); + return -ENODEV; + } + + data->dev = dev; + + k_work_init_delayable(&data->work, longpress_deferred); + + return 0; +} + +static const struct input_driver_api input_longpress_api = { +}; + +#define INPUT_LONGPRESS_INIT(index) \ + static void longpress_cb_##index(struct input_event *evt, bool sync) \ + { \ + longpress_cb(DEVICE_DT_GET(DT_INST(index, DT_DRV_COMPAT)), evt, sync); \ + } \ + INPUT_LISTENER_CB_DEFINE(DEVICE_DT_GET(DT_INST_PHANDLE(index, input)), \ + longpress_cb_##index); \ + static const uint16_t input_longpress_codes_##index[] = DT_INST_PROP(index, long_codes); \ + static const int input_longpress_delays_ms_##index[] = DT_INST_PROP(index, long_delays_ms); \ + static const struct longpress_config longpress_config_##index = { \ + .input_dev = DEVICE_DT_GET(DT_INST_PHANDLE(index, input)), \ + .in_code = DT_INST_PROP(index, input_code), \ + .steps = DT_INST_PROP_LEN(index, long_codes), \ + .short_code = DT_INST_PROP(index, short_code), \ + .long_codes = input_longpress_codes_##index, \ + .long_delays_ms = input_longpress_delays_ms_##index, \ + }; \ + static struct longpress_data longpress_data_##index; \ + DEVICE_DT_INST_DEFINE(index, longpress_init, NULL, \ + &longpress_data_##index, &longpress_config_##index, \ + POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, &input_longpress_api); + +DT_INST_FOREACH_STATUS_OKAY(INPUT_LONGPRESS_INIT)