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

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

commit 2bf531b3f4f424b18f846e2b9dbbe179c04e0ae0
Author: Filipe Cavalcanti <[email protected]>
AuthorDate: Wed Aug 5 22:32:12 2026 -0300

    boards/risc-v: LCD support on esp32p4-tab5
    
    Adds support for st7121 and st7123 variants for LCD support
    on the esp32p4-tab5 board. Includes power management though IO
    expander under 'hmi_power' and new board KConfig options.
    
    Signed-off-by: Filipe Cavalcanti <[email protected]>
---
 boards/risc-v/esp32p4/esp32p4-tab5/Kconfig         |  67 +++
 boards/risc-v/esp32p4/esp32p4-tab5/include/board.h |  38 +-
 .../risc-v/esp32p4/esp32p4-tab5/src/CMakeLists.txt |  15 +-
 boards/risc-v/esp32p4/esp32p4-tab5/src/Make.defs   |  13 +
 .../risc-v/esp32p4/esp32p4-tab5/src/esp32p4-tab5.h | 109 +++++
 .../esp32p4/esp32p4-tab5/src/esp32p4_bringup.c     |  65 ++-
 .../esp32p4/esp32p4-tab5/src/esp32p4_display.c     | 485 +++++++++++++++++++++
 .../esp32p4/esp32p4-tab5/src/esp32p4_hmi_power.c   | 187 ++++++++
 .../esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.c  | 375 ++++++++++++++++
 .../esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.h  |  75 ++++
 .../esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.c  | 425 ++++++++++++++++++
 .../esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.h  |  74 ++++
 .../esp32p4/esp32p4-tab5/src/esp32p4_mipi_dpi.c    |  69 +++
 13 files changed, 1990 insertions(+), 7 deletions(-)

diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/Kconfig 
b/boards/risc-v/esp32p4/esp32p4-tab5/Kconfig
index ed922979b37..64cbc517d40 100644
--- a/boards/risc-v/esp32p4/esp32p4-tab5/Kconfig
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/Kconfig
@@ -5,6 +5,10 @@
 
 if ARCH_BOARD_ESP32P4_TAB5
 
+config ESP32P4_TAB5_HMI_POWER
+       bool
+       default n
+
 config ESP32P4_TAB5_IOEXPANDER
        bool "Enable the Tab5 board IO Expander"
        default n
@@ -31,4 +35,67 @@ if ESP32P4_TAB5_IOEXPANDER
                        Enable the second IO Expander.
 endif
 
+config ESP32P4_TAB5_LCD_POWER
+       bool "LCD / MIPI PHY power and reset path"
+       default n
+       select ESP32P4_TAB5_IOEXPANDER
+       select ESP32P4_TAB5_HMI_POWER
+       ---help---
+               At board bring-up (after I2C0 init), enable on-chip LDO channel 
3
+               at 2500 mV for VDD_MIPI_DPHY and raise LCD_EN on expander pin 
P4.
+
+               This does NOT initialize the MIPI-DSI host, the LCD panel, or
+               /dev/fb0 by itself.  Enable ESP32P4_TAB5_MIPI_DSI (and
+               ESPRESSIF_MIPI_DSI) to register the host after this power path.
+               Enable ESP32P4_TAB5_LCD for panel initialization + /dev/fb0.
+
+config ESP32P4_TAB5_MIPI_DSI
+       bool "Register MIPI-DSI host at bring-up"
+       default n
+       depends on ESP32P4_TAB5_LCD_POWER && ESPRESSIF_MIPI_DSI
+       ---help---
+               After the LCD power path succeeds, call
+               esp_mipi_dsi_initialize() so the generic MIPI-DSI host is
+               registered (LP DCS/generic transfer ready).
+
+               Does NOT run panel initialization or create /dev/fb0 by itself;
+               enable ESP32P4_TAB5_LCD for that.
+
+config ESP32P4_TAB5_LCD
+       bool "LCD panel and /dev/fb0"
+       default n
+       depends on ESP32P4_TAB5_MIPI_DSI
+       select DRIVERS_VIDEO
+       select VIDEO_FB
+       select FB_UPDATE
+       ---help---
+               Bring up the selected Tab5 MIPI-DSI panel (M5Stack init table 
and
+               720×1280 RGB565 timings), allocate a PSRAM framebuffer, bind it
+               to the DSI bridge via DW-GDMA, and register /dev/fb0.
+
+config ESP32P4_TAB5_LCD_APO_TEST
+       bool "All-Pixels-On flash after FB bring-up"
+       default y
+       depends on ESP32P4_TAB5_LCD
+       ---help---
+               After /dev/fb0 is ready, force All-Pixels-On for ~1 s, then
+               All-Pixels-Off and restore the solid red test fill.
+
+choice
+       prompt "Tab5 LCD panel controller"
+       default ESP32P4_TAB5_LCD_ST7121
+       depends on ESP32P4_TAB5_LCD
+
+config ESP32P4_TAB5_LCD_ST7121
+       bool "ST7121"
+       ---help---
+               Use the ST7121 initialization table and display timings.
+
+config ESP32P4_TAB5_LCD_ST7123
+       bool "ST7123"
+       ---help---
+               Use the ST7123 initialization table and display timings.
+
+endchoice
+
 endif # ARCH_BOARD_ESP32P4_TAB5
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/include/board.h 
b/boards/risc-v/esp32p4/esp32p4-tab5/include/board.h
index 7dea43e0b41..07b0b78f387 100644
--- a/boards/risc-v/esp32p4/esp32p4-tab5/include/board.h
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/include/board.h
@@ -55,18 +55,46 @@
 
 #define BUTTON_BOOT            35
 
-/* Display - MIPI-DSI ILI9881C (NOT implemented; pins documented only)
+/* Display - MIPI-DSI ST7121/ST7123 (M5Stack Tab5 reference timings)
  *
  * The DSI data/clock lanes are dedicated MIPI pins (not GPIO-muxed) powered
- * by VDD_MIPI_DPHY; only the backlight enable is a GPIO.  Panel reset is the
- * board power-on reset (BSP_LCD_RST = NC) driven by IO expander 0x43 P4.
+ * by VDD_MIPI_DPHY (LDO ch3 @ 2500 mV).  Backlight enable is GPIO22.
+ * BSP_LCD_RST is NC; LCD_EN / reset control is PI4IOE5V6408 @0x43 P4.
+ *
+ * Enable /dev/fb0 with CONFIG_ESP32P4_TAB5_LCD (see configs/lcd), which
+ * selects power + MIPI host + VIDEO_FB.  Select the matching panel with
+ * CONFIG_ESP32P4_TAB5_LCD_ST7121 or CONFIG_ESP32P4_TAB5_LCD_ST7123.
  */
 
 #define TAB5_GPIO_LCD_BL_EN    22  /* Backlight enable -> ME2212 boost EN */
 
-/* Touch - GT911 (NOT implemented; pins documented only) */
+#define TAB5_MIPI_DSI_H_RES              720
+#define TAB5_MIPI_DSI_V_RES              1280
+#define TAB5_MIPI_DSI_DPI_CLK_MHZ        70
+#define TAB5_MIPI_DSI_HSYNC_PULSE_WIDTH  2
+#define TAB5_MIPI_DSI_HSYNC_BACK_PORCH   40
+#define TAB5_MIPI_DSI_HSYNC_FRONT_PORCH  40
+#define TAB5_MIPI_DSI_LANES              2
+
+#ifdef CONFIG_ESP32P4_TAB5_LCD_ST7123
+#  define TAB5_LCD_PANEL_NAME             "ST7123"
+#  define TAB5_MIPI_DSI_VSYNC_PULSE_WIDTH  2
+#  define TAB5_MIPI_DSI_VSYNC_BACK_PORCH   8
+#  define TAB5_MIPI_DSI_VSYNC_FRONT_PORCH  220
+#  define TAB5_MIPI_DSI_LANE_BITRATE_MBPS  1000
+#else
+#  define TAB5_LCD_PANEL_NAME             "ST7121"
+#  define TAB5_MIPI_DSI_VSYNC_PULSE_WIDTH  20
+#  define TAB5_MIPI_DSI_VSYNC_BACK_PORCH   24
+#  define TAB5_MIPI_DSI_VSYNC_FRONT_PORCH  200
+#  define TAB5_MIPI_DSI_LANE_BITRATE_MBPS  965
+#endif
+
+/* Touch - ST7121/ST7123 TDDI @0x55 (NOT implemented; INT GPIO23).  Older
+ * ILI9881 units used GT911 @0x14 on the same INT pin.
+ */
 
