路径:/u-boot/drivers/video/drm/panel-lh24030c50.c

// SPDX-License-Identifier: GPL-2.0+
/*
 * LH24030C50 RGB LCD panel driver based on the ST7789V controller.
 *
 * GPIO bitbang SPI for panel initialization, RGB interface for display data.
 */

#include <common.h>
#include <dm.h>
#include <errno.h>
#include <backlight.h>
#include <asm/gpio.h>
#include <power/regulator.h>
#include <linux/media-bus-format.h>
#include <drm_modes.h>

#include "rockchip_display.h"
#include "rockchip_panel.h"

struct lh24030c50_priv {
	struct gpio_desc reset;
	struct gpio_desc sclk;
	struct gpio_desc mosi;
	struct gpio_desc cs;
	struct udevice *power_supply;
	struct udevice *backlight;
	bool prepared;
	bool enabled;
};

/*
 * 9-bit SPI bitbang: DC bit + 8 data bits, MSB first.
 * CS active low, SCLK idles low, data clocked on rising edge.
 */
static void lh24030c50_spi_xfer(struct lh24030c50_priv *priv, int dc, u8 data)
{
	int i;

	dm_gpio_set_value(&priv->cs, 0);
	udelay(2);

	/* DC bit */
	dm_gpio_set_value(&priv->sclk, 0);
	dm_gpio_set_value(&priv->mosi, dc ? 1 : 0);
	udelay(2);
	dm_gpio_set_value(&priv->sclk, 1);
	udelay(2);

	/* 8 data bits, MSB first */
	for (i = 0; i < 8; i++) {
		dm_gpio_set_value(&priv->sclk, 0);
		dm_gpio_set_value(&priv->mosi, (data >> (7 - i)) & 1);
		udelay(2);
		dm_gpio_set_value(&priv->sclk, 1);
		udelay(2);
	}

	dm_gpio_set_value(&priv->sclk, 0);
	udelay(1);
	dm_gpio_set_value(&priv->cs, 1);
	udelay(1);
}

static void lh24030c50_write_cmd(struct lh24030c50_priv *priv, u8 cmd)
{
	lh24030c50_spi_xfer(priv, 0, cmd);
}

static void lh24030c50_write_data(struct lh24030c50_priv *priv, u8 data)
{
	lh24030c50_spi_xfer(priv, 1, data);
}

static void lh24030c50_spi_idle(struct lh24030c50_priv *priv)
{
	dm_gpio_set_value(&priv->cs, 1);
	dm_gpio_set_value(&priv->sclk, 0);
	dm_gpio_set_value(&priv->mosi, 0);
}

/*
 * ST7789V LCD initialization sequence.
 * This matches the kernel panel-lh24030c50.c LCD_Init().
 */
