From: Svyatoslav Ryhel Date: Tue, 25 Apr 2023 07:51:43 +0000 (+0300) Subject: video: bridge: add Solomon SSD2825 DSI/LVDS driver X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=b0268244d321dc2e103338052f1c8ba9b892fece;p=u-boot.git video: bridge: add Solomon SSD2825 DSI/LVDS driver SSD2825 is an innovative and cost-effective MIPI Bridge Chip solution targeting high resolution smartphones. It can convert 24bit RGB interface into 4-lane MIPI-DSI interface to drive extremely high resolution display modules of up to 800 x 1366, while supporting AMOLED, a-si LCD or LTPS advanced panel technologies for smartphone applications. Bridge is wrapped in panel uClass model for wider compatibility. Tested-by: Andreas Westman Dorcsak # LG P880 T30 Tested-by: Svyatoslav Ryhel # LG P895 T30 Signed-off-by: Svyatoslav Ryhel --- diff --git a/drivers/video/bridge/Kconfig b/drivers/video/bridge/Kconfig index 765f7380b8..2311ca2d1a 100644 --- a/drivers/video/bridge/Kconfig +++ b/drivers/video/bridge/Kconfig @@ -33,3 +33,10 @@ config VIDEO_BRIDGE_ANALOGIX_ANX6345 help The Analogix ANX6345 is RGB-to-DP converter. It enables an eDP LCD panel to be connected to an parallel LCD interface. + +config VIDEO_BRIDGE_SOLOMON_SSD2825 + bool "Solomon SSD2825 bridge driver" + depends on PANEL && DM_GPIO + select VIDEO_MIPI_DSI + help + Solomon SSD2824 SPI RGB-DSI bridge driver wrapped into panel uClass. diff --git a/drivers/video/bridge/Makefile b/drivers/video/bridge/Makefile index 45e54ac176..22625c8bc6 100644 --- a/drivers/video/bridge/Makefile +++ b/drivers/video/bridge/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_VIDEO_BRIDGE) += video-bridge-uclass.o obj-$(CONFIG_VIDEO_BRIDGE_PARADE_PS862X) += ps862x.o obj-$(CONFIG_VIDEO_BRIDGE_NXP_PTN3460) += ptn3460.o obj-$(CONFIG_VIDEO_BRIDGE_ANALOGIX_ANX6345) += anx6345.o +obj-$(CONFIG_VIDEO_BRIDGE_SOLOMON_SSD2825) += ssd2825.o diff --git a/drivers/video/bridge/ssd2825.c b/drivers/video/bridge/ssd2825.c new file mode 100644 index 0000000000..cea20dcffa --- /dev/null +++ b/drivers/video/bridge/ssd2825.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2022 Svyatoslav Ryhel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSD2825_DEVICE_ID_REG 0xB0 +#define SSD2825_RGB_INTERFACE_CTRL_REG_1 0xB1 +#define SSD2825_RGB_INTERFACE_CTRL_REG_2 0xB2 +#define SSD2825_RGB_INTERFACE_CTRL_REG_3 0xB3 +#define SSD2825_RGB_INTERFACE_CTRL_REG_4 0xB4 +#define SSD2825_RGB_INTERFACE_CTRL_REG_5 0xB5 +#define SSD2825_RGB_INTERFACE_CTRL_REG_6 0xB6 +#define SSD2825_NON_BURST BIT(2) +#define SSD2825_BURST BIT(3) +#define SSD2825_PCKL_HIGH BIT(13) +#define SSD2825_HSYNC_HIGH BIT(14) +#define SSD2825_VSYNC_HIGH BIT(15) +#define SSD2825_CONFIGURATION_REG 0xB7 +#define SSD2825_CONF_REG_HS BIT(0) +#define SSD2825_CONF_REG_CKE BIT(1) +#define SSD2825_CONF_REG_SLP BIT(2) +#define SSD2825_CONF_REG_VEN BIT(3) +#define SSD2825_CONF_REG_HCLK BIT(4) +#define SSD2825_CONF_REG_CSS BIT(5) +#define SSD2825_CONF_REG_DCS BIT(6) +#define SSD2825_CONF_REG_REN BIT(7) +#define SSD2825_CONF_REG_ECD BIT(8) +#define SSD2825_CONF_REG_EOT BIT(9) +#define SSD2825_CONF_REG_LPE BIT(10) +#define SSD2825_VC_CTRL_REG 0xB8 +#define SSD2825_PLL_CTRL_REG 0xB9 +#define SSD2825_PLL_CONFIGURATION_REG 0xBA +#define SSD2825_CLOCK_CTRL_REG 0xBB +#define SSD2825_PACKET_SIZE_CTRL_REG_1 0xBC +#define SSD2825_PACKET_SIZE_CTRL_REG_2 0xBD +#define SSD2825_PACKET_SIZE_CTRL_REG_3 0xBE +#define SSD2825_PACKET_DROP_REG 0xBF +#define SSD2825_OPERATION_CTRL_REG 0xC0 +#define SSD2825_MAX_RETURN_SIZE_REG 0xC1 +#define SSD2825_RETURN_DATA_COUNT_REG 0xC2 +#define SSD2825_ACK_RESPONSE_REG 0xC3 +#define SSD2825_LINE_CTRL_REG 0xC4 +#define SSD2825_INTERRUPT_CTRL_REG 0xC5 +#define SSD2825_INTERRUPT_STATUS_REG 0xC6 +#define SSD2825_ERROR_STATUS_REG 0xC7 +#define SSD2825_DATA_FORMAT_REG 0xC8 +#define SSD2825_DELAY_ADJ_REG_1 0xC9 +#define SSD2825_DELAY_ADJ_REG_2 0xCA +#define SSD2825_DELAY_ADJ_REG_3 0xCB +#define SSD2825_DELAY_ADJ_REG_4 0xCC +#define SSD2825_DELAY_ADJ_REG_5 0xCD +#define SSD2825_DELAY_ADJ_REG_6 0xCE +#define SSD2825_HS_TX_TIMER_REG_1 0xCF +#define SSD2825_HS_TX_TIMER_REG_2 0xD0 +#define SSD2825_LP_RX_TIMER_REG_1 0xD1 +#define SSD2825_LP_RX_TIMER_REG_2 0xD2 +#define SSD2825_TE_STATUS_REG 0xD3 +#define SSD2825_SPI_READ_REG 0xD4 +#define SSD2825_PLL_LOCK_REG 0xD5 +#define SSD2825_TEST_REG 0xD6 +#define SSD2825_TE_COUNT_REG 0xD7 +#define SSD2825_ANALOG_CTRL_REG_1 0xD8 +#define SSD2825_ANALOG_CTRL_REG_2 0xD9 +#define SSD2825_ANALOG_CTRL_REG_3 0xDA +#define SSD2825_ANALOG_CTRL_REG_4 0xDB +#define SSD2825_INTERRUPT_OUT_CTRL_REG 0xDC +#define SSD2825_RGB_INTERFACE_CTRL_REG_7 0xDD +#define SSD2825_LANE_CONFIGURATION_REG 0xDE +#define SSD2825_DELAY_ADJ_REG_7 0xDF +#define SSD2825_INPUT_PIN_CTRL_REG_1 0xE0 +#define SSD2825_INPUT_PIN_CTRL_REG_2 0xE1 +#define SSD2825_BIDIR_PIN_CTRL_REG_1 0xE2 +#define SSD2825_BIDIR_PIN_CTRL_REG_2 0xE3 +#define SSD2825_BIDIR_PIN_CTRL_REG_3 0xE4 +#define SSD2825_BIDIR_PIN_CTRL_REG_4 0xE5 +#define SSD2825_BIDIR_PIN_CTRL_REG_5 0xE6 +#define SSD2825_BIDIR_PIN_CTRL_REG_6 0xE7 +#define SSD2825_BIDIR_PIN_CTRL_REG_7 0xE8 +#define SSD2825_CABC_BRIGHTNESS_CTRL_REG_1 0xE9 +#define SSD2825_CABC_BRIGHTNESS_CTRL_REG_2 0xEA +#define SSD2825_CABC_BRIGHTNESS_STATUS_REG 0xEB +#define SSD2825_READ_REG 0xFF +#define SSD2825_SPI_READ_REG_RESET 0xFA + +#define SSD2825_CMD_MASK 0x00 +#define SSD2825_DAT_MASK 0x01 + +#define SSD2825_CMD_SEND BIT(0) +#define SSD2825_DAT_SEND BIT(1) +#define SSD2825_DSI_SEND BIT(2) + +#define SSD2828_LP_CLOCK_DIVIDER(n) (((n) - 1) & 0x3F) +#define SSD2825_LP_MIN_CLK 5000 /* KHz */ +#define SSD2825_REF_MIN_CLK 2000 /* KHz */ + +struct ssd2825_bridge_priv { + struct mipi_dsi_host host; + struct mipi_dsi_device device; + + struct udevice *panel; + struct display_timing timing; + + struct gpio_desc power_gpio; + struct gpio_desc reset_gpio; + + struct clk *tx_clk; + + u32 pll_freq_kbps; /* PLL in kbps */ +}; + +static int ssd2825_spi_write(struct udevice *dev, int reg, + const void *buf, int flags) +{ + u8 command[2]; + + if (flags & SSD2825_CMD_SEND) { + command[0] = SSD2825_CMD_MASK; + command[1] = reg; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + } + + if (flags & SSD2825_DAT_SEND) { + u16 data = *(u16 *)buf; + u8 cmd1, cmd2; + + /* send low byte first and then high byte */ + cmd1 = (data & 0x00FF); + cmd2 = (data & 0xFF00) >> 8; + + command[0] = SSD2825_DAT_MASK; + command[1] = cmd1; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + + command[0] = SSD2825_DAT_MASK; + command[1] = cmd2; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + } + + if (flags & SSD2825_DSI_SEND) { + u16 data = *(u16 *)buf; + data &= 0x00FF; + + debug("%s: dsi command (0x%x)\n", + __func__, data); + + command[0] = SSD2825_DAT_MASK; + command[1] = data; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + } + + return 0; +} + +static int ssd2825_spi_read(struct udevice *dev, int reg, + void *data, int flags) +{ + u8 command[2]; + + command[0] = SSD2825_CMD_MASK; + command[1] = SSD2825_SPI_READ_REG; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + + command[0] = SSD2825_DAT_MASK; + command[1] = SSD2825_SPI_READ_REG_RESET; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + + command[0] = SSD2825_DAT_MASK; + command[1] = 0; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + + command[0] = SSD2825_CMD_MASK; + command[1] = reg; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + + command[0] = SSD2825_CMD_MASK; + command[1] = SSD2825_SPI_READ_REG_RESET; + dm_spi_xfer(dev, 9, &command, + NULL, SPI_XFER_ONCE); + + dm_spi_xfer(dev, 16, NULL, + (u8 *)data, SPI_XFER_ONCE); + + return 0; +} + +static void ssd2825_write_register(struct udevice *dev, u8 reg, + u16 command) +{ + ssd2825_spi_write(dev, reg, &command, + SSD2825_CMD_SEND | + SSD2825_DAT_SEND); +} + +static void ssd2825_write_dsi(struct udevice *dev, const u8 *command, + int len) +{ + int i; + + ssd2825_spi_write(dev, SSD2825_PACKET_SIZE_CTRL_REG_1, &len, + SSD2825_CMD_SEND | SSD2825_DAT_SEND); + + ssd2825_spi_write(dev, SSD2825_PACKET_DROP_REG, NULL, + SSD2825_CMD_SEND); + + for (i = 0; i < len; i++) + ssd2825_spi_write(dev, 0, &command[i], SSD2825_DSI_SEND); +} + +static ssize_t ssd2825_bridge_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct udevice *dev = (struct udevice *)host->dev; + u8 buf = *(u8 *)msg->tx_buf; + u16 config; + int ret; + + ret = ssd2825_spi_read(dev, SSD2825_CONFIGURATION_REG, + &config, 0); + if (ret) + return ret; + + switch (msg->type) { + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + case MIPI_DSI_DCS_LONG_WRITE: + config |= SSD2825_CONF_REG_DCS; + break; + case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM: + case MIPI_DSI_GENERIC_LONG_WRITE: + config &= ~SSD2825_CONF_REG_DCS; + break; + default: + return 0; + } + + ssd2825_write_register(dev, SSD2825_CONFIGURATION_REG, config); + ssd2825_write_register(dev, SSD2825_VC_CTRL_REG, 0x0000); + ssd2825_write_dsi(dev, msg->tx_buf, msg->tx_len); + + if (buf == MIPI_DCS_SET_DISPLAY_ON) { + ssd2825_write_register(dev, SSD2825_CONFIGURATION_REG, + SSD2825_CONF_REG_HS | SSD2825_CONF_REG_VEN | + SSD2825_CONF_REG_DCS | SSD2825_CONF_REG_ECD | + SSD2825_CONF_REG_EOT); + ssd2825_write_register(dev, SSD2825_PLL_CTRL_REG, 0x0001); + ssd2825_write_register(dev, SSD2825_VC_CTRL_REG, 0x0000); + } + + return 0; +} + +static const struct mipi_dsi_host_ops ssd2825_bridge_host_ops = { + .transfer = ssd2825_bridge_transfer, +}; + +/* + * PLL configuration register settings. + * + * See the "PLL Configuration Register Description" in the SSD2825 datasheet. + */ +static u16 construct_pll_config(struct ssd2825_bridge_priv *priv, + u32 desired_pll_freq_kbps, u32 reference_freq_khz) +{ + u32 div_factor = 1, mul_factor, fr = 0; + + while (reference_freq_khz / (div_factor + 1) >= SSD2825_REF_MIN_CLK) + div_factor++; + if (div_factor > 31) + div_factor = 31; + + mul_factor = DIV_ROUND_UP(desired_pll_freq_kbps * div_factor, + reference_freq_khz); + + priv->pll_freq_kbps = reference_freq_khz * mul_factor / div_factor; + + if (priv->pll_freq_kbps >= 501000) + fr = 3; + else if (priv->pll_freq_kbps >= 251000) + fr = 2; + else if (priv->pll_freq_kbps >= 126000) + fr = 1; + + return (fr << 14) | (div_factor << 8) | mul_factor; +} + +static void ssd2825_setup_pll(struct udevice *dev) +{ + struct ssd2825_bridge_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct display_timing *dt = &priv->timing; + u16 pll_config, lp_div; + u32 pclk_mult, tx_freq_khz, pd_lines; + + tx_freq_khz = clk_get_rate(priv->tx_clk) / 1000; + pd_lines = mipi_dsi_pixel_format_to_bpp(device->format); + pclk_mult = pd_lines / device->lanes + 1; + + pll_config = construct_pll_config(priv, pclk_mult * + dt->pixelclock.typ / 1000, + tx_freq_khz); + + lp_div = priv->pll_freq_kbps / (SSD2825_LP_MIN_CLK * 8); + + /* Disable PLL */ + ssd2825_write_register(dev, SSD2825_PLL_CTRL_REG, 0x0000); + ssd2825_write_register(dev, SSD2825_LINE_CTRL_REG, 0x0001); + + /* Set delays */ + ssd2825_write_register(dev, SSD2825_DELAY_ADJ_REG_1, 0x2103); + + /* Set PLL coeficients */ + ssd2825_write_register(dev, SSD2825_PLL_CONFIGURATION_REG, pll_config); + + /* Clock Control Register */ + ssd2825_write_register(dev, SSD2825_CLOCK_CTRL_REG, + SSD2828_LP_CLOCK_DIVIDER(lp_div)); + + /* Enable PLL */ + ssd2825_write_register(dev, SSD2825_PLL_CTRL_REG, 0x0001); + ssd2825_write_register(dev, SSD2825_VC_CTRL_REG, 0x0000); +} + +static int ssd2825_bridge_enable_panel(struct udevice *dev) +{ + struct ssd2825_bridge_priv *priv = dev_get_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct display_timing *dt = &priv->timing; + int ret; + + ret = clk_prepare_enable(priv->tx_clk); + if (ret) { + log_err("error enabling tx_clk (%d)\n", ret); + return ret; + } + + ret = dm_gpio_set_value(&priv->power_gpio, 1); + if (ret) { + log_err("error changing power-gpios (%d)\n", ret); + return ret; + } + mdelay(10); + + ret = dm_gpio_set_value(&priv->reset_gpio, 0); + if (ret) { + log_err("error changing reset-gpios (%d)\n", ret); + return ret; + } + mdelay(10); + + ret = dm_gpio_set_value(&priv->reset_gpio, 1); + if (ret) { + log_err("error changing reset-gpios (%d)\n", ret); + return ret; + } + mdelay(10); + + /* Perform panel HW setup */ + ret = panel_enable_backlight(priv->panel); + if (ret) + return ret; + + /* Perform SW reset */ + ssd2825_write_register(dev, SSD2825_OPERATION_CTRL_REG, 0x0100); + + /* Set panel timings */ + ssd2825_write_register(dev, SSD2825_RGB_INTERFACE_CTRL_REG_1, + dt->vsync_len.typ << 8 | dt->hsync_len.typ); + ssd2825_write_register(dev, SSD2825_RGB_INTERFACE_CTRL_REG_2, + (dt->vsync_len.typ + dt->vback_porch.typ) << 8 | + (dt->hsync_len.typ + dt->hback_porch.typ)); + ssd2825_write_register(dev, SSD2825_RGB_INTERFACE_CTRL_REG_3, + dt->vfront_porch.typ << 8 | dt->hfront_porch.typ); + ssd2825_write_register(dev, SSD2825_RGB_INTERFACE_CTRL_REG_4, + dt->hactive.typ); + ssd2825_write_register(dev, SSD2825_RGB_INTERFACE_CTRL_REG_5, + dt->vactive.typ); + ssd2825_write_register(dev, SSD2825_RGB_INTERFACE_CTRL_REG_6, + SSD2825_HSYNC_HIGH | SSD2825_VSYNC_HIGH | + SSD2825_PCKL_HIGH | SSD2825_NON_BURST | + (3 - device->format)); + ssd2825_write_register(dev, SSD2825_LANE_CONFIGURATION_REG, + device->lanes - 1); + ssd2825_write_register(dev, SSD2825_TEST_REG, 0x0004); + + /* Call PLL configuration */ + ssd2825_setup_pll(dev); + + mdelay(10); + + /* Initial DSI configuration register set */ + ssd2825_write_register(dev, SSD2825_CONFIGURATION_REG, + SSD2825_CONF_REG_CKE | SSD2825_CONF_REG_DCS | + SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT); + ssd2825_write_register(dev, SSD2825_VC_CTRL_REG, 0x0000); + + /* Set up SW panel configuration */ + ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT); + if (ret) + return ret; + + return 0; +} + +static int ssd2825_bridge_set_panel(struct udevice *dev, int percent) +{ + return 0; +} + +static int ssd2825_bridge_panel_timings(struct udevice *dev, + struct display_timing *timing) +{ + struct ssd2825_bridge_priv *priv = dev_get_priv(dev); + + memcpy(timing, &priv->timing, sizeof(*timing)); + + return 0; +} + +static int ssd2825_bridge_probe(struct udevice *dev) +{ + struct ssd2825_bridge_priv *priv = dev_get_priv(dev); + struct spi_slave *slave = dev_get_parent_priv(dev); + struct mipi_dsi_device *device = &priv->device; + struct mipi_dsi_panel_plat *mipi_plat; + int ret; + + ret = spi_claim_bus(slave); + if (ret) { + log_err("SPI bus allocation failed (%d)\n", ret); + return ret; + } + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, + "panel", &priv->panel); + if (ret) { + log_err("cannot get panel: ret=%d\n", ret); + return ret; + } + + panel_get_display_timing(priv->panel, &priv->timing); + + mipi_plat = dev_get_plat(priv->panel); + mipi_plat->device = device; + + priv->host.dev = (struct device *)dev; + priv->host.ops = &ssd2825_bridge_host_ops; + + device->host = &priv->host; + device->lanes = mipi_plat->lanes; + device->format = mipi_plat->format; + device->mode_flags = mipi_plat->mode_flags; + + /* get panel gpios */ + ret = gpio_request_by_name(dev, "power-gpios", 0, + &priv->power_gpio, GPIOD_IS_OUT); + if (ret) { + log_err("could not decode power-gpios (%d)\n", ret); + return ret; + } + + ret = gpio_request_by_name(dev, "reset-gpios", 0, + &priv->reset_gpio, GPIOD_IS_OUT); + if (ret) { + log_err("could not decode reset-gpios (%d)\n", ret); + return ret; + } + + /* get clk */ + priv->tx_clk = devm_clk_get(dev, "tx_clk"); + if (IS_ERR(priv->tx_clk)) { + log_err("cannot get tx_clk: %ld\n", PTR_ERR(priv->tx_clk)); + return PTR_ERR(priv->tx_clk); + } + + return 0; +} + +static const struct panel_ops ssd2825_bridge_ops = { + .enable_backlight = ssd2825_bridge_enable_panel, + .set_backlight = ssd2825_bridge_set_panel, + .get_display_timing = ssd2825_bridge_panel_timings, +}; + +static const struct udevice_id ssd2825_bridge_ids[] = { + { .compatible = "solomon,ssd2825" }, + { } +}; + +U_BOOT_DRIVER(ssd2825) = { + .name = "ssd2825", + .id = UCLASS_PANEL, + .of_match = ssd2825_bridge_ids, + .ops = &ssd2825_bridge_ops, + .probe = ssd2825_bridge_probe, + .priv_auto = sizeof(struct ssd2825_bridge_priv), +};