Create app_common and unify most displays.

This change moves all platform specific code from pw_display
into a new module: app_common. This new module is responsible
for initializig GPIO, SPI, creating the framebuffers and the
display. This allows for a second big change which is moving
pw_display from a facade to a concrete class. This is made
possible by the fact that it no longer need worry about the
display type or any platform details. These are all handled
by pw_display_driver and app_common respectively.

All configuration data (display size, pin assignments, etc.)
are now in the targets and app_common is resposible for
passing these down to the implementation for use.

Bug: none
Change-Id: I2da6e9b1a24f8feb251e7610e460bfdb5008a76c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/experimental/+/120530
Commit-Queue: Chris Mumford <cmumford@google.com>
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
diff --git a/README.md b/README.md
index 6d2aa81..a62e396 100644
--- a/README.md
+++ b/README.md
@@ -165,3 +165,19 @@
 Follow the instructions to flash the MIMXRT595-EVK with the SEGGER J-Link
 firmware and using `arm-none-eabi-gdb` at
 https://pigweed.dev/targets/mimxrt595_evk/target_docs.html#running-and-debugging.
+
+#### Teensy 4.1 ####
+
+https://www.pjrc.com/teensy/loader_cli.html
+
+```bash
+brew install teensy_loader_cli
+```
+
+```sh
+OBJCOPY=/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avr-objcopy
+INFILE=out/arduino_debug/obj/applications/terminal_display/bin/terminal_demo.elf
+OUTFILE=foo.hex
+$OBJCOPY -O ihex -R .eeprom -R .fuse -R .lock -R .signature $INFILE $OUTFILE
+teensy_loader_cli --mcu=TEENSY41 -w -v $OUTFILE
+```
diff --git a/pw_graphics/pw_display_host_imgui/glfw.gni b/applications/app_common/BUILD.gn
similarity index 61%
rename from pw_graphics/pw_display_host_imgui/glfw.gni
rename to applications/app_common/BUILD.gn
index d73111f..ec34126 100644
--- a/pw_graphics/pw_display_host_imgui/glfw.gni
+++ b/applications/app_common/BUILD.gn
@@ -12,8 +12,23 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+import("//build_overrides/pigweed.gni")
+import("$dir_pw_build/facade.gni")
+
 declare_args() {
-  # Location of glfw download.
-  # Install with: pw package install glfw
-  dir_pw_third_party_glfw = ""
+  app_common_BACKEND = ""
+}
+
+config("public_includes") {
+  include_dirs = [ "public" ]
+}
+
+pw_facade("app_common") {
+  backend = app_common_BACKEND
+  public_configs = [ ":public_includes" ]
+  public_deps = [
+    "$dir_pw_display",
+    "$dir_pw_status",
+  ]
+  public = [ "public/app_common/common.h" ]
 }
