This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 1953a7449d25c6280753c6fa0c00d219ed94dedd
Author: Jorge Guzman <[email protected]>
AuthorDate: Thu Jul 2 12:29:42 2026 -0300

    boards/esp32s3: esp32s3-m5-cardputer: add keyboard and LVGL terminal
    
    Add the 56-key matrix keyboard driver for the M5Stack Cardputer.  The keys
    form an 8x7 matrix behind a 74HC138 3-to-8 demultiplexer; the driver scans 
it
    on the low-priority work queue and registers a keyboard device at /dev/kbd0,
    reporting ASCII codes with SHIFT/CTRL and Fn (cursor) handling.
    
    Add an "lvglterm" configuration that runs the on-screen NuttShell terminal
    (apps/examples/lvglterm, physical-keyboard input variant) on the ST7789
    display: the shell output is rendered in an LVGL text area and the keyboard
    feeds the input.  Wi-Fi is included so the on-screen shell can associate 
with
    an access point using wapi.
    
    Signed-off-by: Jorge Guzman <[email protected]>
---
 .../esp32s3/boards/esp32s3-m5-cardputer/index.rst  |  38 +-
 boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig |  29 ++
 .../configs/lvglterm/defconfig                     | 126 +++++++
 .../esp32s3/esp32s3-m5-cardputer/src/Make.defs     |   4 +
 .../src/esp32s3-m5-cardputer.h                     |  20 ++
 .../esp32s3-m5-cardputer/src/esp32s3_board_spi.c   |  19 +
 .../esp32s3-m5-cardputer/src/esp32s3_bringup.c     |  10 +
 .../esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c | 397 +++++++++++++++++++++
 8 files changed, 640 insertions(+), 3 deletions(-)

diff --git 
a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst 
b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst
index b96169482ed..a8258dec019 100644
--- 
a/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst
+++ 
b/Documentation/platforms/xtensa/esp32s3/boards/esp32s3-m5-cardputer/index.rst
@@ -14,9 +14,9 @@ M5Stack Cardputer
 
 The `M5Stack Cardputer <https://docs.m5stack.com/en/core/Cardputer>`_ is a
 pocket-sized computer kit built around an M5Stamp S3 module (ESP32-S3FN8, dual
-Xtensa LX7 @ 240 MHz, 8 MB flash, no PSRAM).  It integrates a 1.14" ST7789
-TFT, an NS4168 I2S speaker, an SPM1423 microphone, an IR transmitter, a
-microSD slot and a Grove port.
+Xtensa LX7 @ 240 MHz, 8 MB flash, no PSRAM).  It integrates a 56-key keyboard,
+a 1.14" ST7789 TFT, an NS4168 I2S speaker, an SPM1423 microphone, an IR
+transmitter, a microSD slot and a Grove port.
 
 .. figure:: esp32-cardputer-image-2.png
    :align: center
@@ -29,6 +29,7 @@ Features
 
 * ESP32-S3FN8 (dual Xtensa LX7 @ 240 MHz), 8 MB flash, no PSRAM
 * Wi-Fi 4 (2.4 GHz) and Bluetooth LE (native ESP32-S3 radio)
+* 56-key keyboard (8x7 matrix behind a 74HC138 demultiplexer)
 * 1.14" 240x135 ST7789v2 TFT on SPI2
 * NS4168 mono I2S Class-D speaker amplifier
 * SPM1423 PDM microphone
@@ -43,12 +44,33 @@ By default the NSH console runs over the 
**USB-Serial-JTAG** peripheral exposed
 on the USB Type-C connector.  It enumerates on the host as ``/dev/ttyACM0``
 (Linux).  No external USB-to-UART bridge is required.
 
+Keyboard
+========
+
+The 56-key keyboard is an 8x7 matrix driven by a 74HC138 3-to-8 demultiplexer:
+three select lines (GPIO8/9/11) choose one of eight rows and seven column lines
+(GPIO13/15/3/4/5/6/7) are read back.  The ``esp32s3_kbd.c`` driver scans the
+matrix on the low-priority work queue and registers a keyboard device at
+``/dev/kbd0``, reporting ASCII key codes with SHIFT/CTRL handling.  Enable it
+with ``CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD`` (the ``lvglterm`` configuration
+turns it on).
+
+.. note::
+
+   The key-position-to-character table follows the M5Cardputer layout; verify
+   it against the printed legends on first bring-up.  Applications read
+   ``/dev/kbd0`` for key events (for example the ``lvglterm`` example, built
+   with its physical-keyboard input variant, feeds it into an on-screen
+   shell).
+
 Pin Mapping
 ===========
 
 =========================== ==========================================
 Peripheral                  ESP32-S3 GPIO
 =========================== ==========================================
