The Hardware and Software Hierarchical Relationship of Display Drivers in Zephyr

In embedded systems, display devices serve as core components for human-machine interaction, and the diversity of their hardware interfaces presents challenges for driver development. The Zephyr RTOS supports a variety of display devices, from low-power OLEDs to high-definition MIPI panels, through a unified driver model. This article explains the hierarchical relationship between hardware and software in drivers and demonstrates this hierarchy in display drivers using device trees.

The Hardware and Software Hierarchical Relationship of Display Drivers in Zephyr

Structurally, the top layer is the Display driver of Zephyr, with different Display device drivers paired with different display interfaces.

Display Driver Interface

The Display driver interface provides a unified Display API, defined in zephyr/include/zephyr/drivers/display.h

static inline int display_write(const struct device *dev, const uint16_t x,
    const uint16_t y,
    const struct display_buffer_descriptor *desc,
    const void *buf);

static inline int display_read(const struct device *dev, const uint16_t x,
          const uint16_t y,
          const struct display_buffer_descriptor *desc,
          void *buf);

static inline int display_clear(const struct device *dev);

static inline void *display_get_framebuffer(const struct device *dev);

static inline int display_blanking_on(const struct device *dev);

static inline int display_blanking_off(const struct device *dev);

static inline int display_set_brightness(const struct device *dev,
      uint8_t brightness);

static inline void display_get_capabilities(const struct device *dev,
         struct display_capabilities *
         capabilities);
          
display_set_pixel_format(const struct device *dev,
    constenum display_pixel_format pixel_format);

static inline int display_set_orientation(const struct device *dev,
       const enum display_orientation
       orientation);

All of the above APIs are self-explanatory, so I will not elaborate on each one. In the implementation of the Display driver, it is sufficient to implement the functions in <span>struct display_driver_api</span>.

__subsystem struct display_driver_api {
 display_blanking_on_api blanking_on;
 display_blanking_off_api blanking_off;
 display_write_api write;
 display_read_api read;
 display_clear_api clear;
 display_get_framebuffer_api get_framebuffer;
 display_set_brightness_api set_brightness;
 display_set_contrast_api set_contrast;
 display_get_capabilities_api get_capabilities;
 display_set_pixel_format_api set_pixel_format;
 display_set_orientation_api set_orientation;
};

In practical operations, the Display driver only needs to implement <span>blanking_on</span>, <span>blanking_off</span>, <span>write</span>, <span>get_capabilities</span>, and <span>set_pixel_format</span> to function. The implementation of the Display driver will vary based on the device interface and display hardware.

Mainstream Display Interfaces Supported by Zephyr

Zephyr supports four mainstream display interfaces from a driver implementation perspective:

  • MIPI DBI
  • MIPI DSI
  • Parallel RGB LCD
  • SPI/I2C

MIPI DBI Interface

The MIPI DBI driver class in Zephyr supports MIPI DBI-compliant display controllers, primarily consisting of three types of interfaces:

  • Type A: Motorola 6800 parallel bus (commonly used for parallel interfaces)
  • Type B: Intel 8080 parallel bus
  • Type C: SPI serial interface
    • OP1: Three-wire (CLK, CS, SDA) 9bit: The first 8 bits are data, the last 1 bit is command/data selection
    • OP2: Three-wire (CLK, CS, SDA) 18bit: The first 7 bits are invalid, the 8th bit is command/data selection, followed by 8 bits of data. Currently, Zephyr’s API does not support this mode
    • OP3: Four-wire (CLK, CS, SDA, DC) 8bit: 8-bit data, with command/data selection via the DC line

The MIPI DBI driver code path in Zephyr is located at ‘zephyr/drivers/mipi_dbi’, primarily including the following files:

  • mipi_dbi_bitbang.c: MIPI DBI Type A and B driver implemented using standard GPIO. Supports 8-bit, 9-bit, and 16-bit data bus widths.
  • mipi_dbi_nxp_flexio_lcdif.c: MIPI DBI Type A and B driver implemented using NXP FlexIO. Supports 8-bit and 16-bit data bus widths.
  • mipi_dbi_nxp_dcnano_lcdif.c: MIPI DBI Type A and B driver implemented using the NXP DCNANO LCDIF controller. Supports 8-bit, 9-bit, and 16-bit data bus widths.
  • mipi_dbi_nxp_lcdic.c: MIPI DBI Type A/B driver implemented using the NXP LCDIC controller. Supports 8-bit, 9-bit, and 16-bit data bus widths. Supports TYPE C OP1 and OP3 modes.
  • mipi_dbi_stm32_fmc.c: MIPI DBI Type B driver implemented using STM32 FMC. Supports 16-bit data bus width.
  • mipi_dbi_spi.c: MIPI DBI Type C driver implemented using Zephyr’s standard SPI driver. Supports OP1 and OP3 modes.