static void lh24030c50_lcd_init(struct lh24030c50_priv *priv)
{
	lh24030c50_spi_idle(priv);

	/* Software Reset */
	lh24030c50_write_cmd(priv, 0x01);
	mdelay(150);

	/* Sleep Out */
	lh24030c50_write_cmd(priv, 0x11);
	mdelay(120);

	/* MADCTL: RGB mode */
	lh24030c50_write_cmd(priv, 0x36); //控制 RGB/BGR、屏幕镜像、扫描方向
	lh24030c50_write_data(priv, 0x00);

	/* COLMOD: 18-bit/pixel (RGB666) */
	lh24030c50_write_cmd(priv, 0x3a);
	lh24030c50_write_data(priv, 0x06);

	/* RAMCTRL */
	lh24030c50_write_cmd(priv, 0xB0);
	lh24030c50_write_data(priv, 0x12); /* RM=1, DM=01(RGB) */
	lh24030c50_write_data(priv, 0xC0); /* EPF1 EPF0=11 */

	/* Porch setting */
	lh24030c50_write_cmd(priv, 0xB1);
	lh24030c50_write_data(priv, 0x40);
	lh24030c50_write_data(priv, 0x04);
	lh24030c50_write_data(priv, 0x0a);

	/* Frame Rate Control */
	lh24030c50_write_cmd(priv, 0xB2);
	lh24030c50_write_data(priv, 0x0C);
	lh24030c50_write_data(priv, 0x0C);
	lh24030c50_write_data(priv, 0x00);
	lh24030c50_write_data(priv, 0x33);
	lh24030c50_write_data(priv, 0x33);

	/* Gate Control */
	lh24030c50_write_cmd(priv, 0xB7);
	lh24030c50_write_data(priv, 0x35);

	/* VCOM Setting */
	lh24030c50_write_cmd(priv, 0xBB);
	lh24030c50_write_data(priv, 0x2B);

	/* Power Control 1 */
	lh24030c50_write_cmd(priv, 0xC0);
	lh24030c50_write_data(priv, 0x2C);

	/* Power Control 2 */
	lh24030c50_write_cmd(priv, 0xC2);
	lh24030c50_write_data(priv, 0x01);

	/* Power Control 3 */
	lh24030c50_write_cmd(priv, 0xC3);
	lh24030c50_write_data(priv, 0x11);

	/* Power Control 4 */
	lh24030c50_write_cmd(priv, 0xC4);
	lh24030c50_write_data(priv, 0x20);

	/* VCOM Control 1 */
	lh24030c50_write_cmd(priv, 0xC6);
	lh24030c50_write_data(priv, 0x0F);

	/* Power Control A */
	lh24030c50_write_cmd(priv, 0xD0);
	lh24030c50_write_data(priv, 0xA4);
	lh24030c50_write_data(priv, 0xA1);

	/* Positive Gamma Correction */
	lh24030c50_write_cmd(priv, 0xE0);
	lh24030c50_write_data(priv, 0xD0);
	lh24030c50_write_data(priv, 0x00);
	lh24030c50_write_data(priv, 0x06);
	lh24030c50_write_data(priv, 0x09);
	lh24030c50_write_data(priv, 0x0B);
	lh24030c50_write_data(priv, 0x2A);
	lh24030c50_write_data(priv, 0x3C);
	lh24030c50_write_data(priv, 0x55);
	lh24030c50_write_data(priv, 0x4B);
	lh24030c50_write_data(priv, 0x08);
	lh24030c50_write_data(priv, 0x16);
	lh24030c50_write_data(priv, 0x14);
	lh24030c50_write_data(priv, 0x19);
	lh24030c50_write_data(priv, 0x20);

	/* Negative Gamma Correction */
	lh24030c50_write_cmd(priv, 0xE1);
	lh24030c50_write_data(priv, 0xD0);
	lh24030c50_write_data(priv, 0x00);
	lh24030c50_write_data(priv, 0x06);
	lh24030c50_write_data(priv, 0x09);
	lh24030c50_write_data(priv, 0x0B);
	lh24030c50_write_data(priv, 0x29);
	lh24030c50_write_data(priv, 0x36);
	lh24030c50_write_data(priv, 0x54);
	lh24030c50_write_data(priv, 0x4B);
	lh24030c50_write_data(priv, 0x0D);
	lh24030c50_write_data(priv, 0x16);
	lh24030c50_write_data(priv, 0x14);
	lh24030c50_write_data(priv, 0x21);
	lh24030c50_write_data(priv, 0x20);

	lh24030c50_write_cmd(priv, 0x21); // 开启像素反转,解决黑白颠倒
}

static void lh24030c50_prepare(struct rockchip_panel *panel)
{
	struct lh24030c50_priv *priv = dev_get_priv(panel->dev);

	if (priv->prepared)
		return;

	/* Enable power supply */
	if (priv->power_supply)
		regulator_set_enable(priv->power_supply, true);

	/* Reset sequence: assert for 50ms, then deassert */
	dm_gpio_set_value(&priv->reset, 1);
	mdelay(50);
	dm_gpio_set_value(&priv->reset, 0);
	mdelay(50);

	/* SPI idle then run LCD init */
	lh24030c50_spi_idle(priv);
	mdelay(150);

	lh24030c50_lcd_init(priv);

	priv->prepared = true;
}

static void lh24030c50_unprepare(struct rockchip_panel *panel)
{
	struct lh24030c50_priv *priv = dev_get_priv(panel->dev);

	if (!priv->prepared)
		return;

	/* Sleep In */
	lh24030c50_write_cmd(priv, 0x10);
	mdelay(120);

	dm_gpio_set_value(&priv->reset, 1);
	lh24030c50_spi_idle(priv);

	if (priv->power_supply)
		regulator_set_enable(priv->power_supply, false);

	priv->prepared = false;
}

static void lh24030c50_enable(struct rockchip_panel *panel)
{
	struct lh24030c50_priv *priv = dev_get_priv(panel->dev);

	if (priv->enabled)
		return;

	/* Wait for first frame to settle, then turn display on */
	mdelay(20);
	lh24030c50_write_cmd(priv, 0x29);
	mdelay(50);

	/* Enable backlight */
	if (priv->backlight)
		backlight_enable(priv->backlight);

	priv->enabled = true;
}