+Keyboard demux select       8 (A0), 9 (A1), 11 (A2)
+Keyboard columns            13, 15, 3, 4, 5, 6, 7
 ST7789 SPI2 SCLK/MOSI/CS    36 / 35 / 37
 ST7789 DC/RST/backlight     34 / 33 / 38
 microSD SPI3 SCK/MISO       40 / 39
@@ -116,3 +138,13 @@ lvgl
     Graphics support with LVGL on the ST7789 display.  Run the LVGL demo::
 
         nsh> lvgldemo
+
+lvglterm
+    On-screen NuttShell terminal with LVGL and the physical keyboard.  Runs
+    the ``lvglterm`` example built with its physical-keyboard input variant:
+    NSH output is rendered in an LVGL text area and the keyboard
+    (``/dev/kbd0``) feeds the input.  Wi-Fi is included, so the on-screen
+    shell can associate with an access point using ``wapi``.  ``Fn`` + ``;`` /
+    ``.`` scroll the output::
+
+        nsh> lvglterm
diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig 
b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig
index a07197f7079..6fbd53d15cf 100644
--- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig
+++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/Kconfig
@@ -5,4 +5,33 @@
 
 if ARCH_BOARD_ESP32S3_M5_CARDPUTER
 
+config ESP32S3_M5_CARDPUTER_KEYBOARD
+       bool "Cardputer matrix keyboard"
+       default n
+       select INPUT
+       select INPUT_KEYBOARD
+       select SCHED_LPWORK
+       ---help---
+               Enable the M5Stack Cardputer 56-key matrix keyboard driver.  The
+               driver scans the 74HC138-multiplexed key matrix and registers a
+               keyboard device (/dev/kbdN).
+
+if ESP32S3_M5_CARDPUTER_KEYBOARD
+
+config ESP32S3_M5_CARDPUTER_KBD_BUFNUM
+       int "Cardputer keyboard event buffer size"
+       default 8
+       ---help---
+               Number of key events buffered by the Cardputer matrix keyboard
+               driver before the oldest events are overwritten.
+
+config ESP32S3_M5_CARDPUTER_KBD_DEBUG
+       bool "Cardputer keyboard scan debug logging"
+       default n
+       ---help---
+               Log every detected key matrix transition to the syslog.  Useful 
to
+               bring up the keyboard and to verify the key map.
+
+endif # ESP32S3_M5_CARDPUTER_KEYBOARD
+
 endif # ARCH_BOARD_ESP32S3_M5_CARDPUTER
