blob: fe0d0d0584603e9bd6c1bbee70c1c41a0ff03c97 [file] [log] [blame]
/*
* 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 <zephyr/drivers/display.h>
#include <zephyr/drivers/mipi_dsi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
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)