-#define TAB5_GPIO_TP_INT       23  /* GT911 touch interrupt */
+#define TAB5_GPIO_TP_INT       23  /* Touch interrupt */
 
 /* Audio - ES8388 codec (out) + ES7210 mic array (in), I2S (pins only) */
 
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/CMakeLists.txt 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/CMakeLists.txt
index 3ee63748b93..1461f115824 100644
--- a/boards/risc-v/esp32p4/esp32p4-tab5/src/CMakeLists.txt
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/CMakeLists.txt
@@ -24,7 +24,20 @@ set(SRCS esp32p4_boot.c esp32p4_bringup.c)
 
 if(CONFIG_ESP32P4_TAB5_IOEXPANDER)
   list(APPEND SRCS esp32p4_ioexpander.c)
-endif
+endif()
+
+if(CONFIG_ESP32P4_TAB5_HMI_POWER)
+  list(APPEND SRCS esp32p4_hmi_power.c)
+endif()
+
+if(CONFIG_ESP32P4_TAB5_LCD)
+  list(APPEND SRCS esp32p4_display.c esp32p4_mipi_dpi.c)
+  if(CONFIG_ESP32P4_TAB5_LCD_ST7121)
+    list(APPEND SRCS esp32p4_lcd_st7121.c)
+  elseif(CONFIG_ESP32P4_TAB5_LCD_ST7123)
+    list(APPEND SRCS esp32p4_lcd_st7123.c)
+  endif()
+endif()
 
 if(CONFIG_BOARDCTL)
   if(CONFIG_BOARDCTL_RESET)
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/Make.defs 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/Make.defs
index 7a99aee61fc..46f7f7281fb 100644
--- a/boards/risc-v/esp32p4/esp32p4-tab5/src/Make.defs
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/Make.defs
@@ -28,6 +28,19 @@ ifeq ($(CONFIG_ESP32P4_TAB5_IOEXPANDER),y)
   CSRCS += esp32p4_ioexpander.c
 endif
 
+ifeq ($(CONFIG_ESP32P4_TAB5_HMI_POWER),y)
+  CSRCS += esp32p4_hmi_power.c
+endif
+
+ifeq ($(CONFIG_ESP32P4_TAB5_LCD),y)
+  CSRCS += esp32p4_display.c esp32p4_mipi_dpi.c
+  ifeq ($(CONFIG_ESP32P4_TAB5_LCD_ST7121),y)
+    CSRCS += esp32p4_lcd_st7121.c
+  else ifeq ($(CONFIG_ESP32P4_TAB5_LCD_ST7123),y)
+    CSRCS += esp32p4_lcd_st7123.c
+  endif
+endif
+
 ifeq ($(CONFIG_BOARDCTL),y)
   ifeq ($(CONFIG_BOARDCTL_RESET),y)
     CSRCS += esp32p4_reset.c
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4-tab5.h 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4-tab5.h
index 8718f84a56f..5c7950cda82 100644
--- a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4-tab5.h
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4-tab5.h
@@ -28,6 +28,13 @@
  ****************************************************************************/
 
 #include <nuttx/config.h>
+#include <nuttx/compiler.h>
+
+#include <stdbool.h>
+
+#ifdef CONFIG_ESPRESSIF_MIPI_DSI
+#  include "espressif/esp_mipi_dsi.h"
+#endif
 
 /****************************************************************************
  * Public Data
@@ -56,6 +63,108 @@
 
 int esp_bringup(void);
 
+#ifdef CONFIG_ESP32P4_TAB5_LCD_POWER
+
+/****************************************************************************
+ * Name: tab5_mipi_phy_power
+ *
+ * Description:
+ *   Enable or disable LDO channel 3 (VDD_MIPI_DPHY @ 2500 mV).
+ *
+ * Input Parameters:
+ *   on - True to enable the LDO channel, false to disable it.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_mipi_phy_power(bool on);
+
+/****************************************************************************
+ * Name: tab5_lcd_enable
+ *
+ * Description:
+ *   Drive PI4IOE P4 (BSP_LCD_EN).  true = enable (high).
+ *
+ * Input Parameters:
+ *   enable - True to enable the LCD rail, false to disable it.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_lcd_enable(bool enable);
+
+/****************************************************************************
+ * Name: tab5_lcd_backlight
+ *
+ * Description:
+ *   Drive backlight enable GPIO22 (ME2212 boost EN).
+ *
+ * Input Parameters:
+ *   on - True to enable the backlight, false to disable it.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_lcd_backlight(bool on);
+
+/****************************************************************************
+ * Name: tab5_lcd_power_init
+ *
+ * Description:
+ *   Power MIPI PHY LDO, init PI4IOE, and enable LCD (P4 high).
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_lcd_power_init(void);
+
+#endif /* CONFIG_ESP32P4_TAB5_LCD_POWER */
+
+#ifdef CONFIG_ESP32P4_TAB5_LCD
+
+/****************************************************************************
+ * Name: tab5_fb_reload_test_pattern
+ *
+ * Description:
+ *   Re-fill the plane with the bring-up test color and cache write-back.
+ *   Call after fb_register(): the generic FB driver memset()s the plane
+ *   and would otherwise leave a black DMA buffer.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_fb_reload_test_pattern(void);
+
+/****************************************************************************
+ * Name: tab5_mipi_dsi_dpi_config
+ *
+ * Description:
+ *   Fill DPI timing for the selected Tab5 ST712x panel.  Arch
+ *   host has no panel defaults — board code supplies this to
+ *   esp_mipi_dsi_configure_dpi().
+ *
+ * Input Parameters:
+ *   cfg - Pointer to the DPI configuration structure.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+void tab5_mipi_dsi_dpi_config(FAR struct esp_mipi_dsi_dpi_config_s *cfg);
+
+#endif /* CONFIG_ESP32P4_TAB5_LCD */
+
 #ifdef CONFIG_ESP32P4_TAB5_IOEXPANDER
 /****************************************************************************
  * Name: tab5_pi4ioe_init
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_bringup.c 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_bringup.c
index 8de05b13a2d..ebfae61ee92 100644
--- a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_bringup.c
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_bringup.c
@@ -42,6 +42,14 @@
 #  include "esp_board_i2c.h"
 #endif
 
+#ifdef CONFIG_ESP32P4_TAB5_MIPI_DSI
+#  include "espressif/esp_mipi_dsi.h"
+#endif
+
+#ifdef CONFIG_ESP32P4_TAB5_LCD
+#  include <nuttx/video/fb.h>
+#endif
+
 #include <arch/board/board.h>
 
 #include "esp32p4-tab5.h"
@@ -103,11 +111,66 @@ int esp_bringup(void)
   ret = tab5_pi4ioe_init();
   if (ret < 0)
     {
-      syslog(LOG_ERR, "Failed to initialize Tab5 IO expanders: %d\n", ret);
+      syslog(LOG_ERR, "ERROR: failed to init IO expanders: %d\n", ret);
+      return ret;
+    }
+#endif
+
+#ifdef CONFIG_ESP32P4_TAB5_LCD_POWER
+  /* MIPI PHY LDO + PI4IOE LCD_EN (before optional DSI host) */
+
+  ret = tab5_lcd_power_init();
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: failed to init LCD power path: %d\n", ret);
       return ret;
     }
 #endif
 