diff --git 
a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/configs/lvglterm/defconfig 
b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/configs/lvglterm/defconfig
new file mode 100644
index 00000000000..349e37a7e95
--- /dev/null
+++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/configs/lvglterm/defconfig
@@ -0,0 +1,126 @@
+#
+# This file is autogenerated: PLEASE DO NOT EDIT IT.
+#
+# You can use "make menuconfig" to make any modifications to the installed 
.config file.
+# You can then do "make savedefconfig" to generate a new defconfig file that 
includes your
+# modifications.
+#
+# CONFIG_NSH_ARGCAT is not set
+# CONFIG_NSH_CMDOPT_HEXDUMP is not set
+# CONFIG_SPI_CALLBACK is not set
+CONFIG_ALLOW_BSD_COMPONENTS=y
+CONFIG_ARCH="xtensa"
+CONFIG_ARCH_BOARD="esp32s3-m5-cardputer"
+CONFIG_ARCH_BOARD_COMMON=y
+CONFIG_ARCH_BOARD_ESP32S3_M5_CARDPUTER=y
+CONFIG_ARCH_CHIP="esp32s3"
+CONFIG_ARCH_CHIP_ESP32S3=y
+CONFIG_ARCH_CHIP_ESP32S3MINI1N8=y
+CONFIG_ARCH_INTERRUPTSTACK=2048
+CONFIG_ARCH_IRQ_TO_NDX=y
+CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC=y
+CONFIG_ARCH_NUSER_INTERRUPTS=2
+CONFIG_ARCH_STACKDUMP=y
+CONFIG_ARCH_XTENSA=y
+CONFIG_BOARDCTL_RESET=y
+CONFIG_BOARD_LOOPSPERMSEC=16717
+CONFIG_BUILTIN=y
+CONFIG_DEBUG_FULLOPT=y
+CONFIG_DEBUG_SYMBOLS=y
+CONFIG_DRIVERS_IEEE80211=y
+CONFIG_DRIVERS_WIRELESS=y
+CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD=y
+CONFIG_ESP32S3_SPI2=y
+CONFIG_ESP32S3_SPI2_CLKPIN=36
+CONFIG_ESP32S3_SPI2_CSPIN=37
+CONFIG_ESP32S3_SPI2_MISOPIN=-1
+CONFIG_ESP32S3_SPI2_MOSIPIN=35
+CONFIG_ESP32S3_SPI3=y
+CONFIG_ESP32S3_SPI3_CLKPIN=40
+CONFIG_ESP32S3_SPI3_CSPIN=12
+CONFIG_ESP32S3_SPI3_MISOPIN=39
+CONFIG_ESP32S3_SPI3_MOSIPIN=14
+CONFIG_ESP32S3_SPIFLASH=y
+CONFIG_ESP32S3_SPIFLASH_SPIFFS=y
+CONFIG_ESP32S3_SPI_SWCS=y
+CONFIG_ESP32S3_USBSERIAL=y
+CONFIG_ESPRESSIF_WIFI=y
+CONFIG_EXAMPLES_LVGLTERM=y
+CONFIG_EXAMPLES_LVGLTERM_FONT_UNSCII_8=y
+CONFIG_EXAMPLES_LVGLTERM_INPUT_KBD=y
+CONFIG_FAT_LCNAMES=y
+CONFIG_FAT_LFN=y
+CONFIG_FS_FAT=y
+CONFIG_FS_PROCFS=y
+CONFIG_GRAPHICS_LVGL=y
+CONFIG_IDLETHREAD_STACKSIZE=3072
+CONFIG_INIT_ENTRYPOINT="nsh_main"
+CONFIG_INIT_STACKSIZE=3072
+CONFIG_INTELHEX_BINARY=y
+CONFIG_IOB_BUFSIZE=400
+CONFIG_IOB_NBUFFERS=100
+CONFIG_IOB_NCHAINS=32
+CONFIG_IOB_THROTTLE=40
+CONFIG_LCD=y
+CONFIG_LCD_DEV=y
+CONFIG_LCD_FRAMEBUFFER=y
+CONFIG_LCD_ST7789=y
+CONFIG_LCD_ST7789_DATA_ENDIAN_LITTLE=y
+CONFIG_LCD_ST7789_FREQUENCY=40000000
+CONFIG_LCD_ST7789_XOFFSET=52
+CONFIG_LCD_ST7789_XRES=135
+CONFIG_LCD_ST7789_YOFFSET=40
+CONFIG_LCD_ST7789_YRES=240
+CONFIG_LIBC_EXECFUNCS=y
+CONFIG_LIBC_PERROR_STDOUT=y
+CONFIG_LIBC_STRERROR=y
+CONFIG_LINE_MAX=64
+CONFIG_LV_FONT_UNSCII_16=y
+CONFIG_LV_USE_CLIB_MALLOC=y
+CONFIG_LV_USE_CLIB_SPRINTF=y
+CONFIG_LV_USE_CLIB_STRING=y
+CONFIG_LV_USE_DEMO_WIDGETS=y
+CONFIG_LV_USE_LOG=y
+CONFIG_LV_USE_NUTTX=y
+CONFIG_LV_USE_NUTTX_LCD=y
+CONFIG_MMCSD=y
+CONFIG_NETDB_DNSCLIENT=y
+CONFIG_NETDEV_LATEINIT=y
+CONFIG_NETDEV_PHY_IOCTL=y
+CONFIG_NETDEV_WIRELESS_IOCTL=y
+CONFIG_NETUTILS_CJSON=y
+CONFIG_NETUTILS_IPERF=y
+CONFIG_NET_ETH_PKTSIZE=1514
+CONFIG_NET_ICMP_SOCKET=y
+CONFIG_NET_TCP=y
+CONFIG_NET_TCP_DELAYED_ACK=y
+CONFIG_NET_TCP_WRITE_BUFFERS=y
+CONFIG_NET_UDP=y
+CONFIG_NET_UDP_WRITE_BUFFERS=y
+CONFIG_NSH_BUILTIN_APPS=y
+CONFIG_NSH_DISABLE_DATE=y
+CONFIG_NSH_FILEIOSIZE=512
+CONFIG_NSH_MMCSDSPIPORTNO=3
+CONFIG_NSH_READLINE=y
+CONFIG_PIPES=y
+CONFIG_PREALLOC_TIMERS=4
+CONFIG_PTHREAD_MUTEX_TYPES=y
+CONFIG_RR_INTERVAL=200
+CONFIG_RTC=y
+CONFIG_RTC_DRIVER=y
+CONFIG_SCHED_WAITPID=y
+CONFIG_SPI_CMDDATA=y
+CONFIG_START_DAY=29
+CONFIG_START_MONTH=11
+CONFIG_START_YEAR=2019
+CONFIG_SYSLOG_BUFFER=y
+CONFIG_SYSTEM_DHCPC_RENEW=y
+CONFIG_SYSTEM_NSH=y
+CONFIG_SYSTEM_PING=y
+CONFIG_TIMER=y
+CONFIG_TLS_TASK_NELEM=4
+CONFIG_WIRELESS=y
+CONFIG_WIRELESS_WAPI=y
+CONFIG_WIRELESS_WAPI_CMDTOOL=y
+CONFIG_WIRELESS_WAPI_INITCONF=y
+CONFIG_WIRELESS_WAPI_STACKSIZE=4096
diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs 
b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs
index 08c948f0b27..cf683d95699 100644
--- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs
+++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/Make.defs
@@ -34,6 +34,10 @@ ifeq ($(CONFIG_LCD_ST7789),y)
 CSRCS += esp32s3_board_lcd_st7789.c
 endif
 
