Add pw_display_driver_mipi for the mimxrt595 target

Major changes in this CL:
* Add a MIPI DSI display driver: pw_display_driver_mipi.
* Add pw_mipi_dsi which is a generic mipi_dsi module somewhat
  similar to pw_spi.
* Add pw_mipi_dsi_mcuxpresso which is a MCUxpresso implementtion
  of pw_mipi_dsi.
* Switched pw_display_driver from Update to GetFramebuffer/
  ReleaseFramebuffer. This is needed because the MCUxpresso
  MIPI DSI API manages the framebuffer and not the application.

Change-Id: Icf386dc1b1b70463a53f89858fb2603398e5f547
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/experimental/+/124830
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
Commit-Queue: Chris Mumford <cmumford@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index bfbf794..0663b34 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -199,6 +199,7 @@
       "//applications/blinky:blinky(//targets/mimxrt595_evk:mimxrt595_evk_debug)",
       "//applications/rpc:all(//targets/mimxrt595_evk:mimxrt595_evk_debug)",
       "//applications/strings:all(//targets/mimxrt595_evk:mimxrt595_evk_debug)",
+      "//applications/terminal_display:all(//targets/mimxrt595_evk:mimxrt595_evk_debug)",
     ]
   }
 
diff --git a/applications/app_common_impl/BUILD.gn b/applications/app_common_impl/BUILD.gn
index 208679d..38703d6 100644
--- a/applications/app_common_impl/BUILD.gn
+++ b/applications/app_common_impl/BUILD.gn
@@ -16,6 +16,7 @@
 import("//build_overrides/pigweed.gni")
 import("$dir_pigweed_experimental/third_party/imgui/imgui.gni")
 import("$dir_pw_build/target_types.gni")
+import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
 
 declare_args() {
   # LCD width in pixels (ex. "320")
@@ -50,6 +51,15 @@
 
   # SPI bus clock pin (ex. "14")
   pw_spi_clock_pin_num = ""
+
+  # framebuffer width in pixels (ex. "320")
+  pw_nxp_buffer_width = ""
+
+  # framebuffer start pixel X coord (ex. "4")
+  pw_nxp_buffer_start_x = "0"
+
+  # framebuffer start pixel Y coord (ex. "4")
+  pw_nxp_buffer_start_y = "0"
 }
 
 config("common_standard_flags") {
@@ -79,6 +89,16 @@
   ]
 }
 
