From a3b8f062f88c8ac3ba0e7c3278afe7c231b24391 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Fri, 7 Jul 2023 18:20:53 +0000 Subject: [PATCH] drivers: display: add driver for HX8394 TFT LCD controller Add driver for HX8394 TFT LCD controller. This controller is driven via MIPI DSI, and is configured for a 720x1280 display Signed-off-by: Daniel DeGrasse --- drivers/display/CMakeLists.txt | 1 + drivers/display/Kconfig | 1 + drivers/display/Kconfig.hx8394 | 10 + drivers/display/display_hx8394.c | 801 +++++++++++++++++++++++++ dts/bindings/display/himax,hx8394.yaml | 24 + 5 files changed, 837 insertions(+) create mode 100644 drivers/display/Kconfig.hx8394 create mode 100644 drivers/display/display_hx8394.c create mode 100644 dts/bindings/display/himax,hx8394.yaml diff --git a/drivers/display/CMakeLists.txt b/drivers/display/CMakeLists.txt index 30e31ed2b11..20bd8659aff 100644 --- a/drivers/display/CMakeLists.txt +++ b/drivers/display/CMakeLists.txt @@ -22,6 +22,7 @@ zephyr_library_sources_ifdef(CONFIG_ST7735R display_st7735r.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_HX8394 display_hx8394.c) zephyr_library_sources_ifdef(CONFIG_MICROBIT_DISPLAY mb_display.c diff --git a/drivers/display/Kconfig b/drivers/display/Kconfig index 6654f14d87e..f6a39f0f984 100644 --- a/drivers/display/Kconfig +++ b/drivers/display/Kconfig @@ -39,5 +39,6 @@ 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.hx8394" endif # DISPLAY diff --git a/drivers/display/Kconfig.hx8394 b/drivers/display/Kconfig.hx8394 new file mode 100644 index 00000000000..0c36e125e1c --- /dev/null +++ b/drivers/display/Kconfig.hx8394 @@ -0,0 +1,10 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +config HX8394 + bool "HX8394 display driver" + default y + depends on MIPI_DSI + depends on DT_HAS_HIMAX_HX8394_ENABLED + help + Enable driver for HX8394 display driver. diff --git a/drivers/display/display_hx8394.c b/drivers/display/display_hx8394.c new file mode 100644 index 00000000000..9a71197b481 --- /dev/null +++ b/drivers/display/display_hx8394.c @@ -0,0 +1,801 @@ +/* + * Copyright 2023, NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT himax_hx8394 + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(hx8394, CONFIG_DISPLAY_LOG_LEVEL); + +struct hx8394_config { + const struct device *mipi_dsi; + const struct gpio_dt_spec reset_gpio; + const struct gpio_dt_spec bl_gpio; + uint8_t num_of_lanes; + uint8_t pixel_format; + uint16_t panel_width; + uint16_t panel_height; + uint8_t channel; +}; + +/* MIPI DCS commands specific to this display driver */ +#define HX8394_SETMIPI 0xBA +#define HX8394_MIPI_LPTX_BTA_READ BIT(6) +#define HX8394_MIPI_LP_CD_DIS BIT(5) +#define HX8394_MIPI_TA_6TL 0x3 +#define HX8394_MIPI_DPHYCMD_LPRX_8NS 0x40 +#define HX8394_MIPI_DPHYCMD_LPRX_66mV 0x10 +#define HX8394_MIPI_DPHYCMD_LPTX_SRLIM 0x8 +#define HX8394_MIPI_DPHYCMD_LDO_1_55V 0x60 +#define HX8394_MIPI_DPHYCMD_HSRX_7X 0x8 +#define HX8394_MIPI_DPHYCMD_HSRX_100OHM 0x2 +#define HX8394_MIPI_DPHYCMD_LPCD_1X 0x1 + +#define HX8394_SET_ADDRESS 0x36 +#define HX8394_FLIP_HORIZONTAL BIT(1) +#define HX8394_FLIP_VERTICAL BIT(0) + +#define HX8394_SETPOWER 0xB1 +#define HX8394_POWER_AP_1_0UA 0x8 +#define HX8394_POWER_HX5186 0x40 +#define HX8394_POWER_VRHP_4_8V 0x12 +#define HX8394_POWER_VRHN_4_8V 0x12 +#define HX8394_POWER_VPPS_8_25V 0x60 +#define HX8394_POWER_XDK_X2 0x1 +#define HX8394_POWER_VSP_FBOFF 0x8 +#define HX8394_POWER_FS0_DIV_8 0x2 +#define HX8394_POWER_CLK_OPT_VGH_HSYNC_RST 0x10 +#define HX8394_POWER_CLK_OPT_VGL_HSYNC_RST 0x20 +#define HX8394_POWER_FS2_DIV_192 0x4 +#define HX8394_POWER_FS1_DIV_224 0x50 +#define HX8394_POWER_BTP_5_55V 0x11 +#define HX8394_POWER_VGH_RATIO_2VSPVSN 0x60 +#define HX8394_POWER_BTN_5_55V 0x11 +#define HX8394_POWER_VGL_RATIO_2VSPVSN 0x60 +#define HX8394_POWER_VGHS_16V 0x57 +#define HX8394_POWER_VGLS_12_4V 0x47 + +#define HX8394_SETDISP 0xB2 +#define HX8394_DISP_COL_INV 0x0 +#define HX8394_DISP_MESSI_ENB 0x80 +#define HX8394_DISP_NL_1280 0x64 +#define HX8394_DISP_BP_14 0xC +#define HX8394_DISP_FP_15 0xD +#define HX8394_DISP_RTN_144 0x2F + +#define HX8394_SETCYC 0xB4 + +#define HX8394_SETGIP0 0xD3 +#define HX8394_GIP0_EQ_OPT_BOTH 0x0 +#define HX8394_GIP0_EQ_HSYNC_NORMAL 0x0 +#define HX8394_GIP0_EQ_VSEL_VSSA 0x0 +#define HX8394_SHP_START_4 0x40 +#define HX8394_SCP_WIDTH_7X_HSYNC 0x7 +#define HX8394_CHR0_12X_HSYNC 0xA +#define HX8394_CHR1_18X_HSYNC 0x10 + +#define HX8394_SETGIP1 0xD5 + +#define HX8394_SETGIP2 0xD6 + +#define HX8394_SETVCOM 0xB6 +#define HX8394_VCMC_F_1_76V 0x92 +#define HX8394_VCMC_B_1_76V 0x92 + +#define HX8394_SETGAMMA 0xE0 + +#define HX8394_SETPANEL 0xCC +#define HX8394_COLOR_BGR BIT(0) +#define HX8394_REV_PANEL BIT(1) + +#define HX8394_SETBANK 0xBD + +#define HX8394_SET_TEAR 0x35 +#define HX8394_TEAR_VBLANK 0x0 + +#define HX8394_SETEXTC 0xB9 +#define HX8394_EXTC1_MAGIC 0xFF +#define HX8394_EXTC2_MAGIC 0x83 +#define HX8394_EXTC3_MAGIC 0x94 + + +const uint8_t enable_extension[] = { + HX8394_SETEXTC, + HX8394_EXTC1_MAGIC, + HX8394_EXTC2_MAGIC, + HX8394_EXTC3_MAGIC, +}; + +const uint8_t address_config[] = { + HX8394_SET_ADDRESS, + HX8394_FLIP_HORIZONTAL +}; + +const uint8_t power_config[] = { + HX8394_SETPOWER, + (HX8394_POWER_HX5186 | HX8394_POWER_AP_1_0UA), + HX8394_POWER_VRHP_4_8V, + (HX8394_POWER_VPPS_8_25V | HX8394_POWER_VRHN_4_8V), + (HX8394_POWER_VSP_FBOFF | HX8394_POWER_XDK_X2), + (HX8394_POWER_CLK_OPT_VGL_HSYNC_RST | + HX8394_POWER_CLK_OPT_VGH_HSYNC_RST | + HX8394_POWER_FS0_DIV_8), + (HX8394_POWER_FS1_DIV_224 | HX8394_POWER_FS2_DIV_192), + (HX8394_POWER_VGH_RATIO_2VSPVSN | HX8394_POWER_BTP_5_55V), + (HX8394_POWER_VGL_RATIO_2VSPVSN | HX8394_POWER_BTN_5_55V), + HX8394_POWER_VGHS_16V, + HX8394_POWER_VGLS_12_4V +}; + +const uint8_t line_config[] = { + HX8394_SETDISP, + HX8394_DISP_COL_INV, + HX8394_DISP_MESSI_ENB, + HX8394_DISP_NL_1280, + HX8394_DISP_BP_14, + HX8394_DISP_FP_15, + HX8394_DISP_RTN_144 +}; + +const uint8_t cycle_config[] = { + HX8394_SETCYC, + 0x73, /* SPON delay */ + 0x74, /* SPOFF delay */ + 0x73, /* CON delay */ + 0x74, /* COFF delay */ + 0x73, /* CON1 delay */ + 0x74, /* COFF1 delay */ + 0x1, /* EQON time */ + 0xC, /* SON time */ + 0x86, /* SOFF time */ + 0x75, /* SAP1_P, SAP2 (1st and second stage op amp bias) */ + 0x00, /* DX2 off, EQ off, EQ_MI off */ + 0x3F, /* DX2 off period setting */ + 0x73, /* SPON_MPU delay */ + 0x74, /* SPOFF_MPU delay */ + 0x73, /* CON_MPU delay */ + 0x74, /* COFF_MPU delay */ + 0x73, /* CON1_MPU delay */ + 0x74, /* COFF1_MPU delay */ + 0x1, /* EQON_MPU time */ + 0xC, /* SON_MPU time */ + 0x86 /* SOFF_MPU time */ +}; + +const uint8_t gip0_config[] = { + HX8394_SETGIP0, + (HX8394_GIP0_EQ_OPT_BOTH | HX8394_GIP0_EQ_HSYNC_NORMAL), + HX8394_GIP0_EQ_VSEL_VSSA, + 0x7, /* EQ_DELAY_ON1 (in cycles of TCON CLK */ + 0x7, /* EQ_DELAY_OFF1 (in cycles of TCON CLK */ + 0x40, /* GPWR signal frequency (64x per frame) */ + 0x7, /* GPWR signal non overlap timing (in cycles of TCON */ + 0xC, /* GIP dummy clock for first CKV */ + 0x00, /* GIP dummy clock for second CKV */ + /* Group delays. Sets start/end signal delay from VYSNC + * falling edge in multiples of HSYNC + */ + 0x8, /* SHR0_2 = 8, SHR0_3 = 0 */ + 0x10, /* SHR0_1 = 1, SHR0[11:8] = 0x0 */ + 0x8, /* SHR0 = 0x8 */ + 0x0, /* SHR0_GS[11:8]. Unset. */ + 0x8, /* SHR0_GS = 0x8 */ + 0x54, /* SHR1_3 = 0x5, SHR1_2 = 0x4 */ + 0x15, /* SHR1_1 = 0x1, SHR1[11:8] = 0x5 */ + 0xA, /* SHR1[7:0] = 0xA (SHR1 = 0x50A) */ + 0x5, /* SHR1_GS[11:8] = 0x5 */ + 0xA, /* SHR1_GS[7:0] = 0xA (SHR1_GS = 0x50A) */ + 0x2, /* SHR2_3 = 0x0, SHR2_2 = 0x2 */ + 0x15, /* SHR2_1 = 0x1, SHR2[11:8] = 0x5 */ + 0x6, /* SHR2[7:0] = 0x6 (SHR2 = 0x506) */ + 0x5, /* SHR2_GS[11:8] = 0x5 */ + 0x6, /* SHR2_GS[7:0 = 0x6 (SHR2_GS = 0x506) */ + (HX8394_SHP_START_4 | HX8394_SCP_WIDTH_7X_HSYNC), + 0x44, /* SHP2 = 0x4, SHP1 = 0x4 */ + HX8394_CHR0_12X_HSYNC, + HX8394_CHR0_12X_HSYNC, + 0x4B, /* CHP0 = 4x hsync, CCP0 = 0xB */ + HX8394_CHR1_18X_HSYNC, + 0x7, /* CHR1_GS = 9x hsync */ + 0x7, /* CHP1 = 1x hsync, CCP1 = 0x7 */ + /* These parameters are not documented in datasheet */ + 0xC, + 0x40 +}; + +const uint8_t gip1_config[] = { + HX8394_SETGIP1, + /* Select output clock sources + * See COSn_L/COSn_R values in datasheet + */ + 0x1C, /* COS1_L */ + 0x1C, /* COS1_R */ + 0x1D, /* COS2_L */ + 0x1D, /* COS2_R */ + 0x00, /* COS3_L */ + 0x01, /* COS3_R */ + 0x02, /* COS4_L */ + 0x03, /* COS4_R */ + 0x04, /* COS5_L */ + 0x05, /* COS5_R */ + 0x06, /* COS6_L */ + 0x07, /* COS6_R */ + 0x08, /* COS7_L */ + 0x09, /* COS7_R */ + 0x0A, /* COS8_L */ + 0x0B, /* COS8_R */ + 0x24, /* COS9_L */ + 0x25, /* COS9_R */ + 0x18, /* COS10_L */ + 0x18, /* COS10_R */ + 0x26, /* COS11_L */ + 0x27, /* COS11_R */ + 0x18, /* COS12_L */ + 0x18, /* COS12_R */ + 0x18, /* COS13_L */ + 0x18, /* COS13_R */ + 0x18, /* COS14_L */ + 0x18, /* COS14_R */ + 0x18, /* COS15_L */ + 0x18, /* COS15_R */ + 0x18, /* COS16_L */ + 0x18, /* COS16_R */ + 0x18, /* COS17_L */ + 0x18, /* COS17_R */ + 0x18, /* COS18_L */ + 0x18, /* COS18_R */ + 0x18, /* COS19_L */ + 0x18, /* COS19_R */ + 0x20, /* COS20_L */ + 0x21, /* COS20_R */ + 0x18, /* COS21_L */ + 0x18, /* COS21_R */ + 0x18, /* COS22_L */ + 0x18 /* COS22_R */ +}; + +const uint8_t gip2_config[] = { + HX8394_SETGIP2, + /* Select output clock sources for GS mode. + * See COSn_L_GS/COSn_R_GS values in datasheet + */ + 0x1C, /* COS1_L_GS */ + 0x1C, /* COS1_R_GS */ + 0x1D, /* COS2_L_GS */ + 0x1D, /* COS2_R_GS */ + 0x07, /* COS3_L_GS */ + 0x06, /* COS3_R_GS */ + 0x05, /* COS4_L_GS */ + 0x04, /* COS4_R_GS */ + 0x03, /* COS5_L_GS */ + 0x02, /* COS5_R_GS */ + 0x01, /* COS6_L_GS */ + 0x00, /* COS6_R_GS */ + 0x0B, /* COS7_L_GS */ + 0x0A, /* COS7_R_GS */ + 0x09, /* COS8_L_GS */ + 0x08, /* COS8_R_GS */ + 0x21, /* COS9_L_GS */ + 0x20, /* COS9_R_GS */ + 0x18, /* COS10_L_GS */ + 0x18, /* COS10_R_GS */ + 0x27, /* COS11_L_GS */ + 0x26, /* COS11_R_GS */ + 0x18, /* COS12_L_GS */ + 0x18, /* COS12_R_GS */ + 0x18, /* COS13_L_GS */ + 0x18, /* COS13_R_GS */ + 0x18, /* COS14_L_GS */ + 0x18, /* COS14_R_GS */ + 0x18, /* COS15_L_GS */ + 0x18, /* COS15_R_GS */ + 0x18, /* COS16_L_GS */ + 0x18, /* COS16_R_GS */ + 0x18, /* COS17_L_GS */ + 0x18, /* COS17_R_GS */ + 0x18, /* COS18_L_GS */ + 0x18, /* COS18_R_GS */ + 0x18, /* COS19_L_GS */ + 0x18, /* COS19_R_GS */ + 0x25, /* COS20_L_GS */ + 0x24, /* COS20_R_GS */ + 0x18, /* COS21_L_GS */ + 0x18, /* COS21_R_GS */ + 0x18, /* COS22_L_GS */ + 0x18 /* COS22_R_GS */ +}; + +const uint8_t vcom_config[] = { + HX8394_SETVCOM, + HX8394_VCMC_F_1_76V, + HX8394_VCMC_B_1_76V +}; + +const uint8_t gamma_config[] = { + HX8394_SETGAMMA, + 0x00, /* VHP0 */ + 0x0A, /* VHP1 */ + 0x15, /* VHP2 */ + 0x1B, /* VHP3 */ + 0x1E, /* VHP4 */ + 0x21, /* VHP5 */ + 0x24, /* VHP6 */ + 0x22, /* VHP7 */ + 0x47, /* VMP0 */ + 0x56, /* VMP1 */ + 0x65, /* VMP2 */ + 0x66, /* VMP3 */ + 0x6E, /* VMP4 */ + 0x82, /* VMP5 */ + 0x88, /* VMP6 */ + 0x8B, /* VMP7 */ + 0x9A, /* VMP8 */ + 0x9D, /* VMP9 */ + 0x98, /* VMP10 */ + 0xA8, /* VMP11 */ + 0xB9, /* VMP12 */ + 0x5D, /* VLP0 */ + 0x5C, /* VLP1 */ + 0x61, /* VLP2 */ + 0x66, /* VLP3 */ + 0x6A, /* VLP4 */ + 0x6F, /* VLP5 */ + 0x7F, /* VLP6 */ + 0x7F, /* VLP7 */ + 0x00, /* VHN0 */ + 0x0A, /* VHN1 */ + 0x15, /* VHN2 */ + 0x1B, /* VHN3 */ + 0x1E, /* VHN4 */ + 0x21, /* VHN5 */ + 0x24, /* VHN6 */ + 0x22, /* VHN7 */ + 0x47, /* VMN0 */ + 0x56, /* VMN1 */ + 0x65, /* VMN2 */ + 0x65, /* VMN3 */ + 0x6E, /* VMN4 */ + 0x81, /* VMN5 */ + 0x87, /* VMN6 */ + 0x8B, /* VMN7 */ + 0x98, /* VMN8 */ + 0x9D, /* VMN9 */ + 0x99, /* VMN10 */ + 0xA8, /* VMN11 */ + 0xBA, /* VMN12 */ + 0x5D, /* VLN0 */ + 0x5D, /* VLN1 */ + 0x62, /* VLN2 */ + 0x67, /* VLN3 */ + 0x6B, /* VLN4 */ + 0x72, /* VLN5 */ + 0x7F, /* VLN6 */ + 0x7F /* VLN7 */ +}; + +const uint8_t hx8394_cmd1[] = {0xC0U, 0x1FU, 0x31U}; + +const uint8_t panel_config[] = { + HX8394_SETPANEL, + (HX8394_COLOR_BGR | HX8394_REV_PANEL) +}; + +const uint8_t hx8394_cmd2[] = {0xD4, 0x2}; + +const uint8_t hx8394_bank2[] = { + 0xD8U, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, + 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, + 0xFFU +}; + +const uint8_t hx8394_bank1[] = {0xB1U, 0x00U}; + +const uint8_t hx8394_bank0[] = { + 0xBFU, 0x40U, 0x81U, 0x50U, + 0x00U, 0x1AU, 0xFCU, 0x01 +}; + +const uint8_t hx8394_cmd3[] = {0xC6U, 0xEDU}; + +const uint8_t tear_config[] = {HX8394_SET_TEAR, HX8394_TEAR_VBLANK | 0x3}; + +static int hx8394_write(const struct device *dev, const uint16_t x, + const uint16_t y, + const struct display_buffer_descriptor *desc, + const void *buf) +{ + LOG_WRN("Write not supported, use LCD controller display driver"); + return 0; +} + +static int hx8394_read(const struct device *dev, const uint16_t x, + const uint16_t y, + const struct display_buffer_descriptor *desc, + void *buf) +{ + LOG_WRN("Read not implemented"); + return -ENOTSUP; +} + +static void *hx8394_get_framebuffer(const struct device *dev) +{ + LOG_WRN("Direct framebuffer access not implemented"); + return NULL; +} + +static int hx8394_blanking_off(const struct device *dev) +{ + const struct hx8394_config *config = dev->config; + + if (config->bl_gpio.port != NULL) { + return gpio_pin_set_dt(&config->bl_gpio, 1); + } else { + return -ENOTSUP; + } +} + +static int hx8394_blanking_on(const struct device *dev) +{ + const struct hx8394_config *config = dev->config; + + if (config->bl_gpio.port != NULL) { + return gpio_pin_set_dt(&config->bl_gpio, 0); + } else { + return -ENOTSUP; + } +} + +static int hx8394_set_brightness(const struct device *dev, + const uint8_t brightness) +{ + LOG_WRN("Set brightness not implemented"); + return -ENOTSUP; +} + +static int hx8394_set_contrast(const struct device *dev, + const uint8_t contrast) +{ + LOG_WRN("Set contrast not implemented"); + return -ENOTSUP; +} + +static int hx8394_set_pixel_format(const struct device *dev, + const enum display_pixel_format pixel_format) +{ + const struct hx8394_config *config = dev->config; + + if (pixel_format == config->pixel_format) { + return 0; + } + LOG_WRN("Pixel format change not implemented"); + return -ENOTSUP; +} + +static int hx8394_set_orientation(const struct device *dev, + const enum display_orientation orientation) +{ + const struct hx8394_config *config = dev->config; + uint8_t param[2] = {0}; + + /* Note- this simply flips the scan direction of the display + * driver. Can be useful if your application needs the display + * flipped on the X or Y axis + */ + param[0] = HX8394_SET_ADDRESS; + switch (orientation) { + case DISPLAY_ORIENTATION_NORMAL: + /* Default orientation for this display flips image on x axis */ + param[1] = HX8394_FLIP_HORIZONTAL; + break; + case DISPLAY_ORIENTATION_ROTATED_90: + param[1] = HX8394_FLIP_VERTICAL; + break; + case DISPLAY_ORIENTATION_ROTATED_180: + param[1] = 0; + break; + case DISPLAY_ORIENTATION_ROTATED_270: + param[1] = HX8394_FLIP_HORIZONTAL | HX8394_FLIP_VERTICAL; + break; + default: + return -ENOTSUP; + } + return mipi_dsi_generic_write(config->mipi_dsi, config->channel, param, 2); +} + +static void hx8394_get_capabilities(const struct device *dev, + struct display_capabilities *capabilities) +{ + const struct hx8394_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 = config->pixel_format; + capabilities->current_pixel_format = config->pixel_format; + capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL; +} + +static const struct display_driver_api hx8394_api = { + .blanking_on = hx8394_blanking_on, + .blanking_off = hx8394_blanking_off, + .write = hx8394_write, + .read = hx8394_read, + .get_framebuffer = hx8394_get_framebuffer, + .set_brightness = hx8394_set_brightness, + .set_contrast = hx8394_set_contrast, + .get_capabilities = hx8394_get_capabilities, + .set_pixel_format = hx8394_set_pixel_format, + .set_orientation = hx8394_set_orientation, +}; + +static int hx8394_init(const struct device *dev) +{ + const struct hx8394_config *config = dev->config; + int ret; + struct mipi_dsi_device mdev; + uint8_t param[2]; + uint8_t setmipi[7] = { + HX8394_SETMIPI, + (HX8394_MIPI_LPTX_BTA_READ | HX8394_MIPI_LP_CD_DIS), + HX8394_MIPI_TA_6TL, + (HX8394_MIPI_DPHYCMD_LPRX_8NS | + HX8394_MIPI_DPHYCMD_LPRX_66mV | + HX8394_MIPI_DPHYCMD_LPTX_SRLIM), + (HX8394_MIPI_DPHYCMD_LDO_1_55V | + HX8394_MIPI_DPHYCMD_HSRX_7X | + HX8394_MIPI_DPHYCMD_HSRX_100OHM | + HX8394_MIPI_DPHYCMD_LPCD_1X), + /* The remaining parameters here are not documented */ + 0xB2U, 0xC0U}; + + mdev.data_lanes = config->num_of_lanes; + mdev.pixfmt = config->pixel_format; + /* HX8394 runs in video mode */ + mdev.mode_flags = MIPI_DSI_MODE_VIDEO; + + ret = mipi_dsi_attach(config->mipi_dsi, config->channel, &mdev); + if (ret < 0) { + LOG_ERR("Could not attach to MIPI-DSI host"); + return ret; + } + + if (gpio_is_ready_dt(&config->reset_gpio)) { + /* Regulator API will have supplied power to the display + * driver. Per datasheet, we must wait 1ms for the RESX + * pin to be valid. + */ + k_sleep(K_MSEC(1)); + /* Initialize reset GPIO */ + ret = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + return ret; + } + /* Pull reset GPIO low */ + gpio_pin_set_dt(&config->reset_gpio, 0); + /* Datasheet says we must keep reset pin low at least 10us. + * hold it low for 1ms to be safe. + */ + k_sleep(K_MSEC(1)); + gpio_pin_set_dt(&config->reset_gpio, 1); + /* Per datasheet, we must delay at least 50ms before first + * host command + */ + k_sleep(K_MSEC(50)); + } + /* Enable extended commands */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + enable_extension, sizeof(enable_extension)); + if (ret < 0) { + return ret; + } + + /* Set the number of lanes to DSISETUP0 parameter */ + setmipi[1] |= (config->num_of_lanes - 1); + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + setmipi, sizeof(setmipi)); + if (ret < 0) { + return ret; + } + + /* Set scan direction */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + address_config, sizeof(address_config)); + if (ret < 0) { + return ret; + } + + /* Set voltage and current targets */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + power_config, sizeof(power_config)); + if (ret < 0) { + return ret; + } + + /* Setup display line count and front/back porch size */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + line_config, sizeof(line_config)); + if (ret < 0) { + return ret; + } + + /* Setup display cycle counts (in counts of TCON CLK) */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + cycle_config, sizeof(cycle_config)); + if (ret < 0) { + return ret; + } + + /* Set group delay values */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + gip0_config, sizeof(gip0_config)); + if (ret < 0) { + return ret; + } + + + /* Set group clock selections */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + gip1_config, sizeof(gip1_config)); + if (ret < 0) { + return ret; + } + + /* Set group clock selections for GS mode */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + gip2_config, sizeof(gip2_config)); + if (ret < 0) { + return ret; + } + + /* Delay for a moment before setting VCOM. It is not clear + * from the datasheet why this is required, but without this + * delay the panel stops responding to additional commands + */ + k_msleep(1); + /* Set VCOM voltage config */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + vcom_config, sizeof(vcom_config)); + if (ret < 0) { + return ret; + } + + /* Set manufacturer supplied gamma values */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + gamma_config, sizeof(gamma_config)); + if (ret < 0) { + return ret; + } + + /* This command is not documented in datasheet, but is included + * in the display initialization done by MCUXpresso SDK + */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + hx8394_cmd1, sizeof(hx8394_cmd1)); + if (ret < 0) { + return ret; + } + + /* Set panel to BGR mode, and reverse colors */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + panel_config, sizeof(panel_config)); + if (ret < 0) { + return ret; + } + + /* This command is not documented in datasheet, but is included + * in the display initialization done by MCUXpresso SDK + */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + hx8394_cmd2, sizeof(hx8394_cmd2)); + if (ret < 0) { + return ret; + } + + /* Write values to manufacturer register banks */ + param[0] = HX8394_SETBANK; + param[1] = 0x2; + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + param, 2); + if (ret < 0) { + return ret; + } + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + hx8394_bank2, sizeof(hx8394_bank2)); + if (ret < 0) { + return ret; + } + param[1] = 0x0; + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + param, 2); + if (ret < 0) { + return ret; + } + /* Select bank 1 */ + param[1] = 0x1; + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + param, 2); + if (ret < 0) { + return ret; + } + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + hx8394_bank1, sizeof(hx8394_bank1)); + if (ret < 0) { + return ret; + } + /* Select bank 0 */ + param[1] = 0x0; + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + param, 2); + if (ret < 0) { + return ret; + } + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + hx8394_bank0, sizeof(hx8394_bank0)); + if (ret < 0) { + return ret; + } + + /* This command is not documented in datasheet, but is included + * in the display initialization done by MCUXpresso SDK + */ + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + hx8394_cmd3, sizeof(hx8394_cmd3)); + if (ret < 0) { + return ret; + } + + ret = mipi_dsi_generic_write(config->mipi_dsi, config->channel, + tear_config, sizeof(tear_config)); + if (ret < 0) { + return ret; + } + + ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel, + MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0); + if (ret < 0) { + return ret; + } + /* We must delay 120ms after exiting sleep mode per datasheet */ + k_sleep(K_MSEC(120)); + ret = mipi_dsi_dcs_write(config->mipi_dsi, config->channel, + MIPI_DCS_SET_DISPLAY_ON, NULL, 0); + + if (config->bl_gpio.port != NULL) { + ret = gpio_pin_configure_dt(&config->bl_gpio, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + LOG_ERR("Could not configure bl GPIO (%d)", ret); + return ret; + } + } + + return ret; +} + +#define HX8394_PANEL(id) \ + static const struct hx8394_config hx8394_config_##id = { \ + .mipi_dsi = DEVICE_DT_GET(DT_INST_BUS(id)), \ + .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(id, reset_gpios, {0}), \ + .bl_gpio = GPIO_DT_SPEC_INST_GET_OR(id, bl_gpios, {0}), \ + .num_of_lanes = DT_INST_PROP_BY_IDX(id, data_lanes, 0), \ + .pixel_format = DT_INST_PROP(id, pixel_format), \ + .panel_width = DT_INST_PROP(id, width), \ + .panel_height = DT_INST_PROP(id, height), \ + .channel = DT_INST_REG_ADDR(id), \ + }; \ + DEVICE_DT_INST_DEFINE(id, \ + &hx8394_init, \ + NULL, \ + NULL, \ + &hx8394_config_##id, \ + POST_KERNEL, \ + CONFIG_APPLICATION_INIT_PRIORITY, \ + &hx8394_api); + +DT_INST_FOREACH_STATUS_OKAY(HX8394_PANEL) diff --git a/dts/bindings/display/himax,hx8394.yaml b/dts/bindings/display/himax,hx8394.yaml new file mode 100644 index 00000000000..c137ad60fb2 --- /dev/null +++ b/dts/bindings/display/himax,hx8394.yaml @@ -0,0 +1,24 @@ +# +# Copyright 2023 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: Himax HX8394 Panel + +compatible: "himax,hx8394" + +include: [mipi-dsi-device.yaml, display-controller.yaml] + +properties: + reset-gpios: + type: phandle-array + description: | + The RESX pin is asserted to disable the sensor causing a hard + reset. The sensor receives this as an active-low signal. + + bl-gpios: + type: phandle-array + description: | + The BLn pin is asserted to control the backlight of the panel. + The sensor receives this as an active-high signal.