diff --git a/drivers/display/CMakeLists.txt b/drivers/display/CMakeLists.txt index 32e8bc190b9a2..03fa3bd121914 100644 --- a/drivers/display/CMakeLists.txt +++ b/drivers/display/CMakeLists.txt @@ -34,6 +34,7 @@ zephyr_library_sources_ifdef(CONFIG_ST7796S display_st7796s.c) zephyr_library_sources_ifdef(CONFIG_STM32_LTDC display_stm32_ltdc.c) zephyr_library_sources_ifdef(CONFIG_RM68200 display_rm68200.c) zephyr_library_sources_ifdef(CONFIG_RM67162 display_rm67162.c) +zephyr_library_sources_ifdef(CONFIG_HX8379C display_hx8379c.c) zephyr_library_sources_ifdef(CONFIG_HX8394 display_hx8394.c) zephyr_library_sources_ifdef(CONFIG_GC9X01X display_gc9x01x.c) zephyr_library_sources_ifdef(CONFIG_LED_STRIP_MATRIX display_led_strip_matrix.c) diff --git a/drivers/display/Kconfig b/drivers/display/Kconfig index 7d4ada3bd6ab0..18df9a00eb167 100644 --- a/drivers/display/Kconfig +++ b/drivers/display/Kconfig @@ -51,6 +51,7 @@ source "drivers/display/Kconfig.max7219" source "drivers/display/Kconfig.intel_multibootfb" source "drivers/display/Kconfig.mcux_dcnano_lcdif" source "drivers/display/Kconfig.otm8009a" +source "drivers/display/Kconfig.hx8379c" source "drivers/display/Kconfig.hx8394" source "drivers/display/Kconfig.gc9x01x" source "drivers/display/Kconfig.led_strip_matrix" diff --git a/drivers/display/Kconfig.hx8379c b/drivers/display/Kconfig.hx8379c new file mode 100644 index 0000000000000..667cb0d397c7b --- /dev/null +++ b/drivers/display/Kconfig.hx8379c @@ -0,0 +1,21 @@ +# Copyright 2025 Charles Dias +# SPDX-License-Identifier: Apache-2.0 + +config HX8379C + bool "HX8379C display controller driver" + default y + depends on DT_HAS_HIMAX_HX8379C_ENABLED + select MIPI_DSI + help + Enable driver for HX8379C display controller. + +if HX8379C + +config DISPLAY_HX8379C_INIT_PRIORITY + int "Initialization priority" + default APPLICATION_INIT_PRIORITY + + help + HX8379C display driver initialization priority. + +endif diff --git a/drivers/display/display_hx8379c.c b/drivers/display/display_hx8379c.c new file mode 100644 index 0000000000000..fe0d0d0584603 --- /dev/null +++ b/drivers/display/display_hx8379c.c @@ -0,0 +1,432 @@ +/* + * Copyright 2025, Charles Dias + * + * SPDX-License-Identifier: Apache-2.0 + * + * Driver implementation based on ST code sample DSI_VideoMode_SingleBuffer from : + * https://github.com/STMicroelectronics/STM32CubeU5/tree/main/Projects/STM32U5x9J-DK/ + */ + +#define DT_DRV_COMPAT himax_hx8379c + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(display_hx8379c, CONFIG_DISPLAY_LOG_LEVEL); + +/* MIPI DCS commands specific to this display driver */ + +/* Set power */ +#define HX8379C_SETPOWER 0xB1 +/* Set display related register */ +#define HX8379C_SETDISP 0xB2 +/* Set display cycle timing */ +#define HX8379C_SETCYC 0xB4 +/* Set VCOM voltage */ +#define HX8379C_SETVCOM 0xB6 +/* Set extended command set */ +#define HX8379C_SETEXTC 0xB9 +/* Set register bank partition index */ +#define HX8379C_SETBANK 0xBD +/* Set DGC LUT */ +#define HX8379C_SETDGC_LUT 0xC1 +/* + * Register 0xC7 is not mentioned in the datasheet, but searching the internet, + * it is available for other Himax displays as SETTCON. + */ +#define HX8379C_SETTCON 0xC7 +/* Set panel related register */ +#define HX8379C_SETPANEL 0xCC +/* SETOFFSET */ +#define HX8379C_SETOFFSET 0xD2 +/* Set GIP timing */ +#define HX8379C_SETGIP_0 0xD3 +/* Set forward GIP sequence */ +#define HX8379C_SETGIP_1 0xD5 +/* Set backward GIP sequence */ +#define HX8379C_SETGIP_2 0xD6 +/* Set gamma curve related setting */ +#define HX8379C_SETGAMMA 0xE0 + +struct hx8379c_config { + const struct device *mipi_dsi; + const struct gpio_dt_spec reset_gpio; + uint16_t panel_width; + uint16_t panel_height; + uint16_t hsync; + uint16_t hbp; + uint16_t hfp; + uint16_t vfp; + uint16_t vbp; + uint16_t vsync; + uint8_t data_lanes; + uint8_t pixel_format; + uint8_t channel; +}; + +static int hx8379c_transmit(const struct device *dev, uint8_t cmd, + const void *tx_data, size_t tx_len) +{ + const struct hx8379c_config *config = dev->config; + + return mipi_dsi_dcs_write(config->mipi_dsi, config->channel, cmd, tx_data, tx_len); +} + + +static int hx8379c_blanking_on(const struct device *dev) +{ + int ret; + + ret = hx8379c_transmit(dev, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0); + if (ret < 0) { + LOG_ERR("Failed to turn off display (%d)", ret); + return ret; + } + + return 0; +} + +static int hx8379c_blanking_off(const struct device *dev) +{ + int ret; + + ret = hx8379c_transmit(dev, MIPI_DCS_SET_DISPLAY_ON, NULL, 0); + if (ret < 0) { + LOG_ERR("Failed to turn on display (%d)", ret); + return ret; + } + + return 0; +} + +static void hx8379c_get_capabilities(const struct device *dev, + struct display_capabilities *capabilities) +{ + const struct hx8379c_config *config = dev->config; + + memset(capabilities, 0, sizeof(struct display_capabilities)); + capabilities->x_resolution = config->panel_width; + capabilities->y_resolution = config->panel_height; + capabilities->supported_pixel_formats = (PIXEL_FORMAT_RGB_565 | PIXEL_FORMAT_RGB_888); + capabilities->current_pixel_format = config->pixel_format; + capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL; +} + +static DEVICE_API(display, hx8379c_api) = { + .blanking_on = hx8379c_blanking_on, + .blanking_off = hx8379c_blanking_off, + .get_capabilities = hx8379c_get_capabilities, +}; + +static int hx8379c_configure(const struct device *dev) +{ + int ret; + + LOG_DBG("Configuring HX8379C DSI controller..."); + + /* CMD Mode */ + static const uint8_t enable_extension[3] = {0xFF, 0x83, 0x79}; + + ret = hx8379c_transmit(dev, HX8379C_SETEXTC, enable_extension, sizeof(enable_extension)); + if (ret < 0) { + LOG_ERR("Controller init step 1 failed (%d)", ret); + return ret; + } + + static const uint8_t power_config[16] = {0x44, 0x1C, 0x1C, 0x37, 0x57, 0x90, 0xD0, 0xE2, + 0x58, 0x80, 0x38, 0x38, 0xF8, 0x33, 0x34, 0x42}; + + ret = hx8379c_transmit(dev, HX8379C_SETPOWER, power_config, sizeof(power_config)); + if (ret < 0) { + LOG_ERR("Controller SETPOWER failed (%d)", ret); + return ret; + } + + /* + * NOTE: Parameter count discrepancy with datasheet HX8379C datasheet specifies 6 + * parameters for SETDISP (0xB2) command, but STM32Cube reference implementation uses 9 + * parameters. This follows the STM32 implementation which has been validated on actual + * hardware. The extra parameters may be undocumented extensions or revision-specific. + */ + static const uint8_t line_config[9] = {0x80, 0x14, 0x0C, 0x30, 0x20, 0x50, 0x11, 0x42, + 0x1D}; + + ret = hx8379c_transmit(dev, HX8379C_SETDISP, line_config, sizeof(line_config)); + if (ret < 0) { + LOG_ERR("Controller SETDISP failed (%d)", ret); + return ret; + } + + static const uint8_t cycle_config[10] = {0x01, 0xAA, 0x01, 0xAF, 0x01, 0xAF, 0x10, 0xEA, + 0x1C, 0xEA}; + + ret = hx8379c_transmit(dev, HX8379C_SETCYC, cycle_config, sizeof(cycle_config)); + if (ret < 0) { + LOG_ERR("Controller timing config failed (%d)", ret); + return ret; + } + + static const uint8_t tcon_config[4] = {0x00, 0x00, 0x00, 0xC0}; + + ret = hx8379c_transmit(dev, HX8379C_SETTCON, tcon_config, sizeof(tcon_config)); + if (ret < 0) { + LOG_ERR("Controller SETTCON failed (%d)", ret); + return ret; + } + + static const uint8_t panel_config[1] = {0x02}; + + ret = hx8379c_transmit(dev, HX8379C_SETPANEL, panel_config, sizeof(panel_config)); + if (ret < 0) { + LOG_ERR("Controller register SETPANEL failed (%d)", ret); + return ret; + } + + static const uint8_t offset_config[1] = {0x77}; + + ret = hx8379c_transmit(dev, HX8379C_SETOFFSET, offset_config, sizeof(offset_config)); + if (ret < 0) { + LOG_ERR("Controller register SETOFFSET failed (%d)", ret); + return ret; + } + + /* + * NOTE: Parameter count discrepancy with datasheet HX8379C datasheet specifies 29 + * parameters for SETGIP_0 (0xD3) command, but STM32Cube reference implementation uses 37 + * parameters. This follows the STM32 implementation which has been validated on actual + * hardware. The difference may be due to datasheet version or implementation optimization. + */ + static const uint8_t gip0_config[37] = {0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x08, 0x32, + 0x10, 0x01, 0x00, 0x01, 0x03, 0x72, 0x03, 0x72, + 0x00, 0x08, 0x00, 0x08, 0x33, 0x33, 0x05, 0x05, + 0x37, 0x05, 0x05, 0x37, 0x0A, 0x00, 0x00, 0x00, + 0x0A, 0x00, 0x01, 0x00, 0x0E}; + + ret = hx8379c_transmit(dev, HX8379C_SETGIP_0, gip0_config, sizeof(gip0_config)); + if (ret < 0) { + LOG_ERR("Controller register SETGIP_0 failed (%d)", ret); + return ret; + } + + /* + * NOTE: Parameter count discrepancy with datasheet HX8379C datasheet specifies 35 + * parameters for SETGIP_1 (0xD5) command, but STM32Cube reference implementation uses 34 + * parameters. This follows the STM32 implementation which has been validated on actual + * hardware. The difference may be due to datasheet version or implementation optimization. + */ + static const uint8_t gip1_config[34] = {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x19, 0x19, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, + 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06, + 0x23, 0x22, 0x21, 0x20, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00}; + + ret = hx8379c_transmit(dev, HX8379C_SETGIP_1, gip1_config, sizeof(gip1_config)); + if (ret < 0) { + LOG_ERR("Controller register SETGIP_1 failed (%d)", ret); + return ret; + } + + static const uint8_t gip2_config[32] = {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x19, 0x19, 0x18, 0x18, 0x19, 0x19, 0x18, 0x18, + 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, + 0x20, 0x21, 0x22, 0x23, 0x18, 0x18, 0x18, 0x18}; + + ret = hx8379c_transmit(dev, HX8379C_SETGIP_2, gip2_config, sizeof(gip2_config)); + if (ret < 0) { + LOG_ERR("Controller register SETGIP_2 failed (%d)", ret); + return ret; + } + + static const uint8_t gamma_config[42] = {0x00, 0x16, 0x1B, 0x30, 0x36, 0x3F, 0x24, 0x40, + 0x09, 0x0D, 0x0F, 0x18, 0x0E, 0x11, 0x12, 0x11, + 0x14, 0x07, 0x12, 0x13, 0x18, 0x00, 0x17, 0x1C, + 0x30, 0x36, 0x3F, 0x24, 0x40, 0x09, 0x0C, 0x0F, + 0x18, 0x0E, 0x11, 0x14, 0x11, 0x12, 0x07, 0x12, + 0x14, 0x18}; + + ret = hx8379c_transmit(dev, HX8379C_SETGAMMA, gamma_config, sizeof(gamma_config)); + if (ret < 0) { + LOG_ERR("Controller gamma configuration failed (%d)", ret); + return ret; + } + + static const uint8_t vcom_config[3] = {0x2C, 0x2C, 0x00}; + + ret = hx8379c_transmit(dev, HX8379C_SETVCOM, vcom_config, sizeof(vcom_config)); + if (ret < 0) { + LOG_ERR("Controller register SETVCOM failed (%d)", ret); + return ret; + } + + static const uint8_t bank0_config[1] = {0x00}; + + ret = hx8379c_transmit(dev, HX8379C_SETBANK, bank0_config, sizeof(bank0_config)); + if (ret < 0) { + LOG_ERR("Controller register HX8379C_SETBANK(0) failed (%d)", ret); + return ret; + } + + static const uint8_t lut1_config[43] = {0x01, 0x00, 0x07, 0x0F, 0x16, 0x1F, 0x27, 0x30, + 0x38, 0x40, 0x47, 0x4E, 0x56, 0x5D, 0x65, 0x6D, + 0x74, 0x7D, 0x84, 0x8A, 0x90, 0x99, 0xA1, 0xA9, + 0xB0, 0xB6, 0xBD, 0xC4, 0xCD, 0xD4, 0xDD, 0xE5, + 0xEC, 0xF3, 0x36, 0x07, 0x1C, 0xC0, 0x1B, 0x01, + 0xF1, 0x34, 0x00}; + + ret = hx8379c_transmit(dev, HX8379C_SETDGC_LUT, lut1_config, sizeof(lut1_config)); + if (ret < 0) { + LOG_ERR("Controller color LUT 1 failed (%d)", ret); + return ret; + } + + static const uint8_t bank1_config[1] = {0x01}; + + ret = hx8379c_transmit(dev, HX8379C_SETBANK, bank1_config, sizeof(bank1_config)); + if (ret < 0) { + LOG_ERR("Controller register HX8379C_SETBANK(1) failed (%d)", ret); + return ret; + } + + static const uint8_t lut2_config[42] = {0x00, 0x08, 0x0F, 0x16, 0x1F, 0x28, 0x31, 0x39, + 0x41, 0x48, 0x51, 0x59, 0x60, 0x68, 0x70, 0x78, + 0x7F, 0x87, 0x8D, 0x94, 0x9C, 0xA3, 0xAB, 0xB3, + 0xB9, 0xC1, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xEE, + 0xF5, 0x3B, 0x1A, 0xB6, 0xA0, 0x07, 0x45, 0xC5, + 0x37, 0x00}; + + ret = hx8379c_transmit(dev, HX8379C_SETDGC_LUT, lut2_config, sizeof(lut2_config)); + if (ret < 0) { + LOG_ERR("Controller color LUT 2 failed (%d)", ret); + return ret; + } + + static const uint8_t bank2_config[1] = {0x02}; + + ret = hx8379c_transmit(dev, HX8379C_SETBANK, bank2_config, sizeof(bank2_config)); + if (ret < 0) { + LOG_ERR("Controller register HX8379C_SETBANK(2) failed (%d)", ret); + return ret; + } + + static const uint8_t lut3_config[42] = {0x00, 0x09, 0x0F, 0x18, 0x21, 0x2A, 0x34, 0x3C, + 0x45, 0x4C, 0x56, 0x5E, 0x66, 0x6E, 0x76, 0x7E, + 0x87, 0x8E, 0x95, 0x9D, 0xA6, 0xAF, 0xB7, 0xBD, + 0xC5, 0xCE, 0xD5, 0xDF, 0xE7, 0xEE, 0xF4, 0xFA, + 0xFF, 0x0C, 0x31, 0x83, 0x3C, 0x5B, 0x56, 0x1E, + 0x5A, 0xFF}; + + ret = hx8379c_transmit(dev, HX8379C_SETDGC_LUT, lut3_config, sizeof(lut3_config)); + if (ret < 0) { + LOG_ERR("Controller color LUT 3 failed (%d)", ret); + return ret; + } + + static const uint8_t bank00_config[1] = {0x00}; + + ret = hx8379c_transmit(dev, HX8379C_SETBANK, bank00_config, sizeof(bank00_config)); + if (ret < 0) { + LOG_ERR("Controller register HX8379C_SETBANK(0) failed (%d)", ret); + return ret; + } + + /* Exit Sleep Mode */ + ret = hx8379c_transmit(dev, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0); + if (ret < 0) { + LOG_ERR("Exit sleep mode failed (%d)", ret); + return ret; + } + + k_msleep(120); + + /* Display On */ + ret = hx8379c_blanking_off(dev); + if (ret < 0) { + LOG_ERR("Display blanking off failed (%d)", ret); + return ret; + } + + k_msleep(120); + + LOG_DBG("Display Controller configured successfully"); + return 0; +} + +static int hx8379c_init(const struct device *dev) +{ + const struct hx8379c_config *config = dev->config; + struct mipi_dsi_device mdev; + int ret; + + if (config->reset_gpio.port) { + if (!gpio_is_ready_dt(&config->reset_gpio)) { + LOG_ERR("Reset GPIO device is not ready"); + return -ENODEV; + } + ret = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Failed to configure reset GPIO (%d)", ret); + return ret; + } + k_msleep(11); + ret = gpio_pin_set_dt(&config->reset_gpio, 1); + if (ret < 0) { + LOG_ERR("Failed to activate reset GPIO (%d)", ret); + return ret; + } + k_msleep(150); + } + + /* attach to MIPI-DSI host */ + mdev.data_lanes = config->data_lanes; + mdev.pixfmt = config->pixel_format; + mdev.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM; + + mdev.timings.hactive = config->panel_width; + mdev.timings.hsync = config->hsync; + mdev.timings.hbp = config->hbp; + mdev.timings.hfp = config->hfp; + mdev.timings.vactive = config->panel_height; + mdev.timings.vfp = config->vfp; + mdev.timings.vbp = config->vbp; + mdev.timings.vsync = config->vsync; + + ret = mipi_dsi_attach(config->mipi_dsi, config->channel, &mdev); + if (ret < 0) { + LOG_ERR("Failed to attach to MIPI-DSI host (%d)", ret); + return ret; + } + + ret = hx8379c_configure(dev); + if (ret < 0) { + LOG_ERR("Failed to configure display (%d)", ret); + return ret; + } + + LOG_DBG("HX8379C display controller initialized successfully"); + return 0; +} + +#define HX8379C_CONTROLLER_DEVICE(inst) \ + static const struct hx8379c_config hx8379c_config_##inst = { \ + .mipi_dsi = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}), \ + .data_lanes = DT_INST_PROP_BY_IDX(inst, data_lanes, 0), \ + .panel_width = DT_INST_PROP(inst, width), \ + .panel_height = DT_INST_PROP(inst, height), \ + .pixel_format = DT_INST_PROP(inst, pixel_format), \ + .channel = DT_INST_REG_ADDR(inst), \ + .hsync = DT_PROP(DT_INST_CHILD(inst, display_timings), hsync_len), \ + .hbp = DT_PROP(DT_INST_CHILD(inst, display_timings), hback_porch), \ + .hfp = DT_PROP(DT_INST_CHILD(inst, display_timings), hfront_porch), \ + .vsync = DT_PROP(DT_INST_CHILD(inst, display_timings), vsync_len), \ + .vbp = DT_PROP(DT_INST_CHILD(inst, display_timings), vback_porch), \ + .vfp = DT_PROP(DT_INST_CHILD(inst, display_timings), vfront_porch), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, &hx8379c_init, NULL, \ + NULL, &hx8379c_config_##inst, POST_KERNEL, \ + CONFIG_DISPLAY_HX8379C_INIT_PRIORITY, &hx8379c_api); + +DT_INST_FOREACH_STATUS_OKAY(HX8379C_CONTROLLER_DEVICE) diff --git a/dts/bindings/display/himax,hx8379c.yaml b/dts/bindings/display/himax,hx8379c.yaml new file mode 100644 index 0000000000000..53e0e5e29ec56 --- /dev/null +++ b/dts/bindings/display/himax,hx8379c.yaml @@ -0,0 +1,22 @@ +# +# Copyright 2025 Charles Dias +# +# SPDX-License-Identifier: Apache-2.0 +# + +title: Himax HX8379C display controller + +description: | + The Himax HX8379C is a 16.7M colors TFT-LCD controller + with a maximum 480RGBx864 resolution. + +compatible: "himax,hx8379c" + +include: [mipi-dsi-device.yaml, display-controller.yaml] + +properties: + reset-gpios: + type: phandle-array + description: | + The RESX pin is asserted to disable the controller, causing a hard + reset. The controller receives this as an active-low signal. diff --git a/tests/drivers/build_all/display/app.overlay b/tests/drivers/build_all/display/app.overlay index 99aba3132a20e..9189ba9ea0d84 100644 --- a/tests/drivers/build_all/display/app.overlay +++ b/tests/drivers/build_all/display/app.overlay @@ -487,6 +487,31 @@ vfront-porch = <9>; }; }; + + hx8379c: hx8379c@6 { + status = "okay"; + compatible = "himax,hx8379c"; + reg = <0x6>; + width = <480>; + height = <480>; + data-lanes = <2>; + pixel-format = <0>; + reset-gpios = <&test_gpio 0 0>; + + display-timings { + compatible = "zephyr,panel-timing"; + hsync-active = <1>; + vsync-active = <1>; + de-active = <0>; + pixelclk-active = <0>; + hsync-len = <2>; + hback-porch = <1>; + hfront-porch = <1>; + vsync-len = <1>; + vback-porch = <13>; + vfront-porch = <50>; + }; + }; }; test_spi: spi@33334444 {