+config("common_mimxrt595_flags") {
+  cflags = [
+    "-DLCD_WIDTH=" + pw_lcd_width,
+    "-DLCD_HEIGHT=" + pw_lcd_height,
+    "-DFRAMEBUFFER_WIDTH=" + pw_nxp_buffer_width,
+    "-DFRAMEBUFFER_START_X=" + pw_nxp_buffer_start_x,
+    "-DFRAMEBUFFER_START_Y=" + pw_nxp_buffer_start_y,
+  ]
+}
+
 config("common_host_flags") {
   cflags = [
     "-DLCD_WIDTH=" + pw_lcd_width,
@@ -124,12 +144,19 @@
   remove_configs = [ "$dir_pw_build:strict_warnings" ]
 }
 
-pw_source_set("mimxrt595") {
-  deps = [
-    "$dir_pw_display_driver_null",
-    "//applications/app_common:app_common.facade",
-  ]
-  sources = [ "common_mimxrt595.cc" ]
+if (pw_third_party_mcuxpresso_SDK != "") {
+  pw_source_set("mimxrt595") {
+    public_configs = [ ":common_mimxrt595_flags" ]
+    deps = [
+      "$dir_pw_display",
+      "$dir_pw_display_driver_mipi_dsi",
+      "$dir_pw_framebuffer_pool",
+      "$dir_pw_mipi_dsi_mcuxpresso",
+      "$pw_third_party_mcuxpresso_SDK",
+      "//applications/app_common:app_common.facade",
+    ]
+    sources = [ "common_mimxrt595.cc" ]
+  }
 }
 
 _pico_common_deps = [
diff --git a/applications/app_common_impl/common_arduino.cc b/applications/app_common_impl/common_arduino.cc
index c8d8ec7..96ecc2e 100644
--- a/applications/app_common_impl/common_arduino.cc
+++ b/applications/app_common_impl/common_arduino.cc
@@ -44,11 +44,9 @@
   pw::spi::Device device;
 };
 
-constexpr int kScaleFactor = 1;
-constexpr int kFramebufferWidth = LCD_WIDTH / kScaleFactor;
-constexpr int kFramebufferHeight = LCD_HEIGHT / kScaleFactor;
-constexpr int kNumPixels = kFramebufferWidth * kFramebufferHeight;
-constexpr int kFramebufferRowBytes = kFramebufferWidth * sizeof(uint16_t);
+constexpr pw::coordinates::Size<int> kDisplaySize = {LCD_WIDTH, LCD_HEIGHT};
+constexpr int kNumPixels = LCD_WIDTH * LCD_HEIGHT;
+constexpr int kDisplayRowBytes = sizeof(uint16_t) * LCD_WIDTH;
 
 constexpr pw::spi::Config kSpiConfig8Bit{
     .polarity = pw::spi::ClockPolarity::kActiveHigh,
@@ -79,6 +77,19 @@
 SpiValues s_spi_16_bit(kSpiConfig16Bit,
                        s_spi_chip_selector,
                        s_spi_initiator_mutex);
+uint16_t s_pixel_data[kNumPixels];
+constexpr pw::framebuffer::pool::PoolData s_fb_pool_data = {
+    .fb_addr =
+        {
+            s_pixel_data,
+            nullptr,
+            nullptr,
+        },
+    .num_fb = 1,
+    .size = {LCD_WIDTH, LCD_HEIGHT},
+    .row_bytes = kDisplayRowBytes,
+    .start = {0, 0},
+};
 DisplayDriver s_display_driver({
   .data_cmd_gpio = s_display_dc_pin,
 #if TFT_RST != -1
@@ -87,14 +98,9 @@
   .reset_gpio = nullptr,
 #endif
   .spi_device_8_bit = s_spi_8_bit.device,
-  .spi_device_16_bit = s_spi_16_bit.device,
+  .spi_device_16_bit = s_spi_16_bit.device, .pool_data = s_fb_pool_data,
 });
-uint16_t s_pixel_data[kNumPixels];
-Display s_display(FramebufferRgb565(s_pixel_data,
-                                    kFramebufferWidth,
-                                    kFramebufferHeight,
-                                    kFramebufferRowBytes),
-                  s_display_driver);
+Display s_display(s_display_driver, kDisplaySize);
 
 SpiValues::SpiValues(pw::spi::Config config,
                      pw::spi::ChipSelector& selector,
@@ -114,9 +120,7 @@
 
   SPI.begin();
 
-  PW_TRY(s_display_driver.Init());
-
-  return s_display.Init();
+  return s_display_driver.Init();
 }
 
 // static
diff --git a/applications/app_common_impl/common_host_imgui.cc b/applications/app_common_impl/common_host_imgui.cc
index 5e35085..48ec2d3 100644
--- a/applications/app_common_impl/common_host_imgui.cc
+++ b/applications/app_common_impl/common_host_imgui.cc
@@ -17,6 +17,7 @@
 #include "pw_status/try.h"
 
 using pw::Status;
+using pw::color::color_rgb565_t;
 using pw::framebuffer::FramebufferRgb565;
 
 namespace {
@@ -25,23 +26,29 @@
 constexpr int kFramebufferWidth = LCD_WIDTH / kDisplayScaleFactor;
 constexpr int kFramebufferHeight = LCD_HEIGHT / kDisplayScaleFactor;
 constexpr int kNumPixels = kFramebufferWidth * kFramebufferHeight;
-constexpr int FramebufferRowBytes = sizeof(uint16_t) * kFramebufferWidth;
+constexpr int kFramebufferRowBytes = sizeof(uint16_t) * kFramebufferWidth;
+constexpr pw::coordinates::Size<int> kDisplaySize = {LCD_WIDTH, LCD_HEIGHT};
 
-uint16_t s_pixel_data[kNumPixels];
-pw::display_driver::DisplayDriverImgUI s_display_driver;
-pw::display::DisplayImgUI s_display(FramebufferRgb565(s_pixel_data,
-                                                      kFramebufferWidth,
-                                                      kFramebufferHeight,
-                                                      FramebufferRowBytes),
-                                    s_display_driver);
+color_rgb565_t s_pixel_data[kNumPixels];
+constexpr pw::framebuffer::pool::PoolData s_fb_pool_data = {
+    .fb_addr =
+        {
+            s_pixel_data,
+            nullptr,
+            nullptr,
+        },
+    .num_fb = 1,
+    .size = {kFramebufferWidth, kFramebufferHeight},
+    .row_bytes = kFramebufferRowBytes,
+    .start = {0, 0},
+};
+pw::display_driver::DisplayDriverImgUI s_display_driver(s_fb_pool_data);
+pw::display::DisplayImgUI s_display(s_display_driver, kDisplaySize);
 
 }  // namespace
 
 // static
-Status Common::Init() {
-  PW_TRY(s_display_driver.Init());
-  return s_display.Init();
-}
+Status Common::Init() { return s_display_driver.Init(); }
 
 // static
 pw::display::Display& Common::GetDisplay() { return s_display; }
diff --git a/applications/app_common_impl/common_host_null.cc b/applications/app_common_impl/common_host_null.cc
index 9984d0c..07978a0 100644
--- a/applications/app_common_impl/common_host_null.cc
+++ b/applications/app_common_impl/common_host_null.cc
@@ -21,22 +21,15 @@
 
 namespace {
 
-constexpr int kNumPixels = LCD_WIDTH * LCD_HEIGHT;
-constexpr int kDisplayRowBytes = sizeof(uint16_t) * LCD_WIDTH;
+constexpr pw::coordinates::Size<int> kDisplaySize = {LCD_WIDTH, LCD_HEIGHT};
 
-uint16_t s_pixel_data[kNumPixels];
 pw::display_driver::DisplayDriverNULL s_display_driver;
-pw::display::Display s_display(
-    FramebufferRgb565(s_pixel_data, LCD_WIDTH, LCD_HEIGHT, kDisplayRowBytes),
-    s_display_driver);
+pw::display::Display s_display(s_display_driver, kDisplaySize);
 
 }  // namespace
 
 // static
-Status Common::Init() {
-  PW_TRY(s_display_driver.Init());
-  return s_display.Init();
-}
+Status Common::Init() { return s_display_driver.Init(); }
 
 // static
 pw::display::Display& Common::GetDisplay() { return s_display; }
diff --git a/applications/app_common_impl/common_mimxrt595.cc b/applications/app_common_impl/common_mimxrt595.cc
index eedd04a..541fa1f 100644
--- a/applications/app_common_impl/common_mimxrt595.cc
+++ b/applications/app_common_impl/common_mimxrt595.cc
@@ -12,18 +12,102 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 #include "app_common/common.h"
-#include "pw_display_driver_null/display_driver.h"
+#include "board.h"
+#include "fsl_iopctl.h"
+#include "pin_mux.h"
+#include "pw_display_driver_mipi/display_driver.h"
+#include "pw_mipi_dsi_mcuxpresso/device.h"
+#include "pw_status/try.h"
+
+using pw::Status;
+using pw::color::color_rgb565_t;
+using pw::display::Display;
+using pw::display_driver::DisplayDriverMipiDsi;
+using pw::framebuffer::FramebufferRgb565;
+using pw::mipi::dsi::MCUXpressoDevice;
 
 namespace {
 
-pw::display_driver::DisplayDriverNULL s_display_driver;
-pw::display::Display s_display(pw::framebuffer::FramebufferRgb565(),
-                               s_display_driver);
+// Framebuffer addresses in on-board PSRAM.
+constexpr uint32_t kBuffer0Addr = 0x28000000U;
+constexpr uint32_t kBuffer1Addr = 0x28200000U;
+constexpr video_pixel_format_t kPixelFormat = kVIDEO_PixelFormatRGB565;
+constexpr uint16_t kBufferStrideBytes =
+    FRAMEBUFFER_WIDTH * pw::mipi::dsi::kBytesPerPixel;
+constexpr pw::coordinates::Size<int> kDisplaySize = {LCD_WIDTH, LCD_HEIGHT};
+
+const pw::framebuffer::pool::PoolData s_fb_pool_data = {
+    .fb_addr =
+        {
+            reinterpret_cast<pw::color::color_rgb565_t*>(kBuffer0Addr),
+            reinterpret_cast<pw::color::color_rgb565_t*>(kBuffer1Addr),
+            nullptr,
+        },
+    .num_fb = 2,
+    .size = {LCD_WIDTH, LCD_HEIGHT},
+    .row_bytes = kBufferStrideBytes,
+    .start = {FRAMEBUFFER_START_X, FRAMEBUFFER_START_Y},
+};
+
+MCUXpressoDevice s_mipi_device(s_fb_pool_data,
+                               {.width = LCD_WIDTH, .height = LCD_HEIGHT},
+                               kPixelFormat);
+DisplayDriverMipiDsi s_display_driver(
+    {
+        .mipi_device = s_mipi_device,
+    },
+    kDisplaySize);
+Display s_display(s_display_driver, kDisplaySize);
+
+void InitMipiPins(void) {
+  constexpr uint32_t kPwmModeFunc =
+      (IOPCTL_PIO_FUNC0 | IOPCTL_PIO_PUPD_DI | IOPCTL_PIO_PULLDOWN_EN |
+       IOPCTL_PIO_INBUF_EN | IOPCTL_PIO_SLEW_RATE_NORMAL |
+       IOPCTL_PIO_FULLDRIVE_DI | IOPCTL_PIO_ANAMUX_DI | IOPCTL_PIO_PSEDRAIN_DI |
+       IOPCTL_PIO_INV_DI);
+  IOPCTL_PinMuxSet(IOPCTL, BOARD_MIPI_BL_PORT, BOARD_MIPI_BL_PIN, kPwmModeFunc);
+
+  constexpr uint32_t kPwrEnModeFunc =
+      (IOPCTL_PIO_FUNC0 | IOPCTL_PIO_PUPD_DI | IOPCTL_PIO_PULLDOWN_EN |
+       IOPCTL_PIO_INBUF_EN | IOPCTL_PIO_SLEW_RATE_NORMAL |
+       IOPCTL_PIO_FULLDRIVE_DI | IOPCTL_PIO_ANAMUX_DI | IOPCTL_PIO_PSEDRAIN_DI |
+       IOPCTL_PIO_INV_DI);
+  IOPCTL_PinMuxSet(
+      IOPCTL, BOARD_MIPI_POWER_PORT, BOARD_MIPI_POWER_PIN, kPwrEnModeFunc);
+
+  constexpr uint32_t kPort2Pin18ModeFunc =
+      (IOPCTL_PIO_FUNC0 | IOPCTL_PIO_PUPD_EN | IOPCTL_PIO_PULLDOWN_EN |
+       IOPCTL_PIO_INBUF_EN | IOPCTL_PIO_SLEW_RATE_NORMAL |
+       IOPCTL_PIO_FULLDRIVE_DI | IOPCTL_PIO_ANAMUX_DI | IOPCTL_PIO_PSEDRAIN_DI |
+       IOPCTL_PIO_INV_DI);
+  IOPCTL_PinMuxSet(IOPCTL, 3U, 18U, kPort2Pin18ModeFunc);
+
+  constexpr uint32_t kResetBModeFunc =
+      (IOPCTL_PIO_FUNC0 | IOPCTL_PIO_PUPD_DI | IOPCTL_PIO_PULLDOWN_EN |
+       IOPCTL_PIO_INBUF_EN | IOPCTL_PIO_SLEW_RATE_NORMAL |
+       IOPCTL_PIO_FULLDRIVE_DI | IOPCTL_PIO_ANAMUX_DI | IOPCTL_PIO_PSEDRAIN_DI |
+       IOPCTL_PIO_INV_DI);
+  IOPCTL_PinMuxSet(
+      IOPCTL, BOARD_MIPI_RST_PORT, BOARD_MIPI_RST_PIN, kResetBModeFunc);
+}
 
 }  // namespace
 
 // static
-pw::Status Common::Init() { return s_display.Init(); }
+Status Common::Init() {
+  InitMipiPins();
+  BOARD_InitPsRam();
+
+  GPIO_PortInit(GPIO, BOARD_MIPI_POWER_PORT);
+  GPIO_PortInit(GPIO, BOARD_MIPI_BL_PORT);
+  GPIO_PortInit(GPIO, BOARD_MIPI_RST_PORT);
+  GPIO_PortInit(GPIO, BOARD_MIPI_TE_PORT);
+
+  BOARD_BootClockRUN();
+
+  PW_TRY(s_mipi_device.Init());
+  return s_display_driver.Init();
+}
 
 // static
 pw::display::Display& Common::GetDisplay() { return s_display; }
diff --git a/applications/app_common_impl/common_pico.cc b/applications/app_common_impl/common_pico.cc
index 992aad6..11c86b0 100644
--- a/applications/app_common_impl/common_pico.cc
+++ b/applications/app_common_impl/common_pico.cc
@@ -68,10 +68,11 @@
 };
 
 constexpr int kDisplayScaleFactor = 1;
+constexpr pw::coordinates::Size<int> kDisplaySize{LCD_WIDTH, LCD_HEIGHT};
 constexpr int kFramebufferWidth = LCD_WIDTH / kDisplayScaleFactor;
 constexpr int kFramebufferHeight = LCD_HEIGHT / kDisplayScaleFactor;
 constexpr int kNumPixels = kFramebufferWidth * kFramebufferHeight;
-constexpr int FramebufferRowBytes = sizeof(uint16_t) * kFramebufferWidth;
+constexpr int kFramebufferRowBytes = sizeof(uint16_t) * kFramebufferWidth;
 
 constexpr uint32_t kBaudRate = 31'250'000;
 constexpr pw::spi::Config kSpiConfig8Bit{
@@ -103,6 +104,19 @@
 SpiValues s_spi_16_bit(kSpiConfig16Bit,
                        s_spi_chip_selector,
                        s_spi_initiator_mutex);
+uint16_t s_pixel_data[kNumPixels];
+constexpr pw::framebuffer::pool::PoolData s_fb_pool_data = {
+    .fb_addr =
+        {
+            s_pixel_data,
+            nullptr,
+            nullptr,
+        },
+    .num_fb = 1,
+    .size = {kFramebufferWidth, kFramebufferHeight},
+    .row_bytes = kFramebufferRowBytes,
+    .start = {0, 0},
+};
 DisplayDriver s_display_driver({
   .data_cmd_gpio = s_display_dc_pin,
 #if TFT_RST != -1
@@ -111,14 +125,9 @@
   .reset_gpio = nullptr,
 #endif
   .spi_device_8_bit = s_spi_8_bit.device,
-  .spi_device_16_bit = s_spi_16_bit.device,
+  .spi_device_16_bit = s_spi_16_bit.device, .pool_data = s_fb_pool_data,
 });
-uint16_t pixel_data[kNumPixels];
-Display s_display(FramebufferRgb565(pixel_data,
-                                    kFramebufferWidth,
-                                    kFramebufferHeight,
-                                    FramebufferRowBytes),
-                  s_display_driver);
+Display s_display(s_display_driver, kDisplaySize);
 
 #if TFT_BL != -1
 void SetBacklight(uint16_t brightness) {
@@ -168,9 +177,7 @@
   gpio_set_function(TFT_SCLK, GPIO_FUNC_SPI);
   gpio_set_function(TFT_MOSI, GPIO_FUNC_SPI);
 
-  PW_TRY(s_display_driver.Init());
-
-  return s_display.Init();
+  return s_display_driver.Init();
 }
 
 // static
diff --git a/applications/app_common_impl/common_stm32cube.cc b/applications/app_common_impl/common_stm32cube.cc
index 873ecee..7724b93 100644
--- a/applications/app_common_impl/common_stm32cube.cc
+++ b/applications/app_common_impl/common_stm32cube.cc
@@ -56,6 +56,7 @@
 
 constexpr int kNumPixels = LCD_WIDTH * LCD_HEIGHT;
 constexpr int kDisplayRowBytes = sizeof(uint16_t) * LCD_WIDTH;
+constexpr pw::coordinates::Size<int> kDisplaySize = {LCD_WIDTH, LCD_HEIGHT};
 constexpr pw::spi::Config kSpiConfig8Bit{
     .polarity = pw::spi::ClockPolarity::kActiveHigh,
     .phase = pw::spi::ClockPhase::kFallingEdge,
@@ -82,16 +83,27 @@
 SpiValues s_spi_16_bit(kSpiConfig16Bit,
                        s_spi_chip_selector,
                        s_spi_initiator_mutex);
+uint16_t s_pixel_data[kNumPixels];
+constexpr pw::framebuffer::pool::PoolData s_fb_pool_data = {
+    .fb_addr =
+        {
+            s_pixel_data,
+            nullptr,
+            nullptr,
+        },
+    .num_fb = 1,
+    .size = {LCD_WIDTH, LCD_HEIGHT},
+    .row_bytes = kDisplayRowBytes,
+    .start = {0, 0},
+};
 DisplayDriverILI9341 s_display_driver({
     .data_cmd_gpio = s_display_dc_pin,
     .reset_gpio = nullptr,
     .spi_device_8_bit = s_spi_8_bit.device,
     .spi_device_16_bit = s_spi_16_bit.device,
+    .pool_data = s_fb_pool_data,
 });
-uint16_t pixel_data[kNumPixels];
-Display s_display(
-    FramebufferRgb565(pixel_data, LCD_WIDTH, LCD_HEIGHT, kDisplayRowBytes),
-    s_display_driver);
+Display s_display(s_display_driver, kDisplaySize);
 
 SpiValues::SpiValues(pw::spi::Config config,
                      pw::spi::ChipSelector& selector,
@@ -130,9 +142,7 @@
   };
   HAL_GPIO_Init(GPIOF, &spi_pin_config);
 
-  PW_TRY(s_display_driver.Init());
-
-  return s_display.Init();
+  return s_display_driver.Init();
 }
 
 // static
diff --git a/applications/terminal_display/main.cc b/applications/terminal_display/main.cc
index cf2a178..3362873 100644
--- a/applications/terminal_display/main.cc
+++ b/applications/terminal_display/main.cc
@@ -522,6 +522,8 @@
 
     uint32_t start = pw::spin_delay::Millis();
     framebuffer = display.GetFramebuffer();
+    if (!framebuffer.IsValid())
+      continue;
     pw::draw::Fill(&framebuffer, kBlack);
     DrawFrame(framebuffer, fps_view);
     uint32_t end = pw::spin_delay::Millis();
diff --git a/build_overrides/pigweed.gni b/build_overrides/pigweed.gni
index ee06bbe..9b9cb9f 100644
--- a/build_overrides/pigweed.gni
+++ b/build_overrides/pigweed.gni
@@ -67,6 +67,9 @@
   dir_pw_display_driver_imgui =
       get_path_info("$dir_pigweed_experimental/pw_display_driver_imgui",
                     "abspath")
+  dir_pw_display_driver_mipi_dsi =
+      get_path_info("$dir_pigweed_experimental/pw_display_driver_mipi",
+                    "abspath")
   dir_pw_display_driver_null =
       get_path_info("$dir_pigweed_experimental/pw_display_driver_null",
                     "abspath")
@@ -84,6 +87,14 @@
   dir_pw_framebuffer =
       get_path_info("$dir_pigweed_experimental/pw_graphics/pw_framebuffer",
                     "abspath")
+  dir_pw_framebuffer_pool =
+      get_path_info("$dir_pigweed_experimental/pw_graphics/pw_framebuffer_pool",
+                    "abspath")
+  dir_pw_mipi_dsi =
+      get_path_info("$dir_pigweed_experimental/pw_mipi_dsi", "abspath")
+  dir_pw_mipi_dsi_mcuxpresso =
+      get_path_info("$dir_pigweed_experimental/pw_mipi_dsi_mcuxpresso",
+                    "abspath")
   dir_pw_spi_arduino =
       get_path_info("$dir_pigweed_experimental/pw_spi_arduino", "abspath")
   dir_pw_spi_pico =
diff --git a/pw_display_driver/public/pw_display_driver/display_driver.h b/pw_display_driver/public/pw_display_driver/display_driver.h
index 38075fc..ac96983 100644
--- a/pw_display_driver/public/pw_display_driver/display_driver.h
+++ b/pw_display_driver/public/pw_display_driver/display_driver.h
@@ -22,7 +22,7 @@
 namespace pw::display_driver {
 
 // This interface defines a software display driver. This is the software
-// component responsible for *all* communications with a display controller.
+// component responsible for all communications with a display controller.
 // The display controller is the hardware component of a display that
 // controls pixel values and other physical display properties.
 class DisplayDriver {
@@ -32,10 +32,14 @@
   // Initialize the display controller.
   virtual Status Init() = 0;
 
+  // Return a framebuffer to which the caller may draw. When drawing is complete
+  // the framebuffer must be returned using ReleaseFramebuffer().
+  virtual pw::framebuffer::FramebufferRgb565 GetFramebuffer() = 0;
+
   // Send all pixels in the supplied |framebuffer| to the display controller
-  // for the display.
-  virtual Status Update(
-      const pw::framebuffer::FramebufferRgb565& framebuffer) = 0;
+  // for display.
+  virtual Status ReleaseFramebuffer(
+      pw::framebuffer::FramebufferRgb565 framebuffer) = 0;
 
   // Send a row of pixels to the display. The number of pixels must be <=
   // display width.
diff --git a/pw_display_driver_ili9341/BUILD.gn b/pw_display_driver_ili9341/BUILD.gn
index 16044ee..293f242 100644
--- a/pw_display_driver_ili9341/BUILD.gn
+++ b/pw_display_driver_ili9341/BUILD.gn
@@ -31,6 +31,7 @@
   public_deps = [
     "$dir_pw_digital_io",
     "$dir_pw_display_driver:display_driver",
+    "$dir_pw_framebuffer_pool",
     "$dir_pw_spi:device",
   ]
   sources = [ "display_driver.cc" ]
diff --git a/pw_display_driver_ili9341/display_driver.cc b/pw_display_driver_ili9341/display_driver.cc
index d9d9c2e..2ff2f77 100644
--- a/pw_display_driver_ili9341/display_driver.cc
+++ b/pw_display_driver_ili9341/display_driver.cc
@@ -291,7 +291,15 @@
   return OkStatus();
 }
 
-Status DisplayDriverILI9341::Update(const FramebufferRgb565& frame_buffer) {
+FramebufferRgb565 DisplayDriverILI9341::GetFramebuffer() {
+  return FramebufferRgb565(config_.pool_data.fb_addr[0],
+                           config_.pool_data.size.width,
+                           config_.pool_data.size.height,
+                           config_.pool_data.row_bytes);
+}
+
+Status DisplayDriverILI9341::ReleaseFramebuffer(
+    FramebufferRgb565 frame_buffer) {
   auto transaction = config_.spi_device_16_bit.StartTransaction(
       ChipSelectBehavior::kPerTransaction);
   const uint16_t* fb_data = frame_buffer.GetFramebufferData();
diff --git a/pw_display_driver_ili9341/public/pw_display_driver_ili9341/display_driver.h b/pw_display_driver_ili9341/public/pw_display_driver_ili9341/display_driver.h
index 6495557..8bcdf0f 100644
--- a/pw_display_driver_ili9341/public/pw_display_driver_ili9341/display_driver.h
+++ b/pw_display_driver_ili9341/public/pw_display_driver_ili9341/display_driver.h
@@ -17,6 +17,7 @@
 
 #include "pw_digital_io/digital_io.h"
 #include "pw_display_driver/display_driver.h"
+#include "pw_framebuffer_pool/framebuffer_pool.h"
 #include "pw_spi/device.h"
 
 namespace pw::display_driver {
@@ -36,13 +37,16 @@
     // The SPI device to which the display controller is connected for 16-bit
     // data.
     pw::spi::Device& spi_device_16_bit;
+    const pw::framebuffer::pool::PoolData& pool_data;
   };
 
   DisplayDriverILI9341(const Config& config);
 
   // DisplayDriver implementation:
   Status Init() override;
-  Status Update(const pw::framebuffer::FramebufferRgb565& framebuffer);
+  pw::framebuffer::FramebufferRgb565 GetFramebuffer(void) override;
+  Status ReleaseFramebuffer(
+      pw::framebuffer::FramebufferRgb565 framebuffer) override;
   Status WriteRow(span<uint16_t> row_pixels, int row_idx, int col_idx) override;
   int GetWidth() const override;
   int GetHeight() const override;
diff --git a/pw_display_driver_imgui/BUILD.gn b/pw_display_driver_imgui/BUILD.gn
index fee948f..0ed596d 100644
--- a/pw_display_driver_imgui/BUILD.gn
+++ b/pw_display_driver_imgui/BUILD.gn
@@ -29,7 +29,10 @@
     "$dir_pw_coordinates",
     "$dir_pw_log",
   ]
-  public_deps = [ "$dir_pw_display_driver:display_driver" ]
+  public_deps = [
+    "$dir_pw_display_driver:display_driver",
+    "$dir_pw_framebuffer_pool",
+  ]
   sources = [ "display_driver.cc" ]
   remove_configs = [ "$dir_pw_build:strict_warnings" ]
 }
diff --git a/pw_display_driver_imgui/display_driver.cc b/pw_display_driver_imgui/display_driver.cc
index ec1534e..958f0e1 100644
--- a/pw_display_driver_imgui/display_driver.cc
+++ b/pw_display_driver_imgui/display_driver.cc
@@ -162,7 +162,9 @@
 
 }  // namespace
 
-DisplayDriverImgUI::DisplayDriverImgUI() = default;
+DisplayDriverImgUI::DisplayDriverImgUI(
+    const pw::framebuffer::pool::PoolData& pool_data)
+    : pool_data_(pool_data) {}
 
 Status DisplayDriverImgUI::Init() {
   // Setup window
@@ -390,7 +392,16 @@
   }
 }
 
-Status DisplayDriverImgUI::Update(const FramebufferRgb565& framebuffer) {
+FramebufferRgb565 DisplayDriverImgUI::GetFramebuffer() {
+  return FramebufferRgb565(pool_data_.fb_addr[0],
+                           pool_data_.size.width,
+                           pool_data_.size.height,
+                           pool_data_.row_bytes);
+}
+
+Status DisplayDriverImgUI::ReleaseFramebuffer(FramebufferRgb565 framebuffer) {
+  if (!framebuffer.IsValid())
+    return Status::InvalidArgument();
   RecreateLcdTexture();
 
   // Copy frame_buffer into lcd_pixel_data
diff --git a/pw_display_driver_imgui/public/pw_display_driver_imgui/display_driver.h b/pw_display_driver_imgui/public/pw_display_driver_imgui/display_driver.h
index 97d90ba..16dee11 100644
--- a/pw_display_driver_imgui/public/pw_display_driver_imgui/display_driver.h
+++ b/pw_display_driver_imgui/public/pw_display_driver_imgui/display_driver.h
@@ -15,19 +15,22 @@
 
 #include "pw_coordinates/vec_int.h"
 #include "pw_display_driver/display_driver.h"
+#include "pw_framebuffer_pool/framebuffer_pool.h"
 
 namespace pw::display_driver {
 
 class DisplayDriverImgUI : public DisplayDriver {
  public:
-  DisplayDriverImgUI();
+  DisplayDriverImgUI(const pw::framebuffer::pool::PoolData& pool_data);
 
   bool NewTouchEvent();
   pw::coordinates::Vec3Int GetTouchPoint();
 
   // pw::display_driver::DisplayDriver implementation:
   Status Init() override;
-  Status Update(const pw::framebuffer::FramebufferRgb565& framebuffer) override;
+  pw::framebuffer::FramebufferRgb565 GetFramebuffer() override;
+  Status ReleaseFramebuffer(
+      pw::framebuffer::FramebufferRgb565 framebuffer) override;
   Status WriteRow(span<uint16_t> row_pixels, int row_idx, int col_idx) override;
   int GetWidth() const override;
   int GetHeight() const override;
@@ -35,6 +38,8 @@
  private:
   void RecreateLcdTexture();
   void Render();
+
+  const pw::framebuffer::pool::PoolData& pool_data_;
 };
 
 }  // namespace pw::display_driver
diff --git a/pw_display_driver_mipi/BUILD.gn b/pw_display_driver_mipi/BUILD.gn
new file mode 100644
index 0000000..1d74a1d
--- /dev/null
+++ b/pw_display_driver_mipi/BUILD.gn
@@ -0,0 +1,36 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed 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
+#
+#     https://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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+pw_source_set("pw_display_driver_mipi") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_display_driver_mipi/display_driver.h" ]
+  deps = [ "$dir_pw_log" ]
+  public_deps = [
+    "$dir_pw_coordinates",
+    "$dir_pw_display_driver:display_driver",
+    "$dir_pw_mipi_dsi",
+    "$dir_pw_spi:device",
+    "$dir_pw_status",
+  ]
+  sources = [ "display_driver.cc" ]
+  remove_configs = [ "$dir_pw_build:strict_warnings" ]
+}
diff --git a/pw_display_driver_mipi/display_driver.cc b/pw_display_driver_mipi/display_driver.cc
new file mode 100644
index 0000000..f75f4b8
--- /dev/null
+++ b/pw_display_driver_mipi/display_driver.cc
@@ -0,0 +1,48 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#include "pw_display_driver_mipi/display_driver.h"
+
+using pw::framebuffer::FramebufferRgb565;
+
+namespace pw::display_driver {
+
+DisplayDriverMipiDsi::DisplayDriverMipiDsi(
+    pw::mipi::dsi::Device& device, pw::coordinates::Size<int> display_size)
+    : device_(device), display_size_(display_size) {}
+
+DisplayDriverMipiDsi::~DisplayDriverMipiDsi() = default;
+
+Status DisplayDriverMipiDsi::Init() { return OkStatus(); }
+
+FramebufferRgb565 DisplayDriverMipiDsi::GetFramebuffer() {
+  return device_.GetFramebuffer();
+}
+
+Status DisplayDriverMipiDsi::ReleaseFramebuffer(FramebufferRgb565 framebuffer) {
+  return device_.ReleaseFramebuffer(std::move(framebuffer));
+};
+
+Status DisplayDriverMipiDsi::WriteRow(span<uint16_t> row_pixels,
+                                      int row_idx,
+                                      int col_idx) {
+  // TODO(cmumford): Implement for debugging purposes.
+  return Status::Unimplemented();
+}
+
+int DisplayDriverMipiDsi::GetWidth() const { return display_size_.width; };
+
+int DisplayDriverMipiDsi::GetHeight() const { return display_size_.height; }
+
+}  // namespace pw::display_driver
diff --git a/pw_display_driver_mipi/public/pw_display_driver_mipi/display_driver.h b/pw_display_driver_mipi/public/pw_display_driver_mipi/display_driver.h
new file mode 100644
index 0000000..6027fca
--- /dev/null
+++ b/pw_display_driver_mipi/public/pw_display_driver_mipi/display_driver.h
@@ -0,0 +1,44 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+#pragma once
+
+#include "pw_coordinates/vec_int.h"
+#include "pw_display_driver/display_driver.h"
+#include "pw_mipi_dsi/device.h"
+
+namespace pw::display_driver {
+
+// A display driver that communicates with a display controller over the
+// MIPI Display Serial Interface (MIPI DSI).
+class DisplayDriverMipiDsi : public DisplayDriver {
+ public:
+  DisplayDriverMipiDsi(pw::mipi::dsi::Device& device,
+                       pw::coordinates::Size<int> display_size);
+  ~DisplayDriverMipiDsi() override;
+
+  // pw::display_driver::DisplayDriver implementation:
+  Status Init() override;
+  pw::framebuffer::FramebufferRgb565 GetFramebuffer(void) override;
+  Status ReleaseFramebuffer(
+      pw::framebuffer::FramebufferRgb565 framebuffer) override;
+  Status WriteRow(span<uint16_t> row_pixels, int row_idx, int col_idx) override;
+  int GetWidth() const override;
+  int GetHeight() const override;
+
+ private:
+  pw::mipi::dsi::Device& device_;
+  const pw::coordinates::Size<int> display_size_;
+};
+
+}  // namespace pw::display_driver
diff --git a/pw_display_driver_null/public/pw_display_driver_null/display_driver.h b/pw_display_driver_null/public/pw_display_driver_null/display_driver.h
index c63da73..e4ef816 100644
--- a/pw_display_driver_null/public/pw_display_driver_null/display_driver.h
+++ b/pw_display_driver_null/public/pw_display_driver_null/display_driver.h
@@ -25,8 +25,11 @@
 
   // pw::display_driver::DisplayDriver implementation:
   pw::Status Init() override { return pw::OkStatus(); }
-  pw::Status Update(const pw::framebuffer::FramebufferRgb565&) override {
-    return pw::OkStatus();
+  pw::framebuffer::FramebufferRgb565 GetFramebuffer(void) override {
+    return pw::framebuffer::FramebufferRgb565();
+  }
+  Status ReleaseFramebuffer(pw::framebuffer::FramebufferRgb565) override {
+    return OkStatus();
   }
   Status WriteRow(span<uint16_t>, int, int) override { return pw::OkStatus(); }
   int GetWidth() const override { return 0; };
diff --git a/pw_display_driver_st7789/display_driver.cc b/pw_display_driver_st7789/display_driver.cc
index 5deb13a..3a811fd 100644
--- a/pw_display_driver_st7789/display_driver.cc
+++ b/pw_display_driver_st7789/display_driver.cc
@@ -183,7 +183,15 @@
   return OkStatus();
 }
 
-Status DisplayDriverST7789::Update(const FramebufferRgb565& frame_buffer) {
+FramebufferRgb565 DisplayDriverST7789::GetFramebuffer() {
+  return FramebufferRgb565(config_.pool_data.fb_addr[0],
+                           config_.pool_data.size.width,
+                           config_.pool_data.size.height,
+                           config_.pool_data.row_bytes);
+}
+
+Status DisplayDriverST7789::ReleaseFramebuffer(
+    pw::framebuffer::FramebufferRgb565 frame_buffer) {
   // Let controller know a write is coming.
   {
     auto transaction = config_.spi_device_8_bit.StartTransaction(
diff --git a/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h b/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h
index b3cdd68..23beb6d 100644
--- a/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h
+++ b/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h
@@ -36,6 +36,7 @@
     // The SPI device to which the display controller is connected for 16-bit
     // data.
     pw::spi::Device& spi_device_16_bit;
+    const pw::framebuffer::pool::PoolData& pool_data;
     int screen_width = 320;
     int screen_height = 240;
   };
@@ -44,7 +45,8 @@
 
   // DisplayDriver implementation:
   Status Init() override;
-  Status Update(const pw::framebuffer::FramebufferRgb565& framebuffer);
+  FramebufferRgb565 GetFramebuffer(void) override;
+  Status ReleaseFramebuffer(FramebufferRgb565 framebuffer) override;
   Status WriteRow(span<uint16_t> row_pixels, int row_idx, int col_idx) override;
   int GetWidth() const override { return config_.screen_width; }
   int GetHeight() const override { return config_.screen_height; }
diff --git a/pw_graphics/pw_display/BUILD.gn b/pw_graphics/pw_display/BUILD.gn
index 05e8ecd..82f9b31 100644
--- a/pw_graphics/pw_display/BUILD.gn
+++ b/pw_graphics/pw_display/BUILD.gn
@@ -27,6 +27,7 @@
     "$dir_pw_coordinates",
     "$dir_pw_display_driver:display_driver",
     "$dir_pw_framebuffer",
+    "$dir_pw_framebuffer_pool",
     "$dir_pw_status",
   ]
   sources = [ "display.cc" ]
diff --git a/pw_graphics/pw_display/display.cc b/pw_graphics/pw_display/display.cc
index 22fe620..48d2ea9 100644
--- a/pw_graphics/pw_display/display.cc
+++ b/pw_graphics/pw_display/display.cc
@@ -13,6 +13,10 @@
 // the License.
 #include "pw_display/display.h"
 
+#include <utility>
+
+#include "pw_framebuffer/rgb565.h"
+#include "pw_framebuffer_pool/framebuffer_pool.h"
 #include "pw_status/try.h"
 
 using pw::color::color_rgb565_t;
@@ -20,9 +24,9 @@
 
 namespace pw::display {
 
-Display::Display(FramebufferRgb565 framebuffer,
-                 pw::display_driver::DisplayDriver& display_driver)
-    : framebuffer_(std::move(framebuffer)), display_driver_(display_driver) {}
+Display::Display(pw::display_driver::DisplayDriver& display_driver,
+                 pw::coordinates::Size<int> size)
+    : display_driver_(display_driver), size_(size) {}
 
 Display::~Display() = default;
 
@@ -41,8 +45,8 @@
   constexpr int kBytesPerPixel = sizeof(color_rgb565_t);
   const int num_src_row_pixels = framebuffer.GetRowBytes() / kBytesPerPixel;
 
-  const int num_dst_rows = display_driver_.GetHeight();
-  const int num_dst_cols = display_driver_.GetWidth();
+  const int num_dst_rows = size_.height;
+  const int num_dst_cols = size_.width;
   for (int dst_row_idx = 0; dst_row_idx < num_dst_rows; dst_row_idx++) {
     int src_row_idx = dst_row_idx * fb_last_row_idx / (num_dst_rows - 1);
     PW_ASSERT(src_row_idx >= 0);
@@ -76,20 +80,17 @@
 }
 
 FramebufferRgb565 Display::GetFramebuffer() {
-  return FramebufferRgb565(framebuffer_.GetFramebufferData(),
-                           framebuffer_.GetWidth(),
-                           framebuffer_.GetHeight(),
-                           framebuffer_.GetRowBytes());
+  return display_driver_.GetFramebuffer();
 }
 
 Status Display::ReleaseFramebuffer(FramebufferRgb565 framebuffer) {
   if (!framebuffer.IsValid())
     return Status::InvalidArgument();
-  if (framebuffer.GetWidth() != display_driver_.GetWidth() ||
-      framebuffer.GetHeight() != display_driver_.GetHeight()) {
+  if (framebuffer.GetWidth() != size_.width ||
+      framebuffer.GetHeight() != size_.height) {
     return UpdateNearestNeighbor(framebuffer);
   }
-  return display_driver_.Update(framebuffer);
+  return display_driver_.ReleaseFramebuffer(std::move(framebuffer));
 }
 
 }  // namespace pw::display
diff --git a/pw_graphics/pw_display/display_test.cc b/pw_graphics/pw_display/display_test.cc
index f598c5b..7f161ab 100644
--- a/pw_graphics/pw_display/display_test.cc
+++ b/pw_graphics/pw_display/display_test.cc
@@ -22,16 +22,18 @@
 using pw::color::color_rgb565_t;
 using pw::display_driver::DisplayDriver;
 using pw::framebuffer::FramebufferRgb565;
+using Size = pw::coordinates::Size<int>;
 
 namespace pw::display {
 
 namespace {
 
-constexpr size_t kMaxSavedParams = 10;
+constexpr size_t kMaxSavedParams = 20;
 
 enum class CallFunc {
   Unset,
-  Update,
+  GetFramebuffer,
+  ReleaseFramebuffer,
   WriteRow,
 };
 
@@ -39,7 +41,7 @@
   CallFunc call_func = CallFunc::Unset;
   struct {
     color_rgb565_t* fb_data = nullptr;
-  } update;
+  } release_framebuffer;
   struct {
     size_t num_pixels = 0;
     int row_idx = 0;
@@ -49,15 +51,23 @@
 
 class TestDisplayDriver : public DisplayDriver {
  public:
-  TestDisplayDriver(int width, int height) : width_(width), height_(height) {}
+  TestDisplayDriver(FramebufferRgb565 fb) : framebuffer_(std::move(fb)) {}
   virtual ~TestDisplayDriver() = default;
 
   Status Init() override { return OkStatus(); }
 
-  Status Update(const FramebufferRgb565& framebuffer) override {
+  FramebufferRgb565 GetFramebuffer() override {
+    return FramebufferRgb565(framebuffer_.GetFramebufferData(),
+                             framebuffer_.GetWidth(),
+                             framebuffer_.GetHeight(),
+                             framebuffer_.GetRowBytes());
+  }
+
+  Status ReleaseFramebuffer(FramebufferRgb565 framebuffer) override {
     if (next_call_param_idx_ < kMaxSavedParams) {
-      call_params_[next_call_param_idx_].call_func = CallFunc::Update;
-      call_params_[next_call_param_idx_].update.fb_data =
+      call_params_[next_call_param_idx_].call_func =
+          CallFunc::ReleaseFramebuffer;
+      call_params_[next_call_param_idx_].release_framebuffer.fb_data =
           framebuffer.GetFramebufferData();
       next_call_param_idx_++;
     }
@@ -78,9 +88,9 @@
     return OkStatus();
   }
 
-  int GetWidth() const override { return width_; }
+  int GetWidth() const override { return framebuffer_.GetWidth(); }
 
-  int GetHeight() const override { return height_; }
+  int GetHeight() const override { return framebuffer_.GetHeight(); }
 
   int GetNumCalls() const {
     int count = 0;
@@ -101,24 +111,21 @@
  private:
   size_t next_call_param_idx_ = 0;
   std::array<CallParams, kMaxSavedParams> call_params_;
-  const int width_;
-  const int height_;
+  const FramebufferRgb565 framebuffer_;
 };
 
 TEST(Display, ReleaseNoResize) {
   constexpr int kFramebufferWidth = 2;
   constexpr int kFramebufferHeight = 1;
+  constexpr Size kDisplaySize = {kFramebufferWidth, kFramebufferHeight};
   constexpr int kNumPixels = kFramebufferWidth * kFramebufferHeight;
   constexpr int kFramebufferRowBytes =
       sizeof(color_rgb565_t) * kFramebufferWidth;
   color_rgb565_t pixel_data[kNumPixels];
 
-  TestDisplayDriver test_driver(kFramebufferWidth, kFramebufferHeight);
-  Display display(FramebufferRgb565(pixel_data,
-                                    kFramebufferWidth,
-                                    kFramebufferHeight,
-                                    kFramebufferRowBytes),
-                  test_driver);
+  TestDisplayDriver test_driver(FramebufferRgb565(
+      pixel_data, kFramebufferWidth, kFramebufferHeight, kFramebufferRowBytes));
+  Display display(test_driver, kDisplaySize);
   FramebufferRgb565 fb = display.GetFramebuffer();
   EXPECT_TRUE(fb.IsValid());
   EXPECT_EQ(kFramebufferWidth, fb.GetWidth());
@@ -128,13 +135,12 @@
   display.ReleaseFramebuffer(std::move(fb));
   ASSERT_EQ(1, test_driver.GetNumCalls());
   auto call = test_driver.GetCall(0);
-  EXPECT_EQ(CallFunc::Update, call.call_func);
-  EXPECT_EQ(pixel_data, call.update.fb_data);
+  EXPECT_EQ(CallFunc::ReleaseFramebuffer, call.call_func);
+  EXPECT_EQ(pixel_data, call.release_framebuffer.fb_data);
 }
 
 TEST(Display, ReleaseSmallResize) {
-  constexpr int kDisplayWidth = 8;
-  constexpr int kDisplayHeight = 4;
+  constexpr Size kDisplaySize = {8, 4};
   constexpr int kFramebufferWidth = 2;
   constexpr int kFramebufferHeight = 1;
   constexpr int kNumPixels = kFramebufferWidth * kFramebufferHeight;
@@ -142,12 +148,9 @@
       sizeof(color_rgb565_t) * kFramebufferWidth;
   color_rgb565_t pixel_data[kNumPixels];
 
-  TestDisplayDriver test_driver(kDisplayWidth, kDisplayHeight);
-  Display display(FramebufferRgb565(pixel_data,
-                                    kFramebufferWidth,
-                                    kFramebufferHeight,
-                                    kFramebufferRowBytes),
-                  test_driver);
+  TestDisplayDriver test_driver(FramebufferRgb565(
+      pixel_data, kFramebufferWidth, kFramebufferHeight, kFramebufferRowBytes));
+  Display display(test_driver, kDisplaySize);
   FramebufferRgb565 fb = display.GetFramebuffer();
   EXPECT_TRUE(fb.IsValid());
   EXPECT_EQ(kFramebufferWidth, fb.GetWidth());
@@ -183,8 +186,7 @@
 
 TEST(Display, ReleaseWideResize) {
   // Display width > resize buffer (80 px.) will cause two writes per row.
-  constexpr int kDisplayWidth = 90;
-  constexpr int kDisplayHeight = 4;
+  constexpr Size kDisplaySize = {90, 4};
   constexpr int kFramebufferWidth = 2;
   constexpr int kFramebufferHeight = 1;
   constexpr int kNumPixels = kFramebufferWidth * kFramebufferHeight;
@@ -192,12 +194,9 @@
       sizeof(color_rgb565_t) * kFramebufferWidth;
   color_rgb565_t pixel_data[kNumPixels];
 
-  TestDisplayDriver test_driver(kDisplayWidth, kDisplayHeight);
-  Display display(FramebufferRgb565(pixel_data,
-                                    kFramebufferWidth,
-                                    kFramebufferHeight,
-                                    kFramebufferRowBytes),
-                  test_driver);
+  TestDisplayDriver test_driver(FramebufferRgb565(
+      pixel_data, kFramebufferWidth, kFramebufferHeight, kFramebufferRowBytes));
+  Display display(test_driver, kDisplaySize);
   FramebufferRgb565 fb = display.GetFramebuffer();
   EXPECT_TRUE(fb.IsValid());
   EXPECT_EQ(kFramebufferWidth, fb.GetWidth());
diff --git a/pw_graphics/pw_display/public/pw_display/display.h b/pw_graphics/pw_display/public/pw_display/display.h
index ac1b8c3..473184f 100644
--- a/pw_graphics/pw_display/public/pw_display/display.h
+++ b/pw_graphics/pw_display/public/pw_display/display.h
@@ -16,6 +16,7 @@
 #include "pw_coordinates/vec_int.h"
 #include "pw_display_driver/display_driver.h"
 #include "pw_framebuffer/rgb565.h"
+#include "pw_framebuffer_pool/framebuffer_pool.h"
 #include "pw_status/status.h"
 
 namespace pw::display {
@@ -26,15 +27,14 @@
 // use for rendering.
 class Display {
  public:
-  Display(pw::framebuffer::FramebufferRgb565 framebuffer,
-          pw::display_driver::DisplayDriver& display_driver);
+  Display(pw::display_driver::DisplayDriver& display_driver,
+          pw::coordinates::Size<int> size);
   virtual ~Display();
 
-  // Initialize the display instance.
-  Status Init() { return OkStatus(); }
-
   // Return a framebuffer to which the caller may draw. When drawing is complete
-  // the framebuffer must be returned using ReleaseFramebuffer().
+  // the framebuffer must be returned using ReleaseFramebuffer(). An invalid
+  // framebuffer may be returned, so the caller should verify it is valid
+  // before use.
   pw::framebuffer::FramebufferRgb565 GetFramebuffer();
 
   // Release the framebuffer back to the display. The display will
@@ -46,10 +46,10 @@
   Status ReleaseFramebuffer(pw::framebuffer::FramebufferRgb565 framebuffer);
 
   // Return the width (in pixels) of the associated display.
-  int GetWidth() const { return framebuffer_.GetWidth(); }
+  int GetWidth() const { return size_.width; }
 
   // Return the height (in pixels) of the associated display.
-  int GetHeight() const { return framebuffer_.GetHeight(); }
+  int GetHeight() const { return size_.height; }
 
   // Does the associated screen have a touch screen?
   virtual bool TouchscreenAvailable() const { return false; }
@@ -68,8 +68,8 @@
   Status UpdateNearestNeighbor(
       const pw::framebuffer::FramebufferRgb565& framebuffer);
 
-  pw::framebuffer::FramebufferRgb565 framebuffer_;
   pw::display_driver::DisplayDriver& display_driver_;
+  const pw::coordinates::Size<int> size_;
 };
 
 }  // namespace pw::display
diff --git a/pw_graphics/pw_display_imgui/display.cc b/pw_graphics/pw_display_imgui/display.cc
index 71c4cac..25962e0 100644
--- a/pw_graphics/pw_display_imgui/display.cc
+++ b/pw_graphics/pw_display_imgui/display.cc
@@ -16,10 +16,9 @@
 namespace pw::display {
 
 DisplayImgUI::DisplayImgUI(
-    pw::framebuffer::FramebufferRgb565 framebuffer,
-    pw::display_driver::DisplayDriverImgUI& display_driver)
-    : Display(std::move(framebuffer), display_driver),
-      display_driver_(display_driver) {}
+    pw::display_driver::DisplayDriverImgUI& display_driver,
+    pw::coordinates::Size<int> size)
+    : Display(display_driver, size), display_driver_(display_driver) {}
 
 DisplayImgUI::~DisplayImgUI() = default;
 
diff --git a/pw_graphics/pw_display_imgui/public/pw_display_imgui/display.h b/pw_graphics/pw_display_imgui/public/pw_display_imgui/display.h
index ac5940e..52c43e3 100644
--- a/pw_graphics/pw_display_imgui/public/pw_display_imgui/display.h
+++ b/pw_graphics/pw_display_imgui/public/pw_display_imgui/display.h
@@ -16,7 +16,6 @@
 #include "pw_coordinates/vec_int.h"
 #include "pw_display/display.h"
 #include "pw_display_driver_imgui/display_driver.h"
-#include "pw_framebuffer/rgb565.h"
 #include "pw_status/status.h"
 
 namespace pw::display {
@@ -24,8 +23,8 @@
 // A display that uses ImgUI and supports touch input.
 class DisplayImgUI : public Display {
  public:
-  DisplayImgUI(pw::framebuffer::FramebufferRgb565 framebuffer,
-               pw::display_driver::DisplayDriverImgUI& display_driver);
+  DisplayImgUI(pw::display_driver::DisplayDriverImgUI& display_driver,
+               pw::coordinates::Size<int> size);
   ~DisplayImgUI();
 
   bool TouchscreenAvailable() const override { return true; }
diff --git a/pw_graphics/pw_framebuffer_pool/BUILD.gn b/pw_graphics/pw_framebuffer_pool/BUILD.gn
new file mode 100644
index 0000000..52777a2
--- /dev/null
+++ b/pw_graphics/pw_framebuffer_pool/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed 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
+#
+#     https://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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+pw_source_set("pw_framebuffer_pool") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_framebuffer_pool/framebuffer_pool.h" ]
+  public_deps = [
+    "$dir_pw_color",
+    "$dir_pw_coordinates",
+  ]
+}
diff --git a/pw_graphics/pw_framebuffer_pool/public/pw_framebuffer_pool/framebuffer_pool.h b/pw_graphics/pw_framebuffer_pool/public/pw_framebuffer_pool/framebuffer_pool.h
new file mode 100644
index 0000000..8dda2f2
--- /dev/null
+++ b/pw_graphics/pw_framebuffer_pool/public/pw_framebuffer_pool/framebuffer_pool.h
@@ -0,0 +1,34 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#pragma once
+
+#include <array>
+
+#include "pw_color/color.h"
+#include "pw_coordinates/vec_int.h"
+
+namespace pw::framebuffer::pool {
+
+constexpr size_t kMaxFramebufferCount = 3;
+
+struct PoolData {
+  std::array<pw::color::color_rgb565_t*, kMaxFramebufferCount> fb_addr;
+  size_t num_fb;  // <= fb_addr.size().
+  pw::coordinates::Size<int> size;
+  int row_bytes;
+  pw::coordinates::Vector2<int> start;
+};
+
+}  // namespace pw::framebuffer::pool
diff --git a/pw_mipi_dsi/BUILD.gn b/pw_mipi_dsi/BUILD.gn
new file mode 100644
index 0000000..d0ac6d1
--- /dev/null
+++ b/pw_mipi_dsi/BUILD.gn
@@ -0,0 +1,36 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed 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
+#
+#     https://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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+
+config("public_include_path") {
+  include_dirs = [ "public" ]
+  visibility = [ ":*" ]
+}
+
+pw_source_set("pw_mipi_dsi") {
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_mipi_dsi/device.h" ]
+  public_deps = [
+    "$dir_pw_framebuffer",
+    "$dir_pw_status",
+  ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_mipi_dsi/docs.rst b/pw_mipi_dsi/docs.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_mipi_dsi/docs.rst
diff --git a/pw_mipi_dsi/public/pw_mipi_dsi/device.h b/pw_mipi_dsi/public/pw_mipi_dsi/device.h
new file mode 100644
index 0000000..4fb6d98
--- /dev/null
+++ b/pw_mipi_dsi/public/pw_mipi_dsi/device.h
@@ -0,0 +1,46 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#pragma once
+
+#include "pw_framebuffer/rgb565.h"
+#include "pw_status/status.h"
+
+namespace pw::mipi::dsi {
+
+using pw::framebuffer::FramebufferRgb565;
+
+// Interface for to a MIPI Display Serial Interface(1) implementation.
+//
+// (1) https://www.mipi.org/specifications/dsi
+class Device {
+ public:
+  constexpr static size_t kMaxFramebufferCount = 3;
+
+  virtual ~Device() = default;
+
+  // Retrieve a framebuffer for rendering. An invalid framebuffer will be
+  // returned if there are no available framebuffers which can happen if
+  // all framebuffers are in the process of being sent to the display.
+  virtual pw::framebuffer::FramebufferRgb565 GetFramebuffer(void) = 0;
+
+  // Begin the process of transporting the |framebuffer| to the display.
+  virtual Status ReleaseFramebuffer(
+      pw::framebuffer::FramebufferRgb565 framebuffer) = 0;
+
+ protected:
+  Device() = default;
+};
+
+}  // namespace pw::mipi::dsi
diff --git a/pw_mipi_dsi_mcuxpresso/BUILD.gn b/pw_mipi_dsi_mcuxpresso/BUILD.gn
new file mode 100644
index 0000000..45ba90d
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/BUILD.gn
@@ -0,0 +1,60 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed 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
+#
+#     https://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.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
+
+declare_args() {
+  # Use Smart DMA for MIPI DSI transfers (0: disabled, 1: enabled).
+  pw_mipi_dsi_use_smart_dma = "1"
+}
+
+config("default_config") {
+  include_dirs = [ "public" ]
+  visibility = [ ":*" ]
+
+  cflags = [ "-DUSE_DSI_SMARTDMA=" + pw_mipi_dsi_use_smart_dma ]
+}
+
+pw_source_set("pw_mipi_dsi_mcuxpresso") {
+  public_configs = [ ":default_config" ]
+  public = [
+    "public/pw_mipi_dsi_mcuxpresso/device.h",
+    "public/pw_mipi_dsi_mcuxpresso/framebuffer_device.h",
+  ]
+  public_deps = [
+    "$dir_pw_framebuffer",
+    "$dir_pw_framebuffer_pool",
+    "$dir_pw_mipi_dsi",
+    "$dir_pw_status",
+  ]
+  deps = [
+    "$dir_pw_assert",
+    "$dir_pw_log",
+    "$pw_third_party_mcuxpresso_SDK",
+  ]
+  sources = [
+    "common.cc",
+    "common.h",
+    "device.cc",
+    "framebuffer_device.cc",
+  ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_mipi_dsi_mcuxpresso/common.cc b/pw_mipi_dsi_mcuxpresso/common.cc
new file mode 100644
index 0000000..9b7a27b
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/common.cc
@@ -0,0 +1,43 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#include "common.h"
+
+namespace pw::mipi::dsi {
+
+Status MCUXpressoToPigweedStatus(status_t mcux_status) {
+  switch (mcux_status) {
+    case kStatus_Success:
+      return OkStatus();
+    case kStatus_Fail:
+      return Status::Internal();
+    case kStatus_ReadOnly:
+      return Status::PermissionDenied();
+    case kStatus_OutOfRange:
+      return Status::OutOfRange();
+    case kStatus_InvalidArgument:
+      return Status::InvalidArgument();
+    case kStatus_Timeout:
+      return Status::DeadlineExceeded();
+    case kStatus_NoTransferInProgress:
+      return Status::FailedPrecondition();
+    case kStatus_Busy:
+      return Status::FailedPrecondition();
+    case kStatus_NoData:
+      return Status::Unavailable();
+  }
+  return Status::Internal();
+}
+
+}  // namespace pw::mipi::dsi
diff --git a/pw_mipi_dsi_mcuxpresso/common.h b/pw_mipi_dsi_mcuxpresso/common.h
new file mode 100644
index 0000000..263f380
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/common.h
@@ -0,0 +1,24 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#pragma once
+
+#include "fsl_common.h"
+#include "pw_status/status.h"
+
+namespace pw::mipi::dsi {
+
+Status MCUXpressoToPigweedStatus(status_t mcux_status);
+
+}  // namespace pw::mipi::dsi
diff --git a/pw_mipi_dsi_mcuxpresso/device.cc b/pw_mipi_dsi_mcuxpresso/device.cc
new file mode 100644
index 0000000..4e929eb
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/device.cc
@@ -0,0 +1,448 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#include "pw_mipi_dsi_mcuxpresso/device.h"
+
+#include <cstring>
+
+#include "board.h"
+#include "common.h"
+#include "fsl_gpio.h"
+#include "fsl_inputmux.h"
+#include "fsl_mipi_dsi.h"
+#include "fsl_mipi_dsi_smartdma.h"
+#include "fsl_power.h"
+#include "pin_mux.h"
+
+using pw::color::color_rgb565_t;
+using pw::framebuffer::FramebufferRgb565;
+
+namespace pw::mipi::dsi {
+
+namespace {
+
+constexpr uint8_t kMipiDsiLaneNum = 1;
+constexpr IRQn_Type kMipiDsiIrqn = MIPI_IRQn;
+constexpr int kVideoLayer = 0;
+
+// This class is currently a singleton because the some callbacks and IRQ
+// handlers do not have a user-data param.
+MCUXpressoDevice* s_device;
+
+extern "C" {
+void GPIO_INTA_DriverIRQHandler(void) {
+  uint32_t intStat = GPIO_PortGetInterruptStatus(GPIO, BOARD_MIPI_TE_PORT, 0);
+
+  GPIO_PortClearInterruptFlags(GPIO, BOARD_MIPI_TE_PORT, 0, intStat);
+
+  if (s_device && intStat & (1U << BOARD_MIPI_TE_PIN)) {
+    s_device->DisplayTEPinHandler();
+  }
+}
+
+void SDMA_DriverIRQHandler(void) { SMARTDMA_HandleIRQ(); }
+}  // extern "C"
+
+}  // namespace
+
+MCUXpressoDevice::MCUXpressoDevice(
+    const pw::framebuffer::pool::PoolData& fb_pool,
+    const pw::coordinates::Size<uint16_t>& panel_size,
+    video_pixel_format_t pixel_format)
+    : fb_pool_(fb_pool),
+      fbdev_(kVideoLayer),
+      dsi_device_({
+          .virtualChannel = 0,
+          .xferFunc = MCUXpressoDevice::DSI_Transfer,
+          .memWriteFunc = MCUXpressoDevice::DSI_MemWrite,
+          .callback = nullptr,
+          .userData = nullptr,
+      }),
+      rm67162_resource_({
+          .dsiDevice = &dsi_device_,
+          .pullResetPin = MCUXpressoDevice::PullPanelResetPin,
+          .pullPowerPin = MCUXpressoDevice::PullPanelPowerPin,
+      }),
+      display_handle_({
+          .resource = &rm67162_resource_,
+          .ops = &rm67162_ops,
+          .width = panel_size.width,
+          .height = panel_size.height,
+          .pixelFormat = pixel_format,
+      }),
+      dc_fb_dsi_cmd_handle_({
+          .dsiDevice = &dsi_device_,
+          .panelHandle = &display_handle_,
+          .initTimes = 0,
+          .enabledLayerCount = 0,
+          .layers = {},
+          .useTEPin = true,
+      }),
+      panel_config_({
+          .commonConfig =
+              {
+                  .resolution =
+                      FSL_VIDEO_RESOLUTION(panel_size.width, panel_size.height),
+                  .hsw = 0,  // Unused.
+                  .hfp = 0,  // Unused.
+                  .hbp = 0,  // Unused.
+                  .vsw = 0,  // Unused.
+                  .vfp = 0,  // Unused.
+                  .vbp = 0,  // Unused.
+                  .controlFlags = 0,
+                  .dsiLanes = kMipiDsiLaneNum,
+                  .pixelClock_Hz = 0,  // Unsure of correct value.
+                  .pixelFormat = pixel_format,
+              },
+          .useTEPin = true,
+      }),
+      dc_({
+          .ops = &g_dcFbOpsDsiCmd,
+          .prvData = &dc_fb_dsi_cmd_handle_,
+          .config = &panel_config_,
+      }) {
+  PW_ASSERT(s_device == nullptr);
+  s_device = this;
+}
+
+MCUXpressoDevice::~MCUXpressoDevice() = default;
+
+Status MCUXpressoDevice::Init() {
+  if (fb_pool_.num_fb == 0)
+    return Status::InvalidArgument();
+
+  Status s = PrepareDisplayController();
+  if (!s.ok())
+    return s;
+
+  s = fbdev_.Init(&dc_, fb_pool_);
+  if (!s.ok())
+    return s;
+
+  // Clear buffer to black - it is shown once screen is enabled.
+  void* buffer = fbdev_.GetFramebuffer();
+  if (!buffer) {
+    return Status::Internal();
+  }
+  std::memset(buffer, 0, fb_pool_.row_bytes * fb_pool_.size.height);
+  s = fbdev_.WriteFramebuffer(buffer);
+  if (!s.ok())
+    return s;
+
+  return fbdev_.Enable();
+}
+
+FramebufferRgb565 MCUXpressoDevice::GetFramebuffer() {
+  return FramebufferRgb565(
+      static_cast<color_rgb565_t*>(fbdev_.GetFramebuffer()),
+      fb_pool_.size.width,
+      fb_pool_.size.height,
+      fb_pool_.row_bytes);
+}
+
+Status MCUXpressoDevice::ReleaseFramebuffer(FramebufferRgb565 framebuffer) {
+  if (!framebuffer.IsValid())
+    return Status::InvalidArgument();
+  void* data = framebuffer.GetFramebufferData();
+  return fbdev_.WriteFramebuffer(data);
+}
+
+Status MCUXpressoDevice::PrepareDisplayController(void) {
+  Status status = InitDisplayInterface();
+  if (!status.ok())
+    return status;
+
+#if USE_DSI_SMARTDMA
+  InitSmartDMA();
+  status_t s = DSI_TransferCreateHandleSMARTDMA(
+      MIPI_DSI_HOST,
+      &dsi_smartdma_driver_handle_,
+      MCUXpressoDevice::DsiSmartDMAMemWriteCallback,
+      this);
+
+  return MCUXpressoToPigweedStatus(s);
+
+#else
+
+  NVIC_SetPriority(kMipiDsiIrqn, 6);
+
+  memset(&dsi_mem_write_ctx_, 0, sizeof(DSIMemWriteContext));
+
+  return MCUXpressoToPigweedStatus(
+      DSI_TransferCreateHandle(MIPI_DSI_HOST,
+                               &dsi_driver_handle_,
+                               MCUXpressoDevice::DsiMemWriteCallback,
+                               this));
+#endif
+}
+
+// static
+Status MCUXpressoDevice::InitDisplayInterface() {
+  RESET_SetPeripheralReset(kMIPI_DSI_PHY_RST_SHIFT_RSTn);
+  InitMipiDsiClock();
+  RESET_ClearPeripheralReset(kMIPI_DSI_CTRL_RST_SHIFT_RSTn);
+  SetMipiDsiConfig();
+  RESET_ClearPeripheralReset(kMIPI_DSI_PHY_RST_SHIFT_RSTn);
+  return InitLcdPanel();
+}
+
+Status MCUXpressoDevice::InitLcdPanel() {
+  const gpio_pin_config_t pinConfig = {
+      .pinDirection = kGPIO_DigitalOutput,
+      .outputLogic = 0,
+  };
+
+  GPIO_PinInit(GPIO, BOARD_MIPI_POWER_PORT, BOARD_MIPI_POWER_PIN, &pinConfig);
+  GPIO_PinInit(GPIO, BOARD_MIPI_RST_PORT, BOARD_MIPI_RST_PIN, &pinConfig);
+
+  InitMipiPanelTEPin();
+
+  return OkStatus();
+}
+
+// static
+void MCUXpressoDevice::InitMipiDsiClock(void) {
+  POWER_DisablePD(kPDRUNCFG_APD_MIPIDSI_SRAM);
+  POWER_DisablePD(kPDRUNCFG_PPD_MIPIDSI_SRAM);
+  POWER_DisablePD(kPDRUNCFG_PD_MIPIDSI);
+  POWER_ApplyPD();
+
+  CLOCK_AttachClk(kFRO_DIV1_to_MIPI_DPHYESC_CLK);
+  CLOCK_SetClkDiv(kCLOCK_DivDphyEscRxClk, 4);
+  CLOCK_SetClkDiv(kCLOCK_DivDphyEscTxClk, 3);
+  mipi_dsi_tx_esc_clk_freq_hz_ = CLOCK_GetMipiDphyEscTxClkFreq();
+
+  CLOCK_AttachClk(kAUX1_PLL_to_MIPI_DPHY_CLK);
+#if (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB565)
+  CLOCK_InitSysPfd(kCLOCK_Pfd3, 30);
+#else
+  CLOCK_InitSysPfd(kCLOCK_Pfd3, 19);
+#endif
+  CLOCK_SetClkDiv(kCLOCK_DivDphyClk, 1);
+  mipi_dsi_dphy_bit_clk_freq_hz_ = CLOCK_GetMipiDphyClkFreq();
+}
+
+// static
+void MCUXpressoDevice::InitMipiPanelTEPin(void) {
+  const gpio_pin_config_t tePinConfig = {
+      .pinDirection = kGPIO_DigitalInput,
+      .outputLogic = 0,
+  };
+
+  gpio_interrupt_config_t te_pin_int_config = {kGPIO_PinIntEnableEdge,
+                                               kGPIO_PinIntEnableHighOrRise};
+
+  GPIO_PinInit(GPIO, BOARD_MIPI_TE_PORT, BOARD_MIPI_TE_PIN, &tePinConfig);
+
+  GPIO_SetPinInterruptConfig(
+      GPIO, BOARD_MIPI_TE_PORT, BOARD_MIPI_TE_PIN, &te_pin_int_config);
+
+  GPIO_PinEnableInterrupt(GPIO, BOARD_MIPI_TE_PORT, BOARD_MIPI_TE_PIN, 0);
+
+  NVIC_SetPriority(GPIO_INTA_IRQn, 3);
+
+  NVIC_EnableIRQ(GPIO_INTA_IRQn);
+}
+
+// static
+void MCUXpressoDevice::SetMipiDsiConfig() {
+  dsi_config_t dsiConfig;
+  dsi_dphy_config_t dphyConfig;
+
+  DSI_GetDefaultConfig(&dsiConfig);
+  dsiConfig.numLanes = kMipiDsiLaneNum;
+  dsiConfig.autoInsertEoTp = true;
+
+  DSI_GetDphyDefaultConfig(&dphyConfig,
+                           mipi_dsi_dphy_bit_clk_freq_hz_,
+                           mipi_dsi_tx_esc_clk_freq_hz_);
+
+  DSI_Init(MIPI_DSI_HOST, &dsiConfig);
+
+  DSI_InitDphy(MIPI_DSI_HOST, &dphyConfig, 0);
+}
+
+#if USE_DSI_SMARTDMA
+// static
+void MCUXpressoDevice::InitSmartDMA() {
+  RESET_ClearPeripheralReset(kINPUTMUX_RST_SHIFT_RSTn);
+
+  INPUTMUX_Init(INPUTMUX);
+  INPUTMUX_AttachSignal(INPUTMUX, 0, kINPUTMUX_MipiIrqToSmartDmaInput);
+
+  INPUTMUX_Deinit(INPUTMUX);
+
+  POWER_DisablePD(kPDRUNCFG_APD_SMARTDMA_SRAM);
+  POWER_DisablePD(kPDRUNCFG_PPD_SMARTDMA_SRAM);
+  POWER_ApplyPD();
+
+  RESET_ClearPeripheralReset(kSMART_DMA_RST_SHIFT_RSTn);
+  CLOCK_EnableClock(kCLOCK_Smartdma);
+
+  SMARTDMA_InitWithoutFirmware();
+  NVIC_EnableIRQ(SDMA_IRQn);
+
+  NVIC_SetPriority(SDMA_IRQn, 3);
+}
+#endif
+
+// static
+status_t MCUXpressoDevice::DSI_Transfer(dsi_transfer_t* xfer) {
+  return DSI_TransferBlocking(MIPI_DSI_HOST, xfer);
+}
+
+// static
+status_t MCUXpressoDevice::DSI_MemWrite(uint8_t virtualChannel,
+                                        const uint8_t* data,
+                                        uint32_t length) {
+#if USE_DSI_SMARTDMA
+  dsi_smartdma_write_mem_transfer_t xfer = {
+#if (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB565)
+    .inputFormat = kDSI_SMARTDMA_InputPixelFormatRGB565,
+    .outputFormat = kDSI_SMARTDMA_OutputPixelFormatRGB565,
+#elif (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB888)
+    .inputFormat = kDSI_SMARTDMA_InputPixelFormatRGB888,
+    .outputFormat = kDSI_SMARTDMA_OutputPixelFormatRGB888,
+#else
+    .inputFormat = kDSI_SMARTDMA_InputPixelFormatXRGB8888,
+    .outputFormat = kDSI_SMARTDMA_OutputPixelFormatRGB888,
+#endif /* DEMO_RM67162_BUFFER_FORMAT */
+    .data = data,
+    .dataSize = length,
+
+    .virtualChannel = virtualChannel,
+    .disablePixelByteSwap = false,
+  };
+
+  return DSI_TransferWriteMemorySMARTDMA(
+      MIPI_DSI_HOST, &s_device->dsi_smartdma_driver_handle_, &xfer);
+
+#else /* USE_DSI_SMARTDMA */
+
+  status_t status;
+
+  if (s_device->dsi_mem_write_ctx_.ongoing) {
+    return kStatus_Fail;
+  }
+
+  s_device->dsi_mem_write_xfer_.virtualChannel = virtualChannel;
+  s_device->dsi_mem_write_xfer_.flags = kDSI_TransferUseHighSpeed;
+  s_device->dsi_mem_write_xfer_.sendDscCmd = true;
+
+  s_device->dsi_mem_write_ctx_.ongoing = true;
+  s_device->dsi_mem_write_ctx_.tx_data = data;
+  s_device->dsi_mem_write_ctx_.num_bytes_remaining = length;
+  s_device->dsi_mem_write_ctx_.dsc_cmd = kMIPI_DCS_WriteMemoryStart;
+
+  status = s_device->DsiMemWriteSendChunck();
+
+  if (status != kStatus_Success) {
+    /* Memory write does not start actually. */
+    s_device->dsi_mem_write_ctx_.ongoing = false;
+  }
+
+  return status;
+#endif
+}
+
+// static
+void MCUXpressoDevice::PullPanelResetPin(bool pullUp) {
+  if (pullUp) {
+    GPIO_PinWrite(GPIO, BOARD_MIPI_RST_PORT, BOARD_MIPI_RST_PIN, 1);
+  } else {
+    GPIO_PinWrite(GPIO, BOARD_MIPI_RST_PORT, BOARD_MIPI_RST_PIN, 0);
+  }
+}
+
+// static
+void MCUXpressoDevice::PullPanelPowerPin(bool pullUp) {
+  if (pullUp) {
+    GPIO_PinWrite(GPIO, BOARD_MIPI_POWER_PORT, BOARD_MIPI_POWER_PIN, 1);
+  } else {
+    GPIO_PinWrite(GPIO, BOARD_MIPI_POWER_PORT, BOARD_MIPI_POWER_PIN, 0);
+  }
+}
+
+// static
+void MCUXpressoDevice::DisplayTEPinHandler() {
+  DC_FB_DSI_CMD_TE_IRQHandler(&dc_);
+}
+
+// static
+status_t MCUXpressoDevice::DsiMemWriteSendChunck(void) {
+  uint32_t curSendLen;
+  uint32_t i;
+
+  curSendLen = kMaxDSITxArraySize > dsi_mem_write_ctx_.num_bytes_remaining
+                   ? dsi_mem_write_ctx_.num_bytes_remaining
+                   : kMaxDSITxArraySize;
+
+  dsi_mem_write_xfer_.txDataType = kDSI_TxDataDcsLongWr;
+  dsi_mem_write_xfer_.dscCmd = dsi_mem_write_ctx_.dsc_cmd;
+  dsi_mem_write_xfer_.txData = dsi_mem_write_tmp_array_;
+  dsi_mem_write_xfer_.txDataSize = curSendLen;
+
+#if (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB565)
+  for (i = 0; i < curSendLen; i += 2) {
+    dsi_mem_write_tmp_array_[i] = *(dsi_mem_write_ctx_.tx_data + 1);
+    dsi_mem_write_tmp_array_[i + 1] = *(dsi_mem_write_ctx_.tx_data);
+
+    dsi_mem_write_ctx_.tx_data += 2;
+  }
+#else
+  for (i = 0; i < curSendLen; i += 3) {
+    dsi_mem_write_tmp_array_[i] = *(dsi_mem_write_ctx_.tx_data + 2);
+    dsi_mem_write_tmp_array_[i + 1] = *(dsi_mem_write_ctx_.tx_data + 1);
+    dsi_mem_write_tmp_array_[i + 2] = *(dsi_mem_write_ctx_.tx_data);
+
+    dsi_mem_write_ctx_.tx_data += 3;
+  }
+#endif
+
+  dsi_mem_write_ctx_.num_bytes_remaining -= curSendLen;
+  dsi_mem_write_ctx_.dsc_cmd = kMIPI_DCS_WriteMemoryContinue;
+
+  return DSI_TransferNonBlocking(
+      MIPI_DSI_HOST, &dsi_driver_handle_, &dsi_mem_write_xfer_);
+}
+
+// static
+void MCUXpressoDevice::DsiMemWriteCallback(MIPI_DSI_HOST_Type* base,
+                                           dsi_handle_t* handle,
+                                           status_t status,
+                                           void* userData) {
+  MCUXpressoDevice* device = static_cast<MCUXpressoDevice*>(userData);
+  if ((kStatus_Success == status) &&
+      (device->dsi_mem_write_ctx_.num_bytes_remaining > 0)) {
+    status = device->DsiMemWriteSendChunck();
+    if (kStatus_Success == status) {
+      return;
+    }
+  }
+
+  device->dsi_mem_write_ctx_.ongoing = false;
+  MIPI_DSI_MemoryDoneDriverCallback(status, &device->dsi_device_);
+}
+
+// static
+void MCUXpressoDevice::DsiSmartDMAMemWriteCallback(
+    MIPI_DSI_HOST_Type* base,
+    dsi_smartdma_handle_t* handle,
+    status_t status,
+    void* userData) {
+  MCUXpressoDevice* device = static_cast<MCUXpressoDevice*>(userData);
+  MIPI_DSI_MemoryDoneDriverCallback(status, &device->dsi_device_);
+}
+
+}  // namespace pw::mipi::dsi
diff --git a/pw_mipi_dsi_mcuxpresso/docs.rst b/pw_mipi_dsi_mcuxpresso/docs.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/docs.rst
diff --git a/pw_mipi_dsi_mcuxpresso/framebuffer_device.cc b/pw_mipi_dsi_mcuxpresso/framebuffer_device.cc
new file mode 100644
index 0000000..f4f12c5
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/framebuffer_device.cc
@@ -0,0 +1,128 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#include "pw_mipi_dsi_mcuxpresso/framebuffer_device.h"
+
+#include "common.h"
+#include "pw_assert/assert.h"
+#include "pw_status/try.h"
+
+namespace pw::mipi::dsi {
+
+FramebufferDevice::FramebufferDevice(uint8_t layer)
+    : dc_(nullptr), layer_(layer), enabled_(false) {
+  VIDEO_MEMPOOL_InitEmpty(&video_mempool_);
+}
+
+Status FramebufferDevice::Init(
+    const dc_fb_t* dc, const pw::framebuffer::pool::PoolData& pool_data) {
+  PW_TRY(InitDisplayController(dc));
+  return InitVideoMemPool(pool_data);
+}
+
+Status FramebufferDevice::InitDisplayController(const dc_fb_t* dc) {
+  PW_ASSERT(!dc_);
+  dc_ = dc;
+
+  status_t status = dc_->ops->init(dc);
+  if (status != kStatus_Success) {
+    return MCUXpressoToPigweedStatus(status);
+  }
+
+  dc_fb_info_t buff_info;
+
+  status = dc_->ops->getLayerDefaultConfig(dc_, layer_, &buff_info);
+  if (status != kStatus_Success) {
+    return MCUXpressoToPigweedStatus(status);
+  }
+
+  dc_->ops->setCallback(dc_, layer_, BufferSwitchOffCallback, this);
+
+  status = dc_->ops->setLayerConfig(dc_, layer_, &buff_info);
+  if (status != kStatus_Success) {
+    return MCUXpressoToPigweedStatus(status);
+  }
+
+  return OkStatus();
+}
+
+Status FramebufferDevice::InitVideoMemPool(
+    const pw::framebuffer::pool::PoolData& pool_data) {
+  if (enabled_) {
+    return Status::FailedPrecondition();
+  }
+
+  for (uint8_t i = 0; i < pool_data.num_fb; i++) {
+    VIDEO_MEMPOOL_Put(&video_mempool_, pool_data.fb_addr[i]);
+  }
+
+  return OkStatus();
+}
+
+Status FramebufferDevice::Close() {
+  if (!dc_)
+    return Status::FailedPrecondition();
+
+  status_t status = dc_->ops->deinit(dc_);
+  dc_ = nullptr;
+  return MCUXpressoToPigweedStatus(status);
+}
+
+Status FramebufferDevice::Enable() {
+  if (enabled_)
+    return OkStatus();
+
+  status_t status = kStatus_Success;
+
+  if ((dc_->ops->getProperty(dc_) & (uint32_t)kDC_FB_ReserveFrameBuffer) ==
+      0U) {
+    status = dc_->ops->enableLayer(dc_, layer_);
+    if (status != kStatus_Success) {
+      enabled_ = true;
+    }
+  }
+
+  return MCUXpressoToPigweedStatus(status);
+}
+
+Status FramebufferDevice::Disable() {
+  if (!enabled_)
+    return OkStatus();
+
+  status_t status = dc_->ops->disableLayer(dc_, layer_);
+  enabled_ = false;
+
+  return MCUXpressoToPigweedStatus(status);
+}
+
+Status FramebufferDevice::WriteFramebuffer(void* frameBuffer) {
+  return MCUXpressoToPigweedStatus(
+      dc_->ops->setFrameBuffer(dc_, layer_, frameBuffer));
+}
+
+void* FramebufferDevice::GetFramebuffer() {
+  return VIDEO_MEMPOOL_Get(&video_mempool_);
+}
+
+void FramebufferDevice::BufferSwitchOff(void* buffer) {
+  VIDEO_MEMPOOL_Put(&video_mempool_, buffer);
+}
+
+// static
+void FramebufferDevice::BufferSwitchOffCallback(void* param, void* buffer) {
+  PW_ASSERT(param != nullptr);
+  static_cast<FramebufferDevice*>(param)->BufferSwitchOff(buffer);
+}
+
+}  // namespace pw::mipi::dsi
diff --git a/pw_mipi_dsi_mcuxpresso/public/pw_mipi_dsi_mcuxpresso/device.h b/pw_mipi_dsi_mcuxpresso/public/pw_mipi_dsi_mcuxpresso/device.h
new file mode 100644
index 0000000..9728b15
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/public/pw_mipi_dsi_mcuxpresso/device.h
@@ -0,0 +1,105 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#pragma once
+
+#include "fsl_dc_fb_dsi_cmd.h"
+#include "fsl_mipi_dsi.h"
+#include "fsl_mipi_dsi_smartdma.h"
+#include "fsl_rm67162.h"
+#include "pw_coordinates/vec_int.h"
+#include "pw_framebuffer_pool/framebuffer_pool.h"
+#include "pw_mipi_dsi/device.h"
+#include "pw_mipi_dsi_mcuxpresso/framebuffer_device.h"
+#include "pw_status/status.h"
+
+namespace pw::mipi::dsi {
+
+constexpr size_t kBytesPerPixel = sizeof(pw::color::color_rgb565_t);
+constexpr uint32_t kMaxDSITxArraySize =
+    (((FSL_DSI_TX_MAX_PAYLOAD_BYTE)-1U) / kBytesPerPixel) * kBytesPerPixel;
+
+#if !defined(USE_DSI_SMARTDMA)
+#error "USE_DSI_SMARTDMA not defined"
+#endif
+
+// MIPI DSI Device implementation for the MCUXpresso platform.
+class MCUXpressoDevice : public Device {
+ public:
+  MCUXpressoDevice(const pw::framebuffer::pool::PoolData& fb_pool,
+                   const pw::coordinates::Size<uint16_t>& panel_size,
+                   video_pixel_format_t pixel_format);
+  virtual ~MCUXpressoDevice();
+
+  Status Init();
+
+  // pw::mipi::dsi::Device implementation:
+  pw::framebuffer::FramebufferRgb565 GetFramebuffer() override;
+  Status ReleaseFramebuffer(
+      pw::framebuffer::FramebufferRgb565 framebuffer) override;
+
+ public:
+  static status_t DSI_Transfer(dsi_transfer_t* xfer);
+  static status_t DSI_MemWrite(uint8_t virtualChannel,
+                               const uint8_t* data,
+                               uint32_t length);
+  static void PullPanelResetPin(bool pullUp);
+  static void PullPanelPowerPin(bool pullUp);
+  void DisplayTEPinHandler();
+  Status PrepareDisplayController();
+
+ private:
+  struct DSIMemWriteContext {
+    volatile bool ongoing;
+    const uint8_t* tx_data;
+    uint32_t num_bytes_remaining;
+    uint8_t dsc_cmd;
+  };
+
+  static void DsiSmartDMAMemWriteCallback(MIPI_DSI_HOST_Type* base,
+                                          dsi_smartdma_handle_t* handle,
+                                          status_t status,
+                                          void* userData);
+  static void DsiMemWriteCallback(MIPI_DSI_HOST_Type* base,
+                                  dsi_handle_t* handle,
+                                  status_t status,
+                                  void* userData);
+  status_t DsiMemWriteSendChunck();
+  Status InitDisplayInterface();
+  Status InitLcdPanel();
+  void InitMipiPanelTEPin();
+  void InitMipiDsiClock();
+  void SetMipiDsiConfig();
+#if USE_DSI_SMARTDMA
+  void InitSmartDMA();
+#endif
+
+  const pw::framebuffer::pool::PoolData& fb_pool_;
+  FramebufferDevice fbdev_;
+  dsi_smartdma_handle_t dsi_smartdma_driver_handle_ = {};
+  DSIMemWriteContext dsi_mem_write_ctx_ = {};
+  dsi_transfer_t dsi_mem_write_xfer_ = {};
+  dsi_handle_t dsi_driver_handle_ = {};
+  uint8_t dsi_mem_write_tmp_array_[kMaxDSITxArraySize];
+  uint32_t mipi_dsi_tx_esc_clk_freq_hz_ = 0;
+  uint32_t mipi_dsi_dphy_bit_clk_freq_hz_ = 0;
+  mipi_dsi_device_t dsi_device_;
+  rm67162_resource_t rm67162_resource_;
+  display_handle_t display_handle_;
+  dc_fb_dsi_cmd_handle_t dc_fb_dsi_cmd_handle_;
+  const dc_fb_dsi_cmd_config_t panel_config_;
+  dc_fb_t dc_;
+};
+
+}  // namespace pw::mipi::dsi
diff --git a/pw_mipi_dsi_mcuxpresso/public/pw_mipi_dsi_mcuxpresso/framebuffer_device.h b/pw_mipi_dsi_mcuxpresso/public/pw_mipi_dsi_mcuxpresso/framebuffer_device.h
new file mode 100644
index 0000000..38fa366
--- /dev/null
+++ b/pw_mipi_dsi_mcuxpresso/public/pw_mipi_dsi_mcuxpresso/framebuffer_device.h
@@ -0,0 +1,70 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+
+#pragma once
+
+#include <cstdint>
+
+#include "fsl_dc_fb.h"
+#include "fsl_video_common.h"
+#include "pw_framebuffer_pool/framebuffer_pool.h"
+#include "pw_status/status.h"
+
+namespace pw::mipi::dsi {
+
+constexpr uint16_t kMaxBufferCount = 3;
+
+// FramebufferDevice manages a pool of framebuffers and is responsible for
+// writing them to the display using NXPs display controller provided by the
+// driver.dc-fb-common.MIMXRT595S SDK component. The framebuffer pool is
+// managed by the driver.video-common.MIMXRT595S SDK component.
+class FramebufferDevice {
+ public:
+  // Create a default uninitialized instance. Init must be called to fully
+  // initialize an instance before it can be used.
+  FramebufferDevice(uint8_t layer);
+
+  pw::Status Init(const dc_fb_t* dc,
+                  const pw::framebuffer::pool::PoolData& pool_data);
+
+  // Close the device.
+  pw::Status Close();
+
+  // Enable the device.
+  pw::Status Enable();
+
+  // Disable the device.
+  pw::Status Disable();
+
+  // Send the framebuffer data to the device.
+  pw::Status WriteFramebuffer(void* buffer);
+
+  // Retrieve an unused framebuffer. If all framebuffers are still in the
+  // process of being transported to the device nullptr will be returned.
+  void* GetFramebuffer();
+
+ private:
+  static void BufferSwitchOffCallback(void* param, void* buffer);
+
+  void BufferSwitchOff(void* buffer);
+  pw::Status InitDisplayController(const dc_fb_t* dc);
+  Status InitVideoMemPool(const pw::framebuffer::pool::PoolData& pool_data);
+
+  video_mempool_t video_mempool_;
+  const dc_fb_t* dc_;    // NXP Display controller.
+  const uint8_t layer_;  // The video layer to write to.
+  bool enabled_;         // Has this instance been initialized.
+};
+
+}  // namespace pw::mipi::dsi
diff --git a/targets/mimxrt595_evk/target_toolchains.gni b/targets/mimxrt595_evk/target_toolchains.gni
index d67e5d9..920e17a 100644
--- a/targets/mimxrt595_evk/target_toolchains.gni
+++ b/targets/mimxrt595_evk/target_toolchains.gni
@@ -39,6 +39,13 @@
       forward_variables_from(toolchain_overrides, "*")
 
       app_common_BACKEND = "//applications/app_common_impl:mimxrt595"
+      pw_mipi_dsi_use_smart_dma = 1
+      pw_lcd_width = 400
+      pw_lcd_height = 392
+      pw_nxp_buffer_width = 392
+      pw_nxp_buffer_start_x = 4
+      pw_nxp_buffer_start_y = 0
+
       pw_board_led_BACKEND = "$dir_pw_board_led_mimxrt595_evk"
       pw_spin_delay_BACKEND = "$dir_pw_spin_delay_mcuxpresso"
       pw_touchscreen_BACKEND = "$dir_pw_touchscreen_null"