diff --git a/applications/app_common/README.md b/applications/app_common/README.md
new file mode 100644
index 0000000..f1245a8
--- /dev/null
+++ b/applications/app_common/README.md
@@ -0,0 +1,7 @@
+# Application Commend
+This is a facade that has implementations for each target. app_common is
+a module that contains platform specific initialization code which is common
+to all (or at least some) executables in //applications.
+
+The target implementations of this facade are located in
+//applications/app_common_impl.
diff --git a/applications/app_common/public/app_common/common.h b/applications/app_common/public/app_common/common.h
new file mode 100644
index 0000000..9b8652c
--- /dev/null
+++ b/applications/app_common/public/app_common/common.h
@@ -0,0 +1,31 @@
+// Copyright 2022 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_display/display.h"
+#include "pw_status/status.h"
+
+// This class is used for initialization and to create the objects which
+// are common to the test applications.
+class Common {
+ public:
+  Common() = delete;
+
+  // Initialize application common objects. This must be called before
+  // any other methods in this class.
+  static pw::Status Init();
+
+  // Return an initialized display.
+  static pw::display::Display& GetDisplay();
+};
diff --git a/applications/app_common_impl/BUILD.gn b/applications/app_common_impl/BUILD.gn
new file mode 100644
index 0000000..21b77ca
--- /dev/null
+++ b/applications/app_common_impl/BUILD.gn
@@ -0,0 +1,193 @@
+# Copyright 2022 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/pi_pico.gni")
+import("//build_overrides/pigweed.gni")
+import("$dir_pigweed_experimental/third_party/imgui/imgui.gni")
+import("$dir_pw_build/target_types.gni")
+
+declare_args() {
+  # LCD width in pixels (ex. "320")
+  pw_lcd_width = ""
+
+  # LCD height in pixels (ex. "240")
+  pw_lcd_height = ""
+
+  # LCD backlight pin (ex "4", or "-1" if unused)
+  pw_lcd_backlight = "-1"
+
+  # LCD reset GPIO pin (ex "2", or "-1" if unused)
+  pw_lcd_rst_pin_num = "-1"
+
+  # GPIO Port of LCD CS pin (ex. "A")
+  pw_lcd_cs_port_char = ""
+
+  # GPIO Pin of LDD CS (ex. "10")
+  pw_lcd_cs_pin_num = ""
+
+  # GPIO Port of LCD DC pin (ex. "B")
+  pw_lcd_dc_port_char = ""
+
+  # GPIO Pin of LDD dc (ex. "11")
+  pw_lcd_dc_pin_num = ""
+
+  # SPI bus MISO pin (ex. "12", or "-1" if unusedsable)
+  pw_spi_miso_pin_num = "-1"
+
+  # SPI bus MOSI pin (ex. "13")
+  pw_spi_mosi_pin_num = ""
+
+  # SPI bus clock pin (ex. "14")
+  pw_spi_clock_pin_num = ""
+}
+
+config("common_standard_flags") {
+  cflags = [
+    "-DLCD_WIDTH=" + pw_lcd_width,
+    "-DLCD_HEIGHT=" + pw_lcd_height,
+    "-DTFT_BL=" + pw_lcd_backlight,
+    "-DTFT_CS=" + pw_lcd_cs_pin_num,
+    "-DTFT_DC=" + pw_lcd_dc_pin_num,
+    "-DTFT_RST=" + pw_lcd_rst_pin_num,
+    "-DTFT_MISO=" + pw_spi_miso_pin_num,
+    "-DTFT_MOSI=" + pw_spi_mosi_pin_num,
+    "-DTFT_SCLK=" + pw_spi_clock_pin_num,
+  ]
+}
+
+config("common_stm32_flags") {
+  cflags = [
+    "-DLCD_WIDTH=" + pw_lcd_width,
+    "-DLCD_HEIGHT=" + pw_lcd_height,
+    "-DLCD_BL=" + pw_lcd_backlight,
+    "-DLCD_CS_PORT_CHAR=" + pw_lcd_cs_port_char,
+    "-DLCD_CS_PIN_NUM=" + pw_lcd_cs_pin_num,
+    "-DLCD_DC_PORT_CHAR=" + pw_lcd_dc_port_char,
+    "-DLCD_DC_PIN_NUM=" + pw_lcd_dc_pin_num,
+    "-DLCD_RST_PIN_NUM=" + pw_lcd_rst_pin_num,
+  ]
+}
+
+config("common_host_flags") {
+  cflags = [
+    "-DLCD_WIDTH=" + pw_lcd_width,
+    "-DLCD_HEIGHT=" + pw_lcd_height,
+  ]
+}
+
+pw_source_set("stm32cube") {
+  public_configs = [ ":common_stm32_flags" ]
+  deps = [
+    "$dir_pw_digital_io_stm32cube",
+    "$dir_pw_display",
+    "$dir_pw_display_driver_ili9341",
+    "$dir_pw_spi_stm32f429i_disc1_stm32cube",
+    "$dir_pw_sync:borrow",
+    "$dir_pw_sync:mutex",
+    "//applications/app_common:app_common.facade",
+  ]
+  sources = [ "common_stm32cube.cc" ]
+}
+
+pw_source_set("stm32") {
+  deps = [
+    "$dir_pw_display_driver_null",
+    "//applications/app_common:app_common.facade",
+  ]
+  sources = [ "common_stm32.cc" ]
+}
+
+pw_source_set("arduino") {
+  public_configs = [ ":common_standard_flags" ]
+  deps = [
+    "$dir_pw_digital_io_arduino",
+    "$dir_pw_display",
+    "$dir_pw_display_driver_ili9341",
+    "$dir_pw_spi_arduino",
+    "$dir_pw_sync:borrow",
+    "$dir_pw_sync:mutex",
+    "$dir_pw_third_party/arduino:arduino_core_sources",
+    "//applications/app_common:app_common.facade",
+  ]
+  sources = [ "common_arduino.cc" ]
+  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" ]
+}
+
+pw_source_set("pico_ili9341") {
+  public_configs = [ ":common_standard_flags" ]
+  cflags = [ "-DDISPLAY_TYPE_ILI9341" ]
+  deps = [
+    "$PICO_ROOT/src/common/pico_base",
+    "$PICO_ROOT/src/common/pico_stdlib",
+    "$PICO_ROOT/src/rp2_common/hardware_spi",
+    "$dir_pw_digital_io_pico",
+    "$dir_pw_display",
+    "$dir_pw_display_driver_ili9341",
+    "$dir_pw_log",
+    "$dir_pw_spi_pico",
+    "$dir_pw_sync:borrow",
+    "$dir_pw_sync:mutex",
+    "//applications/app_common:app_common.facade",
+  ]
+  sources = [ "common_pico.cc" ]
+  remove_configs = [ "$dir_pw_build:strict_warnings" ]
+}
+
+pw_source_set("pico_st7789") {
+  public_configs = [ ":common_standard_flags" ]
+  cflags = [ "-DDISPLAY_TYPE_ST7789" ]
+  deps = [
+    "$PICO_ROOT/src/common/pico_base",
+    "$PICO_ROOT/src/common/pico_stdlib",
+    "$PICO_ROOT/src/rp2_common/hardware_spi",
+    "$dir_pw_digital_io_pico",
+    "$dir_pw_display",
+    "$dir_pw_display_driver_st7789",
+    "$dir_pw_log",
+    "$dir_pw_spi_pico",
+    "$dir_pw_sync:borrow",
+    "$dir_pw_sync:mutex",
+    "//applications/app_common:app_common.facade",
+  ]
+  sources = [ "common_pico.cc" ]
+  remove_configs = [ "$dir_pw_build:strict_warnings" ]
+}
+
+pw_source_set("host_imgui") {
+  public_configs = [ ":common_host_flags" ]
+  deps = [
+    "$dir_pw_display_driver_imgui",
+    "$dir_pw_display_imgui",
+    "//applications/app_common:app_common.facade",
+  ]
+  sources = [ "common_host_imgui.cc" ]
+}
+
+pw_source_set("host_null") {
+  public_configs = [ ":common_host_flags" ]
+  deps = [
+    "$dir_pw_display",
+    "$dir_pw_display_driver_null",
+    "//applications/app_common:app_common.facade",
+  ]
+  sources = [ "common_host_null.cc" ]
+}
diff --git a/applications/app_common_impl/common_arduino.cc b/applications/app_common_impl/common_arduino.cc
new file mode 100644
index 0000000..babf6d5
--- /dev/null
+++ b/applications/app_common_impl/common_arduino.cc
@@ -0,0 +1,118 @@
+// Copyright 2022 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 "app_common/common.h"
+#include "pw_digital_io_arduino/digital_io.h"
+#include "pw_display_driver_ili9341/display_driver.h"
+#include "pw_spi_arduino/chip_selector.h"
+#include "pw_spi_arduino/initiator.h"
+#include "pw_sync/borrow.h"
+#include "pw_sync/mutex.h"
+
+using pw::Status;
+using pw::display::Display;
+using DisplayDriver = pw::display_driver::DisplayDriverILI9341;
+using pw::framebuffer::FramebufferRgb565;
+using pw::spi::Device;
+using pw::spi::Initiator;
+using pw::sync::Borrowable;
+using pw::sync::VirtualMutex;
+using DigitalOut = pw::digital_io::ArduinoDigitalOut;
+using SpiChipSelector = pw::spi::ArduinoChipSelector;
+using SpiInitiator = pw::spi::ArduinoInitiator;
+
+namespace {
+
+struct SpiValues {
+  SpiValues(pw::spi::Config config,
+            pw::spi::ChipSelector& selector,
+            pw::sync::VirtualMutex& initiator_mutex);
+
+  pw::spi::ArduinoInitiator initiator;
+  pw::sync::Borrowable<pw::spi::Initiator> borrowable_initiator;
+  pw::spi::Device device;
+};
+
+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,
+    .phase = pw::spi::ClockPhase::kFallingEdge,
+    .bits_per_word = pw::spi::BitsPerWord(8),
+    .bit_order = pw::spi::BitOrder::kMsbFirst,
+};
+constexpr pw::spi::Config kSpiConfig16Bit{
+    .polarity = pw::spi::ClockPolarity::kActiveHigh,
+    .phase = pw::spi::ClockPhase::kFallingEdge,
+    .bits_per_word = pw::spi::BitsPerWord(16),
+    .bit_order = pw::spi::BitOrder::kMsbFirst,
+};
+
+DigitalOut s_display_dc_pin(TFT_DC);
+#if TFT_RST != -1
+DigitalOut s_display_reset_pin(TFT_RST);
+#endif
+DigitalOut s_display_cs_pin(TFT_CS);
+SpiChipSelector s_spi_chip_selector(s_display_cs_pin);
+SpiInitiator s_spi_initiator;
+VirtualMutex s_spi_initiator_mutex;
+Borrowable<Initiator> s_borrowable_spi_initiator(s_spi_initiator,
+                                                 s_spi_initiator_mutex);
+SpiValues s_spi_8_bit(kSpiConfig8Bit,
+                      s_spi_chip_selector,
+                      s_spi_initiator_mutex);
+SpiValues s_spi_16_bit(kSpiConfig16Bit,
+                       s_spi_chip_selector,
+                       s_spi_initiator_mutex);
+DisplayDriver s_display_driver({
+  .data_cmd_gpio = s_display_dc_pin,
+#if TFT_RST != -1
+  .reset_gpio = &s_display_reset_pin,
+#else
+  .reset_gpio = nullptr,
+#endif
+  .spi_device_8_bit = s_spi_8_bit.device,
+  .spi_device_16_bit = s_spi_16_bit.device,
+});
+uint16_t s_pixel_data[kNumPixels];
+Display s_display(
+    FramebufferRgb565(s_pixel_data, LCD_WIDTH, LCD_HEIGHT, kDisplayRowBytes),
+    s_display_driver);
+
+SpiValues::SpiValues(pw::spi::Config config,
+                     pw::spi::ChipSelector& selector,
+                     pw::sync::VirtualMutex& initiator_mutex)
+    : borrowable_initiator(initiator, initiator_mutex),
+      device(borrowable_initiator, config, selector) {}
+
+}  // namespace
+
+// static
+Status Common::Init() {
+  s_display_cs_pin.Enable();
+  s_display_dc_pin.Enable();
+#if TFT_RST != -1
+  s_display_reset_pin.Enable();
+#endif
+
+  SPI.begin();
+
+  PW_TRY(s_display_driver.Init());
+
+  return s_display.Init();
+}
+
+// static
+pw::display::Display& Common::GetDisplay() { return s_display; }
diff --git a/applications/app_common_impl/common_host_imgui.cc b/applications/app_common_impl/common_host_imgui.cc
new file mode 100644
index 0000000..f90b57f
--- /dev/null
+++ b/applications/app_common_impl/common_host_imgui.cc
@@ -0,0 +1,42 @@
+// Copyright 2022 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 "app_common/common.h"
+#include "pw_display_driver_imgui/display_driver.h"
+#include "pw_display_imgui/display.h"
+#include "pw_status/try.h"
+
+using pw::Status;
+using pw::framebuffer::FramebufferRgb565;
+
+namespace {
+
+constexpr int kNumPixels = LCD_WIDTH * LCD_HEIGHT;
+constexpr int kDisplayRowBytes = sizeof(uint16_t) * LCD_WIDTH;
+
+uint16_t s_pixel_data[kNumPixels];
+pw::display_driver::DisplayDriverImgUI s_display_driver;
+pw::display::DisplayImgUI s_display(
+    FramebufferRgb565(s_pixel_data, LCD_WIDTH, LCD_HEIGHT, kDisplayRowBytes),
+    s_display_driver);
+
+}  // namespace
+
+// static
+Status Common::Init() {
+  PW_TRY(s_display_driver.Init());
+  return s_display.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
new file mode 100644
index 0000000..9984d0c
--- /dev/null
+++ b/applications/app_common_impl/common_host_null.cc
@@ -0,0 +1,42 @@
+// Copyright 2022 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 "app_common/common.h"
+#include "pw_display/display.h"
+#include "pw_display_driver_null/display_driver.h"
+#include "pw_status/try.h"
+
+using pw::Status;
+using pw::framebuffer::FramebufferRgb565;
+
+namespace {
+
+constexpr int kNumPixels = LCD_WIDTH * LCD_HEIGHT;
+constexpr int kDisplayRowBytes = sizeof(uint16_t) * LCD_WIDTH;
+
+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);
+
+}  // namespace
+
+// static
+Status Common::Init() {
+  PW_TRY(s_display_driver.Init());
+  return s_display.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
new file mode 100644
index 0000000..eedd04a
--- /dev/null
+++ b/applications/app_common_impl/common_mimxrt595.cc
@@ -0,0 +1,29 @@
+// Copyright 2022 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 "app_common/common.h"
+#include "pw_display_driver_null/display_driver.h"
+
+namespace {
+
+pw::display_driver::DisplayDriverNULL s_display_driver;
+pw::display::Display s_display(pw::framebuffer::FramebufferRgb565(),
+                               s_display_driver);
+
+}  // namespace
+
+// static
+pw::Status Common::Init() { return s_display.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
new file mode 100644
index 0000000..cbba548
--- /dev/null
+++ b/applications/app_common_impl/common_pico.cc
@@ -0,0 +1,168 @@
+// Copyright 2022 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 <cstdint>
+
+#include "app_common/common.h"
+
+#define LIB_CMSIS_CORE 0
+#define LIB_PICO_STDIO_SEMIHOSTING 0
+
+#include "hardware/gpio.h"
+#include "pico/stdlib.h"
+#include "pw_digital_io_pico/digital_io.h"
+#include "pw_log/log.h"
+#include "pw_spi_pico/chip_selector.h"
+#include "pw_spi_pico/initiator.h"
+#include "pw_sync/borrow.h"
+#include "pw_sync/mutex.h"
+
+#if defined(DISPLAY_TYPE_ILI9341)
+#include "pw_display_driver_ili9341/display_driver.h"
+using DisplayDriver = pw::display_driver::DisplayDriverILI9341;
+#elif defined(DISPLAY_TYPE_ST7789)
+#include "pw_display_driver_st7789/display_driver.h"
+using DisplayDriver = pw::display_driver::DisplayDriverST7789;
+#else
+#error "Undefined display type"
+#endif
+
+using pw::Status;
+using pw::digital_io::PicoDigitalOut;
+using pw::display::Display;
+using pw::framebuffer::FramebufferRgb565;
+using pw::spi::Device;
+using pw::spi::Initiator;
+using pw::spi::PicoChipSelector;
+using pw::spi::PicoInitiator;
+using pw::sync::Borrowable;
+using pw::sync::VirtualMutex;
+
+namespace {
+
+// Pico spi0 Pins
+#define SPI_PORT spi0
+
+struct SpiValues {
+  SpiValues(pw::spi::Config config,
+            pw::spi::ChipSelector& selector,
+            pw::sync::VirtualMutex& initiator_mutex);
+
+  pw::spi::PicoInitiator initiator;
+  pw::sync::Borrowable<pw::spi::Initiator> borrowable_initiator;
+  pw::spi::Device device;
+};
+
+constexpr int kNumPixels = LCD_WIDTH * LCD_HEIGHT;
+constexpr int kDisplayRowBytes = sizeof(uint16_t) * LCD_WIDTH;
+
+constexpr uint32_t kBaudRate = 31'250'000;
+constexpr pw::spi::Config kSpiConfig8Bit{
+    .polarity = pw::spi::ClockPolarity::kActiveHigh,
+    .phase = pw::spi::ClockPhase::kFallingEdge,
+    .bits_per_word = pw::spi::BitsPerWord(8),
+    .bit_order = pw::spi::BitOrder::kMsbFirst,
+};
+constexpr pw::spi::Config kSpiConfig16Bit{
+    .polarity = pw::spi::ClockPolarity::kActiveHigh,
+    .phase = pw::spi::ClockPhase::kFallingEdge,
+    .bits_per_word = pw::spi::BitsPerWord(16),
+    .bit_order = pw::spi::BitOrder::kMsbFirst,
+};
+
+PicoDigitalOut s_display_dc_pin(TFT_DC);
+#if TFT_RST != -1
+PicoDigitalOut s_display_reset_pin(TFT_RST);
+#endif
+PicoDigitalOut s_display_cs_pin(TFT_CS);
+PicoChipSelector s_spi_chip_selector(s_display_cs_pin);
+PicoInitiator s_spi_initiator(SPI_PORT, kBaudRate);
+VirtualMutex s_spi_initiator_mutex;
+Borrowable<Initiator> s_borrowable_spi_initiator(s_spi_initiator,
+                                                 s_spi_initiator_mutex);
+SpiValues s_spi_8_bit(kSpiConfig8Bit,
+                      s_spi_chip_selector,
+                      s_spi_initiator_mutex);
+SpiValues s_spi_16_bit(kSpiConfig16Bit,
+                       s_spi_chip_selector,
+                       s_spi_initiator_mutex);
+DisplayDriver s_display_driver({
+  .data_cmd_gpio = s_display_dc_pin,
+#if TFT_RST != -1
+  .reset_gpio = &s_display_reset_pin,
+#else
+  .reset_gpio = nullptr,
+#endif
+  .spi_device_8_bit = s_spi_8_bit.device,
+  .spi_device_16_bit = s_spi_16_bit.device,
+});
+uint16_t pixel_data[kNumPixels];
+Display s_display(
+    FramebufferRgb565(pixel_data, LCD_WIDTH, LCD_HEIGHT, kDisplayRowBytes),
+    s_display_driver);
+
+#if TFT_BL != -1
+void SetBacklight(uint16_t brightness) {
+  pwm_config cfg = pwm_get_default_config();
+  pwm_set_wrap(pwm_gpio_to_slice_num(TFT_BL), 65535);
+  pwm_init(pwm_gpio_to_slice_num(TFT_BL), &cfg, true);
+  gpio_set_function(TFT_BL, GPIO_FUNC_PWM);
+
+  pwm_set_gpio_level(TFT_BL, brightness);
+}
+#endif
+
+SpiValues::SpiValues(pw::spi::Config config,
+                     pw::spi::ChipSelector& selector,
+                     pw::sync::VirtualMutex& initiator_mutex)
+    : initiator(SPI_PORT, kBaudRate),
+      borrowable_initiator(initiator, initiator_mutex),
+      device(borrowable_initiator, config, selector) {}
+
+}  // namespace
+
+// static
+Status Common::Init() {
+  // Initialize all of the present standard stdio types that are linked into the
+  // binary.
+  stdio_init_all();
+
+  // Set up the default UART and assign it to the default GPIO's.
+  setup_default_uart();
+
+  s_display_cs_pin.Enable();
+  s_display_dc_pin.Enable();
+#if TFT_RST != -1
+  s_display_reset_pin.Enable();
+#endif
+
+#if TFT_BL != -1
+  SetBacklight(0xffff);  // Full brightness.
+#endif
+
+  unsigned actual_baudrate = spi_init(SPI_PORT, kBaudRate);
+  PW_LOG_DEBUG("Actual Baudrate: %u", actual_baudrate);
+
+#if TFT_MISO != -1
+  gpio_set_function(TFT_MISO, GPIO_FUNC_SPI);
+#endif
+  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();
+}
+
+// static
+pw::display::Display& Common::GetDisplay() { return s_display; }
diff --git a/applications/app_common_impl/common_stm32.cc b/applications/app_common_impl/common_stm32.cc
new file mode 100644
index 0000000..eedd04a
--- /dev/null
+++ b/applications/app_common_impl/common_stm32.cc
@@ -0,0 +1,29 @@
+// Copyright 2022 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 "app_common/common.h"
+#include "pw_display_driver_null/display_driver.h"
+
+namespace {
+
+pw::display_driver::DisplayDriverNULL s_display_driver;
+pw::display::Display s_display(pw::framebuffer::FramebufferRgb565(),
+                               s_display_driver);
+
+}  // namespace
+
+// static
+pw::Status Common::Init() { return s_display.Init(); }
+
+// static
+pw::display::Display& Common::GetDisplay() { return s_display; }
diff --git a/applications/app_common_impl/common_stm32cube.cc b/applications/app_common_impl/common_stm32cube.cc
new file mode 100644
index 0000000..5ef7e96
--- /dev/null
+++ b/applications/app_common_impl/common_stm32cube.cc
@@ -0,0 +1,139 @@
+// Copyright 2022 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 <cstdint>
+
+#include "app_common/common.h"
+#include "pw_digital_io_stm32cube/digital_io.h"
+#include "pw_display_driver_ili9341/display_driver.h"
+#include "pw_spi_stm32f429i_disc1_stm32cube/chip_selector.h"
+#include "pw_spi_stm32f429i_disc1_stm32cube/initiator.h"
+#include "pw_sync/borrow.h"
+#include "pw_sync/mutex.h"
+
+using pw::Status;
+using pw::digital_io::Stm32CubeDigitalOut;
+using pw::display::Display;
+using pw::display_driver::DisplayDriverILI9341;
+using pw::framebuffer::FramebufferRgb565;
+using pw::spi::Device;
+using pw::spi::Initiator;
+using pw::spi::Stm32CubeChipSelector;
+using pw::spi::Stm32CubeInitiator;
+using pw::sync::Borrowable;
+using pw::sync::VirtualMutex;
+
+namespace {
+
+#define _CAT(A, B) A##B
+#define CAT(A, B) _CAT(A, B)
+
+#define LCD_CS_PORT CAT(GPIO, LCD_CS_PORT_CHAR)
+#define LCD_CS_PIN CAT(GPIO_PIN_, LCD_CS_PIN_NUM)
+
+#define LCD_DC_PORT CAT(GPIO, LCD_DC_PORT_CHAR)
+#define LCD_DC_PIN CAT(GPIO_PIN_, LCD_DC_PIN_NUM)
+
+struct SpiValues {
+  SpiValues(pw::spi::Config config,
+            pw::spi::ChipSelector& selector,
+            pw::sync::VirtualMutex& initiator_mutex);
+
+  pw::spi::Stm32CubeInitiator initiator;
+  pw::sync::Borrowable<pw::spi::Initiator> borrowable_initiator;
+  pw::spi::Device device;
+};
+
+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,
+    .phase = pw::spi::ClockPhase::kFallingEdge,
+    .bits_per_word = pw::spi::BitsPerWord(8),
+    .bit_order = pw::spi::BitOrder::kMsbFirst,
+};
+constexpr pw::spi::Config kSpiConfig16Bit{
+    .polarity = pw::spi::ClockPolarity::kActiveHigh,
+    .phase = pw::spi::ClockPhase::kFallingEdge,
+    .bits_per_word = pw::spi::BitsPerWord(16),
+    .bit_order = pw::spi::BitOrder::kMsbFirst,
+};
+
+Stm32CubeDigitalOut s_display_dc_pin(LCD_DC_PORT, LCD_DC_PIN);
+Stm32CubeDigitalOut s_display_cs_pin(LCD_CS_PORT, LCD_CS_PIN);
+Stm32CubeChipSelector s_spi_chip_selector(s_display_cs_pin);
+Stm32CubeInitiator s_spi_initiator;
+VirtualMutex s_spi_initiator_mutex;
+Borrowable<Initiator> s_borrowable_spi_initiator(s_spi_initiator,
+                                                 s_spi_initiator_mutex);
+SpiValues s_spi_8_bit(kSpiConfig8Bit,
+                      s_spi_chip_selector,
+                      s_spi_initiator_mutex);
+SpiValues s_spi_16_bit(kSpiConfig16Bit,
+                       s_spi_chip_selector,
+                       s_spi_initiator_mutex);
+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,
+});
+uint16_t pixel_data[kNumPixels];
+Display s_display(
+    FramebufferRgb565(pixel_data, LCD_WIDTH, LCD_HEIGHT, kDisplayRowBytes),
+    s_display_driver);
+
+SpiValues::SpiValues(pw::spi::Config config,
+                     pw::spi::ChipSelector& selector,
+                     pw::sync::VirtualMutex& initiator_mutex)
+    : borrowable_initiator(initiator, initiator_mutex),
+      device(borrowable_initiator, config, selector) {}
+
+}  // namespace
+
+// static
+Status Common::Init() {
+  __HAL_RCC_GPIOA_CLK_ENABLE();
+  __HAL_RCC_GPIOB_CLK_ENABLE();
+  __HAL_RCC_GPIOC_CLK_ENABLE();
+  __HAL_RCC_GPIOD_CLK_ENABLE();
+  __HAL_RCC_GPIOE_CLK_ENABLE();
+  __HAL_RCC_GPIOF_CLK_ENABLE();
+  __HAL_RCC_GPIOG_CLK_ENABLE();
+  __HAL_RCC_GPIOH_CLK_ENABLE();
+
+  __HAL_RCC_SPI5_CLK_ENABLE();
+
+  s_display_cs_pin.Enable();
+  s_display_dc_pin.Enable();
+
+  // SPI5 GPIO Configuration:
+  // PF7 SPI5_SCK
+  // PF8 SPI5_MISO
+  // PF9 SPI5_MOSI
+  GPIO_InitTypeDef spi_pin_config = {
+      .Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9,
+      .Mode = GPIO_MODE_AF_PP,
+      .Pull = GPIO_NOPULL,
+      .Speed = GPIO_SPEED_FREQ_VERY_HIGH,
+      .Alternate = GPIO_AF5_SPI5,
+  };
+  HAL_GPIO_Init(GPIOF, &spi_pin_config);
+
+  PW_TRY(s_display_driver.Init());
+
+  return s_display.Init();
+}
+
+// static
+pw::display::Display& Common::GetDisplay() { return s_display; }
diff --git a/applications/terminal_display/BUILD.gn b/applications/terminal_display/BUILD.gn
index f414c3a..66e3bfb 100644
--- a/applications/terminal_display/BUILD.gn
+++ b/applications/terminal_display/BUILD.gn
@@ -48,6 +48,7 @@
   sources = [ "main.cc" ]
   deps = [
     ":text_buffer",
+    "$dir_app_common",
     "$dir_pw_color",
     "$dir_pw_containers:vector",
     "$dir_pw_coordinates",
diff --git a/applications/terminal_display/main.cc b/applications/terminal_display/main.cc
index b72d269..ebc65f3 100644
--- a/applications/terminal_display/main.cc
+++ b/applications/terminal_display/main.cc
@@ -20,13 +20,14 @@
 #define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
 
 #include "ansi.h"
+#include "app_common/common.h"
+#include "pw_assert/check.h"
 #include "pw_board_led/led.h"
 #include "pw_color/color.h"
 #include "pw_color/colors_endesga32.h"
 #include "pw_color/colors_pico8.h"
 #include "pw_coordinates/vec2.h"
 #include "pw_coordinates/vec_int.h"
-#include "pw_display/display_backend.h"
 #include "pw_draw/draw.h"
 #include "pw_draw/font_set.h"
 #include "pw_draw/pigweed_farm.h"
@@ -42,7 +43,7 @@
 using pw::color::colors_pico8_rgb565;
 using pw::coordinates::Size;
 using pw::coordinates::Vector2;
-using pw::display::backend::Display;
+using pw::display::Display;
 using pw::draw::FontSet;
 using pw::framebuffer::FramebufferRgb565;
 
@@ -388,22 +389,20 @@
   uint32_t frames = 0;
   int frames_per_second = 0;
 
-  pw::board_led::Init();
+  pw::log_basic::SetOutput(LogCallback);
 
-  Display display;
+  pw::board_led::Init();
+  PW_CHECK_OK(Common::Init());
+
+  Display& display = Common::GetDisplay();
   FramebufferRgb565 frame_buffer;
-  display.InitFramebuffer(&frame_buffer).IgnoreError();
+  PW_CHECK_OK(display.InitFramebuffer(&frame_buffer));
 
   pw::draw::Fill(&frame_buffer, kBlack);
 
-  PW_LOG_INFO("pw::display::Init()");
-  display.Init();
-
   PW_LOG_INFO("pw::touchscreen::Init()");
   pw::touchscreen::Init();
 
-  pw::log_basic::SetOutput(LogCallback);
-
   pw::coordinates::Vec3Int last_frame_touch_state(0, 0, 0);
 
   DrawFrame(frame_buffer);
diff --git a/build_overrides/pigweed.gni b/build_overrides/pigweed.gni
index 426f67b..a3fb12e 100644
--- a/build_overrides/pigweed.gni
+++ b/build_overrides/pigweed.gni
@@ -23,6 +23,9 @@
 
 # Experimental modules.
 declare_args() {
+  dir_app_common =
+      get_path_info("$dir_pigweed_experimental/applications/app_common",
+                    "abspath")
   dir_pw_board_led =
       get_path_info("$dir_pigweed_experimental/pw_board_led", "abspath")
   dir_pw_board_led_arduino =
@@ -70,28 +73,9 @@
   dir_pw_display_driver_st7789 =
       get_path_info("$dir_pigweed_experimental/pw_display_driver_st7789",
                     "abspath")
-  dir_pw_display_host_imgui =
-      get_path_info(
-          "$dir_pigweed_experimental/pw_graphics/pw_display_host_imgui",
-          "abspath")
-  dir_pw_display_null =
-      get_path_info("$dir_pigweed_experimental/pw_graphics/pw_display_null",
+  dir_pw_display_imgui =
+      get_path_info("$dir_pigweed_experimental/pw_graphics/pw_display_imgui",
                     "abspath")
-  dir_pw_display_pico_ili9341 =
-      get_path_info(
-          "$dir_pigweed_experimental/pw_graphics/pw_display_pico_ili9341",
-          "abspath")
-  dir_pw_display_pico_st7789_spi =
-      get_path_info(
-          "$dir_pigweed_experimental/pw_graphics/pw_display_pico_st7789_spi",
-          "abspath")
-  dir_pw_display_stm32f429i_disc1_stm32cube_ili9341 = get_path_info(
-          "$dir_pigweed_experimental/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341",
-          "abspath")
-  dir_pw_display_teensy_ili9341 =
-      get_path_info(
-          "$dir_pigweed_experimental/pw_graphics/pw_display_teensy_ili9341",
-          "abspath")
   dir_pw_draw =
       get_path_info("$dir_pigweed_experimental/pw_graphics/pw_draw", "abspath")
   dir_pw_framebuffer =
diff --git a/pw_graphics/pw_display_host_imgui/BUILD.gn b/pw_digital_io_null/BUILD.gn
similarity index 63%
copy from pw_graphics/pw_display_host_imgui/BUILD.gn
copy to pw_digital_io_null/BUILD.gn
index d8b5aae..8c0f03d 100644
--- a/pw_graphics/pw_display_host_imgui/BUILD.gn
+++ b/pw_digital_io_null/BUILD.gn
@@ -16,20 +16,17 @@
 
 import("$dir_pw_build/target_types.gni")
 
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
+config("public_includes") {
+  include_dirs = [ "public" ]
 }
 
-pw_source_set("pw_display_host_imgui") {
-  public_configs = [ ":backend_config" ]
-  public = [ "public_overrides/pw_display/display_backend.h" ]
-  deps = [ "$dir_pw_display:pw_display.facade" ]
+pw_source_set("pw_digital_io_null") {
+  public_configs = [ ":public_includes" ]
+  public = [ "public/pw_digital_io_null/digital_io.h" ]
+  deps = [ "$dir_pw_log" ]
   public_deps = [
-    "$dir_pw_coordinates",
-    "$dir_pw_display_driver_imgui",
-    "$dir_pw_framebuffer",
+    "$dir_pw_digital_io",
     "$dir_pw_status",
   ]
-  sources = [ "display.cc" ]
+  sources = [ "digital_io.cc" ]
 }
diff --git a/pw_digital_io_null/digital_io.cc b/pw_digital_io_null/digital_io.cc
new file mode 100644
index 0000000..b8769ce
--- /dev/null
+++ b/pw_digital_io_null/digital_io.cc
@@ -0,0 +1,34 @@
+// Copyright 2022 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_digital_io_null/digital_io.h"
+
+#include "pw_log/log.h"
+#include "pw_status/status.h"
+
+namespace pw::digital_io {
+
+NullDigitalOut::NullDigitalOut(uint32_t pin) : pin_(pin) {}
+
+Status NullDigitalOut::DoEnable(bool enable) {
+  enabled_ = enable;
+  PW_LOG_INFO("PIN(%u)[%c]", static_cast<unsigned>(pin_), enabled_ ? '*' : ' ');
+  return OkStatus();
+}
+
+Status NullDigitalOut::DoSetState(State level) {
+  return DoEnable(level == State::kActive);
+}
+
+}  // namespace pw::digital_io
diff --git a/pw_digital_io_null/public/pw_digital_io_null/digital_io.h b/pw_digital_io_null/public/pw_digital_io_null/digital_io.h
new file mode 100644
index 0000000..c71763a
--- /dev/null
+++ b/pw_digital_io_null/public/pw_digital_io_null/digital_io.h
@@ -0,0 +1,36 @@
+// Copyright 2022 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 "pw_digital_io/digital_io.h"
+
+namespace pw::digital_io {
+
+class NullDigitalOut : public DigitalOut {
+ public:
+  NullDigitalOut(uint32_t pin);
+
+  // pw::digital_io::DigitalOut implementation:
+  Status DoEnable(bool enable) override;
+  Status DoSetState(State level) override;
+
+ private:
+  uint32_t pin_;
+  bool enabled_ = false;
+};
+
+}  // namespace pw::digital_io
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 b86fefb..49a65b9 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,8 +36,8 @@
     // The SPI device to which the display controller is connected for 16-bit
     // data.
     pw::spi::Device& spi_device_16_bit;
-    int screen_width;
-    int screen_height;
+    int screen_width = 320;
+    int screen_height = 240;
   };
 
   DisplayDriverST7789(const Config& config);
diff --git a/pw_graphics/pw_display/BUILD.gn b/pw_graphics/pw_display/BUILD.gn
index 03fdc21..aea1cb9 100644
--- a/pw_graphics/pw_display/BUILD.gn
+++ b/pw_graphics/pw_display/BUILD.gn
@@ -13,25 +13,20 @@
 # the License.
 
 import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/facade.gni")
-
-declare_args() {
-  # The backend (driver) to use for a display device.
-  pw_display_BACKEND = ""
-}
+import("$dir_pw_build/target_types.gni")
 
 config("public_includes") {
   include_dirs = [ "public" ]
 }
 
-pw_facade("pw_display") {
-  backend = pw_display_BACKEND
+pw_source_set("pw_display") {
   public_configs = [ ":public_includes" ]
   public = [ "public/pw_display/display.h" ]
   public_deps = [
     "$dir_pw_coordinates",
+    "$dir_pw_display_driver:display_driver",
     "$dir_pw_framebuffer",
     "$dir_pw_status",
   ]
+  sources = [ "display.cc" ]
 }
diff --git a/pw_graphics/pw_display/display.cc b/pw_graphics/pw_display/display.cc
new file mode 100644
index 0000000..4a657ce
--- /dev/null
+++ b/pw_graphics/pw_display/display.cc
@@ -0,0 +1,37 @@
+// Copyright 2022 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/display.h"
+
+namespace pw::display {
+
+Display::Display(pw::framebuffer::FramebufferRgb565 framebuffer,
+                 pw::display_driver::DisplayDriver& display_driver)
+    : framebuffer_(std::move(framebuffer)), display_driver_(display_driver) {}
+
+Display::~Display() = default;
+
+Status Display::InitFramebuffer(
+    pw::framebuffer::FramebufferRgb565* framebuffer) {
+  framebuffer->SetFramebufferData(framebuffer_.GetFramebufferData(),
+                                  framebuffer_.GetWidth(),
+                                  framebuffer_.GetHeight(),
+                                  framebuffer_.GetRowBytes());
+  return OkStatus();
+}
+
+void Display::Update(pw::framebuffer::FramebufferRgb565& framebuffer) {
+  display_driver_.Update(&framebuffer).IgnoreError();
+}
+
+}  // namespace pw::display
diff --git a/pw_graphics/pw_display/public/pw_display/display.h b/pw_graphics/pw_display/public/pw_display/display.h
index dd79984..5510cf3 100644
--- a/pw_graphics/pw_display/public/pw_display/display.h
+++ b/pw_graphics/pw_display/public/pw_display/display.h
@@ -14,38 +14,51 @@
 #pragma once
 
 #include "pw_coordinates/vec_int.h"
+#include "pw_display_driver/display_driver.h"
 #include "pw_framebuffer/rgb565.h"
 #include "pw_status/status.h"
 
 namespace pw::display {
 
-// Defines an interface that all display backends must implement.
+// The display is an object that represents a single display (or screen)
+// attached to the host. There is a 1:1 correspondence with the screen
+// that it manages. It has one or more framebuffers which its clients may
+// use for rendering.
 class Display {
  public:
-  virtual ~Display() = default;
+  Display(pw::framebuffer::FramebufferRgb565 framebuffer,
+          pw::display_driver::DisplayDriver& display_driver);
+  virtual ~Display();
 
-  // Initialize the display.
-  virtual Status Init() = 0;
+  // Initialize the display instance.
+  Status Init() { return OkStatus(); }
 
-  // Initialize the supplied |framebuffer| to the appropriate size for the
-  // display.
-  virtual Status InitFramebuffer(
-      pw::framebuffer::FramebufferRgb565* framebuffer) = 0;
+  // Initialize the |framebuffer| for the caller to draw in.
+  Status InitFramebuffer(pw::framebuffer::FramebufferRgb565* framebuffer);
 
-  // TODO(tonymd): Add a DPI or physical size value.
-  virtual int GetWidth() const = 0;
-  virtual int GetHeight() const = 0;
+  // Return the width (in pixels) of the associated display.
+  int GetWidth() const { return framebuffer_.GetWidth(); }
 
-  // TODO(tonymd): Add update functions for new framebuffer types or make this a
-  // templated class.
-  virtual void Update(pw::framebuffer::FramebufferRgb565& framebuffer) = 0;
+  // Return the height (in pixels) of the associated display.
+  int GetHeight() const { return framebuffer_.GetHeight(); }
 
-  virtual bool TouchscreenAvailable() const = 0;
-  virtual bool NewTouchEvent() = 0;
-  virtual pw::coordinates::Vec3Int GetTouchPoint() = 0;
+  // Transport the pixels in the |framebuffer| to the associated screen.
+  void Update(pw::framebuffer::FramebufferRgb565& framebuffer);
 
- protected:
-  Display() = default;
+  // Does the associated screen have a touch screen?
+  virtual bool TouchscreenAvailable() const { return false; }
+
+  // Is there a new touch event available?
+  virtual bool NewTouchEvent() { return false; }
+
+  // Return the new touch point.
+  virtual pw::coordinates::Vec3Int GetTouchPoint() {
+    return pw::coordinates::Vec3Int{0, 0, 0};
+  }
+
+ private:
+  pw::framebuffer::FramebufferRgb565 framebuffer_;
+  pw::display_driver::DisplayDriver& display_driver_;
 };
 
 }  // namespace pw::display
diff --git a/pw_graphics/pw_display_host_imgui/README.md b/pw_graphics/pw_display_host_imgui/README.md
deleted file mode 100644
index 50410c8..0000000
--- a/pw_graphics/pw_display_host_imgui/README.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# pw_display_host_imgui
-
-## Setup Instructions
-
-1. Install [ImGui](https://github.com/ocornut/imgui) and [glfw](https://www.glfw.org/).
-
-   ```
-   pw package install imgui
-   pw package install glfw
-   ```
-
-2. Install host OS requiremets (only required for Linux) shown below.
-
-## ImGui Requirements
-
-### Linux
-
-- Debian / Ubuntu
-
-  ```sh
-  sudo apt install libglfw3-dev libglfw3
-  ```
-
-- Arch Linux
-
-  ```sh
-  sudo pacman -S glfw-x11
-  ```
-
-3. Compile with:
-
-```
-gn gen out --args="
-dir_pw_third_party_imgui=\"$PWD/.environment/packages/imgui\"
-"
-ninja -C out host
-./out/host_debug/obj/applications/terminal_display/bin/terminal_demo
-```
diff --git a/pw_graphics/pw_display_host_imgui/display.cc b/pw_graphics/pw_display_host_imgui/display.cc
deleted file mode 100644
index bf9341f..0000000
--- a/pw_graphics/pw_display_host_imgui/display.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2022 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.
-
-// LCD Facade using imgui running on a host machine.
-// Much of this code is from the imgui example:
-// https://github.com/ocornut/imgui/tree/master/examples/example_glfw_opengl3
-// As well as the wiki page:
-// https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
-#include "pw_display/display_backend.h"
-
-using pw::framebuffer::FramebufferRgb565;
-
-namespace pw::display::backend {
-
-int Display::GetWidth() const { return kDisplayWidth; }
-
-int Display::GetHeight() const { return kDisplayHeight; }
-
-Status Display::Init() { return display_driver_.Init(); }
-
-Display::Display() = default;
-
-Display::~Display() = default;
-
-void Display::Update(FramebufferRgb565& frame_buffer) {
-  display_driver_.Update(&frame_buffer);
-}
-
-bool Display::TouchscreenAvailable() const { return true; }
-
-bool Display::NewTouchEvent() { return display_driver_.NewTouchEvent(); }
-
-pw::coordinates::Vec3Int Display::GetTouchPoint() {
-  return display_driver_.GetTouchPoint();
-}
-
-Status Display::InitFramebuffer(FramebufferRgb565* framebuffer) {
-  framebuffer->SetFramebufferData(framebuffer_data_,
-                                  kDisplayWidth,
-                                  kDisplayHeight,
-                                  kDisplayWidth * sizeof(uint16_t));
-  return OkStatus();
-}
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_host_imgui/public_overrides/pw_display/display_backend.h b/pw_graphics/pw_display_host_imgui/public_overrides/pw_display/display_backend.h
deleted file mode 100644
index 9d7508c..0000000
--- a/pw_graphics/pw_display_host_imgui/public_overrides/pw_display/display_backend.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2022 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_display/display.h"
-#include "pw_display_driver_imgui/display_driver.h"
-
-namespace pw::display::backend {
-
-class Display : pw::display::Display {
- public:
-  Display();
-  virtual ~Display();
-
-  // pw::display::Display implementation:
-  Status Init() override;
-  Status InitFramebuffer(
-      pw::framebuffer::FramebufferRgb565* framebuffer) override;
-  int GetWidth() const override;
-  int GetHeight() const override;
-  void Update(pw::framebuffer::FramebufferRgb565& framebuffer) override;
-  bool TouchscreenAvailable() const override;
-  bool NewTouchEvent() override;
-  pw::coordinates::Vec3Int GetTouchPoint() override;
-
- private:
-  constexpr static int kDisplayWidth = 320;
-  constexpr static int kDisplayHeight = 240;
-  constexpr static int kNumDisplayPixels = kDisplayWidth * kDisplayHeight;
-
-  pw::display_driver::DisplayDriverImgUI display_driver_;
-  uint16_t framebuffer_data_[kNumDisplayPixels];
-};
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_host_imgui/BUILD.gn b/pw_graphics/pw_display_imgui/BUILD.gn
similarity index 74%
rename from pw_graphics/pw_display_host_imgui/BUILD.gn
rename to pw_graphics/pw_display_imgui/BUILD.gn
index d8b5aae..8a42d8e 100644
--- a/pw_graphics/pw_display_host_imgui/BUILD.gn
+++ b/pw_graphics/pw_display_imgui/BUILD.gn
@@ -13,20 +13,18 @@
 # the License.
 
 import("//build_overrides/pigweed.gni")
-
 import("$dir_pw_build/target_types.gni")
 
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
+config("public_includes") {
+  include_dirs = [ "public" ]
 }
 
-pw_source_set("pw_display_host_imgui") {
-  public_configs = [ ":backend_config" ]
-  public = [ "public_overrides/pw_display/display_backend.h" ]
-  deps = [ "$dir_pw_display:pw_display.facade" ]
+pw_source_set("pw_display_imgui") {
+  public_configs = [ ":public_includes" ]
+  public = [ "public/pw_display_imgui/display.h" ]
   public_deps = [
     "$dir_pw_coordinates",
+    "$dir_pw_display",
     "$dir_pw_display_driver_imgui",
     "$dir_pw_framebuffer",
     "$dir_pw_status",
diff --git a/pw_graphics/pw_display_imgui/display.cc b/pw_graphics/pw_display_imgui/display.cc
new file mode 100644
index 0000000..71c4cac
--- /dev/null
+++ b/pw_graphics/pw_display_imgui/display.cc
@@ -0,0 +1,32 @@
+// Copyright 2022 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_imgui/display.h"
+
+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) {}
+
+DisplayImgUI::~DisplayImgUI() = default;
+
+bool DisplayImgUI::NewTouchEvent() { return display_driver_.NewTouchEvent(); }
+
+pw::coordinates::Vec3Int DisplayImgUI::GetTouchPoint() {
+  return display_driver_.GetTouchPoint();
+}
+
+}  // namespace pw::display
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
new file mode 100644
index 0000000..ac5940e
--- /dev/null
+++ b/pw_graphics/pw_display_imgui/public/pw_display_imgui/display.h
@@ -0,0 +1,39 @@
+// Copyright 2022 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/display.h"
+#include "pw_display_driver_imgui/display_driver.h"
+#include "pw_framebuffer/rgb565.h"
+#include "pw_status/status.h"
+
+namespace pw::display {
+
+// 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();
+
+  bool TouchscreenAvailable() const override { return true; }
+  bool NewTouchEvent() override;
+  pw::coordinates::Vec3Int GetTouchPoint() override;
+
+ private:
+  pw::display_driver::DisplayDriverImgUI& display_driver_;
+};
+
+}  // namespace pw::display
diff --git a/pw_graphics/pw_display_null/BUILD.gn b/pw_graphics/pw_display_null/BUILD.gn
deleted file mode 100644
index 6ecbb5c..0000000
--- a/pw_graphics/pw_display_null/BUILD.gn
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2022 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("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_display_null") {
-  public_configs = [ ":backend_config" ]
-  deps = [ "$dir_pw_display:pw_display.facade" ]
-  public_deps = [ "$dir_pw_display_driver_null" ]
-  public = [ "public_overrides/pw_display/display_backend.h" ]
-  sources = [ "display.cc" ]
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-}
diff --git a/pw_graphics/pw_display_null/display.cc b/pw_graphics/pw_display_null/display.cc
deleted file mode 100644
index 2124df7..0000000
--- a/pw_graphics/pw_display_null/display.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2022 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 <cinttypes>
-
-#include "pw_display/display_backend.h"
-
-using pw::framebuffer::FramebufferRgb565;
-
-namespace pw::display::backend {
-
-namespace {
-
-constexpr int kDisplayWidth = 320;
-constexpr int kDisplayHeight = 240;
-
-}  // namespace
-
-Display::Display() = default;
-
-Display::~Display() = default;
-
-Status Display::Init() { return display_driver_.Init(); }
-
-int Display::GetWidth() const { return kDisplayWidth; }
-
-int Display::GetHeight() const { return kDisplayHeight; }
-
-void Display::Update(pw::framebuffer::FramebufferRgb565& frame_buffer) {
-  display_driver_.Update(&frame_buffer).IgnoreError();
-}
-
-bool Display::TouchscreenAvailable() const { return false; }
-
-bool Display::NewTouchEvent() { return false; }
-
-pw::coordinates::Vec3Int Display::GetTouchPoint() {
-  return pw::coordinates::Vec3Int{0, 0, 0};
-}
-
-Status Display::InitFramebuffer(FramebufferRgb565* framebuffer) {
-  return OkStatus();
-}
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_null/public_overrides/pw_display/display_backend.h b/pw_graphics/pw_display_null/public_overrides/pw_display/display_backend.h
deleted file mode 100644
index a81cda8..0000000
--- a/pw_graphics/pw_display_null/public_overrides/pw_display/display_backend.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2022 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_display/display.h"
-#include "pw_display_driver_null/display_driver.h"
-
-namespace pw::display::backend {
-
-class Display : pw::display::Display {
- public:
-  Display();
-  virtual ~Display();
-
-  // pw::display::Display implementation:
-  Status Init() override;
-  Status InitFramebuffer(
-      pw::framebuffer::FramebufferRgb565* framebuffer) override;
-  int GetWidth() const override;
-  int GetHeight() const override;
-  void Update(pw::framebuffer::FramebufferRgb565& framebuffer) override;
-  bool TouchscreenAvailable() const override;
-  bool NewTouchEvent() override;
-  pw::coordinates::Vec3Int GetTouchPoint() override;
-
- private:
-  pw::display_driver::DisplayDriverNULL display_driver_;
-};
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_pico_ili9341/BUILD.gn b/pw_graphics/pw_display_pico_ili9341/BUILD.gn
deleted file mode 100644
index d6139ef..0000000
--- a/pw_graphics/pw_display_pico_ili9341/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2022 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/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_display_pico_ili9341") {
-  public_configs = [ ":backend_config" ]
-  public_deps = [
-    "$dir_pw_digital_io_pico",
-    "$dir_pw_display_driver_ili9341",
-    "$dir_pw_spi_pico",
-    "$dir_pw_sync:borrow",
-    "$dir_pw_sync:mutex",
-  ]
-  deps = [
-    "$PICO_ROOT/src/common/pico_base",
-    "$PICO_ROOT/src/common/pico_stdlib",
-    "$PICO_ROOT/src/rp2_common/hardware_spi",
-    "$dir_pw_display:pw_display.facade",
-    "$dir_pw_log",
-  ]
-  public = [ "public_overrides/pw_display/display_backend.h" ]
-  sources = [ "display.cc" ]
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-}
diff --git a/pw_graphics/pw_display_pico_ili9341/display.cc b/pw_graphics/pw_display_pico_ili9341/display.cc
deleted file mode 100644
index 14b6884..0000000
--- a/pw_graphics/pw_display_pico_ili9341/display.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2022 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 <stdio.h>
-#include <stdlib.h>
-
-#include <cinttypes>
-#include <cstdint>
-
-#include "pw_display/display_backend.h"
-
-#define LIB_CMSIS_CORE 0
-#define LIB_PICO_STDIO_SEMIHOSTING 0
-
-#include "hardware/gpio.h"
-#include "pico/stdlib.h"
-#include "pw_log/log.h"
-
-namespace pw::display::backend {
-
-namespace {
-
-// Pico spi0 Pins
-#define SPI_PORT spi0
-constexpr int TFT_SCLK = 18;  // SPI0 SCK
-constexpr int TFT_MOSI = 19;  // SPI0 TX
-// Unused
-// constexpr int TFT_MISO = 4;   // SPI0 RX
-constexpr int TFT_CS = 9;    // SPI0 CSn
-constexpr int TFT_DC = 10;   // GP10
-constexpr int TFT_RST = 11;  // GP11
-
-constexpr uint32_t kBaudRate = 31'250'000;
-
-constexpr pw::spi::Config kSpiConfig8Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(8),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-constexpr pw::spi::Config kSpiConfig16Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(16),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-}  // namespace
-
-Display::SpiValues::SpiValues(pw::spi::Config config,
-                              pw::spi::ChipSelector& selector,
-                              pw::sync::VirtualMutex& initiator_mutex)
-    : initiator_(SPI_PORT, kBaudRate),
-      borrowable_initiator_(initiator_, initiator_mutex),
-      device_(borrowable_initiator_, config, selector) {}
-
-Display::Display()
-    : chip_selector_gpio_(TFT_CS),
-      data_cmd_gpio_(TFT_DC),
-      reset_gpio_(TFT_RST),
-      spi_chip_selector_(chip_selector_gpio_),
-      spi_8_bit_(kSpiConfig8Bit, spi_chip_selector_, spi_initiator_mutex_),
-      spi_16_bit_(kSpiConfig16Bit, spi_chip_selector_, spi_initiator_mutex_),
-      display_driver_({
-          .data_cmd_gpio = data_cmd_gpio_,
-          .reset_gpio = &reset_gpio_,
-          .spi_device_8_bit = spi_8_bit_.device_,
-          .spi_device_16_bit = spi_16_bit_.device_,
-      }) {}
-
-Display::~Display() = default;
-
-Status Display::Init() {
-  InitGPIO();
-  InitSPI();
-  return display_driver_.Init();
-}
-
-void Display::Update(pw::framebuffer::FramebufferRgb565& frame_buffer) {
-  display_driver_.Update(&frame_buffer).IgnoreError();
-}
-
-void Display::UpdatePixelDouble(
-    pw::framebuffer::FramebufferRgb565* frame_buffer) {
-  display_driver_.UpdatePixelDouble(frame_buffer).IgnoreError();
-}
-
-Status Display::InitFramebuffer(
-    pw::framebuffer::FramebufferRgb565* framebuffer) {
-  framebuffer->SetFramebufferData(framebuffer_data_,
-                                  kDisplayWidth,
-                                  kDisplayHeight,
-                                  kDisplayWidth * sizeof(uint16_t));
-  return OkStatus();
-}
-
-void Display::InitGPIO() {
-  stdio_init_all();
-  // TODO: This should be a facade
-  setup_default_uart();
-
-  chip_selector_gpio_.Enable();
-  data_cmd_gpio_.Enable();
-  reset_gpio_.Enable();
-}
-
-void Display::InitSPI() {
-  uint actual_baudrate = spi_init(SPI_PORT, kBaudRate);
-  PW_LOG_DEBUG("Actual Baudrate: %u", actual_baudrate);
-
-  // Not currently used (not yet reading from display).
-  // gpio_set_function(TFT_MISO, GPIO_FUNC_SPI);
-  gpio_set_function(TFT_SCLK, GPIO_FUNC_SPI);
-  gpio_set_function(TFT_MOSI, GPIO_FUNC_SPI);
-}
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_pico_ili9341/public_overrides/pw_display/display_backend.h b/pw_graphics/pw_display_pico_ili9341/public_overrides/pw_display/display_backend.h
deleted file mode 100644
index 3bed233..0000000
--- a/pw_graphics/pw_display_pico_ili9341/public_overrides/pw_display/display_backend.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2022 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_digital_io_pico/digital_io.h"
-#include "pw_display/display.h"
-#include "pw_display_driver_ili9341/display_driver.h"
-#include "pw_spi_pico/chip_selector.h"
-#include "pw_spi_pico/initiator.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::display::backend {
-
-class Display : pw::display::Display {
- public:
-  Display();
-  virtual ~Display();
-
-  // pw::display::Display implementation:
-  Status Init() override;
-  Status InitFramebuffer(
-      pw::framebuffer::FramebufferRgb565* framebuffer) override;
-  int GetWidth() const override { return kDisplayWidth; }
-  int GetHeight() const override { return kDisplayHeight; }
-  void Update(pw::framebuffer::FramebufferRgb565& framebuffer) override;
-  bool TouchscreenAvailable() const override { return false; }
-  bool NewTouchEvent() override { return false; }
-  pw::coordinates::Vec3Int GetTouchPoint() override {
-    return pw::coordinates::Vec3Int{0, 0, 0};
-  }
-
- private:
-  struct SpiValues {
-    SpiValues(pw::spi::Config config,
-              pw::spi::ChipSelector& selector,
-              pw::sync::VirtualMutex& initiator_mutex);
-
-    pw::spi::PicoInitiator initiator_;
-    pw::sync::Borrowable<pw::spi::Initiator> borrowable_initiator_;
-    pw::spi::Device device_;
-  };
-
-  constexpr static int kDisplayWidth = 320;
-  constexpr static int kDisplayHeight = 240;
-  constexpr static int kNumDisplayPixels = kDisplayWidth * kDisplayHeight;
-
-  void UpdatePixelDouble(pw::framebuffer::FramebufferRgb565* frame_buffer);
-  void InitGPIO();
-  void InitSPI();
-
-  pw::digital_io::PicoDigitalOut chip_selector_gpio_;
-  pw::digital_io::PicoDigitalOut data_cmd_gpio_;
-  pw::digital_io::PicoDigitalOut reset_gpio_;
-  pw::spi::PicoChipSelector spi_chip_selector_;
-  pw::sync::VirtualMutex spi_initiator_mutex_;
-  SpiValues spi_8_bit_;
-  SpiValues spi_16_bit_;
-  pw::display_driver::DisplayDriverILI9341 display_driver_;
-  uint16_t framebuffer_data_[kNumDisplayPixels];
-};
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_pico_st7789_spi/BUILD.gn b/pw_graphics/pw_display_pico_st7789_spi/BUILD.gn
deleted file mode 100644
index ea58749..0000000
--- a/pw_graphics/pw_display_pico_st7789_spi/BUILD.gn
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2022 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/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_display_pico_st7789_spi") {
-  public_configs = [ ":backend_config" ]
-  public_deps = [
-    "$dir_pw_digital_io_pico",
-    "$dir_pw_display_driver_st7789",
-    "$dir_pw_spi_pico",
-    "$dir_pw_sync:borrow",
-    "$dir_pw_sync:mutex",
-  ]
-  deps = [
-    "$PICO_ROOT/src/common/pico_base",
-    "$PICO_ROOT/src/common/pico_stdlib",
-    "$PICO_ROOT/src/rp2_common/hardware_pwm",
-    "$PICO_ROOT/src/rp2_common/hardware_spi",
-    "$dir_pw_display:pw_display.facade",
-    "$dir_pw_log",
-    "$dir_pw_status",
-  ]
-  public = [ "public_overrides/pw_display/display_backend.h" ]
-  sources = [ "display.cc" ]
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-}
diff --git a/pw_graphics/pw_display_pico_st7789_spi/display.cc b/pw_graphics/pw_display_pico_st7789_spi/display.cc
deleted file mode 100644
index 3025725..0000000
--- a/pw_graphics/pw_display_pico_st7789_spi/display.cc
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2022 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 <stdio.h>
-#include <stdlib.h>
-
-#include <cinttypes>
-#include <cstdint>
-
-#include "pw_display/display_backend.h"
-
-#define LIB_CMSIS_CORE 0
-#define LIB_PICO_STDIO_SEMIHOSTING 0
-
-#include "hardware/gpio.h"
-#include "hardware/pwm.h"
-#include "pico/stdlib.h"
-#include "pw_log/log.h"
-#include "pw_status/try.h"
-
-namespace pw::display::backend {
-
-namespace {
-
-// Pico Display Pack 2 Pins
-// https://shop.pimoroni.com/products/pico-display-pack-2-0
-// --------------------------------------------------------
-constexpr int BACKLIGHT_EN = 20;
-// Pico spi0 Pins
-#define SPI_PORT spi0
-constexpr int TFT_SCLK = 18;  // SPI0 SCK
-constexpr int TFT_MOSI = 19;  // SPI0 TX
-// Unused
-// constexpr int TFT_MISO = 4;   // SPI0 RX
-constexpr int TFT_CS = 17;  // SPI0 CSn
-constexpr int TFT_DC = 16;  // GP10
-// Reset pin is connected to the Pico reset pin (RUN #30)
-// constexpr int TFT_RST = 19;
-
-constexpr uint32_t kBaudRate = 62'500'000;
-
-constexpr pw::spi::Config kSpiConfig8Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(8),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-constexpr pw::spi::Config kSpiConfig16Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(16),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-}  // namespace
-
-Display::SpiValues::SpiValues(pw::spi::Config config,
-                              pw::spi::ChipSelector& selector,
-                              pw::sync::VirtualMutex& initiator_mutex)
-    : initiator_(SPI_PORT, kBaudRate),
-      borrowable_initiator_(initiator_, initiator_mutex),
-      device_(borrowable_initiator_, config, selector) {}
-
-Display::Display()
-    : chip_selector_gpio_(TFT_CS),
-      data_cmd_gpio_(TFT_DC),
-      spi_chip_selector_(chip_selector_gpio_),
-      spi_8_bit_(kSpiConfig8Bit, spi_chip_selector_, spi_initiator_mutex_),
-      spi_16_bit_(kSpiConfig16Bit, spi_chip_selector_, spi_initiator_mutex_),
-      display_driver_({
-          .data_cmd_gpio = data_cmd_gpio_,
-          .reset_gpio = nullptr,
-          .spi_device_8_bit = spi_8_bit_.device_,
-          .spi_device_16_bit = spi_16_bit_.device_,
-          .screen_width = kDisplayWidth,
-          .screen_height = kDisplayHeight,
-      }) {}
-
-Display::~Display() = default;
-
-Status Display::Init() {
-  stdio_init_all();
-  // TODO: This should be a facade
-  setup_default_uart();
-
-  InitGPIO();
-  InitSPI();
-
-  // Init backlight PWM
-  pwm_config cfg = pwm_get_default_config();
-  pwm_set_wrap(pwm_gpio_to_slice_num(BACKLIGHT_EN), 65535);
-  pwm_init(pwm_gpio_to_slice_num(BACKLIGHT_EN), &cfg, true);
-  gpio_set_function(BACKLIGHT_EN, GPIO_FUNC_PWM);
-  // Full Brightness
-  pwm_set_gpio_level(BACKLIGHT_EN, 65535);
-
-  PW_TRY(display_driver_.Init());
-
-  return OkStatus();
-}
-
-void Display::Update(pw::framebuffer::FramebufferRgb565& frame_buffer) {
-  display_driver_.Update(&frame_buffer);
-}
-
-Status Display::InitFramebuffer(
-    pw::framebuffer::FramebufferRgb565* framebuffer) {
-  framebuffer->SetFramebufferData(framebuffer_data_,
-                                  kDisplayWidth,
-                                  kDisplayHeight,
-                                  kDisplayWidth * sizeof(uint16_t));
-  return OkStatus();
-}
-
-void Display::InitGPIO() {
-  gpio_init(TFT_CS);
-  gpio_init(TFT_DC);
-  // gpio_init(TFT_RST); // Unused
-
-  gpio_set_dir(TFT_CS, GPIO_OUT);
-  gpio_set_dir(TFT_DC, GPIO_OUT);
-
-  chip_selector_gpio_.Enable();
-  data_cmd_gpio_.Enable();
-}
-
-void Display::InitSPI() {
-  uint actual_baudrate = spi_init(SPI_PORT, kBaudRate);
-  PW_LOG_DEBUG("Actual Baudrate: %u", actual_baudrate);
-
-  // Not currently used (not yet reading from display).
-  // gpio_set_function(TFT_MISO, GPIO_FUNC_SPI);
-  gpio_set_function(TFT_SCLK, GPIO_FUNC_SPI);
-  gpio_set_function(TFT_MOSI, GPIO_FUNC_SPI);
-}
-
-int Display::GetWidth() const { return kDisplayWidth; }
-
-int Display::GetHeight() const { return kDisplayHeight; }
-
-bool Display::TouchscreenAvailable() const { return false; }
-
-bool Display::NewTouchEvent() { return false; }
-
-pw::coordinates::Vec3Int Display::GetTouchPoint() {
-  return pw::coordinates::Vec3Int{0, 0, 0};
-}
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_pico_st7789_spi/public_overrides/pw_display/display_backend.h b/pw_graphics/pw_display_pico_st7789_spi/public_overrides/pw_display/display_backend.h
deleted file mode 100644
index 2fd48e7..0000000
--- a/pw_graphics/pw_display_pico_st7789_spi/public_overrides/pw_display/display_backend.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2022 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_digital_io_pico/digital_io.h"
-#include "pw_display/display.h"
-#include "pw_display_driver_st7789/display_driver.h"
-#include "pw_spi_pico/chip_selector.h"
-#include "pw_spi_pico/initiator.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::display::backend {
-
-class Display : pw::display::Display {
- public:
-  Display();
-  virtual ~Display();
-
-  // pw::display::Display implementation:
-  Status Init() override;
-  Status InitFramebuffer(
-      pw::framebuffer::FramebufferRgb565* framebuffer) override;
-  int GetWidth() const override;
-  int GetHeight() const override;
-  void Update(pw::framebuffer::FramebufferRgb565& framebuffer) override;
-  bool TouchscreenAvailable() const override;
-  bool NewTouchEvent() override;
-  pw::coordinates::Vec3Int GetTouchPoint() override;
-
- private:
-  struct SpiValues {
-    SpiValues(pw::spi::Config config,
-              pw::spi::ChipSelector& selector,
-              pw::sync::VirtualMutex& initiator_mutex);
-
-    pw::spi::PicoInitiator initiator_;
-    pw::sync::Borrowable<pw::spi::Initiator> borrowable_initiator_;
-    pw::spi::Device device_;
-  };
-
-  constexpr static int kDisplayWidth = 320;
-  constexpr static int kDisplayHeight = 240;
-  constexpr static int kNumDisplayPixels = kDisplayWidth * kDisplayHeight;
-
-  void InitGPIO();
-  void InitSPI();
-
-  pw::digital_io::PicoDigitalOut chip_selector_gpio_;
-  pw::digital_io::PicoDigitalOut data_cmd_gpio_;
-  pw::spi::PicoChipSelector spi_chip_selector_;
-  pw::sync::VirtualMutex spi_initiator_mutex_;
-  SpiValues spi_8_bit_;
-  SpiValues spi_16_bit_;
-  pw::display_driver::DisplayDriverST7789 display_driver_;
-  uint16_t framebuffer_data_[kNumDisplayPixels];
-};
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/BUILD.gn b/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/BUILD.gn
deleted file mode 100644
index d984ff1..0000000
--- a/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/BUILD.gn
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2022 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("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_display_stm32f429i_disc1_stm32cube_ili9341") {
-  public_configs = [ ":backend_config" ]
-  public_deps = [
-    "$dir_pw_digital_io_stm32cube",
-    "$dir_pw_display_driver_ili9341",
-    "$dir_pw_spi_stm32f429i_disc1_stm32cube",
-    "$dir_pw_sync:borrow",
-    "$dir_pw_sync:mutex",
-    "$dir_pw_third_party/stm32cube",
-  ]
-  deps = [
-    "$dir_pw_digital_io",
-    "$dir_pw_display:pw_display.facade",
-  ]
-  public = [ "public_overrides/pw_display/display_backend.h" ]
-  sources = [ "display.cc" ]
-}
diff --git a/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/display.cc b/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/display.cc
deleted file mode 100644
index a48eade..0000000
--- a/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/display.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2022 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/display_backend.h"
-
-using pw::framebuffer::FramebufferRgb565;
-
-namespace pw::display::backend {
-
-namespace {
-
-// CHIP SELECT PORT AND PIN.
-#define LCD_CS_PORT GPIOC
-#define LCD_CS_PIN GPIO_PIN_2
-
-// DATA/COMMAND PORT AND PIN.
-#define LCD_DC_PORT GPIOD
-#define LCD_DC_PIN GPIO_PIN_13
-
-constexpr pw::spi::Config kSpiConfig8Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(8),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-constexpr pw::spi::Config kSpiConfig16Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(16),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-}  // namespace
-
-Display::SpiValues::SpiValues(pw::spi::Config config,
-                              pw::spi::ChipSelector& selector,
-                              pw::sync::VirtualMutex& initiator_mutex)
-    : borrowable_initiator_(initiator_, initiator_mutex),
-      device_(borrowable_initiator_, config, selector) {}
-
-Display::Display()
-    : chip_selector_gpio_(LCD_CS_PORT, LCD_CS_PIN),
-      data_cmd_gpio_(LCD_DC_PORT, LCD_DC_PIN),
-      spi_chip_selector_(chip_selector_gpio_),
-      spi_8_bit_(kSpiConfig8Bit, spi_chip_selector_, spi_initiator_mutex_),
-      spi_16_bit_(kSpiConfig16Bit, spi_chip_selector_, spi_initiator_mutex_),
-      display_driver_({
-          .data_cmd_gpio = data_cmd_gpio_,
-          .reset_gpio = nullptr,
-          .spi_device_8_bit = spi_8_bit_.device_,
-          .spi_device_16_bit = spi_16_bit_.device_,
-      }) {}
-
-Display::~Display() = default;
-
-void Display::InitGPIO() {
-  __HAL_RCC_GPIOA_CLK_ENABLE();
-  __HAL_RCC_GPIOB_CLK_ENABLE();
-  __HAL_RCC_GPIOC_CLK_ENABLE();
-  __HAL_RCC_GPIOD_CLK_ENABLE();
-  __HAL_RCC_GPIOE_CLK_ENABLE();
-  __HAL_RCC_GPIOF_CLK_ENABLE();
-  __HAL_RCC_GPIOG_CLK_ENABLE();
-  __HAL_RCC_GPIOH_CLK_ENABLE();
-
-  chip_selector_gpio_.Enable();
-  data_cmd_gpio_.Enable();
-}
-
-void Display::InitSPI() {
-  __HAL_RCC_SPI5_CLK_ENABLE();
-
-  // SPI5 GPIO Configuration:
-  // PF7 SPI5_SCK
-  // PF8 SPI5_MISO
-  // PF9 SPI5_MOSI
-  GPIO_InitTypeDef spi_pin_config = {
-      .Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9,
-      .Mode = GPIO_MODE_AF_PP,
-      .Pull = GPIO_NOPULL,
-      .Speed = GPIO_SPEED_FREQ_VERY_HIGH,
-      .Alternate = GPIO_AF5_SPI5,
-  };
-  HAL_GPIO_Init(GPIOF, &spi_pin_config);
-}
-
-Status Display::Init() {
-  InitGPIO();
-  InitSPI();
-  PW_TRY(display_driver_.Init());
-  return OkStatus();
-}
-
-void Display::Update(FramebufferRgb565& frame_buffer) {
-  if (kScaleFactor == 1)
-    display_driver_.Update(&frame_buffer);
-  else
-    display_driver_.UpdatePixelDouble(&frame_buffer);
-}
-
-Status Display::InitFramebuffer(FramebufferRgb565* framebuffer) {
-  framebuffer->SetFramebufferData(framebuffer_data_,
-                                  kDisplayWidth,
-                                  kDisplayHeight,
-                                  kDisplayWidth * sizeof(uint16_t));
-  return OkStatus();
-}
-
-bool Display::TouchscreenAvailable() const { return false; }
-
-bool Display::NewTouchEvent() { return false; }
-
-pw::coordinates::Vec3Int Display::GetTouchPoint() {
-  return pw::coordinates::Vec3Int{0, 0, 0};
-}
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/public_overrides/pw_display/display_backend.h b/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/public_overrides/pw_display/display_backend.h
deleted file mode 100644
index 14bca51..0000000
--- a/pw_graphics/pw_display_stm32f429i_disc1_stm32cube_ili9341/public_overrides/pw_display/display_backend.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2022 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 "pw_digital_io_stm32cube/digital_io.h"
-#include "pw_display/display.h"
-#include "pw_display_driver_ili9341/display_driver.h"
-#include "pw_spi_stm32f429i_disc1_stm32cube/chip_selector.h"
-#include "pw_spi_stm32f429i_disc1_stm32cube/initiator.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::display::backend {
-
-constexpr int kILI9341Width = 320;
-constexpr int kILI9341Height = 240;
-
-#if 1
-constexpr int kDisplayWidth = kILI9341Width;
-constexpr int kDisplayHeight = kILI9341Height;
-constexpr int kScaleFactor = 1;
-#else
-constexpr int kDisplayWidth = kILI9341Width / 2;
-constexpr int kDisplayHeight = kILI9341Height / 2;
-constexpr int kScaleFactor = 2;
-#endif
-constexpr int kNumDisplayPixels = kDisplayWidth * kDisplayHeight;
-
-class Display : public pw::display::Display {
- public:
-  Display();
-  virtual ~Display();
-
-  // pw::display::Display implementation:
-  Status Init() override;
-  Status InitFramebuffer(
-      pw::framebuffer::FramebufferRgb565* framebuffer) override;
-  int GetWidth() const override { return kDisplayWidth; }
-  int GetHeight() const override { return kDisplayHeight; }
-  void Update(pw::framebuffer::FramebufferRgb565& framebuffer) override;
-  bool TouchscreenAvailable() const override;
-  bool NewTouchEvent() override;
-  pw::coordinates::Vec3Int GetTouchPoint() override;
-
- private:
-  struct SpiValues {
-    SpiValues(pw::spi::Config config,
-              pw::spi::ChipSelector& selector,
-              pw::sync::VirtualMutex& initiator_mutex);
-
-    pw::spi::Stm32CubeInitiator initiator_;
-    pw::sync::Borrowable<pw::spi::Initiator> borrowable_initiator_;
-    pw::spi::Device device_;
-  };
-
-  void InitGPIO();
-  void InitSPI();
-
-  pw::digital_io::Stm32CubeDigitalOut chip_selector_gpio_;
-  pw::digital_io::Stm32CubeDigitalOut data_cmd_gpio_;
-  pw::spi::Stm32CubeChipSelector spi_chip_selector_;
-  pw::sync::VirtualMutex spi_initiator_mutex_;
-  SpiValues spi_8_bit_;
-  SpiValues spi_16_bit_;
-  pw::display_driver::DisplayDriverILI9341 display_driver_;
-  uint16_t framebuffer_data_[kNumDisplayPixels];
-};
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_teensy_ili9341/BUILD.gn b/pw_graphics/pw_display_teensy_ili9341/BUILD.gn
deleted file mode 100644
index 97359a4..0000000
--- a/pw_graphics/pw_display_teensy_ili9341/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2022 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_arduino_build/arduino.gni")
-import("$dir_pw_build/target_types.gni")
-
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-if (pw_arduino_build_CORE_PATH != "") {
-  pw_source_set("pw_display_teensy_ili9341") {
-    public_configs = [ ":backend_config" ]
-    public_deps = [
-      "$dir_pw_digital_io_arduino",
-      "$dir_pw_display_driver_ili9341",
-      "$dir_pw_spi_arduino",
-      "$dir_pw_sync:borrow",
-      "$dir_pw_sync:mutex",
-    ]
-    deps = [
-      "$dir_pw_display:pw_display.facade",
-      "$dir_pw_third_party/arduino:arduino_core_sources",
-    ]
-    public = [ "public_overrides/pw_display/display_backend.h" ]
-    sources = [ "display.cc" ]
-    remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  }
-}
diff --git a/pw_graphics/pw_display_teensy_ili9341/README.md b/pw_graphics/pw_display_teensy_ili9341/README.md
deleted file mode 100644
index 34b3626..0000000
--- a/pw_graphics/pw_display_teensy_ili9341/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# pw_display_teensy_ili9341
-
-## Setup Instructions
-
-1. Install the Teensyduino core and set the required GN args.
-
-   ```sh
-   pw package install teensy
-   ```
-
-2. Edit `//targets/arduino/target_toolchains.gni` and set:
-
-   ```
-   pw_display_BACKEND = "$dir_pw_display_teensy_ili9341"
-   ```
diff --git a/pw_graphics/pw_display_teensy_ili9341/display.cc b/pw_graphics/pw_display_teensy_ili9341/display.cc
deleted file mode 100644
index dc6f7ba..0000000
--- a/pw_graphics/pw_display_teensy_ili9341/display.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2022 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 <cinttypes>
-#include <cstdint>
-
-#include "pw_display/display_backend.h"
-
-using pw::framebuffer::FramebufferRgb565;
-
-namespace pw::display::backend {
-
-namespace {
-
-constexpr int TFT_DC = 9;
-constexpr int TFT_CS = 32;
-constexpr int TFT_RST = 3;
-
-constexpr pw::spi::Config kSpiConfig8Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(8),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-constexpr pw::spi::Config kSpiConfig16Bit{
-    .polarity = pw::spi::ClockPolarity::kActiveHigh,
-    .phase = pw::spi::ClockPhase::kFallingEdge,
-    .bits_per_word = pw::spi::BitsPerWord(16),
-    .bit_order = pw::spi::BitOrder::kMsbFirst,
-};
-
-}  // namespace
-
-Display::SpiValues::SpiValues(pw::spi::Config config,
-                              pw::spi::ChipSelector& selector,
-                              pw::sync::VirtualMutex& initiator_mutex)
-    : borrowable_initiator_(initiator_, initiator_mutex),
-      device_(borrowable_initiator_, config, selector) {}
-
-Display::Display()
-    : chip_selector_gpio_(TFT_CS),
-      data_cmd_gpio_(TFT_DC),
-      reset_gpio_(TFT_RST),
-      spi_chip_selector_(chip_selector_gpio_),
-      spi_8_bit_(kSpiConfig8Bit, spi_chip_selector_, spi_initiator_mutex_),
-      spi_16_bit_(kSpiConfig16Bit, spi_chip_selector_, spi_initiator_mutex_),
-      display_driver_({
-          .data_cmd_gpio = data_cmd_gpio_,
-          .reset_gpio = &reset_gpio_,
-          .spi_device_8_bit = spi_8_bit_.device_,
-          .spi_device_16_bit = spi_16_bit_.device_,
-      }) {}
-
-Display::~Display() = default;
-
-void Display::InitGPIO() {
-  chip_selector_gpio_.Enable();
-  data_cmd_gpio_.Enable();
-  reset_gpio_.Enable();
-}
-
-void Display::InitSPI() { SPI.begin(); }
-
-Status Display::Init() {
-  InitGPIO();
-  InitSPI();
-  return display_driver_.Init();
-}
-
-void Display::Update(FramebufferRgb565& frame_buffer) {
-  display_driver_.Update(&frame_buffer);
-}
-
-Status Display::InitFramebuffer(FramebufferRgb565* framebuffer) {
-  framebuffer->SetFramebufferData(framebuffer_data_,
-                                  kDisplayWidth,
-                                  kDisplayHeight,
-                                  kDisplayWidth * sizeof(uint16_t));
-  return OkStatus();
-}
-
-bool Display::TouchscreenAvailable() const { return false; }
-
-bool Display::NewTouchEvent() { return false; }
-
-pw::coordinates::Vec3Int Display::GetTouchPoint() {
-  return pw::coordinates::Vec3Int{0, 0, 0};
-}
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_display_teensy_ili9341/public_overrides/pw_display/display_backend.h b/pw_graphics/pw_display_teensy_ili9341/public_overrides/pw_display/display_backend.h
deleted file mode 100644
index 845880e..0000000
--- a/pw_graphics/pw_display_teensy_ili9341/public_overrides/pw_display/display_backend.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2022 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 "pw_digital_io_arduino/digital_io.h"
-#include "pw_display/display.h"
-#include "pw_display_driver_ili9341/display_driver.h"
-#include "pw_spi_arduino/chip_selector.h"
-#include "pw_spi_arduino/initiator.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::display::backend {
-
-constexpr int kDisplayWidth = 320;
-constexpr int kDisplayHeight = 240;
-constexpr int kNumDisplayPixels = kDisplayWidth * kDisplayHeight;
-
-class Display : public pw::display::Display {
- public:
-  Display();
-  virtual ~Display();
-
-  // pw::display::Display implementation:
-  Status Init() override;
-  Status InitFramebuffer(
-      pw::framebuffer::FramebufferRgb565* framebuffer) override;
-  int GetWidth() const override { return kDisplayWidth; }
-  int GetHeight() const override { return kDisplayHeight; }
-  void Update(pw::framebuffer::FramebufferRgb565& framebuffer) override;
-  bool TouchscreenAvailable() const override;
-  bool NewTouchEvent() override;
-  pw::coordinates::Vec3Int GetTouchPoint() override;
-
- private:
-  struct SpiValues {
-    SpiValues(pw::spi::Config config,
-              pw::spi::ChipSelector& selector,
-              pw::sync::VirtualMutex& initiator_mutex);
-
-    pw::spi::ArduinoInitiator initiator_;
-    pw::sync::Borrowable<pw::spi::Initiator> borrowable_initiator_;
-    pw::spi::Device device_;
-  };
-
-  void InitGPIO();
-  void InitSPI();
-
-  pw::digital_io::ArduinoDigitalOut chip_selector_gpio_;
-  pw::digital_io::ArduinoDigitalOut data_cmd_gpio_;
-  pw::digital_io::ArduinoDigitalOut reset_gpio_;
-  pw::spi::ArduinoChipSelector spi_chip_selector_;
-  pw::sync::VirtualMutex spi_initiator_mutex_;
-  SpiValues spi_8_bit_;
-  SpiValues spi_16_bit_;
-  pw::display_driver::DisplayDriverILI9341 display_driver_;
-  uint16_t framebuffer_data_[kNumDisplayPixels];
-};
-
-}  // namespace pw::display::backend
diff --git a/pw_graphics/pw_framebuffer/public/pw_framebuffer/rgb565.h b/pw_graphics/pw_framebuffer/public/pw_framebuffer/rgb565.h
index ba99498..d288e5b 100644
--- a/pw_graphics/pw_framebuffer/public/pw_framebuffer/rgb565.h
+++ b/pw_graphics/pw_framebuffer/public/pw_framebuffer/rgb565.h
@@ -34,10 +34,10 @@
                     int row_bytes);
 
   FramebufferRgb565(const FramebufferRgb565&) = delete;
-  FramebufferRgb565(FramebufferRgb565&& other) = delete;
+  FramebufferRgb565(FramebufferRgb565&& other);
 
   FramebufferRgb565& operator=(const FramebufferRgb565&) = delete;
-  FramebufferRgb565& operator=(FramebufferRgb565&&) = delete;
+  FramebufferRgb565& operator=(FramebufferRgb565&&);
 
   // Has the framebuffer been properly initialized?
   bool IsValid() const { return pixel_data_ != nullptr; };
diff --git a/pw_graphics/pw_framebuffer/rgb565.cc b/pw_graphics/pw_framebuffer/rgb565.cc
index fe8a45f..375aa57 100644
--- a/pw_graphics/pw_framebuffer/rgb565.cc
+++ b/pw_graphics/pw_framebuffer/rgb565.cc
@@ -33,6 +33,23 @@
       height_(height),
       row_bytes_(row_bytes) {}
 
+FramebufferRgb565::FramebufferRgb565(FramebufferRgb565&& other)
+    : pixel_data_(other.pixel_data_),
+      width_(other.width_),
+      height_(other.height_),
+      row_bytes_(other.row_bytes_) {
+  other.pixel_data_ = nullptr;
+}
+
+FramebufferRgb565& FramebufferRgb565::operator=(FramebufferRgb565&& rhs) {
+  pixel_data_ = rhs.pixel_data_;
+  width_ = rhs.width_;
+  height_ = rhs.height_;
+  row_bytes_ = rhs.row_bytes_;
+  rhs.pixel_data_ = nullptr;
+  return *this;
+}
+
 void FramebufferRgb565::SetFramebufferData(color_rgb565_t* data,
                                            int width,
                                            int height,
diff --git a/targets/arduino/target_toolchains.gni b/targets/arduino/target_toolchains.gni
index 3a2d75e..04b96ca 100644
--- a/targets/arduino/target_toolchains.gni
+++ b/targets/arduino/target_toolchains.gni
@@ -33,19 +33,26 @@
       forward_variables_from(_toolchain_base.defaults, "*")
       forward_variables_from(toolchain_overrides, "*")
 
+      app_common_BACKEND = "//applications/app_common_impl:arduino"
+      pw_lcd_width = "320"
+      pw_lcd_height = "240"
+      pw_lcd_cs_pin_num = "32"
+      pw_lcd_dc_pin_num = "9"
+      pw_lcd_rst_pin_num = "3"
+
+      # pw_spi_arduino currently uses the Arduino SPI API which uses hard
+      # coded (via the board config) SPI pin assignments. These are currently
+      # ignored.
+      pw_spi_miso_pin_num = "-1"  # Pin 12 on Teensy 4.1
+      pw_spi_mosi_pin_num = "-1"  # Pin 11 on Teensy 4.1
+      pw_spi_clock_pin_num = "-1"  # Pin 13 on Teensy 4.1
+
       # Configure backend for pw_board_led
       pw_board_led_BACKEND = "$dir_pw_board_led_arduino"
 
       # Configure backend for pw_spin_delay
       pw_spin_delay_BACKEND = "$dir_pw_spin_delay_arduino"
 
-      # Configure backend for pw_display
-      # pw_display_null does nothing.
-      pw_display_BACKEND = "$dir_pw_display_teensy_ili9341"
-
-      # See //pw_display_teensy_ili9341/README.md for instructions.
-      # pw_display_BACKEND = "$dir_pw_display_teensy_ili9341"
-
       # Configure backend for pw_touchscreen
       pw_touchscreen_BACKEND = "$dir_pw_touchscreen_null"
 
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index 92d4231..370726c 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -50,14 +50,14 @@
       # Configure backend for pw_sys_io facade.
       pw_sys_io_BACKEND = dir_pw_sys_io_stdio
 
-      # Configure backend for pw_display
-      # pw_display_null does nothing.
-      pw_display_BACKEND = "$dir_pw_display_null"
+      app_common_BACKEND = "//applications/app_common_impl:host_null"
+      pw_lcd_width = "320"
+      pw_lcd_height = "240"
 
       # pw_display_host_imgui uses imgui and opengl3
       # See //pw_display_host_imgui/README.md for instructions.
       if (dir_pw_third_party_imgui != "") {
-        pw_display_BACKEND = "$dir_pw_display_host_imgui"
+        app_common_BACKEND = "//applications/app_common_impl:host_imgui"
       }
 
       pw_touchscreen_BACKEND = "$dir_pw_touchscreen_null"
diff --git a/targets/mimxrt595_evk/target_toolchains.gni b/targets/mimxrt595_evk/target_toolchains.gni
index f3e5c64..d245e42 100644
--- a/targets/mimxrt595_evk/target_toolchains.gni
+++ b/targets/mimxrt595_evk/target_toolchains.gni
@@ -38,8 +38,8 @@
       forward_variables_from(_toolchain_base.defaults, "*", _excluded_defaults)
       forward_variables_from(toolchain_overrides, "*")
 
+      app_common_BACKEND = "//applications/app_common_impl:mimxrt595"
       pw_board_led_BACKEND = "$dir_pw_board_led_mimxrt595_evk"
-      pw_display_BACKEND = "$dir_pw_display_null"
       pw_spin_delay_BACKEND = "$dir_pw_spin_delay_mcuxpresso"
       pw_touchscreen_BACKEND = "$dir_pw_touchscreen_null"
     }
diff --git a/targets/rp2040/BUILD.gn b/targets/rp2040/BUILD.gn
index 4ef7064..43a4a5a 100644
--- a/targets/rp2040/BUILD.gn
+++ b/targets/rp2040/BUILD.gn
@@ -19,6 +19,7 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
 import("$dir_pw_toolchain/generate_toolchain.gni")
+import("board_configs.gni")
 
 generate_toolchain("rp2040") {
   _excluded_members = [
@@ -32,6 +33,11 @@
   defaults = {
     forward_variables_from(_toolchain_base.defaults, "*")
 
+    # Merge in the app_common_BACKEND and various application settings
+    # which are defined in board_configs.gni.
+    # forward_variables_from(board_config_st7789, "*")
+    forward_variables_from(board_config_ili9341, "*")
+
     pw_build_EXECUTABLE_TARGET_TYPE = "pico_executable"
     pw_build_EXECUTABLE_TARGET_TYPE_FILE =
         get_path_info("$dir_pigweed/targets/rp2040/pico_executable.gni",
@@ -59,16 +65,6 @@
     # Configure backend for pw_spin_delay
     pw_spin_delay_BACKEND = "$dir_pw_spin_delay_pico"
 
-    # Configure backend for pw_display
-    # pw_display_null does nothing.
-    pw_display_BACKEND = "$dir_pw_display_null"
-
-    # pw_display_pico_ili9341 is for a ILI9341 screen connected over SPI.
-    if (PICO_SRC_DIR != "") {
-      pw_display_BACKEND = "$dir_pw_display_pico_ili9341"
-      # pw_display_BACKEND = "$dir_pw_display_pico_st7789_spi"
-    }
-
     # Configure backend for pw_touchscreen
     pw_touchscreen_BACKEND = "$dir_pw_touchscreen_null"
 
diff --git a/targets/rp2040/board_configs.gni b/targets/rp2040/board_configs.gni
new file mode 100644
index 0000000..1100e27
--- /dev/null
+++ b/targets/rp2040/board_configs.gni
@@ -0,0 +1,43 @@
+# Copyright 2022 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.
+
+# These values are merged into the rp2040 toolchain and are used to set the
+# app_common backend and various application settings. More information on
+# common values can be found in //applications/app_common_impl/BUILD.gn.
+
+board_config_ili9341 = {
+  app_common_BACKEND = "//applications/app_common_impl:pico_ili9341"
+  pw_lcd_width = "320"
+  pw_lcd_height = "240"
+  pw_lcd_backlight = "-1"
+  pw_lcd_cs_pin_num = "9"
+  pw_lcd_dc_pin_num = "10"
+  pw_lcd_rst_pin_num = "11"
+  pw_spi_miso_pin_num = "-1"
+  pw_spi_mosi_pin_num = "19"
+  pw_spi_clock_pin_num = "18"
+}
+
+board_config_st7789 = {
+  app_common_BACKEND = "//applications/app_common_impl:pico_st7789"
+  pw_lcd_width = "320"
+  pw_lcd_height = "240"
+  pw_lcd_backlight = "-1"
+  pw_lcd_cs_pin_num = "17"
+  pw_lcd_dc_pin_num = "16"
+  pw_lcd_rst_pin_num = "-1"
+  pw_spi_miso_pin_num = "-1"
+  pw_spi_mosi_pin_num = "19"
+  pw_spi_clock_pin_num = "18"
+}
diff --git a/targets/stm32f207zg-nucleo/target_toolchains.gni b/targets/stm32f207zg-nucleo/target_toolchains.gni
index f824f3e..d4bd2d3 100644
--- a/targets/stm32f207zg-nucleo/target_toolchains.gni
+++ b/targets/stm32f207zg-nucleo/target_toolchains.gni
@@ -70,6 +70,8 @@
   current_cpu = "arm"
   current_os = ""
 
+  app_common_BACKEND = "//applications/app_common_impl:stm32cube"
+
   pw_board_led_BACKEND = dir_pw_board_led_stm32cube
   pw_board_led_stm32cube_gpio_port = "B"
   pw_board_led_stm32cube_gpio_pin = "0"
diff --git a/targets/stm32f429i_disc1/target_toolchains.gni b/targets/stm32f429i_disc1/target_toolchains.gni
index a1c7129..e4fe5f9 100644
--- a/targets/stm32f429i_disc1/target_toolchains.gni
+++ b/targets/stm32f429i_disc1/target_toolchains.gni
@@ -37,6 +37,7 @@
     defaults = {
       forward_variables_from(_toolchain_base.defaults, "*", _excluded_defaults)
       forward_variables_from(toolchain_overrides, "*")
+      app_common_BACKEND = "//applications/app_common_impl:stm32"
       pw_board_led_BACKEND = "$dir_pw_board_led_stm32f429i_disc1"
       pw_spin_delay_BACKEND = "$dir_pw_spin_delay_stm32f429i_disc1"
     }
diff --git a/targets/stm32f429i_disc1_stm32cube/target_toolchains.gni b/targets/stm32f429i_disc1_stm32cube/target_toolchains.gni
index 827f24e..e5e78d1 100644
--- a/targets/stm32f429i_disc1_stm32cube/target_toolchains.gni
+++ b/targets/stm32f429i_disc1_stm32cube/target_toolchains.gni
@@ -89,11 +89,17 @@
   pw_board_led_BACKEND = dir_pw_board_led_stm32cube
   pw_board_led_stm32cube_gpio_port = "G"
   pw_board_led_stm32cube_gpio_pin = "13"
+  app_common_BACKEND = "//applications/app_common_impl:stm32cube"
+  pw_lcd_width = "320"
+  pw_lcd_height = "240"
+  pw_lcd_cs_port_char = "C"
+  pw_lcd_cs_pin_num = "2"
+  pw_lcd_dc_port_char = "D"
+  pw_lcd_dc_pin_num = "13"
+  pw_lcd_rst_pin_num = "-1"
 
   pw_spin_delay_BACKEND = dir_pw_spin_delay_stm32cube
 
-  pw_display_BACKEND = dir_pw_display_stm32f429i_disc1_stm32cube_ili9341
-
   # Configure backend for pw_touchscreen
   pw_touchscreen_BACKEND = "$dir_pw_touchscreen_null"
 
diff --git a/targets/stm32f439zi_nucleo/target_toolchains.gni b/targets/stm32f439zi_nucleo/target_toolchains.gni
index 420d3e4..d210dcb 100644
--- a/targets/stm32f439zi_nucleo/target_toolchains.gni
+++ b/targets/stm32f439zi_nucleo/target_toolchains.gni
@@ -68,6 +68,7 @@
   current_cpu = "arm"
   current_os = ""
 
+  app_common_BACKEND = "//applications/app_common_impl:stm32cube"
   pw_board_led_BACKEND = dir_pw_board_led_stm32cube
   pw_board_led_stm32cube_gpio_port = "B"
   pw_board_led_stm32cube_gpio_pin = "0"
diff --git a/targets/stm32h753zi-nucleo/target_toolchains.gni b/targets/stm32h753zi-nucleo/target_toolchains.gni
index 1d51705..d064835 100644
--- a/targets/stm32h753zi-nucleo/target_toolchains.gni
+++ b/targets/stm32h753zi-nucleo/target_toolchains.gni
@@ -70,6 +70,7 @@
   current_cpu = "arm"
   current_os = ""
 
+  app_common_BACKEND = "//applications/app_common_impl:stm32cube"
   pw_board_led_BACKEND = dir_pw_board_led_stm32cube
   pw_board_led_stm32cube_gpio_port = "B"
   pw_board_led_stm32cube_gpio_pin = "0"
diff --git a/targets/stm32l552ze-nucleo/target_toolchains.gni b/targets/stm32l552ze-nucleo/target_toolchains.gni
index 59a3434..5dff324 100644
--- a/targets/stm32l552ze-nucleo/target_toolchains.gni
+++ b/targets/stm32l552ze-nucleo/target_toolchains.gni
@@ -70,6 +70,7 @@
   current_cpu = "arm"
   current_os = ""
 
+  app_common_BACKEND = "//applications/app_common_impl:stm32cube"
   pw_board_led_BACKEND = dir_pw_board_led_stm32cube
   pw_board_led_stm32cube_gpio_port = "C"
   pw_board_led_stm32cube_gpio_pin = "7"
diff --git a/third_party/glfw/README.md b/third_party/glfw/README.md
index 699d756..e30f46d 100644
--- a/third_party/glfw/README.md
+++ b/third_party/glfw/README.md
@@ -1,7 +1,36 @@
 # GLFW Library
 
 This folder holds the GN build file for the [GLFW](https://www.glfw.org/)
-library. The build file requires the `dir_pw_third_party_glfw` be set.
+library.
+
+
+## Setup Instructions
+
+1. Install [glfw](https://www.glfw.org/).
+
+   ```
+   pw package install glfw
+   ```
+
+2. Install host OS requirements (only required for Linux) shown below.
+
+### Linux
+
+- Debian / Ubuntu
+
+  ```sh
+  sudo apt install libglfw3-dev libglfw3
+  ```
+
+- Arch Linux
+
+  ```sh
+  sudo pacman -S glfw-x11
+  ```
+
+# Building
+
+The build file requires the `dir_pw_third_party_glfw` be set.
 This can be done as so:
 
 ```sh
diff --git a/third_party/imgui/README.md b/third_party/imgui/README.md
index 2455f41..894431a 100644
--- a/third_party/imgui/README.md
+++ b/third_party/imgui/README.md
@@ -1,7 +1,19 @@
 # ImGui Library
 
 This folder holds the GN build file for the [ImGui](https://www.dearimgui.org/)
-library. The build file requires the `dir_pw_third_party_imgui` be set.
+library.
+
+## Setup Instructions
+
+1. Install [ImGui](https://github.com/ocornut/imgui)
+
+   ```
+   pw package install imgui
+   ```
+
+## Building
+
+The build file requires the `dir_pw_third_party_imgui` be set.
 This can be done as so:
 
 ```sh