+ifeq ($(CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD),y)
+CSRCS += esp32s3_kbd.c
+endif
+
 DEPPATH += --dep-path board
 VPATH += :board
 CFLAGS += 
${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)$(CONFIG_ARCH)$(DELIM)src$(DELIM)board$(DELIM)board
diff --git 
a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h 
b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h
index 299e6cd91e6..f9ddc1e14e9 100644
--- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h
+++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3-m5-cardputer.h
@@ -87,5 +87,25 @@ int esp32s3_bringup(void);
 int board_i2c_init(void);
 #endif
 
+/****************************************************************************
+ * Name: esp32s3_kbd_initialize
+ *
+ * Description:
+ *   Initialize and register the M5Stack Cardputer matrix keyboard driver as
+ *   a NuttX keyboard device (/dev/kbdN).
+ *
+ * Input Parameters:
+ *   devpath - The full path to the keyboard device, e.g. "/dev/kbd0".
+ *
+ * Returned Value:
+ *   Zero (OK) is returned on success; A negated errno value is returned on
+ *   any failure.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD
+int esp32s3_kbd_initialize(FAR const char *devpath);
+#endif
+
 #endif /* __ASSEMBLY__ */
 #endif /* 
__BOARDS_XTENSA_ESP32S3_ESP32S3_M5_CARDPUTER_SRC_ESP32S3_M5_CARDPUTER_H */
diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c 
b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c
index 4bd10fcca08..c4fa00fb7dd 100644
--- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c
+++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_board_spi.c
@@ -92,6 +92,25 @@ int esp32s3_spi2_cmddata(struct spi_dev_s *dev, uint32_t 
devid, bool cmd)
 
 #endif
 
