From 59d37d27759761568f851a02e0c00689431be096 Mon Sep 17 00:00:00 2001 From: Michael Owen Date: Sun, 9 Oct 2022 11:09:27 +0800 Subject: [PATCH 1/3] drivers: input: add input subsystem. Input subsystem support public API for input device. Signed-off-by: Michael Owen --- doc/develop/api/overview.rst | 4 + drivers/CMakeLists.txt | 1 + drivers/Kconfig | 2 + drivers/input/CMakeLists.txt | 7 + drivers/input/Kconfig | 34 +++ drivers/input/input_handlers.c | 85 +++++++ drivers/input/input_internal.c | 214 ++++++++++++++++ drivers/input/input_internal.h | 190 ++++++++++++++ include/zephyr/drivers/input.h | 307 +++++++++++++++++++++++ include/zephyr/dt-bindings/input/input.h | 94 +++++++ 10 files changed, 938 insertions(+) create mode 100644 drivers/input/CMakeLists.txt create mode 100644 drivers/input/Kconfig create mode 100644 drivers/input/input_handlers.c create mode 100644 drivers/input/input_internal.c create mode 100644 drivers/input/input_internal.h create mode 100644 include/zephyr/drivers/input.h create mode 100644 include/zephyr/dt-bindings/input/input.h diff --git a/doc/develop/api/overview.rst b/doc/develop/api/overview.rst index b6f686ee6c5ce..963e330183112 100644 --- a/doc/develop/api/overview.rst +++ b/doc/develop/api/overview.rst @@ -320,3 +320,7 @@ between major releases are available in the :ref:`zephyr_release_notes`. * - :ref:`zdsp_api` - Experimental - 3.3 + + * - :ref:`input_api` + - Experimental + - 3.3 diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index b5c19740c30e8..be7db66e3eea9 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -74,3 +74,4 @@ add_subdirectory_ifdef(CONFIG_MM_DRV mm) add_subdirectory_ifdef(CONFIG_RESET reset) add_subdirectory_ifdef(CONFIG_COREDUMP_DEVICE coredump) add_subdirectory_ifdef(CONFIG_FUEL_GAUGE fuel_gauge) +add_subdirectory_ifdef(CONFIG_INPUT input) diff --git a/drivers/Kconfig b/drivers/Kconfig index 23fecff92d10f..a26d893df2949 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -151,4 +151,6 @@ source "drivers/xen/Kconfig" source "drivers/fuel_gauge/Kconfig" +source "drivers/input/Kconfig" + endmenu diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt new file mode 100644 index 0000000000000..36c4378bcd57d --- /dev/null +++ b/drivers/input/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2022, tangchunhui@coros.com +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_INPUT input_internal.c) +zephyr_library_sources_ifdef(CONFIG_USERSPACE input_handlers.c) diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig new file mode 100644 index 0000000000000..7aa734d6492e5 --- /dev/null +++ b/drivers/input/Kconfig @@ -0,0 +1,34 @@ +# Input subsystem configuration options + +# Copyright (c) 2022, tangchunhui@coros.com +# SPDX-License-Identifier: Apache-2.0 + +menuconfig INPUT + bool "Input Subsystem Drivers" + select RING_BUFFER + help + Include input subsystem drivers in system config. + +if INPUT + +config ENABLE_INPUT_ISR_LOCK + bool "Input event interrupt lock" + help + Use k_spin_lock() and k_spin_unlock() to control ring buffer exclusive access. + This is required to be able to report input event in isr context. + +module = INPUT +module-str = input +source "subsys/logging/Kconfig.template.log_config" + +config INPUT_INIT_PRIORITY + int "Input subsystem driver init priority" + default 90 + help + Input subsystem driver initialization priority. + +config INPUT_ATTR_DATA_SIZE + int "Input attribute data max size" + default 64 + +endif # INPUT diff --git a/drivers/input/input_handlers.c b/drivers/input/input_handlers.c new file mode 100644 index 0000000000000..7dc305baecd98 --- /dev/null +++ b/drivers/input/input_handlers.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static inline int z_vrfy_input_setup(const struct device *dev) +{ + Z_OOPS(Z_SYSCALL_DRIVER_INPUT(dev, setup)); + + return z_impl_input_setup((const struct device *)dev); +} +#include + +static inline int z_vrfy_input_release(const struct device *dev) +{ + Z_OOPS(Z_SYSCALL_DRIVER_INPUT(dev, release)); + + return z_impl_input_release((const struct device *)dev); +} +#include + +static inline int z_vrfy_input_attr_get(const struct device *dev, + enum input_attr_type type, union input_attr_data *data) +{ + Z_OOPS(Z_SYSCALL_DRIVER_INPUT(dev, attr_get)); + + if (data) { + union input_attr_data _data; + int retval = 0; + + retval = z_impl_input_attr_get((const struct device *)dev, type, &_data); + Z_OOPS(z_user_to_copy(data, &_data, sizeof(union input_attr_data))); + + return retval; + } + + return z_impl_input_attr_get((const struct device *)dev, type, NULL); +} +#include + +static inline int z_vrfy_input_attr_set(const struct device *dev, + enum input_attr_type type, union input_attr_data *data) +{ + Z_OOPS(Z_SYSCALL_DRIVER_INPUT(dev, attr_set)); + + if (data) { + union input_attr_data _data; + + Z_OOPS(z_user_from_copy(&_data, data, sizeof(union input_attr_data))); + return z_impl_input_attr_set((const struct device *)dev, type, &_data); + } + + return z_impl_input_attr_set((const struct device *)dev, type, NULL); +} + +#include + +static inline int z_vrfy_input_event_read(const struct device *dev, struct input_event *event) +{ + struct input_event _event; + int retval = 0; + + Z_OOPS(Z_SYSCALL_DRIVER_INPUT(dev, event_read)); + + retval = z_impl_input_event_read((const struct device *)dev, &_event); + Z_OOPS(z_user_to_copy(event, &_event, sizeof(struct input_event))); + + return retval; +} +#include + +static inline int z_vrfy_input_event_write(const struct device *dev, struct input_event *event) +{ + struct input_event _event; + + Z_OOPS(Z_SYSCALL_DRIVER_INPUT(dev, event_write)); + Z_OOPS(z_user_from_copy(&_event, event, sizeof(struct input_event))); + + return z_impl_input_event_write((const struct device *)dev, &_event); +} +#include diff --git a/drivers/input/input_internal.c b/drivers/input/input_internal.c new file mode 100644 index 0000000000000..915bbc927d613 --- /dev/null +++ b/drivers/input/input_internal.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "input_internal.h" + +int input_internal_setup(struct input_dev *dev) +{ + int retval = 0; + + if (dev->buf == NULL) { + return -EFAULT; + } + + ring_buf_reset(dev->buf); + +#ifndef CONFIG_ENABLE_INPUT_ISR_LOCK + retval = k_mutex_init(&(dev->mutex)); + if (retval < 0) { + return retval; + } +#endif + + retval = k_sem_init(&(dev->sem), 0, 1); + if (retval < 0) { + return retval; + } + + dev->readtimeo = K_FOREVER; + + return 0; +} + +int input_internal_release(struct input_dev *dev) +{ + k_sem_reset(&(dev->sem)); + + return 0; +} + +int input_internal_attr_get(struct input_dev *dev, + enum input_attr_type type, union input_attr_data *data) +{ + int retval = 0; + + switch (type) { + case INPUT_ATTR_EVENT_READ_TIMEOUT: + { + data->timeout = dev->readtimeo; + break; + } + + default: + retval = -ENOTSUP; + break; + } + + return retval; +} + +int input_internal_attr_set(struct input_dev *dev, + enum input_attr_type type, union input_attr_data *data) +{ + int retval = 0; + + switch (type) { + case INPUT_ATTR_EVENT_READ_TIMEOUT: + { + dev->readtimeo = data->timeout; + + k_sem_give(&(dev->sem)); + + break; + } + + default: + retval = -ENOTSUP; + break; + } + + return retval; +} + +#ifdef CONFIG_ENABLE_INPUT_ISR_LOCK + +#define INPUT_LOCK() ({ \ + key = k_spin_lock(&(dev->lock)); \ + 0; \ +}) + +#define INPUT_UNLOCK() ({ \ + k_spin_unlock(&(dev->lock), key); \ + 0; \ +}) + +#else + +#define INPUT_LOCK() \ + k_mutex_lock(&(dev->mutex), K_FOREVER); + +#define INPUT_UNLOCK() \ + k_mutex_unlock(&(dev->mutex)); + +#endif + +int input_internal_event_read(struct input_dev *dev, struct input_event *event) +{ +#ifdef CONFIG_ENABLE_INPUT_ISR_LOCK + k_spinlock_key_t key; +#endif + uint32_t size = 0; + int retval = 0; + + if (dev->buf == NULL) { + return -EFAULT; + } + + INPUT_ASSERT(!k_is_in_isr(), "Can't read event in isr context."); + + retval = INPUT_LOCK(); + if (retval < 0) { + return retval; + } + + size = ring_buf_size_get(dev->buf); + if (size > 0) { + /* Ring buf size must be a multiple of input event size */ + INPUT_ASSERT(!(size % input_event_size), "Invalid ring buf size"); + + size = ring_buf_get(dev->buf, (uint8_t *)event, input_event_size); + } else if (!K_TIMEOUT_EQ(dev->readtimeo, K_NO_WAIT)) { + do { + + INPUT_UNLOCK(); + + retval = k_sem_take(&(dev->sem), dev->readtimeo); + if (retval < 0) { + return retval; + } + + retval = INPUT_LOCK(); + if (retval < 0) { + return retval; + } + + size = ring_buf_size_get(dev->buf); + if (size > 0) { + /* Ring buf size must be a multiple of input event size */ + INPUT_ASSERT(!(size % input_event_size), "Invalid ring buf size"); + + size = ring_buf_get(dev->buf, (uint8_t *)event, input_event_size); + break; + } + } while (1); + } else { + retval = -EAGAIN; + } + + INPUT_UNLOCK(); + + return retval; +} + +int input_event(struct input_dev *dev, uint16_t type, uint16_t code, int32_t value) +{ + struct input_event event; + int64_t time_stamp; +#ifdef CONFIG_ENABLE_INPUT_ISR_LOCK + k_spinlock_key_t key; +#endif + uint32_t size = 0; + int retval = 0; + +#ifndef CONFIG_ENABLE_INPUT_ISR_LOCK + INPUT_ASSERT(!k_is_in_isr(), "Can't report event in isr context."); +#endif + + if (dev->buf == NULL) { + return -EFAULT; + } + + /* capture initial time stamp */ + time_stamp = k_uptime_get(); + + event.time.tv_sec = (time_stamp / 1000); + event.time.tv_usec = (time_stamp % 1000) * 1000; + + event.type = type; + event.code = code; + event.value = value; + + retval = INPUT_LOCK(); + if (retval < 0) { + return retval; + } + + size = ring_buf_space_get(dev->buf); + if (size > 0) { + /* Ring buf space must be a multiple of input event size */ + INPUT_ASSERT(!(size % input_event_size), "Invalid ring buf space"); + + size = ring_buf_put(dev->buf, (const uint8_t *)&event, input_event_size); + + k_sem_give(&(dev->sem)); + } else { + retval = -EAGAIN; + } + + INPUT_UNLOCK(); + + return retval; +} diff --git a/drivers/input/input_internal.h b/drivers/input/input_internal.h new file mode 100644 index 0000000000000..9cf3ca35fc348 --- /dev/null +++ b/drivers/input/input_internal.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Private input driver APIs + */ + +#ifndef ZEPHYR_DRIVERS_INPUT_INPUT_INTERNAL_H_ +#define ZEPHYR_DRIVERS_INPUT_INPUT_INTERNAL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define INPUT_ASSERT(cond, fmt, ...) { \ + if (!cond) { \ + printk("ASSERT FAIL [%s] @ %s:%d\n", #cond, __FILE__, __LINE__); \ + printk("\t" fmt "\n", ##__VA_ARGS__); \ + k_oops(); \ + } \ +} + +/** + * @brief Input device spec + * + */ +struct input_dev { + struct ring_buf *buf; +#ifdef CONFIG_ENABLE_INPUT_ISR_LOCK + struct k_spinlock lock; +#else + struct k_mutex mutex; +#endif + struct k_sem sem; + k_timeout_t readtimeo; +}; + +/** + * @brief Setup a input internal device instance. + * + * @param dev Pointer to the input device structure for the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +int input_internal_setup(struct input_dev *dev); + +/** + * @brief Release a input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +int input_internal_release(struct input_dev *dev); + +/** + * @brief Get attribute from the input internal device instance. + * + * @param dev Pointer to the device structure for the driver instance. + * @param type The type of attribute which get from the driver instance. + * @param attr Pointer to the attribute structure which get from the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +int input_internal_attr_get(struct input_dev *dev, + enum input_attr_type type, union input_attr_data *data); + +/** + * @brief Set attribute from the input internal device instance. + * + * @param dev Pointer to the device structure for the driver instance. + * @param type The type of attribute which set to the driver instance. + * @param attr Pointer to the attribute structure which set to the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +int input_internal_attr_set(struct input_dev *dev, + enum input_attr_type type, union input_attr_data *data); + +/** + * @brief Read event from the input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * @param event Pointer to the event structure which read from the input internal device instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +int input_internal_event_read(struct input_dev *dev, struct input_event *event); + +/** + * @brief Report event to the input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * @param event Pointer to the event structure which report to the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +int input_event(struct input_dev *dev, uint16_t type, uint16_t code, int32_t value); + +/** + * @brief Write event from the input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * @param event Pointer to the event structure which write to the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +static inline int input_internal_event_write(struct input_dev *dev, struct input_event *event) +{ + return input_event(dev, event->type, event->code, event->value); +} + +/** + * @brief Report EV_SYN event to the input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +static inline int input_sync(struct input_dev *dev) +{ + return input_event(dev, EV_SYN, SYN_REPORT, 0); +} + +/** + * @brief Report EV_KEY event to the input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * @param code Key code which defined in input_key_code.h + * @param value Key event (PRESS, RELEASE, LONGPRESS, HOLDPRESS, LONGRELEASE, etc) + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +static inline int input_report_key(struct input_dev *dev, uint16_t code, int32_t value) +{ + return input_event(dev, EV_KEY, code, value); +} + +/** + * @brief Report EV_REL event to the input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * @param code Relative axes (REL_X, REL_Y, REL_Z, etc) + * @param value Relative Position value + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +static inline int input_report_rel(struct input_dev *dev, unsigned int code, int value) +{ + return input_event(dev, EV_REL, code, value); +} + +/** + * @brief Report EV_ABS event to the input internal device instance. + * + * @param dev Pointer to the input internal device structure for the driver instance. + * @param code Absolute axes (ABS_X, ABS_Y, ABS_Z, etc) + * @param value Relative Position value + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +static inline int input_report_abs(struct input_dev *dev, unsigned int code, int value) +{ + return input_event(dev, EV_ABS, code, value); +} + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_INPUT_INPUT_INTERNAL_H_ */ diff --git a/include/zephyr/drivers/input.h b/include/zephyr/drivers/input.h new file mode 100644 index 0000000000000..05a09f397611d --- /dev/null +++ b/include/zephyr/drivers/input.h @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public API for input devices. + * The scope of this API is report which input event was triggered + * and users can resolve input event by event type in their application. + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_INPUT_H_ +#define ZEPHYR_INCLUDE_DRIVERS_INPUT_H_ + +#include +#include +#include +#include + + +#ifdef CONFIG_NEWLIB_LIBC + +#include + +#ifdef __NEWLIB__ +#include +#else /* __NEWLIB__ */ +#include +/* workaround for older Newlib 2.x, as it lacks sys/_timeval.h */ +struct timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; +#endif /* __NEWLIB__ */ + +#else /* CONFIG_NEWLIB_LIBC */ + +#ifdef CONFIG_ARCH_POSIX +#include +#else +#include +#endif + +#endif /* CONFIG_NEWLIB_LIBC */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief INPUT APIs + * @defgroup input_interface Input Subsystem Driver APIs + * @ingroup io_interfaces + * @{ + */ + +/** + * @brief Input attribute types. + * + */ +enum input_attr_type { + /* Input driver hardware chipid, readonly. */ + INPUT_ATTR_HARDWARE_CHIPID, + /* Input driver firmware version, readonly. */ + INPUT_ATTR_FIRMWARE_VERSION, + /* Input device event read timeout */ + INPUT_ATTR_EVENT_READ_TIMEOUT, +}; + +/** + * @brief Input attribute data union. + * + */ +union input_attr_data { + uint32_t chipid; + uint32_t version; + /* Waiting period to read event, or one of the special values K_NO_WAIT and K_FOREVER. */ + k_timeout_t timeout; + /** + * This is required to convert between custom data and attribute data. + * Custom data size must be less than or equal to CONFIG_INPUT_ATTR_DATA_SIZE. + */ + uint32_t userdata[CONFIG_INPUT_ATTR_DATA_SIZE / sizeof(uint32_t)]; +}; + +BUILD_ASSERT(sizeof(union input_attr_data) <= CONFIG_INPUT_ATTR_DATA_SIZE); + +/** + * @brief Representation of a input event. + * + */ +struct input_event { + /* The time of the event, the time is the system uptime */ + struct timeval time; + /* event type (EV_SYN, EV_KEY, EV_ABS, etc) */ + uint16_t type; + /* event code (Keyboard code, ABS code, etc) */ + uint16_t code; + /* event value (Keyboard value, ABS position, etc) */ + int32_t value; +}; + +#define input_event_size sizeof(struct input_event) + +/** + * @brief Event type + * + */ +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 + +/** + * @brief Synchronization events + * + */ +#define SYN_REPORT 0x00 + +/** + * @brief Relative axes + * + */ +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 + +/** + * @brief Absolute axes + * + */ +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 + +/** + * @brief Event Key value + * + */ +enum keyboard_value { + KEY_RELEASE, + KEY_PRESSED, + KEY_LONG_PRESSED, + KEY_HOLD_PRESSED, + KEY_LONG_RELEASE +}; + +/** + * @cond INTERNAL_HIDDEN + * + * Input subsystem driver API definition and system call entry points. + * + * (Internal use only.) + */ +typedef int (*input_setup_t)(const struct device *dev); +typedef int (*input_release_t)(const struct device *dev); +typedef int (*input_attr_get_t)(const struct device *dev, + enum input_attr_type type, union input_attr_data *data); +typedef int (*input_attr_set_t)(const struct device *dev, + enum input_attr_type type, union input_attr_data *data); +typedef int (*input_event_read_t)(const struct device *dev, struct input_event *event); +typedef int (*input_event_write_t)(const struct device *dev, struct input_event *event); + +__subsystem struct input_driver_api { + input_setup_t setup; + input_release_t release; + input_attr_get_t attr_get; + input_attr_set_t attr_set; + input_event_read_t event_read; + input_event_write_t event_write; +}; +/** + * @endcond + */ + +/** + * @brief Setup a input device instance. + * + * @param dev Pointer to the device structure for the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +__syscall int input_setup(const struct device *dev); + +static inline int z_impl_input_setup(const struct device *dev) +{ + const struct input_driver_api *api = + (struct input_driver_api *)dev->api; + + return api->setup(dev); +} + +/** + * @brief Release a input device instance. + * + * @param dev Pointer to the device structure for the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +__syscall int input_release(const struct device *dev); + +static inline int z_impl_input_release(const struct device *dev) +{ + const struct input_driver_api *api = + (struct input_driver_api *)dev->api; + + return api->release(dev); +} + +/** + * @brief Get attribute from the input device instance. + * + * @param dev Pointer to the device structure for the driver instance. + * @param type The type of attribute which get from the driver instance. + * @param data Pointer to the attribute data structure which get from the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +__syscall int input_attr_get(const struct device *dev, + enum input_attr_type type, union input_attr_data *data); + +static inline int z_impl_input_attr_get(const struct device *dev, + enum input_attr_type type, union input_attr_data *data) +{ + const struct input_driver_api *api = + (struct input_driver_api *)dev->api; + + return api->attr_get(dev, type, data); +} + +/** + * @brief Set attribute to the input device instance. + * + * @param dev Pointer to the device structure for the driver instance. + * @param type The type of attribute which set to the driver instance. + * @param data Pointer to the attribute data structure which set to the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +__syscall int input_attr_set(const struct device *dev, + enum input_attr_type type, union input_attr_data *data); + +static inline int z_impl_input_attr_set(const struct device *dev, + enum input_attr_type type, union input_attr_data *data) +{ + const struct input_driver_api *api = + (struct input_driver_api *)dev->api; + + return api->attr_set(dev, type, data); +} + +/** + * @brief Read event from the input device instance. + * + * @param dev Pointer to the device structure for the driver instance. + * @param event Pointer to the event structure which read from the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +__syscall int input_event_read(const struct device *dev, struct input_event *event); + +static inline int z_impl_input_event_read(const struct device *dev, struct input_event *event) +{ + const struct input_driver_api *api = + (struct input_driver_api *)dev->api; + + return api->event_read(dev, event); +} + +/** + * @brief Write event to the input device instance. Used to simulate input events. + * + * @param dev Pointer to the device structure for the driver instance. + * @param event Pointer to the event structure which write to the driver instance. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +__syscall int input_event_write(const struct device *dev, struct input_event *event); + +static inline int z_impl_input_event_write(const struct device *dev, struct input_event *event) +{ + const struct input_driver_api *api = + (struct input_driver_api *)dev->api; + + return api->event_write(dev, event); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#include + +#endif /* ZEPHYR_INCLUDE_DRIVERS_INPUT_H_ */ diff --git a/include/zephyr/dt-bindings/input/input.h b/include/zephyr/dt-bindings/input/input.h new file mode 100644 index 0000000000000..ed1b14e688cdf --- /dev/null +++ b/include/zephyr/dt-bindings/input/input.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_H_ + +/* keyboard code */ + +#define KEY_CODE_RESERVED 0 +#define KEY_CODE_BACKSPACE 8 +#define KEY_CODE_TAB 9 +#define KEY_CODE_ENTER 13 +#define KEY_CODE_CAPSLOCK 20 +#define KEY_CODE_ESC 27 +#define KEY_CODE_SPACE 32 +#define KEY_CODE_PAGEUP 33 +#define KEY_CODE_PAGEDOWN 34 +#define KEY_CODE_END 35 +#define KEY_CODE_HOME 36 +#define KEY_CODE_LEFT 37 +#define KEY_CODE_UP 38 +#define KEY_CODE_RIGHT 39 +#define KEY_CODE_DOWN 40 +#define KEY_CODE_INSERT 45 +#define KEY_CODE_DELETE 46 + +#define KEY_CODE_0 48 +#define KEY_CODE_1 49 +#define KEY_CODE_2 50 +#define KEY_CODE_3 51 +#define KEY_CODE_4 52 +#define KEY_CODE_5 53 +#define KEY_CODE_6 54 +#define KEY_CODE_7 55 +#define KEY_CODE_8 56 +#define KEY_CODE_9 57 + +#define KEY_CODE_A 65 +#define KEY_CODE_B 66 +#define KEY_CODE_C 67 +#define KEY_CODE_D 68 +#define KEY_CODE_E 69 +#define KEY_CODE_F 70 +#define KEY_CODE_G 71 +#define KEY_CODE_H 72 +#define KEY_CODE_I 73 +#define KEY_CODE_J 74 +#define KEY_CODE_K 75 +#define KEY_CODE_L 76 +#define KEY_CODE_M 77 +#define KEY_CODE_N 78 +#define KEY_CODE_O 79 +#define KEY_CODE_P 80 +#define KEY_CODE_Q 81 +#define KEY_CODE_R 82 +#define KEY_CODE_S 83 +#define KEY_CODE_T 84 +#define KEY_CODE_U 85 +#define KEY_CODE_V 86 +#define KEY_CODE_W 87 +#define KEY_CODE_X 88 +#define KEY_CODE_Y 89 +#define KEY_CODE_Z 90 + +#define KEY_CODE_KP0 96 +#define KEY_CODE_KP1 97 +#define KEY_CODE_KP2 98 +#define KEY_CODE_KP3 99 +#define KEY_CODE_KP4 100 +#define KEY_CODE_KP5 101 +#define KEY_CODE_KP6 102 +#define KEY_CODE_KP7 103 +#define KEY_CODE_KP8 104 +#define KEY_CODE_KP9 105 + +#define KEY_CODE_F1 112 +#define KEY_CODE_F2 113 +#define KEY_CODE_F3 114 +#define KEY_CODE_F4 115 +#define KEY_CODE_F5 116 +#define KEY_CODE_F6 117 +#define KEY_CODE_F7 118 +#define KEY_CODE_F8 119 +#define KEY_CODE_F9 120 +#define KEY_CODE_F10 121 +#define KEY_CODE_F11 122 +#define KEY_CODE_F12 123 + +#define KEY_CODE_TOUCH 0x2f0 + +#endif /*ZEPHYR_INCLUDE_DT_BINDINGS_INPUT_INPUT_H_*/ From 9469b99167ebd75734c093e03df7873198dfdc55 Mon Sep 17 00:00:00 2001 From: Michael Owen Date: Sun, 9 Oct 2022 11:11:30 +0800 Subject: [PATCH 2/3] drivers: input: keyboard: add gpio keyboard driver. This driver scan gpio keyboard status, and report key event to input subsystem. Signed-off-by: Michael Owen --- drivers/input/CMakeLists.txt | 2 + drivers/input/Kconfig | 2 + drivers/input/keyboard/CMakeLists.txt | 4 + drivers/input/keyboard/Kconfig | 20 ++ drivers/input/keyboard/Kconfig.gpio | 29 ++ drivers/input/keyboard/keyboard_gpio.c | 421 +++++++++++++++++++++++++ dts/bindings/input/keyboard-gpio.yaml | 34 ++ 7 files changed, 512 insertions(+) create mode 100644 drivers/input/keyboard/CMakeLists.txt create mode 100644 drivers/input/keyboard/Kconfig create mode 100644 drivers/input/keyboard/Kconfig.gpio create mode 100644 drivers/input/keyboard/keyboard_gpio.c create mode 100644 dts/bindings/input/keyboard-gpio.yaml diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt index 36c4378bcd57d..b4c90a0eda439 100644 --- a/drivers/input/CMakeLists.txt +++ b/drivers/input/CMakeLists.txt @@ -5,3 +5,5 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_INPUT input_internal.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE input_handlers.c) + +add_subdirectory_ifdef(CONFIG_KEYBOARD keyboard) diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 7aa734d6492e5..c2315e9363782 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -17,6 +17,8 @@ config ENABLE_INPUT_ISR_LOCK Use k_spin_lock() and k_spin_unlock() to control ring buffer exclusive access. This is required to be able to report input event in isr context. +source "drivers/input/keyboard/Kconfig" + module = INPUT module-str = input source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/input/keyboard/CMakeLists.txt b/drivers/input/keyboard/CMakeLists.txt new file mode 100644 index 0000000000000..b158b34c9d700 --- /dev/null +++ b/drivers/input/keyboard/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (c) 2022, tangchunhui@coros.com +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_sources_ifdef(CONFIG_KEYBOARD_GPIO keyboard_gpio.c) diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig new file mode 100644 index 0000000000000..ebc27de16967c --- /dev/null +++ b/drivers/input/keyboard/Kconfig @@ -0,0 +1,20 @@ +# Input keyboard configuration options + +# Copyright (c) 2022 tangchunhui2022@gmail.com +# SPDX-License-Identifier: Apache-2.0 + +menuconfig KEYBOARD + bool "Keyboard Drivers" + depends on INPUT + help + Include keyboard drivers in system config. + +if KEYBOARD + +config KEYBOARD_EVENT_MAX_NUMBERS + int "Keyboard event max numbers" + default 20 + +source "drivers/input/keyboard/Kconfig.gpio" + +endif # KEYBOARD diff --git a/drivers/input/keyboard/Kconfig.gpio b/drivers/input/keyboard/Kconfig.gpio new file mode 100644 index 0000000000000..00a02f952b59c --- /dev/null +++ b/drivers/input/keyboard/Kconfig.gpio @@ -0,0 +1,29 @@ +# Input keyboard configuration options + +# Copyright (c) 2022, tangchunhui@coros.com +# SPDX-License-Identifier: Apache-2.0 + +menuconfig KEYBOARD_GPIO + bool "Keyboard GPIO Drivers" + help + Include keyboard gpio drivers in system config. + +if KEYBOARD_GPIO + +config KEYBOARD_GPIO_SCAN_INTERVAL + int "scan interval" + default 10 + +config KEYBOARD_GPIO_TIME_DEBOUNCE + int "debounce time" + default 30 + +config KEYBOARD_GPIO_TIME_LONGPRESS + int "long press time" + default 0 + +config KEYBOARD_GPIO_TIME_HOLDPRESS + int "hold press time" + default 0 + +endif # KEYBOARD_GPIO diff --git a/drivers/input/keyboard/keyboard_gpio.c b/drivers/input/keyboard/keyboard_gpio.c new file mode 100644 index 0000000000000..0c0ea7736ad06 --- /dev/null +++ b/drivers/input/keyboard/keyboard_gpio.c @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT keyboard_gpio + +/** + * @file + * @brief Keyboard-gpio driver + */ + +#include + +#include +#include +#include +#include +#include + +#include "../input_internal.h" + +#include +LOG_MODULE_REGISTER(keyboard_gpio); + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +#define SCAN_INTERVAL CONFIG_KEYBOARD_GPIO_SCAN_INTERVAL +#define TIME_DEBOUNCE CONFIG_KEYBOARD_GPIO_TIME_DEBOUNCE +#define TIME_LONGPRESS CONFIG_KEYBOARD_GPIO_TIME_LONGPRESS +#define TIME_HOLDPRESS CONFIG_KEYBOARD_GPIO_TIME_HOLDPRESS + +/** + * @brief Key information structure + * + * This structure gathers useful information about keyboard controller. + * + * @param label Key label. + * @param code Key code. + * @param debounce_ms Key debounce time + * @param longpress_ms Key long press time + * @param holdpress_ms Key hold press time + */ +struct key_info_dt_spec { + const char *label; + uint16_t code; + uint16_t debounce_ms; + uint16_t longpress_ms; + uint16_t holdpress_ms; +}; + +/** + * @brief Get KEY INFO DT SPEC + * + * @param node_id devicetree node identifier + */ +#define KEY_INFO_DT_SPEC_GET(node_id) \ +{ \ + .label = DT_PROP(node_id, label), \ + .code = DT_PROP(node_id, code), \ + .debounce_ms = DT_PROP_OR(node_id, debounce_ms, 0), \ + .longpress_ms = DT_PROP_OR(node_id, longpress_ms, 0), \ + .holdpress_ms = DT_PROP_OR(node_id, holdpress_ms, 0), \ +} + +enum key_state_t { + KEY_STATE_NONE, + KEY_STATE_PRESSED, + KEY_STATE_LONG_PRESSED, + KEY_STAT_MAXNBR +}; + +enum key_event_t { + KEY_EVENT_RELEASE = KEY_RELEASE, + KEY_EVENT_PRESSED = KEY_PRESSED, + KEY_EVENT_LONG_PRESSED = KEY_LONG_PRESSED, + KEY_EVENT_HOLD_PRESSED = KEY_HOLD_PRESSED, + KEY_EVENT_LONG_RELEASE = KEY_LONG_RELEASE, + KEY_EVENT_NONE = 0xff, +}; + +struct kbd_gpio_driver { + struct gpio_callback gpio_data; + + void *pdata; + bool pressed; + enum key_state_t state; + enum key_event_t event; + + uint32_t count_debounce; + uint32_t count_cycle; +}; + +struct kbd_gpio_data { + const struct device *dev; + struct input_dev *input; + struct k_work_delayable delayed_work; + struct kbd_gpio_driver *driver; +}; + +struct kbd_gpio_config { + uint8_t num_keys; + const struct gpio_dt_spec *gpio; + const struct key_info_dt_spec *info; +}; + +#define MS_TO_CYCLE(ms, default) \ + (((ms) ? (ms) : (default)) / SCAN_INTERVAL) +#define KEY_EVENT_CODE(code, default) \ + ((code != KEY_CODE_RESERVED) ? code : default) +#define KEY_EVENT_CALL(input, code, event) \ +{ \ + input_report_key(input, code, event); \ +} + +static bool kbd_gpio_one_key_proc(const struct device *dev, int idx) +{ + const struct kbd_gpio_config *config = dev->config; + struct kbd_gpio_data *data = dev->data; + struct input_dev *input = data->input; + + const struct gpio_dt_spec *gpio = &config->gpio[idx]; + const struct key_info_dt_spec *info = &config->info[idx]; + struct kbd_gpio_driver *driver = &data->driver[idx]; + + bool pressed = gpio_pin_get(gpio->port, gpio->pin); + + if (driver->state != KEY_STATE_NONE) { + driver->count_cycle++; + } + + if (driver->pressed != pressed) { + if (driver->count_debounce++ >= \ + MS_TO_CYCLE(info->debounce_ms, TIME_DEBOUNCE)) { + driver->pressed = pressed; + driver->count_debounce = 0; + } + } else { + driver->count_debounce = 0; + } + + switch (driver->state) { + case KEY_STATE_NONE: + if (driver->pressed == true) { + driver->event = KEY_EVENT_PRESSED; + driver->state = KEY_STATE_PRESSED; + driver->count_cycle = 0; + KEY_EVENT_CALL(input, \ + KEY_EVENT_CODE(info->code, (idx + 1)), driver->event); + } else { + driver->event = KEY_EVENT_NONE; + } + break; + + case KEY_STATE_PRESSED: + if (driver->pressed == false) { + driver->event = KEY_EVENT_RELEASE; + driver->state = KEY_STATE_NONE; + KEY_EVENT_CALL(input, \ + KEY_EVENT_CODE(info->code, (idx + 1)), driver->event); + } else if (MS_TO_CYCLE(info->longpress_ms, TIME_LONGPRESS) > 0 && + driver->count_cycle >= \ + MS_TO_CYCLE(info->longpress_ms, TIME_LONGPRESS)) { + driver->event = KEY_EVENT_LONG_PRESSED; + driver->state = KEY_STATE_LONG_PRESSED; + driver->count_cycle = 0; + KEY_EVENT_CALL(input, \ + KEY_EVENT_CODE(info->code, (idx + 1)), driver->event); + } + break; + + case KEY_STATE_LONG_PRESSED: + if (driver->pressed == true) { + if (MS_TO_CYCLE(info->holdpress_ms, TIME_HOLDPRESS) > 0 && + driver->count_cycle >= \ + MS_TO_CYCLE(info->holdpress_ms, TIME_HOLDPRESS)) { + driver->event = KEY_EVENT_HOLD_PRESSED; + driver->count_cycle = 0; + KEY_EVENT_CALL(input, + KEY_EVENT_CODE(info->code, (idx + 1)), driver->event); + } + } else { + driver->event = KEY_EVENT_LONG_RELEASE; + driver->state = KEY_STATE_NONE; + KEY_EVENT_CALL(input, + KEY_EVENT_CODE(info->code, (idx + 1)), driver->event); + } + break; + + default: + LOG_ERR("Unknown key state[%d]", driver->state); + break; + } + + /* return key is pressed. */ + return ((pressed || (driver->state != KEY_STATE_NONE)) ? true : false); +} + +static void gpio_isr_handler(const struct device *dev, + struct gpio_callback *cb, uint32_t pins) +{ + struct kbd_gpio_driver *driver = \ + CONTAINER_OF(cb, struct kbd_gpio_driver, gpio_data); + struct kbd_gpio_data *data = driver->pdata; + int work_busy = k_work_delayable_busy_get(&data->delayed_work); + + if (!(work_busy & (K_WORK_DELAYED | K_WORK_QUEUED))) { + k_work_schedule(&data->delayed_work, K_MSEC(SCAN_INTERVAL)); + } +} + +static void delayed_work_handler(struct k_work *work) +{ + struct k_work_delayable *delayed_work = k_work_delayable_from_work(work); + struct kbd_gpio_data *data = \ + CONTAINER_OF(delayed_work, struct kbd_gpio_data, delayed_work); + const struct kbd_gpio_config *config = data->dev->config; + int key_count = 0; + + for (int i = 0; i < config->num_keys; i++) { + if (kbd_gpio_one_key_proc(data->dev, i)) { + key_count++; + } + } + + if (key_count > 0) { + k_work_schedule(&data->delayed_work, K_MSEC(SCAN_INTERVAL)); + } +} + +static int kbd_gpio_setup(const struct device *dev) +{ + const struct kbd_gpio_config *config = dev->config; + struct kbd_gpio_data *data = dev->data; + struct input_dev *input = data->input; + + input_internal_setup(input); + + for (int i = 0; i < config->num_keys; i++) { + const struct gpio_dt_spec *gpio = &config->gpio[i]; + struct kbd_gpio_driver *driver = &data->driver[i]; + + gpio_add_callback(gpio->port, &driver->gpio_data); + } + + /* Maybe key pressed before driver setup. */ + k_work_schedule(&data->delayed_work, K_NO_WAIT); + + return 0; +} + +static int kbd_gpio_release(const struct device *dev) +{ + const struct kbd_gpio_config *config = dev->config; + struct kbd_gpio_data *data = dev->data; + struct input_dev *input = data->input; + + for (int i = 0; i < config->num_keys; i++) { + const struct gpio_dt_spec *gpio = &config->gpio[i]; + struct kbd_gpio_driver *driver = &data->driver[i]; + + gpio_remove_callback(gpio->port, &driver->gpio_data); + } + + k_work_cancel_delayable(&data->delayed_work); + + input_internal_release(input); + + return 0; +} + +static int kbd_gpio_attr_get(const struct device *dev, + enum input_attr_type type, union input_attr_data *attr) +{ + struct kbd_gpio_data *data = dev->data; + struct input_dev *input = data->input; + int retval = 0; + + retval = input_internal_attr_get(input, type, attr); + + return retval; +} + +static int kbd_gpio_attr_set(const struct device *dev, + enum input_attr_type type, union input_attr_data *attr) +{ + struct kbd_gpio_data *data = dev->data; + struct input_dev *input = data->input; + int retval = 0; + + retval = input_internal_attr_set(input, type, attr); + + return retval; +} + +static int kbd_gpio_event_read(const struct device *dev, struct input_event *event) +{ + struct kbd_gpio_data *data = dev->data; + struct input_dev *input = data->input; + int retval = 0; + + retval = input_internal_event_read(input, event); + + return retval; +} + +static int kbd_gpio_event_write(const struct device *dev, struct input_event *event) +{ + struct kbd_gpio_data *data = dev->data; + struct input_dev *input = data->input; + int retval = 0; + + retval = input_internal_event_write(input, event); + + return retval; +} + +static int kbd_gpio_init(const struct device *dev) +{ + const struct kbd_gpio_config *config = dev->config; + struct kbd_gpio_data *data = dev->data; + int err = 0; + + if (config->num_keys <= 0) { + LOG_ERR("%s: no KEYs found (DT child nodes missing)", dev->name); + return -ENODEV; + } + + data->dev = dev; + + k_work_init_delayable(&data->delayed_work, delayed_work_handler); + + memset(data->driver, 0x00, config->num_keys * sizeof(struct kbd_gpio_driver)); + + LOG_DBG("gpio key map:"); + + for (int i = 0; i < config->num_keys; i++) { + const struct gpio_dt_spec *gpio = &config->gpio[i]; + const struct key_info_dt_spec *info = &config->info[i]; + struct kbd_gpio_driver *driver = &data->driver[i]; + + driver->pdata = data; + + if (!device_is_ready(gpio->port)) { + LOG_WRN("gpio port[%s] is not ready", gpio->port->name); + continue; + } + + err = gpio_pin_configure_dt(gpio, GPIO_INPUT); + if (err != 0) { + LOG_WRN("configure extra_flags on gpio[%s %d] fail[%d]", + gpio->port->name, gpio->pin, err); + continue; + } + + err = gpio_pin_interrupt_configure_dt(gpio, GPIO_INT_EDGE_TO_ACTIVE); + if (err != 0) { + LOG_WRN("Configure interrupt on gpio[%s %d] fail[%d]", + gpio->port->name, gpio->pin, err); + continue; + } + + gpio_init_callback(&driver->gpio_data, gpio_isr_handler, BIT(gpio->pin)); + + LOG_DBG("KEY%d: label[%s] gpio[%p %d 0x%04x] code[0x%04x] interval[%d %d %d %d]", + i, info->label, gpio->port, gpio->pin, gpio->dt_flags, info->code, + SCAN_INTERVAL, + (MS_TO_CYCLE(info->debounce_ms, TIME_DEBOUNCE) * SCAN_INTERVAL), + (MS_TO_CYCLE(info->longpress_ms, TIME_LONGPRESS) * SCAN_INTERVAL), + (MS_TO_CYCLE(info->holdpress_ms, TIME_HOLDPRESS) * SCAN_INTERVAL)); + } + + return err; +} + +static const struct input_driver_api kbd_gpio_api = { + .setup = kbd_gpio_setup, + .release = kbd_gpio_release, + .attr_get = kbd_gpio_attr_get, + .attr_set = kbd_gpio_attr_set, + .event_read = kbd_gpio_event_read, + .event_write = kbd_gpio_event_write, +}; + +#define KEY_GPIO_DT_SPEC(key_node_id) GPIO_DT_SPEC_GET(key_node_id, gpios), + +#define KEY_INFO_DT_SPEC(key_node_id) KEY_INFO_DT_SPEC_GET(key_node_id), + +#define KBD_GPIO_DEVICE(i) \ + static const struct gpio_dt_spec gpio_dt_spec_##i[] = { \ + DT_INST_FOREACH_CHILD(i, KEY_GPIO_DT_SPEC)}; \ + static const struct key_info_dt_spec info_dt_sepc_##i[] = { \ + DT_INST_FOREACH_CHILD(i, KEY_INFO_DT_SPEC)}; \ + RING_BUF_DECLARE(input_buf_##i, \ + CONFIG_KEYBOARD_EVENT_MAX_NUMBERS * input_event_size); \ + static struct input_dev kbd_gpio_input_##i = { \ + .buf = &input_buf_##i, \ + }; \ + static struct kbd_gpio_driver \ + kbd_gpio_driver_##i[ARRAY_SIZE(gpio_dt_spec_##i)]; \ + static struct kbd_gpio_data kbd_gpio_data_##i = { \ + .input = &kbd_gpio_input_##i, \ + .driver = kbd_gpio_driver_##i, \ + }; \ + static const struct kbd_gpio_config kbd_gpio_config_##i = { \ + .num_keys = ARRAY_SIZE(gpio_dt_spec_##i), \ + .gpio = gpio_dt_spec_##i, \ + .info = info_dt_sepc_##i, \ + }; \ + DEVICE_DT_INST_DEFINE(i, \ + kbd_gpio_init, NULL, \ + &kbd_gpio_data_##i, \ + &kbd_gpio_config_##i, \ + POST_KERNEL, \ + CONFIG_INPUT_INIT_PRIORITY, \ + &kbd_gpio_api); + +DT_INST_FOREACH_STATUS_OKAY(KBD_GPIO_DEVICE) diff --git a/dts/bindings/input/keyboard-gpio.yaml b/dts/bindings/input/keyboard-gpio.yaml new file mode 100644 index 0000000000000..a68dae2bd1d62 --- /dev/null +++ b/dts/bindings/input/keyboard-gpio.yaml @@ -0,0 +1,34 @@ +# Copyright (c) 2022, tangchunhui@coros.com +# SPDX-License-Identifier: Apache-2.0 + +description: Keyboard GPIO parent node + +compatible: "keyboard-gpio" + +include: base.yaml + +child-binding: + description: Keyboard GPIO child node + properties: + gpios: + type: phandle-array + required: true + label: + type: string + description: Descriptive name of the key + code: + type: int + required: true + description: User-defined key code + debounce-ms: + type: int + required: false + description: Key debounce delay time in milliseconds + longpress-ms: + type: int + required: false + description: Key long press delay time in milliseconds + holdpress-ms: + type: int + required: false + description: Key hold press delay time in milliseconds From 32ac38203a831f1d5452ecf29c084137c8ba8004 Mon Sep 17 00:00:00 2001 From: Michael Owen Date: Sun, 9 Oct 2022 11:13:09 +0800 Subject: [PATCH 3/3] samples: keyboard: add gpio keyboard sample. This sample use input api to get gpio keyboard event. Signed-off-by: Michael Owen --- samples/drivers/keyboard_gpio/CMakeLists.txt | 8 ++ samples/drivers/keyboard_gpio/README.rst | 26 ++++++ .../boards/mimxrt595_evk_cm33.overlay | 26 ++++++ .../boards/nucleo_h743zi.overlay | 19 ++++ samples/drivers/keyboard_gpio/prj.conf | 8 ++ samples/drivers/keyboard_gpio/sample.yaml | 11 +++ samples/drivers/keyboard_gpio/src/main.c | 91 +++++++++++++++++++ 7 files changed, 189 insertions(+) create mode 100644 samples/drivers/keyboard_gpio/CMakeLists.txt create mode 100644 samples/drivers/keyboard_gpio/README.rst create mode 100644 samples/drivers/keyboard_gpio/boards/mimxrt595_evk_cm33.overlay create mode 100644 samples/drivers/keyboard_gpio/boards/nucleo_h743zi.overlay create mode 100644 samples/drivers/keyboard_gpio/prj.conf create mode 100644 samples/drivers/keyboard_gpio/sample.yaml create mode 100644 samples/drivers/keyboard_gpio/src/main.c diff --git a/samples/drivers/keyboard_gpio/CMakeLists.txt b/samples/drivers/keyboard_gpio/CMakeLists.txt new file mode 100644 index 0000000000000..1c5812ccffb1d --- /dev/null +++ b/samples/drivers/keyboard_gpio/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(kscan) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/drivers/keyboard_gpio/README.rst b/samples/drivers/keyboard_gpio/README.rst new file mode 100644 index 0000000000000..ba0054b76329c --- /dev/null +++ b/samples/drivers/keyboard_gpio/README.rst @@ -0,0 +1,26 @@ +.. _keyboard-gpio-sample: + +Keyboard GPIO Interface +#################################### + +Overview +******** + +This sample demonstrates how to use the :ref:`INPUT API `. +These events indicate key presses and releases. + +Building and Running +******************** + +The sample can be built and executed on boards supporting a Keyboard GPIO. +It requires a correct fixture setup. Please connect a Keyboard GPIO to +exercise the functionality. + +Sample output +============= + +.. code-block:: console + + INPUT test with a Keyboard GPIO + Note: You are expected to see several callbacks + as you press and release keys! diff --git a/samples/drivers/keyboard_gpio/boards/mimxrt595_evk_cm33.overlay b/samples/drivers/keyboard_gpio/boards/mimxrt595_evk_cm33.overlay new file mode 100644 index 0000000000000..ad35af3b255ce --- /dev/null +++ b/samples/drivers/keyboard_gpio/boards/mimxrt595_evk_cm33.overlay @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + model = "NXP MIMXRT595-EVK board"; + compatible = "nxp,mimxrt595"; + + keyboard_gpio { + compatible = "keyboard-gpio"; + KEY_OK { + label = "KEY_OK"; + gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; + code = ; + }; + KEY_BACK { + label = "KEY_BACK"; + gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; + code = ; + }; + }; +}; diff --git a/samples/drivers/keyboard_gpio/boards/nucleo_h743zi.overlay b/samples/drivers/keyboard_gpio/boards/nucleo_h743zi.overlay new file mode 100644 index 0000000000000..77458ac03dac6 --- /dev/null +++ b/samples/drivers/keyboard_gpio/boards/nucleo_h743zi.overlay @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include + +/ { + model = "STMicroelectronics STM32H743ZI-NUCLEO board"; + compatible = "st,stm32h743zi-nucleo"; + + keyboard_gpio { + compatible = "keyboard-gpio"; + KEY_ENTER { + label = "KEY_ENTER"; + gpios = <&gpioc 13 GPIO_ACTIVE_HIGH>; + code = ; + longpress-ms = <1000>; + holdpress-ms = <500>; + }; + }; +}; diff --git a/samples/drivers/keyboard_gpio/prj.conf b/samples/drivers/keyboard_gpio/prj.conf new file mode 100644 index 0000000000000..42d677267e65c --- /dev/null +++ b/samples/drivers/keyboard_gpio/prj.conf @@ -0,0 +1,8 @@ +CONFIG_STDOUT_CONSOLE=y +CONFIG_PRINTK=y +CONFIG_INPUT=y +CONFIG_ENABLE_INPUT_ISR_LOCK=y +CONFIG_KEYBOARD=y +CONFIG_KEYBOARD_GPIO=y +CONFIG_LED=y +CONFIG_LED_GPIO=y diff --git a/samples/drivers/keyboard_gpio/sample.yaml b/samples/drivers/keyboard_gpio/sample.yaml new file mode 100644 index 0000000000000..c242aec01b055 --- /dev/null +++ b/samples/drivers/keyboard_gpio/sample.yaml @@ -0,0 +1,11 @@ +sample: + name: keyboard gpio driver sample +tests: + sample.drivers.input.keyboard_gpio: + platform_allow: nucleo_h743zi + tags: drivers + harness: console + harness_config: + type: multi_line + ordered: true + depends_on: KEYBOARD_GPIO diff --git a/samples/drivers/keyboard_gpio/src/main.c b/samples/drivers/keyboard_gpio/src/main.c new file mode 100644 index 0000000000000..7806738e8a1bd --- /dev/null +++ b/samples/drivers/keyboard_gpio/src/main.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022, tangchunhui@coros.com + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct device *kbd; +static struct k_timer esc_timer; + +static const char * const key_value_string[] = { + "KEY_RELEASE", + "KEY_PRESSED", + "KEY_LONG_PRESSED", + "KEY_HOLD_PRESSED", + "KEY_LONG_RELEASE" +}; + +static void esc_timer_callback(struct k_timer *timer) +{ + struct input_event event; + + printk("%s arrived.\n", __func__); + + event.type = EV_KEY; + event.code = KEY_CODE_BACKSPACE; + + event.value = KEY_PRESSED; + input_event_write(kbd, &event); + + event.value = KEY_RELEASE; + input_event_write(kbd, &event); +} + +void main(void) +{ + union input_attr_data data; + struct input_event event; + + kbd = DEVICE_DT_GET_ONE(keyboard_gpio); + + if (!device_is_ready(kbd)) { + printk("kbd device %s is not ready\n", kbd->name); + return; + } + + k_timer_init(&esc_timer, esc_timer_callback, NULL); + + input_setup(kbd); + input_attr_get(kbd, INPUT_ATTR_EVENT_READ_TIMEOUT, &data); + printk("get read timeout %lld\n", data.timeout.ticks); + + while (1) { + k_timeout_t timeouts[] = {K_NO_WAIT, K_MSEC(1000), K_FOREVER}; + static int step; + + data.timeout = timeouts[step]; + step = ((step + 1) % ARRAY_SIZE(timeouts)); + + input_attr_set(kbd, INPUT_ATTR_EVENT_READ_TIMEOUT, &data); + printk("set read timeout %lld\n", data.timeout.ticks); + + input_attr_get(kbd, INPUT_ATTR_EVENT_READ_TIMEOUT, &data); + printk("get read timeout %lld\n", data.timeout.ticks); + + while (1) { + if (input_event_read(kbd, &event) == 0) { + if (event.type != EV_KEY) { + continue; + } + + printk("event [EV_KEY 0x%02x %s]\n", \ + event.code, key_value_string[event.value]); + + if (event.code != KEY_CODE_BACKSPACE) { + k_timer_start(&esc_timer, K_SECONDS(10), K_SECONDS(0)); + } else if (event.value == KEY_RELEASE) { + break; + } + } + } + } +}