Zephyr provides a unified API for MIPI DBI, defined in <span>zephyr/include/zephyr/drivers/mipi_dbi.h</span>.

static inline int mipi_dbi_command_write(const struct device *dev,
      const struct mipi_dbi_config *config,
      uint8_t cmd, const uint8_t *data,
      size_t len);
static inline int mipi_dbi_command_read(const struct device *dev,
     const struct mipi_dbi_config *config,
     uint8_t *cmds, size_t num_cmd,
     uint8_t *response, size_t len);
static inline int mipi_dbi_write_display(const struct device *dev,
      const struct mipi_dbi_config *config,
      const uint8_t *framebuf,
      struct display_buffer_descriptor *desc,
      enum display_pixel_format pixfmt);
static inline int mipi_dbi_reset(const struct device *dev, uint32_t delay_ms);

static inline int mipi_dbi_release(const struct device *dev,
       const struct mipi_dbi_config *config);

static inline int mipi_dbi_configure_te(const struct device *dev,
     uint8_t edge,
     uint32_t delay_us);

In the Display driver, wherever MIPI DBI is needed, these unified MIPI APIs are used, and the specific choice of which DBI driver to use is determined by the device tree, which the Display driver implementation code does not need to concern itself with. In the device tree, this is reflected as the display device being connected to the MIPI DBI bus.

For example: This is the device tree for the st7789v connected to the stm32 fmc bus via the MIPI DBI Type B 16bit driver.