+#ifdef CONFIG_ESP32P4_TAB5_MIPI_DSI
+  /* Tab5 ST712x bus: 2 lanes @ board.h bitrate. */
+
+  struct esp_mipi_dsi_bus_config_s bus_cfg =
+    {
+      .num_data_lanes = TAB5_MIPI_DSI_LANES,
+      .lane_bit_rate_mbps = TAB5_MIPI_DSI_LANE_BITRATE_MBPS,
+    };
+
+  ret = esp_mipi_dsi_initialize(&bus_cfg);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: failed to init MIPI-DSI host: %d\n",
+              ret);
+      return ret;
+    }
+
+  syslog(LOG_INFO, "Tab5: MIPI-DSI host registered\n");
+#endif
+
+#ifdef CONFIG_ESP32P4_TAB5_LCD
+  /* Selected panel DCS + DPI + DW-GDMA FB → /dev/fb0 */
+
+  ret = fb_register(0, 0);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: failed to register /dev/fb0: %d\n", ret);
+    }
+  else
+    {
+      /* fb_register() clears the plane; restore test red for DMA. */
+
+      ret = tab5_fb_reload_test_pattern();
+      if (ret < 0)
+        {
+          syslog(LOG_ERR,
+                  "ERROR: failed to reload FB test pattern: %d\n", ret);
+        }
+
+      syslog(LOG_INFO, "/dev/fb0 registered (%s)\n",
+              TAB5_LCD_PANEL_NAME);
+    }
+#endif
+
 #ifdef CONFIG_FS_TMPFS
   /* Mount the tmpfs file system */
 
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_display.c 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_display.c
new file mode 100644
index 00000000000..7225b722d06
--- /dev/null
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_display.c
@@ -0,0 +1,485 @@
+/****************************************************************************
+ * boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_display.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.
+ *
+ ****************************************************************************/
+
+/* Tab5 display / framebuffer glue (PinePhone-style up_fbinitialize split).
+ *
+ * Sequence (ESP-IDF DPI panel + selected ST712x controller):
+ *   1. Allocate RGB565 FB (PSRAM)
+ *   2. Host already registered by bringup
+ *   3. configure_dpi -> panel DCS -> bind FB -> video_start -> display_on
+ *   4. Backlight on
+ *   5. Optional All-Pixels-On bring-up flash
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <debug.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/compiler.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/video/fb.h>
+#include <nuttx/video/mipi_display.h>
+#include <nuttx/video/mipi_dsi.h>
+
+#include <arch/board/board.h>
+
+#include "espressif/esp_mipi_dsi.h"
+
+#include "esp32p4-tab5.h"
+
+#ifdef CONFIG_ESP32P4_TAB5_LCD_ST7123
+#  include "esp32p4_lcd_st7123.h"
+#  define tab5_panel_initialize tab5_st7123_initialize
+#else
+#  include "esp32p4_lcd_st7121.h"
+#  define tab5_panel_initialize tab5_st7121_initialize
+#endif
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define TAB5_FB_BPP        16
+#define TAB5_FB_WIDTH      TAB5_MIPI_DSI_H_RES
+#define TAB5_FB_HEIGHT     TAB5_MIPI_DSI_V_RES
+#define TAB5_FB_STRIDE     (TAB5_FB_WIDTH * (TAB5_FB_BPP / 8))
+#define TAB5_FB_SIZE       (TAB5_FB_STRIDE * TAB5_FB_HEIGHT)
+
+#define TAB5_FB_TEST_COLOR  0xf800
+
+#define TAB5_DCS_ALL_PIXELS_ON   0x23
+#define TAB5_DCS_ALL_PIXELS_OFF  0x22
+#define TAB5_APO_TEST_MS         1000
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int tab5_getvideoinfo(FAR struct fb_vtable_s *vtable,
+                             FAR struct fb_videoinfo_s *vinfo);
+static int tab5_getplaneinfo(FAR struct fb_vtable_s *vtable, int planeno,
+                             FAR struct fb_planeinfo_s *pinfo);
+#ifdef CONFIG_FB_UPDATE
+static int tab5_updatearea(FAR struct fb_vtable_s *vtable,
+                           FAR const struct fb_area_s *area);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static struct fb_vtable_s g_tab5_vtable =
+{
+  .getvideoinfo = tab5_getvideoinfo,
+  .getplaneinfo = tab5_getplaneinfo,
+#ifdef CONFIG_FB_UPDATE
+  .updatearea   = tab5_updatearea,
+#endif
+};
+
+static struct fb_videoinfo_s g_tab5_video =
+{
+  .fmt     = FB_FMT_RGB16_565,
+  .xres    = TAB5_FB_WIDTH,
+  .yres    = TAB5_FB_HEIGHT,
+  .nplanes = 1,
+};
+
+static struct fb_planeinfo_s g_tab5_plane =
+{
+  .fbmem   = NULL,
+  .fblen   = TAB5_FB_SIZE,
+  .stride  = TAB5_FB_STRIDE,
+  .display = 0,
+  .bpp     = TAB5_FB_BPP,
+  .xres_virtual = TAB5_FB_WIDTH,
+  .yres_virtual = TAB5_FB_HEIGHT,
+  .xoffset = 0,
+  .yoffset = 0,
+};
+
+static FAR uint16_t *g_tab5_fb;
+static bool g_tab5_fb_ready;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tab5_getvideoinfo
+ *
+ * Description:
+ *   Return the video information.
+ *
+ * Input Parameters:
+ *   vtable - The vtable.
+ *   vinfo - The video information.
+ *
+ * Returned Value:
+ *   Zero on success.
+ *
+ ****************************************************************************/
+
+static int tab5_getvideoinfo(FAR struct fb_vtable_s *vtable,
+                             FAR struct fb_videoinfo_s *vinfo)
+{
+  DEBUGASSERT(vtable != NULL && vtable == &g_tab5_vtable && vinfo != NULL);
+  memcpy(vinfo, &g_tab5_video, sizeof(*vinfo));
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tab5_getplaneinfo
+ *
+ * Description:
+ *   Return the plane information.
+ *
+ * Input Parameters:
+ *   vtable - The vtable.
+ *   planeno - The plane number.
+ *   pinfo - The plane information.
+ *
+ * Returned Value:
+ *   Zero on success, -EINVAL if the plane number is invalid.
+ *
+ ****************************************************************************/
+
+static int tab5_getplaneinfo(FAR struct fb_vtable_s *vtable, int planeno,
+                             FAR struct fb_planeinfo_s *pinfo)
+{
+  DEBUGASSERT(vtable != NULL && vtable == &g_tab5_vtable && pinfo != NULL);
+
+  if (planeno != 0 || g_tab5_plane.fbmem == NULL)
+    {
+      return -EINVAL;
+    }
+
+  memcpy(pinfo, &g_tab5_plane, sizeof(*pinfo));
+  return OK;
+}
+
+#ifdef CONFIG_FB_UPDATE
+/****************************************************************************
+ * Name: tab5_updatearea
+ *
+ * Description:
+ *   Update a region of the framebuffer.
+ *
+ * Input Parameters:
+ *   vtable - The vtable.
+ *   area - The area to update.
+ *
+ * Returned Value:
+ *   Zero on success, -EAGAIN if the framebuffer is not ready,
+ *   -EINVAL if the area is out of bounds.
+ ****************************************************************************/
+
+static int tab5_updatearea(FAR struct fb_vtable_s *vtable,
+                           FAR const struct fb_area_s *area)
+{
+  size_t offset;
+  size_t len;
+
+  DEBUGASSERT(vtable != NULL && vtable == &g_tab5_vtable);
+
+  if (g_tab5_fb == NULL)
+    {
+      return -EAGAIN;
+    }
+
+  if (area == NULL)
+    {
+      return esp_mipi_dsi_flush_framebuffer(g_tab5_fb, TAB5_FB_SIZE);
+    }
+
+  if (area->y >= TAB5_FB_HEIGHT || area->x >= TAB5_FB_WIDTH)
+    {
+      return OK;
+    }
+
+  offset = (size_t)area->y * TAB5_FB_STRIDE +
+           (size_t)area->x * (TAB5_FB_BPP / 8);
+  len = (size_t)area->h * TAB5_FB_STRIDE;
+  if (offset + len > TAB5_FB_SIZE)
+    {
+      len = TAB5_FB_SIZE - offset;
+    }
+
+  return esp_mipi_dsi_flush_framebuffer((FAR uint8_t *)g_tab5_fb + offset,
+                                        len);
+}
+#endif /* CONFIG_FB_UPDATE */
+
+/****************************************************************************
+ * Name: tab5_fb_fill
+ *
+ * Description:
+ *   Fill the framebuffer with a given color.
+ *
+ * Input Parameters:
+ *   color - The color to fill the framebuffer with.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void tab5_fb_fill(uint16_t color)
+{
+  size_t i;
+  size_t npix = (size_t)TAB5_FB_WIDTH * TAB5_FB_HEIGHT;
+  int ret;
+
+  for (i = 0; i < npix; i++)
+    {
+      g_tab5_fb[i] = color;
+    }
+
+  ret = esp_mipi_dsi_flush_framebuffer(g_tab5_fb, TAB5_FB_SIZE);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: FB cache C2M sync failed: %d\n", ret);
+    }
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: up_fbinitialize
+ *
+ * Description:
+ *   Initialize the framebuffer.
+ *
+ * Input Parameters:
+ *   display - The display number.
+ *
+ * Returned Value:
+ *   Zero on success, -ENODEV on failure.
+ *
+ ****************************************************************************/
+
+int up_fbinitialize(int display)
+{
+  FAR struct mipi_dsi_host *host;
+  FAR struct mipi_dsi_device *device;
+  struct esp_mipi_dsi_dpi_config_s dpi;
+  int ret;
+
+  if (display != 0)
+    {
+      return -ENODEV;
+    }
+
+  if (g_tab5_fb_ready)
+    {
+      return OK;
+    }
+
+  g_tab5_fb = kumm_memalign(64, TAB5_FB_SIZE);
+  if (g_tab5_fb == NULL)
+    {
+      syslog(LOG_ERR,
+             "ERROR: FB alloc failed (%u bytes; enable PSRAM)\n",
+             (unsigned int)TAB5_FB_SIZE);
+      return -ENOMEM;
+    }
+
+  memset(g_tab5_fb, 0, TAB5_FB_SIZE);
+  g_tab5_plane.fbmem = g_tab5_fb;
+
+  host = esp_mipi_dsi_host_get();
+  if (host == NULL)
+    {
+      syslog(LOG_ERR,
+             "ERROR: MIPI-DSI host not ready "
+             "(enable ESP32P4_TAB5_MIPI_DSI)\n");
+      ret = -EAGAIN;
+      goto errout_fb;
+    }
+
+  /* IDF order: DPI config before panel DCS; display_on after video_start. */
+
+  syslog(LOG_INFO, "Configuring DPI...\n");
+  tab5_mipi_dsi_dpi_config(&dpi);
+  ret = esp_mipi_dsi_configure_dpi(&dpi);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: configure_dpi failed: %d\n", ret);
+      goto errout_fb;
+    }
+
+  syslog(LOG_INFO, "%s panel init...\n",
+         TAB5_LCD_PANEL_NAME);
+  device = tab5_panel_initialize(host);
+  if (device == NULL)
+    {
+      ret = -EIO;
+      goto errout_fb;
+    }
+
+  ret = esp_mipi_dsi_bind_framebuffer(g_tab5_fb, TAB5_FB_SIZE,
+                                      TAB5_FB_WIDTH, TAB5_FB_HEIGHT,
+                                      TAB5_FB_BPP);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: bind_framebuffer failed: %d\n", ret);
+      goto errout_fb;
+    }
+
+  tab5_fb_fill(TAB5_FB_TEST_COLOR);
+
+  ret = mipi_dsi_dcs_exit_sleep_mode(device);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: pre-video sleep_out failed: %d\n",
+             ret);
+      goto errout_fb;
+    }
+
+  up_mdelay(120);
+
+  syslog(LOG_INFO, "Starting video...\n");
+  ret = esp_mipi_dsi_video_start();
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: video_start failed: %d\n", ret);
+      goto errout_fb;
+    }
+
+  ret = mipi_dsi_dcs_set_display_on(device);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: post-video display_on failed: %d\n",
+             ret);
+      goto errout_fb;
+    }
+
+  tab5_fb_fill(TAB5_FB_TEST_COLOR);
+
+  ret = tab5_lcd_backlight(true);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: backlight failed: %d\n", ret);
+    }
+
+  g_tab5_fb_ready = true;
+  syslog(LOG_INFO,
+         "/dev/fb0 ready %ux%u RGB565 @ %p\n",
+         TAB5_FB_WIDTH, TAB5_FB_HEIGHT, g_tab5_fb);
+
+#ifdef CONFIG_ESP32P4_TAB5_LCD_APO_TEST
+  /* White flash proves DCS/BL; then restore normal mode + red FB. */
+
+  mipi_dsi_dcs_write(device, TAB5_DCS_ALL_PIXELS_ON, NULL, 0);
+  up_mdelay(TAB5_APO_TEST_MS);
+  mipi_dsi_dcs_write(device, TAB5_DCS_ALL_PIXELS_OFF, NULL, 0);
+  mipi_dsi_dcs_write(device, MIPI_DCS_ENTER_NORMAL_MODE, NULL, 0);
+  mipi_dsi_dcs_set_display_on(device);
+  tab5_fb_fill(TAB5_FB_TEST_COLOR);
+#endif
+
+  return OK;
+
+errout_fb:
+  kumm_free(g_tab5_fb);
+  g_tab5_fb = NULL;
+  g_tab5_plane.fbmem = NULL;
+  return ret;
+}
+
+/****************************************************************************
+ * Name: up_fbgetvplane
+ *
+ * Description:
+ *   Return the framebuffer vtable.
+ *
+ * Input Parameters:
+ *   display - The display number.
+ *   vplane - The vplane number.
+ *
+ * Returned Value:
+ *   Pointer to the vtable on success, NULL on failure.
+ *
+ ****************************************************************************/
+
+FAR struct fb_vtable_s *up_fbgetvplane(int display, int vplane)
+{
+  if (display != 0 || vplane != 0 || !g_tab5_fb_ready)
+    {
+      return NULL;
+    }
+
+  return &g_tab5_vtable;
+}
+
+/****************************************************************************
+ * Name: up_fbuninitialize
+ *
+ * Description:
+ *   Uninitialize the framebuffer.
+ *
+ * Input Parameters:
+ *   display - The display number.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+void up_fbuninitialize(int display)
+{
+  UNUSED(display);
+}
+
+/****************************************************************************
+ * Name: tab5_fb_reload_test_pattern
+ *
+ * Description:
+ *   Call after fb_register(): the generic FB driver memset()s the
+ *   framebuffer and would otherwise leave a black DMA buffer.
+ *   This sets the framebuffer to the test color (red).
+ *
+ * Returned Value:
+ *   Zero on success, -EAGAIN on failure.
+ *
+ ****************************************************************************/
+
+int tab5_fb_reload_test_pattern(void)
+{
+  if (!g_tab5_fb_ready || g_tab5_fb == NULL)
+    {
+      return -EAGAIN;
+    }
+
+  tab5_fb_fill(TAB5_FB_TEST_COLOR);
+  return OK;
+}
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_hmi_power.c 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_hmi_power.c
new file mode 100644
index 00000000000..267a3a16976
--- /dev/null
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_hmi_power.c
@@ -0,0 +1,187 @@
+/****************************************************************************
+ * boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_hmi_power.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 <syslog.h>
+
+#include <errno.h>
+#include <nuttx/sched.h>
+
+#include "espressif/esp_ldo.h"
+#include "espressif/esp_gpio.h"
+
+#include "esp32p4-tab5.h"
+#include <arch/board/board.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* MIPI PHY LDO channel and voltage */
+
+#define ESP_LDO_MIPI_PHY_CHAN       3
+#define ESP_LDO_MIPI_PHY_VOLTAGE_MV 2500
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static struct esp_ldo_config_t g_mipi_phy_ldo_config =
+{
+  .chan_id = ESP_LDO_MIPI_PHY_CHAN,
+  .voltage_mv = ESP_LDO_MIPI_PHY_VOLTAGE_MV,
+  .handler = NULL,
+};
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tab5_mipi_phy_power
+ *
+ * Description:
+ *   Enable or disable the MIPI PHY LDO.
+ *
+ * Input Parameters:
+ *   on - True to enable the MIPI PHY LDO, false to disable it.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_mipi_phy_power(bool on)
+{
+  int ret = OK;
+  if (on)
+    {
+      ret = esp_ldo_channel_acquire(&g_mipi_phy_ldo_config);
+    }
+  else
+    {
+      ret = esp_ldo_channel_release(&g_mipi_phy_ldo_config);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: tab5_lcd_enable
+ *
+ * Description:
+ *   Enable or disable the LCD rail via IO Expander (low).
+ *
+ * Input Parameters:
+ *   enable - True to enable the LCD rail, false to disable it.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_lcd_enable(bool enable)
+{
+  int ret;
+
+  ret = tab5_pi4ioe_low_write_pin(TAB5_LCD_EN_PIN, enable);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR,
+         "ERROR: failed to write LCD_EN pin: %d\n", ret);
+      return ret;
+    }
+
+  syslog(LOG_INFO, "LCD %s\n", enable ? "enabled" : "disabled");
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tab5_lcd_backlight
+ *
+ * Description:
+ *   Configure GPIO22 as output and drive the backlight enable.
+ *
+ * Input Parameters:
+ *   on - True to enable the backlight, false to disable it.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_lcd_backlight(bool on)
+{
+  int ret;
+
+  ret = esp_configgpio(TAB5_GPIO_LCD_BL_EN, OUTPUT);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: failed to configure backlight GPIO%d: %d\n",
+             TAB5_GPIO_LCD_BL_EN, ret);
+      return ret;
+    }
+
+  esp_gpiowrite(TAB5_GPIO_LCD_BL_EN, on);
+  syslog(LOG_INFO, "Backlight %s\n", on ? "enabled" : "disabled");
+  return OK;
+}
+
+/****************************************************************************
+ * Name: tab5_lcd_power_init
+ *
+ * Description:
+ *   Power MIPI PHY LDO, init PI4IOE, and enable LCD (P4 high).
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ *
+ ****************************************************************************/
+
+int tab5_lcd_power_init(void)
+{
+  int ret;
+
+  ret = tab5_mipi_phy_power(true);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: failed to power on MIPI PHY: %d\n", ret);
+      return ret;
+    }
+
+  ret = tab5_lcd_enable(true);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: failed to enable LCD rail: %d\n", ret);
+      return ret;
+    }
+
+  syslog(LOG_INFO, "LCD power/reset path ready\n");
+  return OK;
+}
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.c 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.c
new file mode 100644
index 00000000000..f49baf5963f
--- /dev/null
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.c
@@ -0,0 +1,375 @@
+/****************************************************************************
+ * boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.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.
+ *
+ ****************************************************************************/
+
+/* ST7121 panel bring-up for M5Stack Tab5.
+ *
+ * Init init table sourced from https://github.com/espressif/esp-iot-solution
+ * under components/display/lcd/esp_lcd_st7121/esp_lcd_st7121.c.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <syslog.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/video/mipi_dsi.h>
+
+#include <arch/board/board.h>
+
+#include "esp32p4-tab5.h"
+#include "esp32p4_lcd_st7121.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define ST7121_NAME               "st7121"
+#define ST7121_COMMAND_DELAY_MS    5
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct st7121_init_cmd_s
+{
+  uint8_t cmd;
+  FAR const uint8_t *data;
+  uint8_t len;
+  uint16_t delay_ms;
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const uint8_t g_st7121_cmd_60a[] =
+{
+  0x71, 0x21, 0xa2
+};
+
+static const uint8_t g_st7121_cmd_60b[] =
+{
+  0x71, 0x21, 0xa3
+};
+
+static const uint8_t g_st7121_cmd_60c[] =
+{
+  0x71, 0x21, 0xa4
+};
+
+static const uint8_t g_st7121_cmd_78[] =
+{
+  0x21
+};
+
+static const uint8_t g_st7121_cmd_79[] =
+{
+  0xef
+};
+
+static const uint8_t g_st7121_cmd_a4[] =
+{
+  0x31
+};
+
+static const uint8_t g_st7121_cmd_b7[] =
+{
+  0x00, 0x00, 0x5f, 0x5f, 0x44, 0x1a
+};
+
+static const uint8_t g_st7121_cmd_b0[] =
+{
+  0x22, 0x6b, 0x11, 0x89, 0x25, 0x43, 0x43
+};
+
+static const uint8_t g_st7121_cmd_bf[] =
+{
+  0xa7, 0xa7
+};
+
+static const uint8_t g_st7121_cmd_a5[] =
+{
+  0xf0, 0x03
+};
+
+static const uint8_t g_st7121_cmd_d7[] =
+{
+  0x10, 0x2c, 0x14, 0x2a, 0x80, 0x80
+};
+
+static const uint8_t g_st7121_cmd_90[] =
+{
+  0x71, 0x23, 0x5a, 0x20, 0x24, 0x11, 0x21
+};
+
+static const uint8_t g_st7121_cmd_a3[] =
+{
+  0x80, 0x01, 0x8c, 0xff, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00,
+  0x00, 0x1e, 0x5c, 0x1e, 0x80, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x46, 0x00, 0x00, 0x1e, 0x5c, 0x1e, 0x80, 0x10, 0xef, 0x58, 0x00,
+  0x00, 0x00, 0xff
+};
+
+static const uint8_t g_st7121_cmd_a6[] =
+{
+  0x0a, 0x00, 0x24, 0x71, 0x36, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xff,
+  0x00, 0x24, 0x71, 0x37, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xff, 0x00,
+  0x24, 0x71, 0x00, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xff, 0x00, 0x2c,
+  0x71, 0x00, 0x01, 0x00, 0x00, 0x68, 0x68, 0xff, 0xff, 0x00, 0x08, 0x80,
+  0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t g_st7121_cmd_a7[] =
+{
+  0x1a, 0x1a, 0xc0, 0x64, 0x40, 0x04, 0x15, 0x40, 0x00, 0x40, 0x00, 0x68,
+  0x68, 0x91, 0xff, 0x08, 0x80, 0x64, 0x40, 0x26, 0x37, 0x40, 0x00, 0x00,
+  0x00, 0x68, 0x68, 0x91, 0xff, 0x08, 0x80, 0x64, 0x40, 0x8c, 0x9d, 0x40,
+  0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xff, 0x08, 0x80, 0x64, 0x40, 0xae,
+  0xbf, 0x00, 0x00, 0x20, 0x00, 0x68, 0x68, 0x91, 0xff, 0x08, 0x80, 0x79
+};
+
+static const uint8_t g_st7121_cmd_ac[] =
+{
+  0x1d, 0x18, 0x19, 0x1d, 0x18, 0x19, 0x04, 0x1c, 0x1d, 0x08, 0x0a, 0x10,
+  0x12, 0x0c, 0x0e, 0x14, 0x16, 0x00, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x18,
+  0x19, 0x1d, 0x18, 0x19, 0x06, 0x1c, 0x1d, 0x09, 0x0b, 0x11, 0x13, 0x0d,
+  0x0f, 0x15, 0x17, 0x02, 0x1d, 0x1d, 0x1d, 0x1d
+};
+
+static const uint8_t g_st7121_cmd_ad[] =
+{
+  0x0c, 0x40, 0x46, 0x00, 0x07, 0x4b, 0x4b, 0xff, 0xff, 0xf0, 0x40, 0x0e,
+  0x01, 0x07, 0x42, 0x42, 0xff, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0xff,
+  0xff
+};
+
+static const uint8_t g_st7121_cmd_ae[] =
+{
+  0xf0, 0xff, 0x03, 0xf0, 0xff, 0x03, 0x00
+};
+
+static const uint8_t g_st7121_cmd_b2[] =
+{
+  0x15, 0x19, 0x05, 0x23, 0x49, 0x2d, 0x03, 0x2e, 0x5c, 0xd2, 0xff, 0x10,
+  0x60, 0xfd, 0x20, 0xc0, 0x00
+};
+
+static const uint8_t g_st7121_cmd_e8[] =
+{
+  0x20, 0x60, 0x04, 0x8e, 0x8e, 0x3e, 0x04, 0xdc, 0xdc, 0x3e, 0x06, 0xfa,
+  0x26, 0x3e
+};
+
+static const uint8_t g_st7121_cmd_75[] =
+{
+  0x03, 0x04
+};
+
+static const uint8_t g_st7121_cmd_e7[] =
+{
+  0x4b, 0x00, 0x00, 0xbe, 0x4b, 0x8c, 0x20, 0x1a, 0xf0, 0x7d, 0x14, 0x7d,
+  0x14, 0x7d, 0x14, 0x7d, 0x14, 0xff, 0x00, 0x32, 0x30, 0x73, 0x00, 0x00,
+  0xc8, 0x6a, 0xff, 0x5a, 0x64, 0x38, 0x88, 0x15, 0xb1, 0x01, 0x01, 0x64,
+  0x01, 0x01, 0x7c, 0xff, 0x1a, 0x51
+};
+
+static const uint8_t g_st7121_cmd_e1[] =
+{
+  0x0c, 0x0c
+};
+
+static const uint8_t g_st7121_cmd_ea[] =
+{
+  0x15, 0x00, 0x01
+};
+
+static const uint8_t g_st7121_cmd_c8[] =
+{
+  0x00, 0x00, 0x04, 0x08, 0x10, 0x00, 0x1f, 0x01, 0x39, 0x3e, 0x00, 0x78,
+  0x06, 0xe2, 0x02, 0x11, 0x33, 0x01, 0x7a, 0x0d, 0x21, 0xc4, 0x0b, 0x19,
+  0x08, 0x32, 0xa0, 0x08, 0x1a, 0x0a, 0xf3, 0x7f, 0x0e, 0xc5, 0xe8, 0x03,
+  0xff
+};
+
+static const uint8_t g_st7121_cmd_c9[] =
+{
+  0x00, 0x00, 0x04, 0x08, 0x10, 0x00, 0x1f, 0x01, 0x39, 0x3e, 0x00, 0x78,
+  0x06, 0xe2, 0x02, 0x11, 0x33, 0x01, 0x7a, 0x0d, 0x21, 0xc4, 0x0b, 0x19,
+  0x08, 0x32, 0xa0, 0x08, 0x1a, 0x0a, 0xf3, 0x7f, 0x0e, 0xc5, 0xe8, 0x03,
+  0xff
+};
+
+static const uint8_t g_st7121_cmd_60d[] =
+{
+  0x71, 0x21, 0x00
+};
+
+static const uint8_t g_st7121_cmd_35[] =
+{
+  0x00
+};
+
+static const struct st7121_init_cmd_s g_st7121_init[] =
+{
+  {0x60, g_st7121_cmd_60a, sizeof(g_st7121_cmd_60a), 0},
+  {0x60, g_st7121_cmd_60b, sizeof(g_st7121_cmd_60b), 0},
+  {0x60, g_st7121_cmd_60c, sizeof(g_st7121_cmd_60c), 0},
+  {0x78, g_st7121_cmd_78, sizeof(g_st7121_cmd_78), 0},
+  {0x79, g_st7121_cmd_79, sizeof(g_st7121_cmd_79), 0},
+  {0xa4, g_st7121_cmd_a4, sizeof(g_st7121_cmd_a4), 0},
+  {0xb7, g_st7121_cmd_b7, sizeof(g_st7121_cmd_b7), 0},
+  {0xb0, g_st7121_cmd_b0, sizeof(g_st7121_cmd_b0), 0},
+  {0xbf, g_st7121_cmd_bf, sizeof(g_st7121_cmd_bf), 0},
+  {0xa5, g_st7121_cmd_a5, sizeof(g_st7121_cmd_a5), 0},
+  {0xd7, g_st7121_cmd_d7, sizeof(g_st7121_cmd_d7), 0},
+  {0x90, g_st7121_cmd_90, sizeof(g_st7121_cmd_90), 0},
+  {0xa3, g_st7121_cmd_a3, sizeof(g_st7121_cmd_a3), 0},
+  {0xa6, g_st7121_cmd_a6, sizeof(g_st7121_cmd_a6), 0},
+  {0xa7, g_st7121_cmd_a7, sizeof(g_st7121_cmd_a7), 0},
+  {0xac, g_st7121_cmd_ac, sizeof(g_st7121_cmd_ac), 0},
+  {0xad, g_st7121_cmd_ad, sizeof(g_st7121_cmd_ad), 0},
+  {0xae, g_st7121_cmd_ae, sizeof(g_st7121_cmd_ae), 0},
+  {0xb2, g_st7121_cmd_b2, sizeof(g_st7121_cmd_b2), 0},
+  {0xe8, g_st7121_cmd_e8, sizeof(g_st7121_cmd_e8), 0},
+  {0x75, g_st7121_cmd_75, sizeof(g_st7121_cmd_75), 0},
+  {0xe7, g_st7121_cmd_e7, sizeof(g_st7121_cmd_e7), 0},
+  {0xe1, g_st7121_cmd_e1, sizeof(g_st7121_cmd_e1), 0},
+  {0xea, g_st7121_cmd_ea, sizeof(g_st7121_cmd_ea), 0},
+  {0xc8, g_st7121_cmd_c8, sizeof(g_st7121_cmd_c8), 0},
+  {0xc9, g_st7121_cmd_c9, sizeof(g_st7121_cmd_c9), 0},
+  {0x60, g_st7121_cmd_60d, sizeof(g_st7121_cmd_60d), 0},
+  {0x11, NULL, 0, 80},
+  {0x29, NULL, 0, 800},
+  {0x35, g_st7121_cmd_35, sizeof(g_st7121_cmd_35), 0},
+};
+
+#define ST7121_INIT_COUNT (sizeof(g_st7121_init) / sizeof(g_st7121_init[0]))
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: st7121_send_init
+ ****************************************************************************/
+
+static int st7121_send_init(FAR struct mipi_dsi_device *device)
+{
+  unsigned int i;
+  ssize_t n;
+
+  for (i = 0; i < ST7121_INIT_COUNT; i++)
+    {
+      FAR const struct st7121_init_cmd_s *cmd = &g_st7121_init[i];
+
+      n = mipi_dsi_dcs_write(device, cmd->cmd, cmd->data, cmd->len);
+      if (n < 0)
+        {
+          syslog(LOG_ERR,
+                 "ERROR: DCS 0x%02x failed: %zd (cmd %u/%u)\n",
+                 cmd->cmd, n, i + 1, ST7121_INIT_COUNT);
+          return (int)n;
+        }
+
+      if (cmd->delay_ms > 0)
+        {
+          up_mdelay(cmd->delay_ms);
+        }
+
+      up_mdelay(ST7121_COMMAND_DELAY_MS);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tab5_st7121_initialize
+ ****************************************************************************/
+
+FAR struct mipi_dsi_device *tab5_st7121_initialize(
+      FAR struct mipi_dsi_host *host)
+{
+  FAR struct mipi_dsi_device *device;
+  int ret;
+
+  if (host == NULL)
+    {
+      return NULL;
+    }
+
+  tab5_lcd_backlight(false);
+
+  ret = tab5_lcd_enable(true);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: LCD_EN enable failed: %d\n", ret);
+      return NULL;
+    }
+
+  device = mipi_dsi_device_register(host, ST7121_NAME, 0);
+  if (device == NULL)
+    {
+      syslog(LOG_ERR, "ERROR: mipi_dsi_device_register failed\n");
+      return NULL;
+    }
+
+  device->lanes = TAB5_MIPI_DSI_LANES;
+  device->format = MIPI_DSI_FMT_RGB565;
+  device->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+                       MIPI_DSI_MODE_LPM;
+  device->hs_rate = TAB5_MIPI_DSI_LANE_BITRATE_MBPS * 1000000UL;
+  device->lp_rate = 0;
+
+  ret = mipi_dsi_attach(device);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: attach failed: %d\n", ret);
+      return NULL;
+    }
+
+  ret = mipi_dsi_dcs_soft_reset(device);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: soft reset failed: %d\n", ret);
+      return NULL;
+    }
+
+  up_mdelay(120);
+
+  ret = st7121_send_init(device);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: DCS init failed: %d\n", ret);
+      return NULL;
+    }
+
+  return device;
+}
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.h 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.h
new file mode 100644
index 00000000000..e2f672c54ab
--- /dev/null
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+ * boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7121.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_RISCV_ESP32P4_ESP32P4_TAB5_SRC_ESP32P4_LCD_ST7121_H
+#define __BOARDS_RISCV_ESP32P4_ESP32P4_TAB5_SRC_ESP32P4_LCD_ST7121_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <nuttx/video/mipi_dsi.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifndef __ASSEMBLY__
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: tab5_st7121_initialize
+ *
+ * Description:
+ *   Register the ST7121 as a mipi_dsi_device, attach to the Espressif host,
+ *   power-cycle LCD_EN (BSP_LCD_RST is NC), issue DCS soft-reset, and send
+ *   the M5Stack Tab5 ST7121 vendor DCS initialization table.  Does not start
+ *   video; board code calls display_on after video_start.
+ *
+ * Input Parameters:
+ *   host - Registered MIPI-DSI host (esp_mipi_dsi_host_get())
+ *
+ * Returned Value:
+ *   Pointer to the registered device on success; NULL on failure.
+ *
+ ****************************************************************************/
+#ifdef CONFIG_ESP32P4_TAB5_LCD_ST7121
+FAR struct mipi_dsi_device *tab5_st7121_initialize(
+      FAR struct mipi_dsi_host *host);
+#endif
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* __BOARDS_RISCV_ESP32P4_ESP32P4_TAB5_SRC_ESP32P4_LCD_ST7121_H */
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.c 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.c
new file mode 100644
index 00000000000..1b3d120ce6f
--- /dev/null
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.c
@@ -0,0 +1,425 @@
+/****************************************************************************
+ * boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.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.
+ *
+ ****************************************************************************/
+
+/* ST7123 panel bring-up for M5Stack Tab5.
+ *
+ * Init init table sourced from https://github.com/espressif/esp-iot-solution
+ * under components/display/lcd/esp_lcd_st7123/esp_lcd_st7123.c.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <syslog.h>
+#include <string.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/video/mipi_dsi.h>
+
+#include <arch/board/board.h>
+
+#include "esp32p4-tab5.h"
+#include "esp32p4_lcd_st7123.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define ST7123_NAME  "st7123"
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct st7123_init_cmd_s
+{
+  uint8_t cmd;
+  FAR const uint8_t *data;
+  uint8_t len;
+  uint16_t delay_ms;
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* ESP-BSP Tab5 ST7123 vendor table (disp_init_data_st7123). */
+
+static const uint8_t g_st7123_cmd_60a[] =
+{
+  0x71, 0x23, 0xa2
+};
+
+static const uint8_t g_st7123_cmd_60b[] =
+{
+  0x71, 0x23, 0xa3
+};
+
+static const uint8_t g_st7123_cmd_60c[] =
+{
+  0x71, 0x23, 0xa4
+};
+
+static const uint8_t g_st7123_cmd_a4[] =
+{
+  0x31
+};
+
+static const uint8_t g_st7123_cmd_d7[] =
+{
+  0x10, 0x0a, 0x10, 0x2a, 0x80, 0x80
+};
+
+static const uint8_t g_st7123_cmd_90[] =
+{
+  0x71, 0x23, 0x5a, 0x20, 0x24, 0x09, 0x09
+};
+
+static const uint8_t g_st7123_cmd_a3[] =
+{
+  0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00,
+  0x00, 0x1e, 0x5c, 0x1e, 0x80, 0x00, 0x4f, 0x05, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x46, 0x00, 0x00, 0x1e, 0x5c, 0x1e, 0x80, 0x00, 0x6f, 0x58, 0x00,
+  0x00, 0x00, 0xff
+};
+
+static const uint8_t g_st7123_cmd_a6[] =
+{
+  0x03, 0x00, 0x24, 0x55, 0x36, 0x00, 0x39, 0x00, 0x6e, 0x6e, 0x91, 0xff,
+  0x00, 0x24, 0x55, 0x38, 0x00, 0x37, 0x00, 0x6e, 0x6e, 0x91, 0xff, 0x00,
+  0x24, 0x11, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x6e, 0x91, 0xff, 0x00, 0xec,
+  0x11, 0x00, 0x03, 0x00, 0x03, 0x6e, 0x6e, 0xff, 0xff, 0x00, 0x08, 0x80,
+  0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t g_st7123_cmd_a7[] =
+{
+  0x19, 0x19, 0x80, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x44, 0x03, 0x6e,
+  0x6e, 0x91, 0xff, 0x08, 0x80, 0x64, 0x40, 0x25, 0x34, 0x40, 0x00, 0x02,
+  0x01, 0x6e, 0x6e, 0x91, 0xff, 0x08, 0x80, 0x64, 0x40, 0x00, 0x00, 0x40,
+  0x00, 0x00, 0x00, 0x6e, 0x6e, 0x91, 0xff, 0x08, 0x80, 0x64, 0x40, 0x00,
+  0x00, 0x00, 0x00, 0x20, 0x00, 0x6e, 0x6e, 0x84, 0xff, 0x08, 0x80, 0x44
+};
+
+static const uint8_t g_st7123_cmd_ac[] =
+{
+  0x03, 0x19, 0x19, 0x18, 0x18, 0x06, 0x13, 0x13, 0x11, 0x11, 0x08, 0x08,
+  0x0a, 0x0a, 0x1c, 0x1c, 0x07, 0x07, 0x00, 0x00, 0x02, 0x02, 0x01, 0x19,
+  0x19, 0x18, 0x18, 0x06, 0x12, 0x12, 0x10, 0x10, 0x09, 0x09, 0x0b, 0x0b,
+  0x1c, 0x1c, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01
+};
+
+static const uint8_t g_st7123_cmd_ad[] =
+{
+  0xf0, 0x00, 0x46, 0x00, 0x03, 0x50, 0x50, 0xff, 0xff, 0xf0, 0x40, 0x06,
+  0x01, 0x07, 0x42, 0x42, 0xff, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0xff,
+  0xff
+};
+
+static const uint8_t g_st7123_cmd_ae[] =
+{
+  0xfe, 0x3f, 0x3f, 0xfe, 0x3f, 0x3f, 0x00
+};
+
+static const uint8_t g_st7123_cmd_b2[] =
+{
+  0x15, 0x19, 0x05, 0x23, 0x49, 0xaf, 0x03, 0x2e, 0x5c, 0xd2, 0xff, 0x10,
+  0x20, 0xfd, 0x20, 0xc0, 0x00
+};
+
+static const uint8_t g_st7123_cmd_e8[] =
+{
+  0x20, 0x6f, 0x04, 0x97, 0x97, 0x3e, 0x04, 0xdc, 0xdc, 0x3e, 0x06, 0xfa,
+  0x26, 0x3e
+};
+
+static const uint8_t g_st7123_cmd_75[] =
+{
+  0x03, 0x04
+};
+
+static const uint8_t g_st7123_cmd_e7[] =
+{
+  0x3b, 0x00, 0x00, 0x7c, 0xa1, 0x8c, 0x20, 0x1a, 0xf0, 0xb1, 0x50, 0x00,
+  0x50, 0xb1, 0x50, 0xb1, 0x50, 0xd8, 0x00, 0x55, 0x00, 0xb1, 0x00, 0x45,
+  0xc9, 0x6a, 0xff, 0x5a, 0xd8, 0x18, 0x88, 0x15, 0xb1, 0x01, 0x01, 0x77
+};
+
+static const uint8_t g_st7123_cmd_ea[] =
+{
+  0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2c
+};
+
+static const uint8_t g_st7123_cmd_b0[] =
+{
+  0x22, 0x43, 0x11, 0x61, 0x25, 0x43, 0x43
+};
+
+static const uint8_t g_st7123_cmd_b7[] =
+{
+  0x00, 0x00, 0x73, 0x73
+};
+
+static const uint8_t g_st7123_cmd_bf[] =
+{
+  0xa6, 0xaa
+};
+
+static const uint8_t g_st7123_cmd_a9[] =
+{
+  0x00, 0x00, 0x73, 0xff, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03
+};
+
+static const uint8_t g_st7123_cmd_c8[] =
+{
+  0x00, 0x00, 0x10, 0x1f, 0x36, 0x00, 0x5d, 0x04, 0x9d, 0x05, 0x10, 0xf2,
+  0x06, 0x60, 0x03, 0x11, 0xad, 0x00, 0xef, 0x01, 0x22, 0x2e, 0x0e, 0x74,
+  0x08, 0x32, 0xdc, 0x09, 0x33, 0x0f, 0xf3, 0x77, 0x0d, 0xb0, 0xdc, 0x03,
+  0xff
+};
+
+static const uint8_t g_st7123_cmd_c9[] =
+{
+  0x00, 0x00, 0x10, 0x1f, 0x36, 0x00, 0x5d, 0x04, 0x9d, 0x05, 0x10, 0xf2,
+  0x06, 0x60, 0x03, 0x11, 0xad, 0x00, 0xef, 0x01, 0x22, 0x2e, 0x0e, 0x74,
+  0x08, 0x32, 0xdc, 0x09, 0x33, 0x0f, 0xf3, 0x77, 0x0d, 0xb0, 0xdc, 0x03,
+  0xff
+};
+
+static const uint8_t g_st7123_cmd_36[] =
+{
+  0x03
+};
+
+static const uint8_t g_st7123_cmd_35[] =
+{
+  0x00
+};
+
+static const struct st7123_init_cmd_s g_st7123_init[] =
+{
+  {
+    0x60, g_st7123_cmd_60a, sizeof(g_st7123_cmd_60a), 0
+  },
+  {
+    0x60, g_st7123_cmd_60b, sizeof(g_st7123_cmd_60b), 0
+  },
+  {
+    0x60, g_st7123_cmd_60c, sizeof(g_st7123_cmd_60c), 0
+  },
+  {
+    0xa4, g_st7123_cmd_a4, sizeof(g_st7123_cmd_a4), 0
+  },
+  {
+    0xd7, g_st7123_cmd_d7, sizeof(g_st7123_cmd_d7), 0
+  },
+  {
+    0x90, g_st7123_cmd_90, sizeof(g_st7123_cmd_90), 0
+  },
+  {
+    0xa3, g_st7123_cmd_a3, sizeof(g_st7123_cmd_a3), 0
+  },
+  {
+    0xa6, g_st7123_cmd_a6, sizeof(g_st7123_cmd_a6), 0
+  },
+  {
+    0xa7, g_st7123_cmd_a7, sizeof(g_st7123_cmd_a7), 0
+  },
+  {
+    0xac, g_st7123_cmd_ac, sizeof(g_st7123_cmd_ac), 0
+  },
+  {
+    0xad, g_st7123_cmd_ad, sizeof(g_st7123_cmd_ad), 0
+  },
+  {
+    0xae, g_st7123_cmd_ae, sizeof(g_st7123_cmd_ae), 0
+  },
+  {
+    0xb2, g_st7123_cmd_b2, sizeof(g_st7123_cmd_b2), 0
+  },
+  {
+    0xe8, g_st7123_cmd_e8, sizeof(g_st7123_cmd_e8), 0
+  },
+  {
+    0x75, g_st7123_cmd_75, sizeof(g_st7123_cmd_75), 0
+  },
+  {
+    0xe7, g_st7123_cmd_e7, sizeof(g_st7123_cmd_e7), 0
+  },
+  {
+    0xea, g_st7123_cmd_ea, sizeof(g_st7123_cmd_ea), 0
+  },
+  {
+    0xb0, g_st7123_cmd_b0, sizeof(g_st7123_cmd_b0), 0
+  },
+  {
+    0xb7, g_st7123_cmd_b7, sizeof(g_st7123_cmd_b7), 0
+  },
+  {
+    0xbf, g_st7123_cmd_bf, sizeof(g_st7123_cmd_bf), 0
+  },
+  {
+    0xa9, g_st7123_cmd_a9, sizeof(g_st7123_cmd_a9), 0
+  },
+  {
+    0xc8, g_st7123_cmd_c8, sizeof(g_st7123_cmd_c8), 0
+  },
+  {
+    0xc9, g_st7123_cmd_c9, sizeof(g_st7123_cmd_c9), 0
+  },
+  {
+    0x36, g_st7123_cmd_36, sizeof(g_st7123_cmd_36), 0
+  },
+
+  /* 0x11 / 0x29: 0-param DCS (dummy 0x00 as 1-param broke sleep-out).
+   * Delays match ESP-BSP disp_init_data_st7123 (120 / 50).
+   */
+
+  {
+    0x11, NULL, 0, 120
+  },
+  {
+    0x29, NULL, 0, 50
+  },
+  {
+    0x35, g_st7123_cmd_35, sizeof(g_st7123_cmd_35), 0
+  },
+};
+
+#define ST7123_INIT_COUNT (sizeof(g_st7123_init) / sizeof(g_st7123_init[0]))
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: st7123_send_init
+ *
+ * Description:
+ *   Send the ST7123 vendor init table.
+ *
+ * Input Parameters:
+ *   device - Pointer to the MIPI-DSI device structure.
+ *
+ * Returned Value:
+ *   Zero on success, -1 on failure.
+ ****************************************************************************/
+
+static int st7123_send_init(FAR struct mipi_dsi_device *device)
+{
+  unsigned int i;
+  ssize_t n;
+
+  for (i = 0; i < ST7123_INIT_COUNT; i++)
+    {
+      FAR const struct st7123_init_cmd_s *cmd = &g_st7123_init[i];
+
+      n = mipi_dsi_dcs_write(device, cmd->cmd, cmd->data, cmd->len);
+      if (n < 0)
+        {
+          syslog(LOG_ERR,
+                 "ERROR: DCS 0x%02x failed: %zd (cmd %u/%u)\n",
+                 cmd->cmd, n, i + 1, ST7123_INIT_COUNT);
+          return (int)n;
+        }
+
+      if (cmd->delay_ms > 0)
+        {
+          up_mdelay(cmd->delay_ms);
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tab5_st7123_initialize
+ *
+ * Description:
+ *   Register the ST7123 as a mipi_dsi_device, attach to the Espressif host,
+ *   pulse LCD reset via PI4IOE, and send the ESP-BSP Tab5 vendor DCS init
+ *   table (disp_init_data_st7123[]). Does not start video; board code
+ *   calls display_on after video_start.
+ *
+ * Input Parameters:
+ *   host - Pointer to the MIPI-DSI host structure.
+ *
+ * Returned Value:
+ *   Pointer to the MIPI-DSI device structure on success, NULL on failure.
+ ****************************************************************************/
+
+FAR struct mipi_dsi_device *tab5_st7123_initialize(
+      FAR struct mipi_dsi_host *host)
+{
+  FAR struct mipi_dsi_device *device;
+  int ret;
+
+  if (host == NULL)
+    {
+      return NULL;
+    }
+
+  ret = tab5_lcd_enable(true);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: LCD_EN enable failed: %d\n", ret);
+      return NULL;
+    }
+
+  device = mipi_dsi_device_register(host, ST7123_NAME, 0);
+  if (device == NULL)
+    {
+      syslog(LOG_ERR, "ERROR: mipi_dsi_device_register failed\n");
+      return NULL;
+    }
+
+  device->lanes = TAB5_MIPI_DSI_LANES;
+  device->format = MIPI_DSI_FMT_RGB565;
+  device->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+                       MIPI_DSI_MODE_LPM;
+  device->hs_rate = TAB5_MIPI_DSI_LANE_BITRATE_MBPS * 1000000UL;
+  device->lp_rate = 0;
+
+  ret = mipi_dsi_attach(device);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: attach failed: %d\n", ret);
+      return NULL;
+    }
+
+  ret = st7123_send_init(device);
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: DCS init failed: %d\n", ret);
+      return NULL;
+    }
+
+  return device;
+}
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.h 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.h
new file mode 100644
index 00000000000..b3ba31785ab
--- /dev/null
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+ * boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_lcd_st7123.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_RISCV_ESP32P4_ESP32P4_TAB5_SRC_ESP32P4_LCD_ST7123_H
+#define __BOARDS_RISCV_ESP32P4_ESP32P4_TAB5_SRC_ESP32P4_LCD_ST7123_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <nuttx/video/mipi_dsi.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifndef __ASSEMBLY__
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: tab5_st7123_initialize
+ *
+ * Description:
+ *   Register the ST7123 as a mipi_dsi_device, attach to the Espressif host,
+ *   pulse LCD reset via PI4IOE, and send the ESP-BSP Tab5 vendor DCS init
+ *   table (disp_init_data_st7123[]). Does not start video; board code
+ *   calls display_on after video_start.
+ *
+ * Input Parameters:
+ *   host - Registered MIPI-DSI host (esp_mipi_dsi_host_get())
+ *
+ * Returned Value:
+ *   Pointer to the registered device on success; NULL on failure.
+ *
+ ****************************************************************************/
+#ifdef CONFIG_ESP32P4_TAB5_LCD_ST7123
+FAR struct mipi_dsi_device *tab5_st7123_initialize(
+      FAR struct mipi_dsi_host *host);
+#endif
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* __BOARDS_RISCV_ESP32P4_ESP32P4_TAB5_SRC_ESP32P4_LCD_ST7123_H */
diff --git a/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_mipi_dpi.c 
b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_mipi_dpi.c
new file mode 100644
index 00000000000..c03c62c2fde
--- /dev/null
+++ b/boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_mipi_dpi.c
@@ -0,0 +1,69 @@
+/****************************************************************************
+ * boards/risc-v/esp32p4/esp32p4-tab5/src/esp32p4_mipi_dpi.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 <assert.h>
+
+#include <nuttx/video/mipi_dsi.h>
+
+#include <arch/board/board.h>
+
+#include "esp32p4-tab5.h"
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: tab5_mipi_dsi_dpi_config
+ *
+ * Description:
+ *   Configure the MIPI-DSI DPI configuration.
+ *
+ * Input Parameters:
+ *   cfg - Pointer to the MIPI-DSI DPI configuration structure.
+ *
+ * Returned Value:
+ *   None.
+ ****************************************************************************/
+
+void tab5_mipi_dsi_dpi_config(FAR struct esp_mipi_dsi_dpi_config_s *cfg)
+{
+  DEBUGASSERT(cfg != NULL);
+
+  cfg->h_res = TAB5_MIPI_DSI_H_RES;
+  cfg->v_res = TAB5_MIPI_DSI_V_RES;
+  cfg->hsync_pulse_width = TAB5_MIPI_DSI_HSYNC_PULSE_WIDTH;
+  cfg->hsync_back_porch = TAB5_MIPI_DSI_HSYNC_BACK_PORCH;
+  cfg->hsync_front_porch = TAB5_MIPI_DSI_HSYNC_FRONT_PORCH;
+  cfg->vsync_pulse_width = TAB5_MIPI_DSI_VSYNC_PULSE_WIDTH;
+  cfg->vsync_back_porch = TAB5_MIPI_DSI_VSYNC_BACK_PORCH;
+  cfg->vsync_front_porch = TAB5_MIPI_DSI_VSYNC_FRONT_PORCH;
+  cfg->dpi_clock_freq_mhz = TAB5_MIPI_DSI_DPI_CLK_MHZ;
+  cfg->virtual_channel = 0;
+  cfg->format = MIPI_DSI_FMT_RGB565;
+}

Reply via email to