Add support for AMOLED panels based on the ChipWealth CH13726A MIPI DSI display driver IC.
The driver configures the MIPI DSI interface for 4 data lanes in RGB888 video mode, performs the panel reset sequence, sends the vendor-specific initialization commands required by the controller, and enables the panel using the standard MIPI DCS Sleep Out and Display On commands. This driver is based on an earlier implementation by Pratham Malaviya. Signed-off-by: Advait Dhamorikar <[email protected]> --- v1->v2 - Expand CC list based on get_maintainer.pl output. v2 -> v3 Link to v2: https://lore.kernel.org/all/[email protected]/ - Add regulator support using the U-Boot regulator framework. - Minor code cleanup. drivers/video/Kconfig | 8 + drivers/video/Makefile | 1 + drivers/video/chipwealth-ch13726a.c | 221 ++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 drivers/video/chipwealth-ch13726a.c diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index c2acc13139c..a214626f376 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -762,6 +762,14 @@ config VIDEO_LCD_SPI_MISO option takes a string in the format understood by 'sunxi_name_to_gpio' function, e.g. PH1 for pin 1 of port H. +config VIDEO_PANEL_CHIPWEALTH_CH13726A + bool "ChipWealth CH13726A AMOLED panel support" + depends on PANEL && BACKLIGHT + select VIDEO_MIPI_DSI + help + Say Y here if you want to enable support for AMOLED panels + based on the ChipWealth CH13726A MIPI DSI display driver IC. + source "drivers/video/meson/Kconfig" config VIDEO_MVEBU diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 082b8967982..efa7e288691 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o obj-$(CONFIG_VIDEO_LCD_TDO_TL070WSH30) += tdo-tl070wsh30.o obj-$(CONFIG_VIDEO_LCD_SONY_L4F00430T01) += sony-l4f00430t01.o obj-$(CONFIG_VIDEO_LCD_SAMSUNG_S6E63M0) += samsung-s6e63m0.o +obj-$(CONFIG_VIDEO_PANEL_CHIPWEALTH_CH13726A) += chipwealth-ch13726a.o obj-$(CONFIG_VIDEO_MCDE_SIMPLE) += mcde_simple.o obj-${CONFIG_VIDEO_MESON} += meson/ obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o diff --git a/drivers/video/chipwealth-ch13726a.c b/drivers/video/chipwealth-ch13726a.c new file mode 100644 index 00000000000..18f1dbc2e78 --- /dev/null +++ b/drivers/video/chipwealth-ch13726a.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ChipWealth CH13726A DSI panel driver + * + * Copyright (C) 2026 Mecha Systems + * Author(s): + * Advait Dhamorikar <[email protected]> + * Pratham Malaviya <[email protected]> + */ + +#include <backlight.h> +#include <dm.h> +#include <mipi_dsi.h> +#include <panel.h> +#include <asm/gpio.h> +#include <linux/delay.h> +#include <log.h> + +struct ch13726a_panel_priv { + struct udevice *vdd; + struct udevice *vddio; + struct udevice *vdd1v2; + struct udevice *avdd; + struct udevice *reg; + struct udevice *backlight; + struct gpio_desc reset; +}; + +static const struct display_timing default_timing = { + .pixelclock.typ = 59832000, + .hactive.typ = 1080, + .hfront_porch.typ = 12, + .hback_porch.typ = 12, + .hsync_len.typ = 28, + .vactive.typ = 1240, + .vfront_porch.typ = 2, + .vback_porch.typ = 2, + .vsync_len.typ = 2, +}; + +#define dcs_write_seq(dev, cmd, seq...) \ +({ \ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); \ + const u8 d[] = { cmd, seq }; \ + int ret; \ + \ + ret = mipi_dsi_dcs_write_buffer(plat->device, \ + d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + log_err("failed to write dcs sequence: %d\n", ret);\ + ret; \ +}) + +static int ch13726a_hw_init(struct udevice *dev) +{ + int ret; + + ret = dcs_write_seq(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x00); + if (ret < 0) { + log_debug("%s: Init command failed (%d)\n", + __func__, ret); + return ret; + } + + ret = dcs_write_seq(dev, MIPI_DSI_V_SYNC_END, 0x00); + if (ret < 0) { + log_debug("%s: Init command failed (%d)\n", + __func__, ret); + return ret; + } + + mdelay(120); + + ret = dcs_write_seq(dev, MIPI_DSI_GENERIC_LONG_WRITE, 0x00); + if (ret < 0) { + log_debug("%s: Init command failed (%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +static void ch13726a_reset(struct udevice *dev) +{ + struct ch13726a_panel_priv *priv = dev_get_priv(dev); + + dm_gpio_set_value(&priv->reset, true); + mdelay(10); + dm_gpio_set_value(&priv->reset, false); + mdelay(10); + dm_gpio_set_value(&priv->reset, true); + mdelay(5); +} + +static int ch13726a_set_backlight(struct udevice *dev, int percent) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + struct mipi_dsi_device *device = plat->device; + u8 brightness; + + if (percent < 0) + percent = 0; + else if (percent > 100) + percent = 100; + + brightness = (percent * 255) / 100; + + return mipi_dsi_dcs_set_display_brightness(device, brightness); +} + +static int ch13726a_panel_enable_backlight(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + struct mipi_dsi_device *device = plat->device; + int ret; + + ret = mipi_dsi_attach(device); + if (ret < 0) + return ret; + + ch13726a_reset(dev); + + ret = ch13726a_hw_init(dev); + if (ret) + return ret; + + ret = mipi_dsi_dcs_exit_sleep_mode(device); + if (ret) + return ret; + + mdelay(120); + + ret = mipi_dsi_dcs_set_display_on(device); + if (ret) + return ret; + + return 0; +} + +static int ch13726a_panel_get_display_timing(struct udevice *dev, + struct display_timing *timings) +{ + memcpy(timings, &default_timing, sizeof(*timings)); + return 0; +} + +static int ch13726a_panel_of_to_plat(struct udevice *dev) +{ + struct ch13726a_panel_priv *priv = dev_get_priv(dev); + int ret; + + if (CONFIG_IS_ENABLED(DM_REGULATOR)) { + ret = device_get_supply_regulator(dev, "vdd-supply", + &priv->vdd); + if (ret && ret != -ENOENT) + return ret; + + ret = device_get_supply_regulator(dev, "vddio-supply", + &priv->vddio); + if (ret && ret != -ENOENT) + return ret; + + ret = device_get_supply_regulator(dev, "vdd1v2-supply", + &priv->vdd1v2); + if (ret && ret != -ENOENT) + return ret; + + ret = device_get_supply_regulator(dev, "avdd-supply", + &priv->avdd); + if (ret && ret != -ENOENT) + return ret; + } + + ret = gpio_request_by_name(dev, "reset-gpios", 0, + &priv->reset, + GPIOD_IS_OUT); + if (ret) { + log_err("%s: cannot get reset GPIO (%d)\n", + __func__, ret); + if (ret != -ENOENT) + return ret; + } + + return 0; +} + +static int ch13726a_panel_probe(struct udevice *dev) +{ + struct mipi_dsi_panel_plat *plat = dev_get_plat(dev); + + plat->lanes = 4; + plat->format = MIPI_DSI_FMT_RGB888; + plat->mode_flags = MIPI_DSI_MODE_VIDEO | + MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_MODE_LPM; + + return 0; +} + +static const struct panel_ops ch13726a_panel_ops = { + .enable_backlight = ch13726a_panel_enable_backlight, + .set_backlight = ch13726a_set_backlight, + .get_display_timing = ch13726a_panel_get_display_timing, +}; + +static const struct udevice_id ch13726a_panel_ids[] = { + { .compatible = "chipwealth,ch13726a" }, + { } +}; + +U_BOOT_DRIVER(ch13726a_panel) = { + .name = "ch13726a_panel", + .id = UCLASS_PANEL, + .of_match = ch13726a_panel_ids, + .ops = &ch13726a_panel_ops, + .of_to_plat = ch13726a_panel_of_to_plat, + .probe = ch13726a_panel_probe, + .plat_auto = sizeof(struct mipi_dsi_panel_plat), + .priv_auto = sizeof(struct ch13726a_panel_priv), +}; -- 2.43.0