&amp;fmc {
 pinctrl-0 = &lt;&amp;fmc_a0_pf0 &amp;fmc_ne1_pc7 &amp;fmc_nwe_pd5 &amp;fmc_noe_pd4
       &amp;fmc_d0_pd14 &amp;fmc_d1_pd15 &amp;fmc_d2_pd0 &amp;fmc_d3_pd1
       &amp;fmc_d4_pe7 &amp;fmc_d5_pe8 &amp;fmc_d6_pe9 &amp;fmc_d7_pe10
       &amp;fmc_d8_pe11 &amp;fmc_d9_pe12 &amp;fmc_d10_pe13 &amp;fmc_d11_pe14
       &amp;fmc_d12_pe15 &amp;fmc_d13_pd8 &amp;fmc_d14_pd9 &amp;fmc_d15_pd10&gt;;
 pinctrl-names = "default";
 status = "okay";

 sram {
  compatible = "st,stm32-fmc-nor-psram";

#address-cells = &lt;1&gt;;
#size-cells = &lt;0&gt;;

  bank@0 {
   reg = &lt;0x0&gt;;
   st,control = &lt;STM32_FMC_DATA_ADDRESS_MUX_DISABLE
    STM32_FMC_MEMORY_TYPE_SRAM
    STM32_FMC_NORSRAM_MEM_BUS_WIDTH_16
    STM32_FMC_BURST_ACCESS_MODE_DISABLE
    STM32_FMC_WAIT_SIGNAL_POLARITY_LOW
    STM32_FMC_WAIT_TIMING_BEFORE_WS
    STM32_FMC_WRITE_OPERATION_ENABLE
    STM32_FMC_WAIT_SIGNAL_DISABLE
    STM32_FMC_EXTENDED_MODE_DISABLE
    STM32_FMC_ASYNCHRONOUS_WAIT_DISABLE
    STM32_FMC_WRITE_BURST_DISABLE
    STM32_FMC_CONTINUOUS_CLOCK_SYNC_ONLY
    STM32_FMC_WRITE_FIFO_DISABLE
    STM32_FMC_PAGE_SIZE_NONE&gt;;
   st,timing = &lt;1 1 32 0 2 2 STM32_FMC_ACCESS_MODE_A&gt;;

   fmc-mipi-dbi {
    compatible = "st,stm32-fmc-mipi-dbi";
    reset-gpios = &lt;&amp;gpioh 13 GPIO_ACTIVE_LOW&gt;;
    power-gpios = &lt;&amp;gpioc 6 GPIO_ACTIVE_LOW&gt;;
    register-select-pin = &lt;0&gt;;
    #address-cells = &lt;1&gt;;
    #size-cells = &lt;0&gt;;

    st7789v: lcd-panel@0 {
     compatible = "sitronix,st7789v";
     reg = &lt;0&gt;;
     mipi-mode = "MIPI_DBI_MODE_8080_BUS_16_BIT";
     /* A write cycle should be 68ns */
     mipi-max-frequency = &lt;14705882&gt;;
     width = &lt;240&gt;;
     height = &lt;240&gt;;
     x-offset = &lt;0&gt;;
     y-offset = &lt;0&gt;;
     vcom = &lt;0x1F&gt;;
     gctrl = &lt;0x35&gt;;
     vdvs = &lt;0x20&gt;;
     mdac = &lt;0x00&gt;;
     gamma = &lt;0x01&gt;;
     colmod = &lt;0x05&gt;;
     lcm = &lt;0x2c&gt;;
     porch-param = [0c 0c 00 33 33];
     cmd2en-param = [5a 69 02 00];
     pwctrl1-param = [a4 a1];
     pvgam-param = [D0 08 11 08 0C 15 39 33 50 36 13 14 29 2D];
     nvgam-param = [D0 08 10 08 06 06 39 44 51 0B 16 14 2F 31];
     ram-param = [00 F0];
     rgb-param = [40 02 14];
    };
   };
  };
 };

For example: This is the device tree for the st7789v connected to the esp32 SPI bus via the MIPI DBI Type C 4-wire driver.

&amp;mipi_dbi {
    compatible = "zephyr,mipi-dbi-spi";
    spi-dev = &lt;&amp;spi2&gt;;
    dc-gpios = &lt;&amp;gpio1 1 GPIO_ACTIVE_HIGH&gt;;      /* G33 */
    reset-gpios = &lt;&amp;gpio1 2 GPIO_ACTIVE_LOW&gt;;    /* G34 */
    write-only;
    #address-cells = &lt;1&gt;;
    #size-cells = &lt;0&gt;;

    st7789v: st7789v@0 {
        compatible = "sitronix,st7789v";
        reg = &lt;0&gt;;
        mipi-max-frequency = &lt;27000000&gt;;

        width = &lt;128&gt;;
        height = &lt;128&gt;;
        x-offset = &lt;2&gt;;
        y-offset = &lt;1&gt;;

        vcom = &lt;0x28&gt;;
        gctrl = &lt;0x35&gt;;
        vrhs = &lt;0x10&gt;;
        vdvs = &lt;0x20&gt;;
        mdac = &lt;0x00&gt;;
        gamma = &lt;0x01&gt;;
        colmod = &lt;0x55&gt;;
        lcm = &lt;0x0c&gt;;
        porch-param = [0c 0c 00 33 33];
        cmd2en-param = [5a 69 02 00];
        pwctrl1-param = [a4 a1];
        pvgam-param = [d0 00 02 07 0a 28 32 44 42 06 0e 12 14 17];
        nvgam-param = [d0 00 02 07 0a 28 31 54 47 0e 1c 17 1b 1e];
        ram-param = [00 E0];
        rgb-param = [40 02 14];
    };

};

MIPI DSI Interface

MIPI DSI (Display Serial Interface) is a high-speed serial display interface standard established by the MIPI Alliance, designed primarily for mobile devices (such as smartphones and tablets), and has now expanded to automotive displays, AR/VR, and other fields. The MIPI DSI driver code path in Zephyr is located at ‘zephyr/drivers/mipi_dbi’, primarily including the following files:

  • dsi_stm32.c: STM32 DSI controller driver
  • dsi_renesas_ra.c: Renesas RA DSI controller driver
  • dsi_mcux.c: NXP MCUX DSI controller driver
  • dsi_mcux_2l.c: Enhanced driver for the NXP MCUX DSI controller targeting the next generation of NXP chips, focusing on improving hardware acceleration capabilities (DMA/LCDIF), power control, and clock flexibility

Zephyr provides a unified API for MIPI DSI, defined in <span>zephyr/include/zephyr/drivers/mipi_dsi.h</span>.

static inline int mipi_dsi_attach(const struct device *dev,
      uint8_t channel,
      const struct mipi_dsi_device *mdev);

static inline ssize_t mipi_dsi_transfer(const struct device *dev,
     uint8_t channel,
     struct mipi_dsi_msg *msg);

ssize_t mipi_dsi_generic_read(const struct device *dev, uint8_t channel,
         const void *params, size_t nparams,
         void *buf, size_t len);

ssize_t mipi_dsi_generic_write(const struct device *dev, uint8_t channel,
          const void *buf, size_t len);

ssize_t mipi_dsi_dcs_read(const struct device *dev, uint8_t channel,
     uint8_t cmd, void *buf, size_t len);

ssize_t mipi_dsi_dcs_write(const struct device *dev, uint8_t channel,
      uint8_t cmd, const void *buf, size_t len);

static inline int mipi_dsi_detach(const struct device *dev,
      uint8_t channel,
      const struct mipi_dsi_device *mdev);

In the Display driver, wherever MIPI DSI is needed, these unified MIPI APIs are used, and the specific choice of which DBI driver to use is determined by the device tree, which the Display driver implementation code does not need to concern itself with. In the device tree, this is reflected as the display device being connected to the MIPI DSI bus.

For example: This is the device tree for the st7701 connected to the rt1160 MIPI DSI.

zephyr_mipi_dsi: &amp;mipi_dsi {
    dphy-ref-frequency = &lt;24000000&gt;;
 st7701: st7701@0 {
  status = "okay";
  compatible = "sitronix,st7701";
  reg = &lt;0x0&gt;;
  height = &lt;800&gt;;
  width = &lt;480&gt;;
  data-lanes = &lt;2&gt;;
  pixel-format = &lt;MIPI_DSI_PIXFMT_RGB565&gt;;
  rotation = &lt;0&gt;;
  gip-e0 = [E0 00 00 02];
  gip-e1 = [E1 08 00 0A 00 07 00 09 00 00 33 33];
  gip-e2 = [E2 00 00 00 00 00 00 00 00 00 00 00 00 00];
  gip-e3 = [E3 00 00 33 33];
  gip-e4 = [E4 44 44];
  gip-e5 = [E5 0E 60 A0 A0 10 60 A0 A0 0A 60 A0 A0 0C 60 A0 A0];
  gip-e6 = [E6 00 00 33 33];
  gip-e7 = [E7 44 44];
  gip-e8 = [E8 0D 60 A0 A0 0F 60 A0 A0 09 60 A0 A0 0B 60 A0 A0];
  gip-eb = [EB 02 01 E4 E4 44 00 40];
  gip-ec = [EC 02 01];
  gip-ed = [ED AB 89 76 54 01 FF FF FF FF FF FF 10 45 67 98 BA];
  pvgamctrl = [B0 40 C9 91 0D 12 07 02 09 09 1F 04 50 0F E4 29 DF];
  nvgamctrl = [B1 40 CB D0 11 92 07 00 08 07 1C 06 53 12 63 EB DF];

  display-timings {
   compatible = "zephyr,panel-timing";
   hsync-active = &lt;1&gt;;
   vsync-active = &lt;0&gt;;
   de-active = &lt;0&gt;;
   pixelclk-active = &lt;0&gt;;
   hback-porch = &lt;40&gt;;
   hsync-len = &lt;32&gt;;
   hfront-porch = &lt;8&gt;;
   vback-porch = &lt;6&gt;;
   vsync-len = &lt;8&gt;;
   vfront-porch = &lt;9&gt;;
  };
 };
};

LCD RGB

Zephyr does not abstract the LCD RGB interface; the Display driver directly calls the HAL interfaces provided by the SoC vendor. For example, <span>display_mcux_elcdif.c</span>, <span>display_renesas_ra.c</span>, etc. This is typically paired with general-purpose LCD RGB parallel screens, where the screen parameters are set in the device tree.

For example, below is the device tree for an external LCD RGB interface screen connected to the rt1062, where <span>display-timings</span> describes the screen parameters of the external screen.

&amp;lcdif {
 status = "okay";
 width = &lt;480&gt;;
 height = &lt;480&gt;;
 display-timings {
  compatible = "zephyr,panel-timing";
  hsync-len = &lt;41&gt;;
  hfront-porch = &lt;4&gt;;
  hback-porch = &lt;8&gt;;
  vsync-len = &lt;10&gt;;
  vfront-porch = &lt;4&gt;;
  vback-porch = &lt;2&gt;;
  de-active= &lt;1&gt;;
  pixelclk-active = &lt;1&gt;;
  hsync-active = &lt;0&gt;;
  vsync-active = &lt;0&gt;;
  clock-frequency = &lt;9210240&gt;;
 };
 pixel-format = &lt;PANEL_PIXEL_FORMAT_ARGB_8888&gt;;
 data-bus-width = "24-bit";
 pinctrl-0 = &lt;&amp;pinmux_lcdif&gt;;
 pinctrl-names = "default";
 backlight-gpios = &lt;&amp;gpio2 31 GPIO_ACTIVE_HIGH&gt;;
};

SPI/I2C

There are also some small display devices that do not use the three standard display interfaces mentioned above, but instead have their own protocols, transmitting data via SPI or I2C, such as <span>ssd1306.c</span>, <span>display_max7219.c</span>, etc. These require the display driver to call the standard SPI or I2C APIs provided by Zephyr according to the hardware characteristics of the screen. In the device tree, this is reflected as the display device being connected to the SPI/I2C bus.

For example, below is the device tree for the ssd1306 connected to the esp32’s I2C bus.

&amp;i2c0 {
 status = "okay";
 clock-frequency = &lt;I2C_BITRATE_STANDARD&gt;;
 sda-gpios = &lt;&amp;gpio0 21 GPIO_OPEN_DRAIN&gt;;
 scl-gpios = &lt;&amp;gpio0 22 GPIO_OPEN_DRAIN&gt;;
 pinctrl-0 = &lt;&amp;i2c0_default&gt;;
 pinctrl-names = "default";
 ssd1306_128x64: ssd1306@3c {
  compatible = "solomon,ssd1306fb";
  reg = &lt;0x3c&gt;;
  width = &lt;128&gt;;
  height = &lt;64&gt;;
  segment-offset = &lt;0&gt;;
  page-offset = &lt;0&gt;;
  display-offset = &lt;0&gt;;
  multiplex-ratio = &lt;63&gt;;
  segment-remap;
  com-invdir;
  prechargep = &lt;0x22&gt;;
 };
};

For example, below is the device tree for the max7219 connected to the esp32’s SPI bus.

&amp;spi3 {
 #address-cells = &lt;1&gt;;
#size-cells = &lt;0&gt;;
 status = "okay";
 pinctrl-0 = &lt;&amp;spim3_default&gt;;
 pinctrl-names = "default";
 cs-gpios = &lt;&amp;gpio0 18 GPIO_ACTIVE_LOW&gt;;

 max7219_max7219_8x8: max7219@0 {
  compatible = "maxim,max7219";
  reg = &lt;0&gt;;
  spi-max-frequency = &lt;1000000&gt;;
  num-cascading = &lt;1&gt;;
  intensity = &lt;0&gt;;
  scan-limit = &lt;7&gt;;
 };
};

References

https://docs.zephyrproject.org/latest/hardware/peripherals/display/index.html https://docs.zephyrproject.org/latest/hardware/peripherals/mipi_dbi.html https://docs.zephyrproject.org/latest/hardware/peripherals/mipi_dsi.html

This article is reprinted from HalfCoder. Click here to read the original article for more information.

The Hardware and Software Hierarchical Relationship of Display Drivers in Zephyr

Contact Linux Foundation APAC

The Linux Foundation is a non-profit organization and an important part of the technology ecosystem.

The Linux Foundation supports the creation of a sustainable open-source ecosystem by providing financial and intellectual resources, infrastructure, services, events, and training. Through collaborative efforts in the creation of shared technologies, the Linux Foundation and its projects have formed extraordinarily successful investments. Please follow the LFAPAC (Linux Foundation APAC) WeChat public account.

Leave a Comment