diff --git a/Documentation/components/drivers/character/input/images/keypad-example.png b/Documentation/components/drivers/character/input/images/keypad-example.png new file mode 100644 index 00000000000..9fb4ea439c4 --- /dev/null +++ b/Documentation/components/drivers/character/input/images/keypad-example.png @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Documentation/components/drivers/character/input/index.rst b/Documentation/components/drivers/character/input/index.rst index c1317ebd493..3ef4746c003 100644 --- a/Documentation/components/drivers/character/input/index.rst +++ b/Documentation/components/drivers/character/input/index.rst @@ -5,6 +5,7 @@ Input Devices .. toctree:: :caption: Supported Drivers + keypad-keyboard.rst keypad.rst sbutton.rst diff --git a/Documentation/components/drivers/character/input/keypad-keyboard.rst b/Documentation/components/drivers/character/input/keypad-keyboard.rst new file mode 100644 index 00000000000..5e726b887dd --- /dev/null +++ b/Documentation/components/drivers/character/input/keypad-keyboard.rst @@ -0,0 +1,143 @@ +======================= +Keyboard/Keypad Drivers +======================= + + +**Keypads vs. Keyboards** Keyboards and keypads are really the same +devices for NuttX. A keypad is thought of as simply a keyboard with +fewer keys. + +**Special Commands**. In NuttX, a keyboard/keypad driver is simply +a character driver that may have an (optional) encoding/decoding +layer on the data returned by the character driver. A keyboard may +return simple text data (alphabetic, numeric, and punctuation) or +control characters (enter, control-C, etc.) when a key is pressed. +We can think about this the "normal" keyboard data stream. +However, in addition, most keyboards support actions that cannot +be represented as text or control data. Such actions include +things like cursor controls (home, up arrow, page down, etc.), +editing functions (insert, delete, etc.), volume controls, (mute, +volume up, etc.) and other special functions. In this case, some +special encoding may be required to multiplex the normal text data +and special command key press data streams. + +**Key Press and Release Events** Sometimes the time that a key is +released is needed by applications as well. Thus, in addition to +normal and special key press events, it may also be necessary to +encode normal and special key release events. + +**Encoding/Decoding** Layer. An optional encoding/decoding layer +can be used with the basic character driver to encode the keyboard +events into the text data stream. The function interfaces that +comprise that encoding/decoding layer are defined in the header +file ``include/nuttx/input/kbd_code.h``. These functions provide +a matched set of (a) driver encoding interfaces, and (b) +application decoding interfaces. + +#. **Driver Encoding Interfaces**. These are interfaces used by + the keyboard/keypad driver to encode keyboard events and data. + + - ``kbd_press()`` + + **Function Prototype:** + + **Description:** + + **Input Parameters:** + + - ``ch``: The character to be added to the output stream. + - ``stream``: An instance of ``lib_outstream_s`` to perform + the actual low-level put operation. + + **Returned Value:** + + - ``kbd_release()`` + + **Function Prototype:** + + **Description:** + + **Input Parameters:** + + - ``ch``: The character associated with the key that was + released. + - ``stream``: An instance of ``lib_outstream_s`` to perform + the actual low-level put operation. + + **Returned Value:** + + - ``kbd_specpress()`` + + **Function Prototype:** + + **Description:** + + **Input Parameters:** + + - ``keycode``: The command to be added to the output + stream. The enumeration ``enum kbd_keycode_e keycode`` + identifies all commands known to the system. + - ``stream``: An instance of ``lib_outstream_s`` to perform + the actual low-level put operation. + + **Returned Value:** + + - ``kbd_specrel()`` + + **Function Prototype:** + + **Description:** + + **Input Parameters:** + + - ``keycode``: The command to be added to the output + stream. The enumeration ``enum kbd_keycode_e keycode`` + identifies all commands known to the system. + - ``stream``: An instance of ``lib_outstream_s`` to perform + the actual low-level put operation. + + **Returned Value:** + +#. **Application Decoding Interfaces**. These are user interfaces + to decode the values returned by the keyboard/keypad driver. + + - ``kbd_decode()`` + + **Function Prototype:** + + **Description:** + + **Input Parameters:** + + - ``stream``: An instance of ``lib_instream_s`` to perform + the actual low-level get operation. + - ``pch``: The location to save the returned value. This + may be either a normal, character code or a special + command (i.e., a value from ``enum kbd_getstate_s``. + - ``state``: A user provided buffer to support parsing. + This structure should be cleared the first time that + ``kbd_decode()`` is called. + + **Returned Value:** + + - ``KBD_PRESS`` (0)**: Indicates the successful receipt + of normal, keyboard data. This corresponds to a keypress + event. The returned value in ``pch`` is a simple byte of + text or control data. + - ``KBD_RELEASE`` (1)**: Indicates a key release event. + The returned value in ``pch`` is the byte of text or + control data corresponding to the released key. + - ``KBD_SPECPRESS`` (2)**: Indicates the successful + receipt of a special keyboard command. The returned value + in ``pch`` is a value from ``enum kbd_getstate_s``. + - ``KBD_SPECREL`` (3)**: Indicates a special command key + release event. The returned value in ``pch`` is a value + from ``enum kbd_getstate_s``. + - ``KBD_ERROR`` (``EOF``)**: An error has getting the + next character (reported by the ``stream``). Normally + indicates the end of file. + +**I/O Streams**. Notice the use of the abstract I/O streams in +these interfaces. These stream interfaces are defined in +``include/nuttx/streams.h``. + diff --git a/Documentation/components/drivers/character/input/keypad.rst b/Documentation/components/drivers/character/input/keypad.rst index 3724b8daa59..93d357bc7ac 100644 --- a/Documentation/components/drivers/character/input/keypad.rst +++ b/Documentation/components/drivers/character/input/keypad.rst @@ -1,142 +1,83 @@ ======================= -Keyboard/Keypad Drivers +Matrix Keypad (KMATRIX) ======================= -**Keypads vs. Keyboards** Keyboards and keypads are really the -same devices for NuttX. A keypad is thought of as simply a -keyboard with fewer keys. +**What is a Keypad?** +A keypad is a small keyboard with a limited set of keys, typically +arranged in a matrix. It is commonly used for numeric input, access +control, or simple user interfaces. -**Special Commands**. In NuttX, a keyboard/keypad driver is simply -a character driver that may have an (optional) encoding/decoding -layer on the data returned by the character driver. A keyboard may -return simple text data (alphabetic, numeric, and punctuation) or -control characters (enter, control-C, etc.) when a key is pressed. -We can think about this the "normal" keyboard data stream. -However, in addition, most keyboards support actions that cannot -be represented as text or control data. Such actions include -things like cursor controls (home, up arrow, page down, etc.), -editing functions (insert, delete, etc.), volume controls, (mute, -volume up, etc.) and other special functions. In this case, some -special encoding may be required to multiplex the normal text data -and special command key press data streams. +For example, a typical 12-key numeric keypad looks like this: -**Key Press and Release Events** Sometimes the time that a key is -released is needed by applications as well. Thus, in addition to -normal and special key press events, it may also be necessary to -encode normal and special key release events. +.. image:: images/keypad-example.png + :alt: Example of a 12-key matrix keypad + :align: center + :width: 200px -**Encoding/Decoding** Layer. An optional encoding/decoding layer -can be used with the basic character driver to encode the keyboard -events into the text data stream. The function interfaces that -comprise that encoding/decoding layer are defined in the header -file ``include/nuttx/input/kbd_code.h``. These functions provide -an matched set of (a) driver encoding interfaces, and (b) -application decoding interfaces. +**Purpose**. The KMATRIX driver provides a generic keypad +implementation for boards that expose a switch matrix through GPIOs. +It periodically scans rows and columns, detects state changes with a +simple debounce, and emits keyboard events through the common keyboard +upper-half. This makes the device available as a character driver +(e.g., ``/dev/keypad0``) using the standard keyboard +interfaces. -#. **Driver Encoding Interfaces**. These are interfaces used by - the keyboard/keypad driver to encode keyboard events and data. +**Why Polling**. This first version uses polling to be broadly usable +on any board with available GPIOs, without requiring per-board IRQ +wiring, pin interrupt capabilities, or expander-specific interrupt +support. Polling also simplifies early bring-up and makes the driver +predictable while the keymap and GPIO configuration are validated. +Future iterations are expected to add interrupt-driven scanning and +I2C expander variants; the GPIO polling path remains a good baseline +and fallback. - - ``kbd_press()`` +**Driver Overview**. The KMATRIX lower-half scans the matrix and calls +``keyboard_event()`` when it detects a press or release. The keyboard +upper-half registers the character device at the requested ``devpath`` +and stores events in a circular buffer. Applications read +``struct keyboard_event_s`` from the device or use the optional +kbd-codec layer. - **Function Prototype:** +**Board Support**. To support KMATRIX, a board must provide: - **Description:** +#. **GPIO Definitions** - **Input Parameters:** + - Define the row and column GPIOs (arrays of pins). + - Provide a keymap array indexed by ``row * ncols + col``. - - ``ch``: The character to be added to the output stream. - - ``stream``: An instance of ``lib_outstream_s`` to perform - the actual low-level put operation. +#. **Configuration Callbacks** - **Returned Value:** + - ``config_row(pin)``: Configure a row GPIO as output. + - ``config_col(pin)``: Configure a column GPIO as input with pull-up + or pull-down consistent with the wiring. + - ``row_set(pin, active)``: Drive a row active/inactive. For the + STM32F4Discovery example, rows are driven low to activate. + - ``col_get(pin)``: Read a column and return ``true`` when pressed. - - ``kbd_release()`` +#. **Registration Hook** - **Function Prototype:** + - Implement ``board_kmatrix_initialize(const char *devpath)`` to + call ``kmatrix_register(&config, devpath)``. + - Invoke the board hook during bring-up (for example, + ``board_kmatrix_initialize("/dev/keypad0")``). - **Description:** +**Reference Implementation (STM32F4Discovery)**. The current reference +is in ``boards/arm/stm32/common/src/stm32_kmatrix_gpio.c``: - **Input Parameters:** +- Rows: ``BOARD_KMATRIX_ROW0..3`` (outputs) +- Columns: ``BOARD_KMATRIX_COL0..2`` (inputs with pull-up) +- Keymap: 4x3 phone keypad layout +- Callbacks: ``km_stm32_config_row``, ``km_stm32_config_col``, + ``km_stm32_row_set``, ``km_stm32_col_get`` +- Registration: ``board_kmatrix_initialize()`` calls + ``kmatrix_register()`` - - ``ch``: The character associated with the key that was - released. - - ``stream``: An instance of ``lib_outstream_s`` to perform - the actual low-level put operation. +**Data Path Summary**. - **Returned Value:** - - - ``kbd_specpress()`` - - **Function Prototype:** - - **Description:** - - **Input Parameters:** - - - ``keycode``: The command to be added to the output - stream. The enumeration ``enum kbd_keycode_e keycode`` - identifies all commands known to the system. - - ``stream``: An instance of ``lib_outstream_s`` to perform - the actual low-level put operation. - - **Returned Value:** - - - ``kbd_specrel()`` - - **Function Prototype:** - - **Description:** - - **Input Parameters:** - - - ``keycode``: The command to be added to the output - stream. The enumeration ``enum kbd_keycode_e keycode`` - identifies all commands known to the system. - - ``stream``: An instance of ``lib_outstream_s`` to perform - the actual low-level put operation. - - **Returned Value:** - -#. **Application Decoding Interfaces**. These are user interfaces - to decode the values returned by the keyboard/keypad driver. - - - ``kbd_decode()`` - - **Function Prototype:** - - **Description:** - - **Input Parameters:** - - - ``stream``: An instance of ``lib_instream_s`` to perform - the actual low-level get operation. - - ``pch``: The location to save the returned value. This - may be either a normal, character code or a special - command (i.e., a value from ``enum kbd_getstate_s``. - - ``state``: A user provided buffer to support parsing. - This structure should be cleared the first time that - ``kbd_decode()`` is called. - - **Returned Value:** - - - ``KBD_PRESS`` (0)**: Indicates the successful receipt - of normal, keyboard data. This corresponds to a keypress - event. The returned value in ``pch`` is a simple byte of - text or control data. - - ``KBD_RELEASE`` (1)**: Indicates a key release event. - The returned value in ``pch`` is the byte of text or - control data corresponding to the released key. - - ``KBD_SPECPRESS`` (2)**: Indicates the successful - receipt of a special keyboard command. The returned value - in ``pch`` is a value from ``enum kbd_getstate_s``. - - ``KBD_SPECREL`` (3)**: Indicates a special command key - release event. The returned value in ``pch`` is a value - from ``enum kbd_getstate_s``. - - ``KBD_ERROR`` (``EOF``)**: An error has getting the - next character (reported by the ``stream``). Normally - indicates the end of file. - -**I/O Streams**. Notice the use of the abstract I/O streams in -these interfaces. These stream interfaces are defined in -``include/nuttx/streams.h``. +- Board calls ``board_kmatrix_initialize("/dev/keypad0")`` +- ``kmatrix_register()`` configures GPIOs and calls + ``keyboard_register(&lower, devpath, buflen)`` +- The upper-half registers the device node at ``devpath`` +- ``kmatrix_scan_worker()`` calls ``keyboard_event()`` on press/release +- Applications read events from the device node diff --git a/boards/arm/stm32/common/include/stm32_kmatrix_gpio.h b/boards/arm/stm32/common/include/stm32_kmatrix_gpio.h new file mode 100644 index 00000000000..6615fcc9b36 --- /dev/null +++ b/boards/arm/stm32/common/include/stm32_kmatrix_gpio.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * boards/arm/stm32/common/include/stm32_kmatrix_gpio.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __BOARDS_ARM_STM32_COMMON_INCLUDE_STM32_KMATRIX_GPIO_H +#define __BOARDS_ARM_STM32_COMMON_INCLUDE_STM32_KMATRIX_GPIO_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Type Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: board_kmatrix_initialize + * + * Description: + * This function is called by application-specific setup logic to + * configure the keyboard matrix device. + * + * Input Parameters: + * devpath - The device path, typically "/dev/kbd0" + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int board_kmatrix_initialize(const char *devpath); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __BOARDS_ARM_STM32_COMMON_INCLUDE_STM32_KMATRIX_GPIO_H */ diff --git a/boards/arm/stm32/common/include/stm32_kmatrix_i2c.h b/boards/arm/stm32/common/include/stm32_kmatrix_i2c.h new file mode 100644 index 00000000000..82669a943db --- /dev/null +++ b/boards/arm/stm32/common/include/stm32_kmatrix_i2c.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * boards/arm/stm32/common/include/stm32_kmatrix_i2c.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __BOARDS_ARM_STM32_COMMON_INCLUDE_STM32_KMATRIX_I2C_H +#define __BOARDS_ARM_STM32_COMMON_INCLUDE_STM32_KMATRIX_I2C_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Type Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: board_kmatrix_i2c_initialize + * + * Description: + * This function is called by application-specific setup logic to + * configure the keyboard matrix device using an I2C GPIO expander. + * + * Input Parameters: + * devpath - The device path, typically "/dev/kbd0" + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int board_kmatrix_i2c_initialize(const char *devpath); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __BOARDS_ARM_STM32_COMMON_INCLUDE_STM32_KMATRIX_I2C_H */ diff --git a/boards/arm/stm32/common/src/CMakeLists.txt b/boards/arm/stm32/common/src/CMakeLists.txt index d13a75b1a4d..acbd4200a7f 100644 --- a/boards/arm/stm32/common/src/CMakeLists.txt +++ b/boards/arm/stm32/common/src/CMakeLists.txt @@ -158,4 +158,12 @@ if(CONFIG_INPUT_SBUTTON) list(APPEND SRCS stm32_sbutton.c) endif() +if(CONFIG_INPUT_KMATRIX) + list(APPEND SRCS stm32_kmatrix_gpio.c) +endif() + +if(CONFIG_INPUT_KMATRIX_I2C) + list(APPEND SRCS stm32_kmatrix_i2c.c) +endif() + target_sources(board PRIVATE ${SRCS}) diff --git a/boards/arm/stm32/common/src/Make.defs b/boards/arm/stm32/common/src/Make.defs index 1048df7d17a..a86d230436e 100644 --- a/boards/arm/stm32/common/src/Make.defs +++ b/boards/arm/stm32/common/src/Make.defs @@ -166,6 +166,14 @@ ifeq ($(CONFIG_INPUT_SBUTTON),y) CSRCS += stm32_sbutton.c endif +ifeq ($(CONFIG_INPUT_KMATRIX),y) + CSRCS += stm32_kmatrix_gpio.c +endif + +ifeq ($(CONFIG_INPUT_KMATRIX_I2C),y) + CSRCS += stm32_kmatrix_i2c.c +endif + DEPPATH += --dep-path src VPATH += :src CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)$(CONFIG_ARCH)$(DELIM)src$(DELIM)board$(DELIM)src diff --git a/boards/arm/stm32/common/src/stm32_kmatrix_gpio.c b/boards/arm/stm32/common/src/stm32_kmatrix_gpio.c new file mode 100644 index 00000000000..fd107c71687 --- /dev/null +++ b/boards/arm/stm32/common/src/stm32_kmatrix_gpio.c @@ -0,0 +1,355 @@ +/**************************************************************************** + * boards/arm/stm32/common/src/stm32_kmatrix_gpio.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "stm32.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef uint32_t kmatrix_pin_t; + +struct stm32_kmatrix_gpio_config_s +{ + /* Configuration structure as seen by the kmatrix driver */ + + struct kmatrix_config_s config; + + /* Additional private definitions only known to this driver */ + + void *arg; /* Argument to pass if needed */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void km_stm32_config_row(kmatrix_pin_t pin); +static void km_stm32_config_col(kmatrix_pin_t pin); +static void km_stm32_row_set(kmatrix_pin_t pin, bool active); +static bool km_stm32_col_get(kmatrix_pin_t pin); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Row and column GPIO pin definitions for 4x3 keypad matrix on + * STM32F4Discovery + * Rows: PB0-PB3 (outputs) + * Columns: PC0-PC2 (inputs with pull-up) + */ + +static const kmatrix_pin_t g_km_rows[] = +{ + BOARD_KMATRIX_ROW0, + BOARD_KMATRIX_ROW1, + BOARD_KMATRIX_ROW2, + BOARD_KMATRIX_ROW3, +}; + +static const kmatrix_pin_t g_km_cols[] = +{ + BOARD_KMATRIX_COL0, + BOARD_KMATRIX_COL1, + BOARD_KMATRIX_COL2, +}; + +/* Keymap for 4x3 matrix - Standard phone keypad layout + * Rows: 0-3, Columns: 0-2 + */ + +static const uint32_t g_km_keymap[] = +{ + '1', '2', '3', /* Row 0 */ + '4', '5', '6', /* Row 1 */ + '7', '8', '9', /* Row 2 */ + '*', '0', '#', /* Row 3 */ +}; + +/* A reference to a structure of this type must be passed to the kmatrix + * driver. This structure provides information about the configuration + * of the keypad matrix and provides GPIO callbacks. + * + * Memory for this structure is provided by the caller. It is not copied + * by the driver and is presumed to persist while the driver is active. + */ + +static struct stm32_kmatrix_gpio_config_s g_km_config = +{ + .config = + { + .nrows = 4, + .ncols = 3, + .rows = g_km_rows, + .cols = g_km_cols, + .keymap = g_km_keymap, + .poll_interval_ms = CONFIG_INPUT_KMATRIX_POLL_MS, + .config_row = km_stm32_config_row, + .config_col = km_stm32_config_col, + .row_set = km_stm32_row_set, + .col_get = km_stm32_col_get, + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: km_stm32_config_row + * + * Description: + * Configure a row GPIO pin as an output + * + ****************************************************************************/ + +static void km_stm32_config_row(kmatrix_pin_t pin) +{ + iinfo("Configuring row pin as output\n"); + stm32_configgpio(pin); + stm32_gpiowrite(pin, true); /* Initialize to inactive (high) */ +} + +/**************************************************************************** + * Name: km_stm32_config_col + * + * Description: + * Configure a column GPIO pin as an input with pull-up + * + ****************************************************************************/ + +static void km_stm32_config_col(kmatrix_pin_t pin) +{ + iinfo("Configuring column pin as input\n"); + stm32_configgpio(pin); +} + +/**************************************************************************** + * Name: km_stm32_row_set + * + * Description: + * Activate or deactivate a row (logic: active=true sets to 0/low to + * activate the row, active=false sets to 1/high to deactivate) + * + ****************************************************************************/ + +static void km_stm32_row_set(kmatrix_pin_t pin, bool active) +{ + /* With diodes, we drive rows low to activate. + * active=true -> write 0 (low) + * active=false -> write 1 (high) + */ + + stm32_gpiowrite(pin, active ? 0 : 1); +} + +/**************************************************************************** + * Name: km_stm32_col_get + * + * Description: + * Read the state of a column GPIO pin + * + ****************************************************************************/ + +static bool km_stm32_col_get(kmatrix_pin_t pin) +{ + /* With pull-up resistors: + * Key pressed -> column goes low (0) when row is driven low + * Key released -> column stays high (1) + * Return true when pressed (low), false when released (high) + */ + + return !stm32_gpioread(pin); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: board_kmatrix_initialize + * + * Description: + * This function is called by application-specific setup logic to + * configure the keyboard matrix device. + * + * Input Parameters: + * devpath - The device path, typically "/dev/keypad0" + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int board_kmatrix_initialize(const char *devpath) +{ + iinfo("Initializing keyboard matrix at %s\n", devpath); + + /* Register the keyboard matrix with the generic driver */ + + return kmatrix_register(&g_km_config.config, devpath); +} + +int board_kmatrix_diag(int loops, int delay_ms) +{ + int iter = 0; + uint8_t last_bits[4] = + { + 0xff, 0xff, 0xff, 0xff + }; + + const useconds_t pulse_us = 200000; + + iinfo("KMATRIX diag: pin identify pulses (disconnect keypad)\n"); + for (unsigned int r = 0; r < g_km_config.config.nrows; r++) + { + iinfo("Pulse ROW%u\n", r + 1); + stm32_configgpio(g_km_rows[r]); + stm32_gpiowrite(g_km_rows[r], true); + usleep(pulse_us); + stm32_gpiowrite(g_km_rows[r], false); + usleep(pulse_us); + stm32_gpiowrite(g_km_rows[r], true); + usleep(pulse_us); + } + + iinfo("KMATRIX diag: column pulses require BOARD_KMATRIX_COLx_OUT\n"); +#ifdef BOARD_KMATRIX_COL0_OUT + iinfo("Pulse COL1 (output mode)\n"); + stm32_configgpio(BOARD_KMATRIX_COL0_OUT); + stm32_gpiowrite(BOARD_KMATRIX_COL0_OUT, true); + usleep(pulse_us); + stm32_gpiowrite(BOARD_KMATRIX_COL0_OUT, false); + usleep(pulse_us); + stm32_gpiowrite(BOARD_KMATRIX_COL0_OUT, true); + usleep(pulse_us); +#endif + +#ifdef BOARD_KMATRIX_COL1_OUT + iinfo("Pulse COL2 (output mode)\n"); + stm32_configgpio(BOARD_KMATRIX_COL1_OUT); + stm32_gpiowrite(BOARD_KMATRIX_COL1_OUT, true); + usleep(pulse_us); + stm32_gpiowrite(BOARD_KMATRIX_COL1_OUT, false); + usleep(pulse_us); + stm32_gpiowrite(BOARD_KMATRIX_COL1_OUT, true); + usleep(pulse_us); +#endif + +#ifdef BOARD_KMATRIX_COL2_OUT + iinfo("Pulse COL3 (output mode)\n"); + stm32_configgpio(BOARD_KMATRIX_COL2_OUT); + stm32_gpiowrite(BOARD_KMATRIX_COL2_OUT, true); + usleep(pulse_us); + stm32_gpiowrite(BOARD_KMATRIX_COL2_OUT, false); + usleep(pulse_us); + stm32_gpiowrite(BOARD_KMATRIX_COL2_OUT, true); + usleep(pulse_us); +#endif + + for (unsigned int r = 0; r < g_km_config.config.nrows; r++) + { + km_stm32_config_row(g_km_rows[r]); + stm32_gpiowrite(g_km_rows[r], true); + } + + for (unsigned int c = 0; c < g_km_config.config.ncols; c++) + { + km_stm32_config_col(g_km_cols[c]); + } + + iinfo("KMATRIX diag: loops=%d delay_ms=%d\n", loops, delay_ms); + + while (loops <= 0 || iter < loops) + { + for (unsigned int r = 0; r < g_km_config.config.nrows; r++) + { + for (unsigned int rr = 0; rr < g_km_config.config.nrows; rr++) + { + stm32_gpiowrite(g_km_rows[rr], true); + } + + stm32_gpiowrite(g_km_rows[r], false); + usleep(1000); + + if (g_km_config.config.ncols == 3) + { + bool b0 = stm32_gpioread(g_km_cols[0]); + bool b1 = stm32_gpioread(g_km_cols[1]); + bool b2 = stm32_gpioread(g_km_cols[2]); + uint8_t bits = (b0 ? 1 : 0) | + (b1 ? 2 : 0) | + (b2 ? 4 : 0); + + if (bits != last_bits[r]) + { + iinfo("ROW=%u COLS(raw)=%d%d%d\n", + r + 1, b0 ? 1 : 0, b1 ? 1 : 0, b2 ? 1 : 0); + last_bits[r] = bits; + } + } + + for (unsigned int c = 0; c < g_km_config.config.ncols; c++) + { + bool pressed = !stm32_gpioread(g_km_cols[c]); + if (pressed) + { + iinfo("ROW=%u COL=%u\n", r + 1, c + 1); + while (!stm32_gpioread(g_km_cols[c])) + { + usleep(1000); + } + } + } + } + + if (delay_ms > 0) + { + usleep(delay_ms * 1000); + } + + iter++; + } + + return OK; +} diff --git a/boards/arm/stm32/common/src/stm32_kmatrix_i2c.c b/boards/arm/stm32/common/src/stm32_kmatrix_i2c.c new file mode 100644 index 00000000000..da8ab87a60e --- /dev/null +++ b/boards/arm/stm32/common/src/stm32_kmatrix_i2c.c @@ -0,0 +1,210 @@ +/**************************************************************************** + * boards/arm/stm32/common/src/stm32_kmatrix_i2c.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "stm32.h" +#include "stm32_i2c.h" + +#ifdef CONFIG_IOEXPANDER_MCP23X08 +# include +#endif + +#ifdef CONFIG_IOEXPANDER_PCA9538 +# include +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +typedef uint32_t kmatrix_pin_t; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Row and column pin definitions for 4x3 keypad matrix via I2C expander + * + * For MCP23X08/PCA9538 I2C expanders, pins are numbered 0-7. + * + * Example mapping: + * Rows (outputs): Pins 0-3 + * Columns (inputs): Pins 4-6 (with pull-ups) + */ + +static const kmatrix_pin_t g_km_rows[] = +{ + 0, 1, 2, 3, /* Row 0-3: Output pins on expander */ +}; + +static const kmatrix_pin_t g_km_cols[] = +{ + 4, 5, 6, /* Col 0-2: Input pins on expander (with pull-up) */ +}; + +/* Keymap for 4x3 matrix - Standard phone keypad layout + * Rows: 0-3, Columns: 0-2 + */ + +static const uint32_t g_km_keymap[] = +{ + '1', '2', '3', /* Row 0 */ + '4', '5', '6', /* Row 1 */ + '7', '8', '9', /* Row 2 */ + '*', '0', '#', /* Row 3 */ +}; + +/* Get callbacks from I2C driver */ + +extern FAR struct kmatrix_callbacks_s *kmatrix_i2c_get_callbacks(void); + +/* Keyboard matrix configuration structure + * Callbacks are set in board_kmatrix_i2c_initialize. + */ + +static struct kmatrix_config_s g_km_i2c_config = +{ + .nrows = 4, + .ncols = 3, + .rows = g_km_rows, + .cols = g_km_cols, + .keymap = g_km_keymap, + .poll_interval_ms = CONFIG_INPUT_KMATRIX_POLL_MS, +}; + +/* IO expander configuration */ + +#ifdef CONFIG_IOEXPANDER_MCP23X08 +static struct mcp23x08_config_s g_mcp23x08_config = +{ + .address = CONFIG_STM32_KMATRIX_I2C_ADDR, + .frequency = CONFIG_STM32_KMATRIX_I2C_FREQ, +}; +#endif + +#ifdef CONFIG_IOEXPANDER_PCA9538 +static struct pca9538_config_s g_pca9538_config = +{ + .address = CONFIG_STM32_KMATRIX_I2C_ADDR, + .frequency = CONFIG_STM32_KMATRIX_I2C_FREQ, +}; +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/** + * Name: board_kmatrix_i2c_initialize + * + * Description: + * Initialize keyboard matrix driver using I2C GPIO expander. + * This function is called by stm32_bringup.c during initialization. + * + * Input Parameters: + * devpath - Device path (e.g., "/dev/kbd0") + * + * Returned Value: + * Zero on success; negated errno on failure. + */ + +int board_kmatrix_i2c_initialize(const char *devpath) +{ + FAR struct i2c_master_s *i2c; + FAR struct ioexpander_dev_s *ioe; + FAR struct kmatrix_callbacks_s *callbacks; + int ret; + + iinfo("Initializing keyboard matrix via I2C expander\n"); + + /* Initialize I2C bus */ + + i2c = stm32_i2cbus_initialize(CONFIG_STM32_KMATRIX_I2C_BUS); + if (i2c == NULL) + { + ierr("ERROR: Failed to initialize I2C bus %d\n", + CONFIG_STM32_KMATRIX_I2C_BUS); + return -ENODEV; + } + + /* Initialize IO expander */ + +#ifdef CONFIG_IOEXPANDER_MCP23X08 + ioe = mcp23x08_initialize(i2c, &g_mcp23x08_config); + if (ioe == NULL) + { + ierr("ERROR: Failed to initialize MCP23X08\n"); + stm32_i2cbus_uninitialize(i2c); + return -ENODEV; + } + + iinfo("MCP23X08 initialized at 0x%02x\n", CONFIG_STM32_KMATRIX_I2C_ADDR); +#elif defined(CONFIG_IOEXPANDER_PCA9538) + ioe = pca9538_initialize(i2c, &g_pca9538_config); + if (ioe == NULL) + { + ierr("ERROR: Failed to initialize PCA9538\n"); + stm32_i2cbus_uninitialize(i2c); + return -ENODEV; + } + + iinfo("PCA9538 initialized at 0x%02x\n", CONFIG_STM32_KMATRIX_I2C_ADDR); +#else +# error "No IO expander configured" +#endif + + /* Get callbacks from I2C driver */ + + callbacks = kmatrix_i2c_get_callbacks(); + g_km_i2c_config.config_row = callbacks->config_row; + g_km_i2c_config.config_col = callbacks->config_col; + g_km_i2c_config.row_set = callbacks->row_set; + g_km_i2c_config.col_get = callbacks->col_get; + + /* Register keyboard matrix driver */ + + ret = kmatrix_i2c_register(ioe, &g_km_i2c_config, devpath); + if (ret < 0) + { + ierr("ERROR: Failed to register keyboard matrix: %d\n", ret); + stm32_i2cbus_uninitialize(i2c); + return ret; + } + + iinfo("Keyboard matrix I2C driver registered at %s\n", devpath); + return OK; +} diff --git a/boards/arm/stm32/stm32f4discovery/Kconfig b/boards/arm/stm32/stm32f4discovery/Kconfig index 7b86a5cafe3..084ab04bc12 100644 --- a/boards/arm/stm32/stm32f4discovery/Kconfig +++ b/boards/arm/stm32/stm32f4discovery/Kconfig @@ -120,4 +120,23 @@ config PM_SLEEP_WAKEUP_NSEC Number of additional nanoseconds to wait in PM_SLEEP before going to PM_STANDBY mode. +if INPUT_KMATRIX_I2C + +config STM32_KMATRIX_I2C_BUS + int "I2C Bus Number" + default 1 + ---help--- + I2C bus number to use for the keyboard matrix GPIO expander. + Common values: 1 or 2 (depends on available I2C interfaces). + +config STM32_KMATRIX_I2C_ADDR + hex "I2C Slave Address of GPIO Expander" + default 0x20 + ---help--- + I2C slave address of the GPIO expander (PCF8574 or MCP23017). + PCF8574/MCP23017 default addresses (7-bit): + 0x20-0x27: varies with A0-A2 pins (default is 0x20) + +endif # INPUT_KMATRIX_I2C + endif diff --git a/boards/arm/stm32/stm32f4discovery/include/board.h b/boards/arm/stm32/stm32f4discovery/include/board.h index ddc86ddc5fe..7392eff96cb 100644 --- a/boards/arm/stm32/stm32f4discovery/include/board.h +++ b/boards/arm/stm32/stm32f4discovery/include/board.h @@ -472,4 +472,76 @@ #define BOARD_XEN1210_PWMTIMER 1 +/* Keyboard Matrix Configuration */ + +/* Define keyboard matrix row pins (outputs) */ + +#define GPIO_KMATRIX_ROW0 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\ + GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN7) +#define GPIO_KMATRIX_ROW1 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\ + GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN8) +#define GPIO_KMATRIX_ROW2 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\ + GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN9) +#define GPIO_KMATRIX_ROW3 (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\ + GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN10) + +/* Row pins as inputs with pull-up for early diagnostics */ + +#define GPIO_KMATRIX_ROW0_IN (GPIO_INPUT|GPIO_PULLUP|GPIO_SPEED_50MHz|\ + GPIO_PORTE|GPIO_PIN7) +#define GPIO_KMATRIX_ROW1_IN (GPIO_INPUT|GPIO_PULLUP|GPIO_SPEED_50MHz|\ + GPIO_PORTE|GPIO_PIN8) +#define GPIO_KMATRIX_ROW2_IN (GPIO_INPUT|GPIO_PULLUP|GPIO_SPEED_50MHz|\ + GPIO_PORTE|GPIO_PIN9) +#define GPIO_KMATRIX_ROW3_IN (GPIO_INPUT|GPIO_PULLUP|GPIO_SPEED_50MHz|\ + GPIO_PORTE|GPIO_PIN10) + +/* Define keyboard matrix column pins (inputs) */ + +#define GPIO_KMATRIX_COL0 (GPIO_INPUT|GPIO_PULLUP|GPIO_SPEED_50MHz|\ + GPIO_PORTE|GPIO_PIN11) +#define GPIO_KMATRIX_COL1 (GPIO_INPUT|GPIO_PULLUP|GPIO_SPEED_50MHz|\ + GPIO_PORTE|GPIO_PIN13) +#define GPIO_KMATRIX_COL2 (GPIO_INPUT|GPIO_PULLUP|GPIO_SPEED_50MHz|\ + GPIO_PORTE|GPIO_PIN14) + +/* Column pins as outputs for diagnostics only */ + +#define GPIO_KMATRIX_COL0_OUT (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\ + GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN11) +#define GPIO_KMATRIX_COL1_OUT (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\ + GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN13) +#define GPIO_KMATRIX_COL2_OUT (GPIO_OUTPUT|GPIO_PUSHPULL|GPIO_SPEED_50MHz|\ + GPIO_OUTPUT_CLEAR|GPIO_PORTE|GPIO_PIN14) + +/* Board-level KMATRIX pin definitions */ + +#define BOARD_KMATRIX_ROW0 GPIO_KMATRIX_ROW0 +#define BOARD_KMATRIX_ROW1 GPIO_KMATRIX_ROW1 +#define BOARD_KMATRIX_ROW2 GPIO_KMATRIX_ROW2 +#define BOARD_KMATRIX_ROW3 GPIO_KMATRIX_ROW3 + +#define BOARD_KMATRIX_ROW0_IN GPIO_KMATRIX_ROW0_IN +#define BOARD_KMATRIX_ROW1_IN GPIO_KMATRIX_ROW1_IN +#define BOARD_KMATRIX_ROW2_IN GPIO_KMATRIX_ROW2_IN +#define BOARD_KMATRIX_ROW3_IN GPIO_KMATRIX_ROW3_IN + +#define BOARD_KMATRIX_COL0 GPIO_KMATRIX_COL0 +#define BOARD_KMATRIX_COL1 GPIO_KMATRIX_COL1 +#define BOARD_KMATRIX_COL2 GPIO_KMATRIX_COL2 + +#define BOARD_KMATRIX_COL0_OUT GPIO_KMATRIX_COL0_OUT +#define BOARD_KMATRIX_COL1_OUT GPIO_KMATRIX_COL1_OUT +#define BOARD_KMATRIX_COL2_OUT GPIO_KMATRIX_COL2_OUT + +#ifdef CONFIG_INPUT_KMATRIX +int board_kmatrix_diag(int loops, int delay_ms); +#endif + +/* Keyboard Matrix I2C Configuration */ + +#define CONFIG_STM32_KMATRIX_I2C_BUS 1 /* I2C1 */ +#define CONFIG_STM32_KMATRIX_I2C_ADDR 0x20 /* MCP23X08/PCA9538 address */ +#define CONFIG_STM32_KMATRIX_I2C_FREQ 400000 /* 400 kHz */ + #endif /* __BOARDS_ARM_STM32_STM32F4DISCOVERY_INCLUDE_BOARD_H */ diff --git a/boards/arm/stm32/stm32f4discovery/src/stm32_bringup.c b/boards/arm/stm32/stm32f4discovery/src/stm32_bringup.c index 59742accb3e..058e5eead26 100644 --- a/boards/arm/stm32/stm32f4discovery/src/stm32_bringup.c +++ b/boards/arm/stm32/stm32f4discovery/src/stm32_bringup.c @@ -102,6 +102,14 @@ #include "board_sbutton.h" #endif +#ifdef CONFIG_INPUT_KMATRIX +#include "stm32_kmatrix_gpio.h" +#endif + +#ifdef CONFIG_INPUT_KMATRIX_I2C +#include "stm32_kmatrix_i2c.h" +#endif + #ifdef CONFIG_SENSORS_ZEROCROSS #include "stm32_zerocross.h" #endif @@ -414,6 +422,27 @@ int stm32_bringup(void) } #endif +#ifdef CONFIG_INPUT_KMATRIX + /* Initialize and register the keyboard matrix driver */ + + ret = board_kmatrix_initialize(CONFIG_INPUT_KMATRIX_DEVPATH); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: board_kmatrix_initialize() failed: %d\n", ret); + } +#endif + +#ifdef CONFIG_INPUT_KMATRIX_I2C + /* Initialize and register the keyboard matrix driver via I2C expander */ + + ret = board_kmatrix_i2c_initialize("/dev/kbd0"); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: board_kmatrix_i2c_initialize() failed: %d\n", + ret); + } +#endif + #ifdef CONFIG_INPUT_NUNCHUCK /* Register the Nunchuck driver */ diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt index 3b897b1a733..1b088339188 100644 --- a/drivers/input/CMakeLists.txt +++ b/drivers/input/CMakeLists.txt @@ -106,6 +106,14 @@ if(CONFIG_INPUT) list(APPEND SRCS keyboard_upper.c) endif() + if(CONFIG_INPUT_KMATRIX) + list(APPEND SRCS kmatrix.c) + endif() + + if(CONFIG_INPUT_KMATRIX_I2C) + list(APPEND SRCS kmatrix_i2c.c) + endif() + if(CONFIG_INPUT_SBUTTON) list(APPEND SRCS sbutton.c) endif() diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 3917fe6538e..d1695ee6f26 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -723,7 +723,7 @@ config INPUT_SPQ10KBD select I2C ---help--- Enable the Solder Party Q10 BlackBerry Keyboard support. This - exposes itself as a standard keyboard at /dev/kbdN. + exposes itself as a standard keyboard at /dev/keypadN. This keyboard exists both as a standalone module and integrated into the Solder Party Keyboard FeatherWing. Information on this can be found at https://www.solder.party/docs/keyboard-pmod/ @@ -749,4 +749,70 @@ config SPQ10KBD_NPOLLWAITERS endif # INPUT_SPQ10KBD +config INPUT_KMATRIX + bool "Keyboard Matrix Driver" + default n + select INPUT_KEYBOARD + ---help--- + Enable support for keyboard matrix input devices with polling-based + scanning. This driver supports NxM key matrices with debounce and + anti-ghosting using diodes. + +if INPUT_KMATRIX + +config INPUT_KMATRIX_BUFSIZE + int "Keyboard matrix buffer size" + default 64 + ---help--- + Size of the keyboard event buffer for each open file descriptor. + +config INPUT_KMATRIX_POLL_MS + int "Polling interval (milliseconds)" + default 10 + ---help--- + Time interval in milliseconds between matrix scans. Smaller values + provide lower latency but consume more CPU. Default: 10ms. + +config INPUT_KMATRIX_DEBOUNCE + int "Debounce threshold (scan cycles)" + default 3 + ---help--- + Number of consecutive scan cycles a key must maintain the same state + before generating a press/release event. Higher values provide more + robust debounce but increase latency. Default: 3 cycles. + +config INPUT_KMATRIX_DEVPATH + string "Device path" + default "/dev/keypad0" + ---help--- + Path where the keyboard matrix device will be registered. Default: /dev/keypad0 + +config INPUT_KMATRIX_I2C + bool "Keyboard Matrix via I2C GPIO Expander" + depends on INPUT_KMATRIX && I2C + default n + ---help--- + Enable keyboard matrix driver using I2C GPIO expander (PCF8574 or MCP23017). + Requires I2C support to be enabled. + +if INPUT_KMATRIX_I2C + +config INPUT_KMATRIX_I2C_PCF8574 + bool "Use PCF8574 I2C Expander" + default n + ---help--- + Use PCF8574 I2C GPIO expander (8-bit, quasi-bidirectional I/O). + This is a simple, low-cost 8-bit I/O expander commonly used in hobbyist projects. + +config INPUT_KMATRIX_I2C_MCP23017 + bool "Use MCP23017 I2C Expander" + default n + ---help--- + Use MCP23017 I2C GPIO expander (16-bit, configurable I/O directions). + This offers more features and flexibility than PCF8574. + +endif # INPUT_KMATRIX_I2C + +endif # INPUT_KMATRIX + endif # INPUT diff --git a/drivers/input/Make.defs b/drivers/input/Make.defs index 815a4ce847d..66862bbbde3 100644 --- a/drivers/input/Make.defs +++ b/drivers/input/Make.defs @@ -126,6 +126,14 @@ ifeq ($(CONFIG_INPUT_SPQ10KBD),y) CSRCS += spq10kbd.c endif +ifeq ($(CONFIG_INPUT_KMATRIX),y) + CSRCS += kmatrix.c +endif + +ifeq ($(CONFIG_INPUT_KMATRIX_I2C),y) + CSRCS += kmatrix_i2c.c +endif + ifeq ($(CONFIG_INPUT_GOLDFISH_EVENTS),y) CSRCS += goldfish_events.c endif diff --git a/drivers/input/kmatrix.c b/drivers/input/kmatrix.c new file mode 100644 index 00000000000..23375b7c980 --- /dev/null +++ b/drivers/input/kmatrix.c @@ -0,0 +1,362 @@ +/**************************************************************************** + * drivers/input/kmatrix.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct kmatrix_dev_s +{ + FAR const struct kmatrix_config_s *config; /* Board configuration data */ + + mutex_t lock; /* Exclusive access to device */ + struct work_s work; /* Work queue for polling */ + uint16_t poll_interval; /* Polling interval in milliseconds */ + + /* Current and previous state of the matrix (bitfield) */ + + FAR uint8_t *state; /* Current state bitmap */ + FAR uint8_t *debounce; /* Debounce counter */ + + /* Keyboard lower-half registration */ + + struct keyboard_lowerhalf_s lower; +}; + +/**************************************************************************** + * Static Function Prototypes + ****************************************************************************/ + +static void kmatrix_scan_worker(FAR void *arg); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: kmatrix_get_state + * + * Description: + * Get the current state of a key at position (row, col) + * + ****************************************************************************/ + +static bool kmatrix_get_state(FAR struct kmatrix_dev_s *priv, + uint8_t row, uint8_t col) +{ + uint16_t idx = row * priv->config->ncols + col; + uint16_t byte_idx = idx / 8; + uint8_t bit_idx = idx % 8; + + return (priv->state[byte_idx] >> bit_idx) & 1; +} + +/**************************************************************************** + * Name: kmatrix_set_state + * + * Description: + * Set the current state of a key at position (row, col) + * + ****************************************************************************/ + +static void kmatrix_set_state(FAR struct kmatrix_dev_s *priv, + uint8_t row, uint8_t col, bool pressed) +{ + uint16_t idx = row * priv->config->ncols + col; + uint16_t byte_idx = idx / 8; + uint8_t bit_idx = idx % 8; + + if (pressed) + { + priv->state[byte_idx] |= (1 << bit_idx); + } + else + { + priv->state[byte_idx] &= ~(1 << bit_idx); + } +} + +/**************************************************************************** + * Name: kmatrix_inc_debounce + * + * Description: + * Increment debounce counter for a key + * + ****************************************************************************/ + +static void kmatrix_inc_debounce(FAR struct kmatrix_dev_s *priv, + uint8_t row, uint8_t col) +{ + uint16_t idx = row * priv->config->ncols + col; + + if (priv->debounce[idx] < CONFIG_INPUT_KMATRIX_DEBOUNCE) + { + priv->debounce[idx]++; + } +} + +/**************************************************************************** + * Name: kmatrix_reset_debounce + * + * Description: + * Reset debounce counter for a key + * + ****************************************************************************/ + +static void kmatrix_reset_debounce(FAR struct kmatrix_dev_s *priv, + uint8_t row, uint8_t col) +{ + uint16_t idx = row * priv->config->ncols + col; + + priv->debounce[idx] = 0; +} + +/**************************************************************************** + * Name: kmatrix_scan_worker + * + * Description: + * Periodic worker that scans the keyboard matrix and detects key presses + * and releases. + * + ****************************************************************************/ + +static void kmatrix_scan_worker(FAR void *arg) +{ + FAR struct kmatrix_dev_s *priv = (FAR struct kmatrix_dev_s *)arg; + uint8_t row; + uint8_t col; + bool pressed; + bool old_state; + uint32_t keycode; + int ret; + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return; + } + + /* Scan each row */ + + for (row = 0; row < priv->config->nrows; row++) + { + /* Activate this row */ + + priv->config->row_set(priv->config->rows[row], true); + + /* Read each column */ + + for (col = 0; col < priv->config->ncols; col++) + { + pressed = priv->config->col_get(priv->config->cols[col]); + old_state = kmatrix_get_state(priv, row, col); + + /* Check if state changed */ + + if (pressed != old_state) + { + kmatrix_inc_debounce(priv, row, col); + + /* After debounce threshold is reached, update state */ + + if (priv->debounce[row * priv->config->ncols + col] >= + CONFIG_INPUT_KMATRIX_DEBOUNCE) + { + kmatrix_set_state(priv, row, col, pressed); + kmatrix_reset_debounce(priv, row, col); + + /* Generate keyboard event */ + + keycode = priv->config->keymap[ + row * priv->config->ncols + col]; + keyboard_event(&priv->lower, (uint16_t)keycode, + pressed ? KEYBOARD_PRESS : + KEYBOARD_RELEASE); + + iinfo("Key [%d,%d]: %s (code %lu)\n", row, col, + pressed ? "PRESS" : "RELEASE", + (unsigned long)keycode); + } + } + else + { + kmatrix_reset_debounce(priv, row, col); + } + } + + /* Deactivate this row */ + + priv->config->row_set(priv->config->rows[row], false); + } + + nxmutex_unlock(&priv->lock); + + /* Reschedule the worker */ + + work_queue(LPWORK, &priv->work, kmatrix_scan_worker, priv, + MSEC2TICK(priv->poll_interval)); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: kmatrix_register + * + * Description: + * Configure and register a keyboard matrix device. + * + ****************************************************************************/ + +int kmatrix_register(FAR const struct kmatrix_config_s *config, + FAR const char *devpath) +{ + FAR struct kmatrix_dev_s *priv; + int ret; + uint16_t state_size; + uint16_t debounce_size; + uint16_t keys; + + iinfo("Registering keypad matrix: %dx%d at %s\n", config->nrows, + config->ncols, devpath); + + /* Validate configuration */ + + DEBUGASSERT(config != NULL); + DEBUGASSERT(devpath != NULL); + DEBUGASSERT(config->rows != NULL); + DEBUGASSERT(config->cols != NULL); + DEBUGASSERT(config->keymap != NULL); + DEBUGASSERT(config->config_row != NULL); + DEBUGASSERT(config->config_col != NULL); + DEBUGASSERT(config->row_set != NULL); + DEBUGASSERT(config->col_get != NULL); + + /* Allocate driver instance */ + + priv = kmm_zalloc(sizeof(struct kmatrix_dev_s)); + if (!priv) + { + ierr("ERROR: kmm_zalloc(%zu) failed\n", sizeof(struct kmatrix_dev_s)); + return -ENOMEM; + } + + /* Calculate bitmap sizes */ + + keys = config->nrows * config->ncols; + state_size = (keys + 7) / 8; + debounce_size = keys; + + /* Allocate state and debounce bitmaps */ + + priv->state = kmm_zalloc(state_size); + if (!priv->state) + { + ierr("ERROR: Failed to allocate state bitmap\n"); + kmm_free(priv); + return -ENOMEM; + } + + priv->debounce = kmm_zalloc(debounce_size); + if (!priv->debounce) + { + ierr("ERROR: Failed to allocate debounce bitmap\n"); + kmm_free(priv->state); + kmm_free(priv); + return -ENOMEM; + } + + /* Initialize device structure */ + + priv->config = config; + priv->poll_interval = config->poll_interval_ms > 0 ? + config->poll_interval_ms : + CONFIG_INPUT_KMATRIX_POLL_MS; + + nxmutex_init(&priv->lock); + + /* Configure all GPIO pins */ + + for (int i = 0; i < config->nrows; i++) + { + config->config_row(config->rows[i]); + } + + for (int i = 0; i < config->ncols; i++) + { + config->config_col(config->cols[i]); + } + + /* Register as keyboard device */ + + ret = keyboard_register(&priv->lower, devpath, + CONFIG_INPUT_KMATRIX_BUFSIZE); + if (ret < 0) + { + ierr("ERROR: keyboard_register() failed: %d\n", ret); + goto errout_with_priv; + } + + /* Start the scanning worker */ + + work_queue(LPWORK, &priv->work, kmatrix_scan_worker, priv, + MSEC2TICK(priv->poll_interval)); + + iinfo("Keypad matrix registered as %s\n", devpath); + return OK; + +errout_with_priv: + nxmutex_destroy(&priv->lock); + kmm_free(priv->debounce); + kmm_free(priv->state); + kmm_free(priv); + return ret; +} diff --git a/drivers/input/kmatrix_i2c.c b/drivers/input/kmatrix_i2c.c new file mode 100644 index 00000000000..1b366f9c8bd --- /dev/null +++ b/drivers/input/kmatrix_i2c.c @@ -0,0 +1,262 @@ +/**************************************************************************** + * drivers/input/kmatrix_i2c.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Verify IO expander support is enabled */ + +#if !defined(CONFIG_IOEXPANDER_PCA9538) && !defined(CONFIG_IOEXPANDER_MCP23X08) +# error "Either CONFIG_IOEXPANDER_PCA9538 or " \ + "CONFIG_IOEXPANDER_MCP23X08 must be enabled" +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef uint32_t kmatrix_pin_t; + +struct kmatrix_i2c_dev_s +{ + FAR struct ioexpander_dev_s *ioe; /* IO expander device */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void km_i2c_config_row(kmatrix_pin_t pin); +static void km_i2c_config_col(kmatrix_pin_t pin); +static void km_i2c_row_set(kmatrix_pin_t pin, bool active); +static bool km_i2c_col_get(kmatrix_pin_t pin); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Global I2C device instance (simplified - one per board) */ + +static struct kmatrix_i2c_dev_s g_km_i2c_dev; + +/**************************************************************************** + * I2C Keyboard Matrix Callbacks + ****************************************************************************/ + +/** + * Name: km_i2c_config_row + * + * Description: + * Configure row pins as outputs using IO expander API. + */ + +static void km_i2c_config_row(kmatrix_pin_t pin) +{ + int ret; + + iinfo("I2C: Configuring pin %lu as output (row)\n", (unsigned long)pin); + + ret = IOEXP_SETDIRECTION(g_km_i2c_dev.ioe, (uint8_t)pin, + IOEXPANDER_DIRECTION_OUT); + if (ret < 0) + { + ierr("ERROR: Failed to configure row pin %lu: %d\n", + (unsigned long)pin, ret); + } +} + +/** + * Name: km_i2c_config_col + * + * Description: + * Configure column pins as inputs with pull-up using IO expander API. + */ + +static void km_i2c_config_col(kmatrix_pin_t pin) +{ + int ret; + + iinfo("I2C: Configuring pin %lu as input (column)\n", (unsigned long)pin); + + ret = IOEXP_SETDIRECTION(g_km_i2c_dev.ioe, (uint8_t)pin, + IOEXPANDER_DIRECTION_IN_PULLUP); + if (ret < 0) + { + /* PCA9538 does not support IN_PULLUP; fall back to plain input. */ + + iinfo("I2C: IN_PULLUP not supported for pin %lu, falling back to IN\n", + (unsigned long)pin); + + ret = IOEXP_SETDIRECTION(g_km_i2c_dev.ioe, (uint8_t)pin, + IOEXPANDER_DIRECTION_IN); + if (ret < 0) + { + ierr("ERROR: Failed to configure col pin %lu: %d\n", + (unsigned long)pin, ret); + } + } +} + +/** + * Name: km_i2c_row_set + * + * Description: + * Control row output (active-low for matrix with diodes). + */ + +static void km_i2c_row_set(kmatrix_pin_t pin, bool active) +{ + int ret; + + /* For active-low: active=true means write LOW (false) */ + + ret = IOEXP_WRITEPIN(g_km_i2c_dev.ioe, (uint8_t)pin, !active); + if (ret < 0) + { + ierr("ERROR: Failed to set row pin %lu: %d\n", + (unsigned long)pin, ret); + } + + iinfo("I2C: Row set pin %lu to %d\n", (unsigned long)pin, active ? 0 : 1); +} + +/** + * Name: km_i2c_col_get + * + * Description: + * Read column input (active-low with pull-up). + */ + +static bool km_i2c_col_get(kmatrix_pin_t pin) +{ + bool value; + int ret; + + ret = IOEXP_READPIN(g_km_i2c_dev.ioe, (uint8_t)pin, &value); + if (ret < 0) + { + ierr("ERROR: Failed to read col pin %lu: %d\n", + (unsigned long)pin, ret); + return false; + } + + /* Return inverted: true = active (low), false = inactive (high) */ + + bool result = !value; + + iinfo("I2C: Col get pin %lu = %d\n", (unsigned long)pin, result); + + return result; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/** + * Name: kmatrix_i2c_get_callbacks + * + * Description: + * Get the I2C callback functions to use in keyboard matrix config. + * This is called by board adapters to populate the callbacks. + * + * Returned Value: + * Structure with the callback function pointers. + */ + +static struct kmatrix_callbacks_s g_km_i2c_callbacks = +{ + .config_row = km_i2c_config_row, + .config_col = km_i2c_config_col, + .row_set = km_i2c_row_set, + .col_get = km_i2c_col_get, +}; + +FAR struct kmatrix_callbacks_s *kmatrix_i2c_get_callbacks(void) +{ + return &g_km_i2c_callbacks; +} + +/** + * Name: kmatrix_i2c_register + * + * Description: + * Register keyboard matrix driver using I2C GPIO expander. + * The IO expander device must already be initialized. + * + * Input Parameters: + * ioe_dev - IO expander device (from mcp23x08_initialize or + * pca9538_initialize) + * config - Keyboard matrix configuration (with callbacks set) + * devpath - Device path (e.g., "/dev/kbd0") + * + * Returned Value: + * Zero on success; negated errno on failure. + */ + +int kmatrix_i2c_register(FAR struct ioexpander_dev_s *ioe_dev, + FAR const struct kmatrix_config_s *config, + FAR const char *devpath) +{ + int ret; + + if (ioe_dev == NULL) + { + ierr("ERROR: IO expander device is NULL\n"); + return -EINVAL; + } + + iinfo("Initializing keyboard matrix via I2C IO expander\n"); + + /* Store IO expander device in global for callbacks */ + + g_km_i2c_dev.ioe = ioe_dev; + + /* Register the keyboard matrix driver with provided config + * (which must have callbacks already set by the board adapter) + */ + + ret = kmatrix_register(config, devpath); + if (ret < 0) + { + ierr("ERROR: kmatrix_register failed: %d\n", ret); + return ret; + } + + iinfo("Keyboard matrix I2C driver registered successfully\n"); + return OK; +} diff --git a/include/nuttx/input/kmatrix.h b/include/nuttx/input/kmatrix.h new file mode 100644 index 00000000000..cec15acfddd --- /dev/null +++ b/include/nuttx/input/kmatrix.h @@ -0,0 +1,160 @@ +/**************************************************************************** + * include/nuttx/input/kmatrix.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_INPUT_KMATRIX_H +#define __INCLUDE_NUTTX_INPUT_KMATRIX_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef uint32_t kmatrix_pin_t; + +/* Keyboard matrix configuration structure passed to kmatrix_register() */ + +struct kmatrix_config_s +{ + uint8_t nrows; /* Number of rows */ + uint8_t ncols; /* Number of columns */ + FAR const kmatrix_pin_t *rows; /* Array of row GPIO pins */ + FAR const kmatrix_pin_t *cols; /* Array of column GPIO pins */ + + /* Keymap: keycode[row * cols + col] */ + + FAR const uint32_t *keymap; + uint16_t poll_interval_ms; /* Polling interval in milliseconds */ + + /* GPIO callback functions specific to the SoC/board */ + + void (*config_row)(kmatrix_pin_t pin); + void (*config_col)(kmatrix_pin_t pin); + void (*row_set)(kmatrix_pin_t pin, bool active); + bool (*col_get)(kmatrix_pin_t pin); +}; + +#ifdef CONFIG_INPUT_KMATRIX_I2C + +/* Keyboard matrix callback structure for I2C expanders */ + +struct kmatrix_callbacks_s +{ + void (*config_row)(kmatrix_pin_t pin); + void (*config_col)(kmatrix_pin_t pin); + void (*row_set)(kmatrix_pin_t pin, bool active); + bool (*col_get)(kmatrix_pin_t pin); +}; + +#endif /* CONFIG_INPUT_KMATRIX_I2C */ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Name: kmatrix_register + * + * Description: + * Configure and register a keyboard matrix device. This will create the + * /dev/keypadN device node and enable keyboard scanning. + * + * Input Parameters: + * config - The keyboard matrix configuration. This structure is not + * copied; it must persist for the lifetime of the driver. + * devpath - The device path for the /dev/keypadN device. + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int kmatrix_register(FAR const struct kmatrix_config_s *config, + FAR const char *devpath); + +#ifdef CONFIG_INPUT_KMATRIX_I2C + +/* Forward declaration */ + +struct ioexpander_dev_s; + +/**************************************************************************** + * Name: kmatrix_i2c_register + * + * Description: + * Register keyboard matrix driver using I2C GPIO expander. + * The IO expander device must already be initialized using + * mcp23x08_initialize() or pca9538_initialize(). + * + * Input Parameters: + * ioe_dev - IO expander device (from mcp23x08_initialize or + * pca9538_initialize) + * config - The keyboard matrix configuration (with callbacks set) + * devpath - The device path for the /dev/keypadN device + * + * Returned Value: + * Zero is returned on success. Otherwise, a negated errno value is + * returned to indicate the nature of the failure. + * + ****************************************************************************/ + +int kmatrix_i2c_register(FAR struct ioexpander_dev_s *ioe_dev, + FAR const struct kmatrix_config_s *config, + FAR const char *devpath); + +/**************************************************************************** + * Name: kmatrix_i2c_get_callbacks + * + * Description: + * Get the I2C callback functions to use in keyboard matrix config. + * This is called by board adapters to populate the callbacks. + * + * Returned Value: + * Structure with the callback function pointers. + * + ****************************************************************************/ + +FAR struct kmatrix_callbacks_s *kmatrix_i2c_get_callbacks(void); + +#endif /* CONFIG_INPUT_KMATRIX_I2C */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_INPUT_KMATRIX_H */