static void lh24030c50_disable(struct rockchip_panel *panel)
{
	struct lh24030c50_priv *priv = dev_get_priv(panel->dev);

	if (!priv->enabled)
		return;

	/* Disable backlight */
	if (priv->backlight)
		backlight_disable(priv->backlight);

	/* Display Off */
	lh24030c50_write_cmd(priv, 0x28);

	priv->enabled = false;
}

/* Panel display timing: 240x320 @ 7MHz RGB */
static int lh24030c50_get_mode(struct rockchip_panel *panel,
				struct drm_display_mode *mode)
{
	printf("lh24030c50: get_mode called\n");

	mode->clock = 7000;
	mode->hdisplay = 240;
	mode->hsync_start = 240 + 38;
	mode->hsync_end = 240 + 38 + 10;
	mode->htotal = 240 + 38 + 10 + 10;
	mode->vdisplay = 320;
	mode->vsync_start = 320 + 8;
	mode->vsync_end = 320 + 8 + 4;
	mode->vtotal = 320 + 8 + 4 + 4;
	mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
	mode->vrefresh = 70;
	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;

	return 0;
}

static const struct rockchip_panel_funcs lh24030c50_funcs = {
	.prepare   = lh24030c50_prepare,
	.unprepare = lh24030c50_unprepare,
	.enable    = lh24030c50_enable,
	.disable   = lh24030c50_disable,
	.get_mode  = lh24030c50_get_mode,
};

static int lh24030c50_probe(struct udevice *dev)
{
	struct lh24030c50_priv *priv = dev_get_priv(dev);
	struct rockchip_panel *panel;
	int ret;

	/* Reset GPIO (active low) */
	ret = gpio_request_by_name(dev, "reset-gpios", 0, &priv->reset,
				   GPIOD_IS_OUT);
	if (ret) {
		printf("%s: Cannot get reset GPIO: %d\n", __func__, ret);
		return ret;
	}

	/* SPI bitbang GPIOs */
	ret = gpio_request_by_name(dev, "spi-scl-gpios", 0, &priv->sclk,
				   GPIOD_IS_OUT);
	if (ret) {
		printf("%s: Cannot get SPI SCLK GPIO: %d\n", __func__, ret);
		return ret;
	}

	ret = gpio_request_by_name(dev, "spi-sdi-gpios", 0, &priv->mosi,
				   GPIOD_IS_OUT);
	if (ret) {
		printf("%s: Cannot get SPI SDI GPIO: %d\n", __func__, ret);
		return ret;
	}

	ret = gpio_request_by_name(dev, "spi-cs-gpios", 0, &priv->cs,
				   GPIOD_IS_OUT);
	if (ret) {
		printf("%s: Cannot get SPI CS GPIO: %d\n", __func__, ret);
		return ret;
	}

	/* Power supply (optional) */
	ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
					   "power-supply", &priv->power_supply);
	if (ret && ret != -ENOENT && ret != -ENODEV) {
		printf("%s: Cannot get power supply: %d\n", __func__, ret);
		return ret;
	}

	/* Backlight (optional) */
	ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
					   "backlight", &priv->backlight);
	if (ret && ret != -ENOENT && ret != -ENODEV) {
		printf("%s: Cannot get backlight: %d\n", __func__, ret);
		return ret;
	}

	/* Set initial idle state */
	dm_gpio_set_value(&priv->cs, 1);
	dm_gpio_set_value(&priv->sclk, 0);
	dm_gpio_set_value(&priv->mosi, 0);
	dm_gpio_set_value(&priv->reset, 0);

	/* Allocate and register rockchip_panel */
	panel = calloc(1, sizeof(*panel));
	if (!panel)
		return -ENOMEM;

	dev->driver_data = (ulong)panel;
	panel->dev = dev;
	panel->bpc = 6; /* RGB666: 6 bits per component */
	panel->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
	panel->funcs = &lh24030c50_funcs;

	return 0;
}

static const struct udevice_id lh24030c50_ids[] = {
	{ .compatible = "lh,lh24030c50" },
	{ }
};

U_BOOT_DRIVER(lh24030c50) = {
	.name	  = "lh24030c50",
	.id	  = UCLASS_PANEL,
	.of_match = lh24030c50_ids,
	.probe	  = lh24030c50_probe,
	.priv_auto_alloc_size = sizeof(struct lh24030c50_priv),
};