+/****************************************************************************
+ * Name: esp32s3_spi3_cmddata
+ *
+ * Description:
+ *   SPI3 only drives the microSD card, which does not use the SPI cmd/data
+ *   line.  This stub is required to link when CONFIG_SPI_CMDDATA is enabled
+ *   for the ST7789 display on SPI2 while SPI3 is also in use.
+ *
+ ****************************************************************************/
+
+#if defined(CONFIG_ESP32S3_SPI3) && defined(CONFIG_SPI_CMDDATA)
+
+int esp32s3_spi3_cmddata(struct spi_dev_s *dev, uint32_t devid, bool cmd)
+{
+  return -ENODEV;
+}
+
+#endif
+
 /****************************************************************************
  * Name: esp32s3_spi3_status
  *
diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c 
b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c
index 0d3f5c7afad..1c1c6db461d 100644
--- a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c
+++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_bringup.c
@@ -210,6 +210,16 @@ int esp32s3_bringup(void)
     }
 #endif
 
+#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD
+  /* Register the Cardputer matrix keyboard */
+
+  ret = esp32s3_kbd_initialize("/dev/kbd0");
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: Failed to initialize keyboard: %d\n", ret);
+    }
+#endif
+
   /* If we got here then perhaps not all initialization was successful, but
    * at least enough succeeded to bring-up NSH with perhaps reduced
    * capabilities.
diff --git a/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c 
b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c
new file mode 100644
index 00000000000..c4dfef46b13
--- /dev/null
+++ b/boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.c
@@ -0,0 +1,397 @@
+/****************************************************************************
+ * boards/xtensa/esp32s3/esp32s3-m5-cardputer/src/esp32s3_kbd.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 <nuttx/config.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <debug.h>
+#include <syslog.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/clock.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/input/keyboard.h>
+
+#include <arch/board/board.h>
+
+#include "espressif/esp_gpio.h"
+#include "esp32s3-m5-cardputer.h"
+
+#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The 56-key keyboard is an 8x7 matrix.  A 74HC138 3-to-8 demultiplexer
+ * drives one of eight rows low at a time (selected by SEL0..SEL2); the seven
+ * column inputs are read back (a pressed key pulls its column low).
+ *
+ * Electrically this gives an 8x7 grid.  Following the M5Cardputer wiring,
+ * each scanned position (demux index i in 0..7, column j in 0..6) maps to
+ * the physical 4x14 key layout as:
+ *
+ *   physical row    y = i % 4
+ *   physical column x = j + (i / 4) * 7
+ */
+
+#define KBD_NSEL        3
+#define KBD_NCOL        7
+#define KBD_NROW_PHYS   4
+#define KBD_NCOL_PHYS   14
+
+/* Scan/debounce period */
+
+#define KBD_POLL_TICKS  MSEC2TICK(20)
+
+/* Modifier key positions in the physical layout (row, col) */
+
+#define KBD_SHIFT_Y     2
+#define KBD_SHIFT_X     1
+#define KBD_CTRL_Y      3
+#define KBD_CTRL_X      0
+#define KBD_FN_Y        2
+#define KBD_FN_X        0
+
+/* Codes emitted for the Fn + navigation cluster (cursor keys).  They are
+ * placed above the printable ASCII range so applications reading /dev/kbd0
+ * can tell them apart from regular characters.
+ */
+
+#define KBD_CODE_UP     0x80
+#define KBD_CODE_DOWN   0x81
+#define KBD_CODE_LEFT   0x82
+#define KBD_CODE_RIGHT  0x83
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct esp32s3_kbd_s
+{
+  struct keyboard_lowerhalf_s lower;       /* Keyboard upper-half binding */
+  struct work_s work;                      /* Scanning work item */
+
+  /* Debounced key state and the code emitted on each key press */
+
+  bool pressed[KBD_NROW_PHYS][KBD_NCOL_PHYS];
+  uint8_t code[KBD_NROW_PHYS][KBD_NCOL_PHYS];
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const uint8_t g_sel_pins[KBD_NSEL] =
+{
+  CARDPUTER_GPIO_KB_A0, CARDPUTER_GPIO_KB_A1, CARDPUTER_GPIO_KB_A2
+};
+
+static const uint8_t g_col_pins[KBD_NCOL] =
+{
+  CARDPUTER_GPIO_KB_C0, CARDPUTER_GPIO_KB_C1, CARDPUTER_GPIO_KB_C2,
+  CARDPUTER_GPIO_KB_C3, CARDPUTER_GPIO_KB_C4, CARDPUTER_GPIO_KB_C5,
+  CARDPUTER_GPIO_KB_C6
+};
+
+/* Physical 4x14 layout.  '\0' marks a modifier or unused position (these are
+ * handled by their fixed positions, not emitted as characters).  This table
+ * follows the M5Cardputer key layout and should be verified against the
+ * hardware if the printed legends do not match.
+ */
+
+static const char g_normal[KBD_NROW_PHYS][KBD_NCOL_PHYS] =
+{
+  {'`',  '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b'},
+  {'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'},
+  {'\0', '\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '\r'},
+  {'\0', '\0', '\0', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', ' '}
+};
+
+static const char g_shift[KBD_NROW_PHYS][KBD_NCOL_PHYS] =
+{
+  {'~',  '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b'},
+  {'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'},
+  {'\0', '\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '\r'},
+  {'\0', '\0', '\0', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', ' '}
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32s3_kbd_select
+ *
+ * Description:
+ *   Drive the 74HC138 select lines with the given row index (0..7).
+ *
+ ****************************************************************************/
+
+static void esp32s3_kbd_select(uint8_t row)
+{
+  int i;
+
+  for (i = 0; i < KBD_NSEL; i++)
+    {
+      esp_gpiowrite(g_sel_pins[i], (row >> i) & 1);
+    }
+}
+
+/****************************************************************************
+ * Name: esp32s3_kbd_resolve
+ *
+ * Description:
+ *   Resolve a physical key position into a character, applying the SHIFT,
+ *   CTRL and FN modifiers.  Returns 0 for modifier/unused positions.
+ *
+ ****************************************************************************/
+
+static uint8_t esp32s3_kbd_resolve(int y, int x, bool shift, bool ctrl,
+                                   bool fn)
+{
+  char ch;
+
+  /* FN layer: the ; . , / cluster becomes the cursor (arrow) keys, reported
+   * as out-of-band codes so applications can act on them (e.g. scrolling).
+   */
+
+  if (fn)
+    {
+      if (y == 2 && x == 11)
+        {
+          return KBD_CODE_UP;                        /* Fn + ';' */
+        }
+
+      if (y == 3 && x == 11)
+        {
+          return KBD_CODE_DOWN;                      /* Fn + '.' */
+        }
+
+      if (y == 3 && x == 10)
+        {
+          return KBD_CODE_LEFT;                      /* Fn + ',' */
+        }
+
+      if (y == 3 && x == 12)
+        {
+          return KBD_CODE_RIGHT;                     /* Fn + '/' */
+        }
+    }
+
+  ch = shift ? g_shift[y][x] : g_normal[y][x];
+
+  if (ch == '\0')
+    {
+      return 0;
+    }
+
+  /* CTRL turns a letter into its control code (e.g. CTRL-C -> 0x03) */
+
+  if (ctrl && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')))
+    {
+      ch = (char)(ch & 0x1f);
+    }
+
+  return (uint8_t)ch;
+}
+
+/****************************************************************************
+ * Name: esp32s3_kbd_worker
+ *
+ * Description:
+ *   Periodically scan the matrix and report key transitions.
+ *
+ ****************************************************************************/
+
+static void esp32s3_kbd_worker(FAR void *arg)
+{
+  FAR struct esp32s3_kbd_s *priv = arg;
+  bool scan[KBD_NROW_PHYS][KBD_NCOL_PHYS];
+  bool shift;
+  bool ctrl;
+  bool fn;
+  int i;
+  int j;
+  int y;
+  int x;
+
+  memset(scan, 0, sizeof(scan));
+
+  /* Read the full matrix into the physical layout */
+
+  for (i = 0; i < (1 << KBD_NSEL); i++)
+    {
+      esp32s3_kbd_select((uint8_t)i);
+      up_udelay(5);
+
+      for (j = 0; j < KBD_NCOL; j++)
+        {
+          if (!esp_gpioread(g_col_pins[j]))
+            {
+              /* Map the (demux index i, column j) to the physical 4x14 grid
+               * exactly as the M5Cardputer firmware does:
+               *   y = 3 - (i % 4)
+               *   x = (i > 3) ? (2 * j) : (2 * j + 1)
+               */
+
+              y = (KBD_NROW_PHYS - 1) - (i % KBD_NROW_PHYS);
+              x = (i >= KBD_NROW_PHYS) ? (2 * j) : (2 * j + 1);
+              scan[y][x] = true;
+            }
+        }
+    }
+
+  /* Latch the modifier state for this scan */
+
+  shift = scan[KBD_SHIFT_Y][KBD_SHIFT_X];
+  ctrl  = scan[KBD_CTRL_Y][KBD_CTRL_X];
+  fn    = scan[KBD_FN_Y][KBD_FN_X];
+
+  /* Emit press/release transitions */
+
+  for (y = 0; y < KBD_NROW_PHYS; y++)
+    {
+      for (x = 0; x < KBD_NCOL_PHYS; x++)
+        {
+          if (scan[y][x] == priv->pressed[y][x])
+            {
+              continue;
+            }
+
+          priv->pressed[y][x] = scan[y][x];
+
+          if (scan[y][x])
+            {
+              uint8_t code = esp32s3_kbd_resolve(y, x, shift, ctrl, fn);
+#ifdef CONFIG_ESP32S3_M5_CARDPUTER_KBD_DEBUG
+              syslog(LOG_INFO, "kbd: press y=%d x=%d code=0x%02x '%c'\n",
+                     y, x, code, (code >= 0x20 && code < 0x7f) ? code : '.');
+#endif
+              if (code != 0)
+                {
+                  priv->code[y][x] = code;
+                  keyboard_event(&priv->lower, code, KEYBOARD_PRESS);
+                }
+            }
+          else if (priv->code[y][x] != 0)
+            {
+              keyboard_event(&priv->lower, priv->code[y][x],
+                             KEYBOARD_RELEASE);
+              priv->code[y][x] = 0;
+            }
+        }
+    }
+
+  work_queue(LPWORK, &priv->work, esp32s3_kbd_worker, priv, KBD_POLL_TICKS);
+}
+
+/****************************************************************************
+ * Name: esp32s3_kbd_open / esp32s3_kbd_close
+ ****************************************************************************/
+
+static int esp32s3_kbd_open(FAR struct keyboard_lowerhalf_s *lower)
+{
+  return OK;
+}
+
+static int esp32s3_kbd_close(FAR struct keyboard_lowerhalf_s *lower)
+{
+  return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32s3_kbd_initialize
+ *
+ * Description:
+ *   Initialize and register the Cardputer matrix keyboard at devpath
+ *   (e.g. "/dev/kbd0").
+ *
+ ****************************************************************************/
+
+int esp32s3_kbd_initialize(FAR const char *devpath)
+{
+  FAR struct esp32s3_kbd_s *priv;
+  int ret;
+  int i;
+
+  priv = kmm_zalloc(sizeof(struct esp32s3_kbd_s));
+  if (priv == NULL)
+    {
+      return -ENOMEM;
+    }
+
+  priv->lower.open  = esp32s3_kbd_open;
+  priv->lower.close = esp32s3_kbd_close;
+  priv->lower.write = NULL;
+
+  /* Configure the demux select lines as outputs and the column inputs with
+   * pull-ups (a pressed key pulls the selected column low).
+   */
+
+  for (i = 0; i < KBD_NSEL; i++)
+    {
+      esp_configgpio(g_sel_pins[i], OUTPUT);
+      esp_gpiowrite(g_sel_pins[i], 1);
+    }
+
+  for (i = 0; i < KBD_NCOL; i++)
+    {
+      esp_configgpio(g_col_pins[i], INPUT_PULLUP);
+    }
+
+  ret = keyboard_register(&priv->lower, devpath,
+                          CONFIG_ESP32S3_M5_CARDPUTER_KBD_BUFNUM);
+  if (ret < 0)
+    {
+      ierr("ERROR: keyboard_register(%s) failed: %d\n", devpath, ret);
+      kmm_free(priv);
+      return ret;
+    }
+
+  /* Start the periodic scan */
+
+  ret = work_queue(LPWORK, &priv->work, esp32s3_kbd_worker, priv,
+                   KBD_POLL_TICKS);
+  if (ret < 0)
+    {
+      ierr("ERROR: failed to start keyboard scan: %d\n", ret);
+      keyboard_unregister(&priv->lower, devpath);
+      kmm_free(priv);
+      return ret;
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_ESP32S3_M5_CARDPUTER_KEYBOARD */

Reply via email to