aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-04-21 08:54:20 -0600
committerTom Rini <trini@konsulko.com>2024-04-21 08:54:20 -0600
commit1dd659fd626204bb6a6b4f330c27b11a7823bbb0 (patch)
tree44a012a0d9503033a6eca2e158b7ad4273644013
parentc08685289171e68afd4bae2eb2e279cdc49a407d (diff)
parentefe1ceec7ef0c2ce2344dbe066fca0d389a0b4f3 (diff)
downloadu-boot-WIP/21Apr2024.zip
u-boot-WIP/21Apr2024.tar.gz
u-boot-WIP/21Apr2024.tar.bz2
Merge tag 'video-20240421' of https://source.denx.de/u-boot/custodians/u-boot-videoWIP/21Apr2024
CI: https://source.denx.de/u-boot/custodians/u-boot-video/-/pipelines/20466 - simple_panel: support timing parsing from EDID - dw_hdmi: fix gcc-14 compiler warnings - dw_hdmi: support vendor PHY for HDMI - rockchip: add Rockchip INNO HDMI PHY driver - rockchip: RK3328 HDMI and VOP support - evb-rk3328: enable vidconsole support - Tegra DC and DSI improvements and Tegra 114 support - add LG LG070WX3 MIPI DSI panel driver - add Samsung LTL106HL02 MIPI DSI panel driver - add Toshiba TC358768 RGB to DSI bridge support - add basic support for the Parade DP501 transmitter - Tegra 3 panel and bridge driver improvements - simplefb: modernise DT parsing - fdt_simplefb: Enumerate framebuffer info from video handoff - preserve framebuffer if SPL is passing video hand-off - fdt_support: allow reserving FB region without simplefb
-rw-r--r--arch/arm/dts/rk3328-u-boot.dtsi4
-rw-r--r--arch/arm/dts/tegra114-u-boot.dtsi13
-rw-r--r--arch/arm/dts/tegra114.dtsi4
-rw-r--r--arch/arm/dts/tegra30-u-boot.dtsi4
-rw-r--r--arch/arm/dts/tegra30.dtsi2
-rw-r--r--arch/arm/include/asm/arch-rockchip/cru_rk3328.h34
-rw-r--r--arch/arm/include/asm/arch-tegra/dc.h13
-rw-r--r--arch/arm/include/asm/arch-tegra114/pwm.h13
-rw-r--r--arch/arm/include/asm/arch-tegra20/display.h28
-rw-r--r--arch/arm/include/asm/arch-tegra30/display.h28
-rw-r--r--arch/arm/mach-rockchip/Kconfig1
-rw-r--r--boot/fdt_simplefb.c46
-rw-r--r--boot/fdt_support.c21
-rw-r--r--common/Kconfig2
-rw-r--r--configs/roc-cc-rk3328_defconfig5
-rw-r--r--drivers/clk/rockchip/clk_rk3328.c105
-rw-r--r--drivers/phy/rockchip/Kconfig7
-rw-r--r--drivers/phy/rockchip/Makefile1
-rw-r--r--drivers/phy/rockchip/phy-rockchip-inno-hdmi.c885
-rw-r--r--drivers/video/Kconfig17
-rw-r--r--drivers/video/Makefile2
-rw-r--r--drivers/video/bridge/Kconfig19
-rw-r--r--drivers/video/bridge/Makefile2
-rw-r--r--drivers/video/bridge/dp501.c579
-rw-r--r--drivers/video/bridge/ssd2825.c86
-rw-r--r--drivers/video/bridge/tc358768.c985
-rw-r--r--drivers/video/dw_hdmi.c37
-rw-r--r--drivers/video/endeavoru-panel.c128
-rw-r--r--drivers/video/lg-ld070wx3.c186
-rw-r--r--drivers/video/meson/meson_dw_hdmi.c6
-rw-r--r--drivers/video/renesas-r61307.c93
-rw-r--r--drivers/video/renesas-r69328.c81
-rw-r--r--drivers/video/rockchip/Makefile2
-rw-r--r--drivers/video/rockchip/rk3328_hdmi.c126
-rw-r--r--drivers/video/rockchip/rk3328_vop.c83
-rw-r--r--drivers/video/rockchip/rk_hdmi.c11
-rw-r--r--drivers/video/rockchip/rk_hdmi.h3
-rw-r--r--drivers/video/rockchip/rk_vop.c44
-rw-r--r--drivers/video/rockchip/rk_vop.h4
-rw-r--r--drivers/video/samsung-ltl106hl02.c157
-rw-r--r--drivers/video/simple_panel.c102
-rw-r--r--drivers/video/simplefb.c32
-rw-r--r--drivers/video/sunxi/sunxi_dw_hdmi.c14
-rw-r--r--drivers/video/tegra20/Makefile2
-rw-r--r--drivers/video/tegra20/tegra-dc.c222
-rw-r--r--drivers/video/tegra20/tegra-dc.h45
-rw-r--r--drivers/video/tegra20/tegra-dsi.c122
-rw-r--r--drivers/video/tegra20/tegra-dsi.h (renamed from arch/arm/include/asm/arch-tegra30/dsi.h)24
-rw-r--r--drivers/video/tegra20/tegra-mipi.c188
-rw-r--r--drivers/video/tegra20/tegra-pwm-backlight.c3
-rw-r--r--drivers/video/video-uclass.c4
-rw-r--r--include/configs/evb_rk3328.h5
-rw-r--r--include/configs/rk3328_common.h1
-rw-r--r--include/dw_hdmi.h11
-rw-r--r--include/fdt_support.h2
55 files changed, 4219 insertions, 425 deletions
diff --git a/arch/arm/dts/rk3328-u-boot.dtsi b/arch/arm/dts/rk3328-u-boot.dtsi
index e0c6aee..7c5067c 100644
--- a/arch/arm/dts/rk3328-u-boot.dtsi
+++ b/arch/arm/dts/rk3328-u-boot.dtsi
@@ -130,6 +130,10 @@
bootph-all;
};
+&vop {
+ bootph-all;
+};
+
#ifdef CONFIG_ROCKCHIP_SPI_IMAGE
&binman {
simple-bin-spi {
diff --git a/arch/arm/dts/tegra114-u-boot.dtsi b/arch/arm/dts/tegra114-u-boot.dtsi
index 7c11972..6a02714 100644
--- a/arch/arm/dts/tegra114-u-boot.dtsi
+++ b/arch/arm/dts/tegra114-u-boot.dtsi
@@ -1,3 +1,16 @@
#include <config.h>
#include "tegra-u-boot.dtsi"
+
+/ {
+ host1x@50000000 {
+ bootph-all;
+ dc@54200000 {
+ bootph-all;
+ };
+
+ dc@54240000 {
+ bootph-all;
+ };
+ };
+};
diff --git a/arch/arm/dts/tegra114.dtsi b/arch/arm/dts/tegra114.dtsi
index 68ee7f3..250d692 100644
--- a/arch/arm/dts/tegra114.dtsi
+++ b/arch/arm/dts/tegra114.dtsi
@@ -42,7 +42,7 @@
};
dc@54200000 {
- compatible = "nvidia,tegra114-dc", "nvidia,tegra20-dc";
+ compatible = "nvidia,tegra114-dc";
reg = <0x54200000 0x00040000>;
interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA114_CLK_DISP1>,
@@ -61,7 +61,7 @@
};
dc@54240000 {
- compatible = "nvidia,tegra114-dc", "nvidia,tegra20-dc";
+ compatible = "nvidia,tegra114-dc";
reg = <0x54240000 0x00040000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA114_CLK_DISP2>,
diff --git a/arch/arm/dts/tegra30-u-boot.dtsi b/arch/arm/dts/tegra30-u-boot.dtsi
index 3038227..6a02714 100644
--- a/arch/arm/dts/tegra30-u-boot.dtsi
+++ b/arch/arm/dts/tegra30-u-boot.dtsi
@@ -8,5 +8,9 @@
dc@54200000 {
bootph-all;
};
+
+ dc@54240000 {
+ bootph-all;
+ };
};
};
diff --git a/arch/arm/dts/tegra30.dtsi b/arch/arm/dts/tegra30.dtsi
index f198bc0..1177e2a 100644
--- a/arch/arm/dts/tegra30.dtsi
+++ b/arch/arm/dts/tegra30.dtsi
@@ -158,7 +158,7 @@
};
dc@54200000 {
- compatible = "nvidia,tegra30-dc", "nvidia,tegra20-dc";
+ compatible = "nvidia,tegra30-dc";
reg = <0x54200000 0x00040000>;
interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&tegra_car TEGRA30_CLK_DISP1>,
diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3328.h b/arch/arm/include/asm/arch-rockchip/cru_rk3328.h
index 226744d..4ad1d33 100644
--- a/arch/arm/include/asm/arch-rockchip/cru_rk3328.h
+++ b/arch/arm/include/asm/arch-rockchip/cru_rk3328.h
@@ -62,6 +62,40 @@ check_member(rk3328_cru, sdmmc_ext_con[1], 0x39c);
enum apll_frequencies {
APLL_816_MHZ,
APLL_600_MHZ,
+
+ /* CRU_CLK_SEL37_CON */
+ ACLK_VIO_PLL_SEL_CPLL = 0,
+ ACLK_VIO_PLL_SEL_GPLL = 1,
+ ACLK_VIO_PLL_SEL_HDMIPHY = 2,
+ ACLK_VIO_PLL_SEL_USB480M = 3,
+ ACLK_VIO_PLL_SEL_SHIFT = 6,
+ ACLK_VIO_PLL_SEL_MASK = 3 << ACLK_VIO_PLL_SEL_SHIFT,
+ ACLK_VIO_DIV_CON_SHIFT = 0,
+ ACLK_VIO_DIV_CON_MASK = 0x1f << ACLK_VIO_DIV_CON_SHIFT,
+ HCLK_VIO_DIV_CON_SHIFT = 8,
+ HCLK_VIO_DIV_CON_MASK = 0x1f << HCLK_VIO_DIV_CON_SHIFT,
+
+ /* CRU_CLK_SEL39_CON */
+ ACLK_VOP_PLL_SEL_CPLL = 0,
+ ACLK_VOP_PLL_SEL_GPLL = 1,
+ ACLK_VOP_PLL_SEL_HDMIPHY = 2,
+ ACLK_VOP_PLL_SEL_USB480M = 3,
+ ACLK_VOP_PLL_SEL_SHIFT = 6,
+ ACLK_VOP_PLL_SEL_MASK = 3 << ACLK_VOP_PLL_SEL_SHIFT,
+ ACLK_VOP_DIV_CON_SHIFT = 0,
+ ACLK_VOP_DIV_CON_MASK = 0x1f << ACLK_VOP_DIV_CON_SHIFT,
+
+ /* CRU_CLK_SEL40_CON */
+ DCLK_LCDC_PLL_SEL_GPLL = 0,
+ DCLK_LCDC_PLL_SEL_CPLL = 1,
+ DCLK_LCDC_PLL_SEL_SHIFT = 0,
+ DCLK_LCDC_PLL_SEL_MASK = 1 << DCLK_LCDC_PLL_SEL_SHIFT,
+ DCLK_LCDC_SEL_HDMIPHY = 0,
+ DCLK_LCDC_SEL_PLL = 1,
+ DCLK_LCDC_SEL_SHIFT = 1,
+ DCLK_LCDC_SEL_MASK = 1 << DCLK_LCDC_SEL_SHIFT,
+ DCLK_LCDC_DIV_CON_SHIFT = 8,
+ DCLK_LCDC_DIV_CON_MASK = 0xFf << DCLK_LCDC_DIV_CON_SHIFT,
};
void rk3328_configure_cpu(struct rk3328_cru *cru,
diff --git a/arch/arm/include/asm/arch-tegra/dc.h b/arch/arm/include/asm/arch-tegra/dc.h
index 7613d84..ca37184 100644
--- a/arch/arm/include/asm/arch-tegra/dc.h
+++ b/arch/arm/include/asm/arch-tegra/dc.h
@@ -443,6 +443,11 @@ enum win_color_depth_id {
#define WINDOW_D_SELECT BIT(7)
#define WINDOW_H_SELECT BIT(8)
+/* DC_COM_PIN_OUTPUT_POLARITY1 0x307 */
+#define LHS_OUTPUT_POLARITY_LOW BIT(30)
+#define LVS_OUTPUT_POLARITY_LOW BIT(28)
+#define LSC0_OUTPUT_POLARITY_LOW BIT(24)
+
/* DC_DISP_DISP_WIN_OPTIONS 0x402 */
#define CURSOR_ENABLE BIT(16)
#define SOR_ENABLE BIT(25)
@@ -569,12 +574,4 @@ enum {
#define DC_N_WINDOWS 5
#define DC_REG_SAVE_SPACE (DC_N_WINDOWS + 5)
-#define TEGRA_DSI_A "dsi@54300000"
-#define TEGRA_DSI_B "dsi@54400000"
-
-struct tegra_dc_plat {
- struct udevice *dev; /* Display controller device */
- struct dc_ctlr *dc; /* Display controller regmap */
-};
-
#endif /* __ASM_ARCH_TEGRA_DC_H */
diff --git a/arch/arm/include/asm/arch-tegra114/pwm.h b/arch/arm/include/asm/arch-tegra114/pwm.h
new file mode 100644
index 0000000..af39151
--- /dev/null
+++ b/arch/arm/include/asm/arch-tegra114/pwm.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Tegra pulse width frequency modulator definitions
+ *
+ * Copyright (c) 2011 The Chromium OS Authors.
+ */
+
+#ifndef __ASM_ARCH_TEGRA114_PWM_H
+#define __ASM_ARCH_TEGRA114_PWM_H
+
+#include <asm/arch-tegra/pwm.h>
+
+#endif /* __ASM_ARCH_TEGRA114_PWM_H */
diff --git a/arch/arm/include/asm/arch-tegra20/display.h b/arch/arm/include/asm/arch-tegra20/display.h
deleted file mode 100644
index e7b3cff..0000000
--- a/arch/arm/include/asm/arch-tegra20/display.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * (C) Copyright 2010
- * NVIDIA Corporation <www.nvidia.com>
- */
-
-#ifndef __ASM_ARCH_TEGRA_DISPLAY_H
-#define __ASM_ARCH_TEGRA_DISPLAY_H
-
-#include <asm/arch-tegra/dc.h>
-
-/* This holds information about a window which can be displayed */
-struct disp_ctl_win {
- enum win_color_depth_id fmt; /* Color depth/format */
- unsigned bpp; /* Bits per pixel */
- phys_addr_t phys_addr; /* Physical address in memory */
- unsigned x; /* Horizontal address offset (bytes) */
- unsigned y; /* Veritical address offset (bytes) */
- unsigned w; /* Width of source window */
- unsigned h; /* Height of source window */
- unsigned stride; /* Number of bytes per line */
- unsigned out_x; /* Left edge of output window (col) */
- unsigned out_y; /* Top edge of output window (row) */
- unsigned out_w; /* Width of output window in pixels */
- unsigned out_h; /* Height of output window in pixels */
-};
-
-#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/
diff --git a/arch/arm/include/asm/arch-tegra30/display.h b/arch/arm/include/asm/arch-tegra30/display.h
deleted file mode 100644
index 9411525..0000000
--- a/arch/arm/include/asm/arch-tegra30/display.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * (C) Copyright 2010
- * NVIDIA Corporation <www.nvidia.com>
- */
-
-#ifndef __ASM_ARCH_TEGRA_DISPLAY_H
-#define __ASM_ARCH_TEGRA_DISPLAY_H
-
-#include <asm/arch-tegra/dc.h>
-
-/* This holds information about a window which can be displayed */
-struct disp_ctl_win {
- enum win_color_depth_id fmt; /* Color depth/format */
- unsigned int bpp; /* Bits per pixel */
- phys_addr_t phys_addr; /* Physical address in memory */
- unsigned int x; /* Horizontal address offset (bytes) */
- unsigned int y; /* Veritical address offset (bytes) */
- unsigned int w; /* Width of source window */
- unsigned int h; /* Height of source window */
- unsigned int stride; /* Number of bytes per line */
- unsigned int out_x; /* Left edge of output window (col) */
- unsigned int out_y; /* Top edge of output window (row) */
- unsigned int out_w; /* Width of output window in pixels */
- unsigned int out_h; /* Height of output window in pixels */
-};
-
-#endif /*__ASM_ARCH_TEGRA_DISPLAY_H*/
diff --git a/arch/arm/mach-rockchip/Kconfig b/arch/arm/mach-rockchip/Kconfig
index f68a0a4..4f22d9bde 100644
--- a/arch/arm/mach-rockchip/Kconfig
+++ b/arch/arm/mach-rockchip/Kconfig
@@ -180,6 +180,7 @@ config ROCKCHIP_RK3328
select SUPPORT_TPL
select TPL
select TPL_NEEDS_SEPARATE_STACK if TPL
+ imply PRE_CONSOLE_BUFFER
imply ROCKCHIP_COMMON_BOARD
imply ROCKCHIP_SDRAM_COMMON
imply SPL_ROCKCHIP_COMMON_BOARD
diff --git a/boot/fdt_simplefb.c b/boot/fdt_simplefb.c
index 069ced7..837920b 100644
--- a/boot/fdt_simplefb.c
+++ b/boot/fdt_simplefb.c
@@ -12,6 +12,8 @@
#include <asm/global_data.h>
#include <linux/libfdt.h>
#include <video.h>
+#include <spl.h>
+#include <bloblist.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -26,15 +28,29 @@ static int fdt_simplefb_configure_node(void *blob, int off)
struct udevice *dev;
int ret;
- ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
- if (ret)
- return ret;
- uc_priv = dev_get_uclass_priv(dev);
- plat = dev_get_uclass_plat(dev);
- xsize = uc_priv->xsize;
- ysize = uc_priv->ysize;
- bpix = uc_priv->bpix;
- fb_base = plat->base;
+ if (IS_ENABLED(CONFIG_SPL_VIDEO_HANDOFF) && spl_phase() > PHASE_SPL) {
+ struct video_handoff *ho;
+
+ ho = bloblist_find(BLOBLISTT_U_BOOT_VIDEO, sizeof(*ho));
+ if (!ho)
+ return log_msg_ret("Missing video bloblist", -ENOENT);
+
+ xsize = ho->xsize;
+ ysize = ho->ysize;
+ bpix = ho->bpix;
+ fb_base = ho->fb;
+ } else {
+ ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
+ if (ret)
+ return ret;
+ uc_priv = dev_get_uclass_priv(dev);
+ plat = dev_get_uclass_plat(dev);
+ xsize = uc_priv->xsize;
+ ysize = uc_priv->ysize;
+ bpix = uc_priv->bpix;
+ fb_base = plat->base;
+ }
+
switch (bpix) {
case 4: /* VIDEO_BPP16 */
name = "r5g6b5";
@@ -91,7 +107,6 @@ static int fdt_simplefb_enable_existing_node(void *blob)
#if IS_ENABLED(CONFIG_VIDEO)
int fdt_simplefb_enable_and_mem_rsv(void *blob)
{
- struct fdt_memory mem;
int ret;
/* nothing to do when video is not active */
@@ -102,15 +117,6 @@ int fdt_simplefb_enable_and_mem_rsv(void *blob)
if (ret)
return ret;
- /* nothing to do when the frame buffer is not defined */
- if (gd->video_bottom == gd->video_top)
- return 0;
-
- /* reserved with no-map tag the video buffer */
- mem.start = gd->video_bottom;
- mem.end = gd->video_top - 1;
-
- return fdtdec_add_reserved_memory(blob, "framebuffer", &mem, NULL, 0, NULL,
- FDTDEC_RESERVED_MEMORY_NO_MAP);
+ return fdt_add_fb_mem_rsv(blob);
}
#endif
diff --git a/boot/fdt_support.c b/boot/fdt_support.c
index 9844c70..2bd80a9 100644
--- a/boot/fdt_support.c
+++ b/boot/fdt_support.c
@@ -23,6 +23,9 @@
#include <exports.h>
#include <fdtdec.h>
#include <version.h>
+#include <video.h>
+
+DECLARE_GLOBAL_DATA_PTR;
/**
* fdt_getprop_u32_default_node - Return a node's property or a default
@@ -2043,6 +2046,24 @@ int fdt_setup_simplefb_node(void *fdt, int node, u64 base_address, u32 width,
return 0;
}
+#if CONFIG_IS_ENABLED(VIDEO)
+int fdt_add_fb_mem_rsv(void *blob)
+{
+ struct fdt_memory mem;
+
+ /* nothing to do when the frame buffer is not defined */
+ if (gd->video_bottom == gd->video_top)
+ return 0;
+
+ /* reserved with no-map tag the video buffer */
+ mem.start = gd->video_bottom;
+ mem.end = gd->video_top - 1;
+
+ return fdtdec_add_reserved_memory(blob, "framebuffer", &mem, NULL, 0, NULL,
+ FDTDEC_RESERVED_MEMORY_NO_MAP);
+}
+#endif
+
/*
* Update native-mode in display-timings from display environment variable.
* The node to update are specified by path.
diff --git a/common/Kconfig b/common/Kconfig
index 0283701..5e3070e 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -231,7 +231,7 @@ config PRE_CON_BUF_ADDR
default 0x2f000000 if ARCH_SUNXI && MACH_SUN9I
default 0x4f000000 if ARCH_SUNXI && !MACH_SUN9I
default 0x0f000000 if ROCKCHIP_RK3288
- default 0x0f200000 if ROCKCHIP_RK3399
+ default 0x0f200000 if ROCKCHIP_RK3399 || ROCKCHIP_RK3328
help
This sets the start address of the pre-console buffer. This must
be in available memory and is accessed before relocation and
diff --git a/configs/roc-cc-rk3328_defconfig b/configs/roc-cc-rk3328_defconfig
index 6526d26..1dbd39e 100644
--- a/configs/roc-cc-rk3328_defconfig
+++ b/configs/roc-cc-rk3328_defconfig
@@ -62,6 +62,7 @@ CONFIG_DM_ETH_PHY=y
CONFIG_PHY_GIGE=y
CONFIG_ETH_DESIGNWARE=y
CONFIG_GMAC_ROCKCHIP=y
+CONFIG_PHY_ROCKCHIP_INNO_HDMI=y
CONFIG_PHY_ROCKCHIP_INNO_USB2=y
CONFIG_PINCTRL=y
CONFIG_SPL_PINCTRL=y
@@ -94,6 +95,10 @@ CONFIG_USB_OHCI_GENERIC=y
CONFIG_USB_DWC2=y
CONFIG_USB_DWC3=y
CONFIG_USB_DWC3_GENERIC=y
+CONFIG_VIDEO=y
+CONFIG_DISPLAY=y
+CONFIG_VIDEO_ROCKCHIP=y
+CONFIG_DISPLAY_ROCKCHIP_HDMI=y
CONFIG_SPL_TINY_MEMSET=y
CONFIG_TPL_TINY_MEMSET=y
CONFIG_ERRNO_STR=y
diff --git a/drivers/clk/rockchip/clk_rk3328.c b/drivers/clk/rockchip/clk_rk3328.c
index cfec1d9..87075ec 100644
--- a/drivers/clk/rockchip/clk_rk3328.c
+++ b/drivers/clk/rockchip/clk_rk3328.c
@@ -178,6 +178,10 @@ enum {
CLK_I2C3_DIV_CON_SHIFT = 8,
CLK_I2C2_PLL_SEL_SHIFT = 7,
CLK_I2C2_DIV_CON_SHIFT = 0,
+
+ /* CLKSEL_CON40 */
+ CLK_HDMIPHY_DIV_CON_SHIFT = 3,
+ CLK_HDMIPHY_DIV_CON_MASK = 0x7 << CLK_HDMIPHY_DIV_CON_SHIFT,
};
#define VCO_MAX_KHZ (3200 * (MHz / KHz))
@@ -580,6 +584,96 @@ static ulong rk3328_spi_set_clk(struct rk3328_cru *cru, uint hz)
return rk3328_spi_get_clk(cru);
}
+#ifndef CONFIG_SPL_BUILD
+static ulong rk3328_vop_get_clk(struct rk3328_clk_priv *priv, ulong clk_id)
+{
+ struct rk3328_cru *cru = priv->cru;
+ u32 div, con, parent;
+
+ switch (clk_id) {
+ case ACLK_VOP_PRE:
+ con = readl(&cru->clksel_con[39]);
+ div = (con & ACLK_VOP_DIV_CON_MASK) >> ACLK_VOP_DIV_CON_SHIFT;
+ parent = GPLL_HZ;
+ break;
+ case ACLK_VIO_PRE:
+ con = readl(&cru->clksel_con[37]);
+ div = (con & ACLK_VIO_DIV_CON_MASK) >> ACLK_VIO_DIV_CON_SHIFT;
+ parent = GPLL_HZ;
+ break;
+ case DCLK_LCDC:
+ con = readl(&cru->clksel_con[40]);
+ div = (con & DCLK_LCDC_DIV_CON_MASK) >> DCLK_LCDC_DIV_CON_SHIFT;
+ parent = GPLL_HZ;
+ break;
+ default:
+ printf("%s: Unsupported vop get clk#%ld\n", __func__, clk_id);
+ return -ENOENT;
+ }
+
+ return DIV_TO_RATE(parent, div);
+}
+
+static ulong rk3328_vop_set_clk(struct rk3328_clk_priv *priv,
+ ulong clk_id, uint hz)
+{
+ struct rk3328_cru *cru = priv->cru;
+ int src_clk_div;
+ u32 con, parent;
+
+ src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz);
+ assert(src_clk_div - 1 < 31);
+
+ switch (clk_id) {
+ case ACLK_VOP_PRE:
+ rk_clrsetreg(&cru->clksel_con[39],
+ ACLK_VOP_PLL_SEL_MASK | ACLK_VOP_DIV_CON_MASK,
+ ACLK_VOP_PLL_SEL_CPLL << ACLK_VOP_PLL_SEL_SHIFT |
+ (src_clk_div - 1) << ACLK_VOP_DIV_CON_SHIFT);
+ break;
+ case ACLK_VIO_PRE:
+ rk_clrsetreg(&cru->clksel_con[37],
+ ACLK_VIO_PLL_SEL_MASK | ACLK_VIO_DIV_CON_MASK,
+ ACLK_VIO_PLL_SEL_CPLL << ACLK_VIO_PLL_SEL_SHIFT |
+ (src_clk_div - 1) << ACLK_VIO_DIV_CON_SHIFT);
+ break;
+ case DCLK_LCDC:
+ con = readl(&cru->clksel_con[40]);
+ con = (con & DCLK_LCDC_SEL_MASK) >> DCLK_LCDC_SEL_SHIFT;
+ if (con) {
+ parent = readl(&cru->clksel_con[40]);
+ parent = (parent & DCLK_LCDC_PLL_SEL_MASK) >>
+ DCLK_LCDC_PLL_SEL_SHIFT;
+ if (parent)
+ src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz);
+ else
+ src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz);
+
+ rk_clrsetreg(&cru->clksel_con[40],
+ DCLK_LCDC_DIV_CON_MASK,
+ (src_clk_div - 1) <<
+ DCLK_LCDC_DIV_CON_SHIFT);
+ }
+ break;
+ default:
+ printf("%s: Unable to set vop clk#%ld\n", __func__, clk_id);
+ return -EINVAL;
+ }
+
+ return rk3328_vop_get_clk(priv, clk_id);
+}
+#endif
+
+static ulong rk3328_hdmiphy_get_clk(struct rk3328_cru *cru)
+{
+ u32 div, con;
+
+ con = readl(&cru->clksel_con[40]);
+ div = (con & CLK_HDMIPHY_DIV_CON_MASK) >> CLK_HDMIPHY_DIV_CON_SHIFT;
+
+ return DIV_TO_RATE(GPLL_HZ, div);
+}
+
static ulong rk3328_clk_get_rate(struct clk *clk)
{
struct rk3328_clk_priv *priv = dev_get_priv(clk->dev);
@@ -609,6 +703,9 @@ static ulong rk3328_clk_get_rate(struct clk *clk)
case SCLK_SPI:
rate = rk3328_spi_get_clk(priv->cru);
break;
+ case PCLK_HDMIPHY:
+ rate = rk3328_hdmiphy_get_clk(priv->cru);
+ break;
default:
return -ENOENT;
}
@@ -648,7 +745,13 @@ static ulong rk3328_clk_set_rate(struct clk *clk, ulong rate)
case SCLK_SPI:
ret = rk3328_spi_set_clk(priv->cru, rate);
break;
+#ifndef CONFIG_SPL_BUILD
case DCLK_LCDC:
+ case ACLK_VOP_PRE:
+ case ACLK_VIO_PRE:
+ rate = rk3328_vop_set_clk(priv, clk->id, rate);
+ break;
+#endif
case SCLK_PDM:
case SCLK_RTC32K:
case SCLK_UART0:
@@ -663,11 +766,9 @@ static ulong rk3328_clk_set_rate(struct clk *clk, ulong rate)
case ACLK_PERI_PRE:
case HCLK_PERI:
case PCLK_PERI:
- case ACLK_VIO_PRE:
case HCLK_VIO_PRE:
case ACLK_RGA_PRE:
case SCLK_RGA:
- case ACLK_VOP_PRE:
case ACLK_RKVDEC_PRE:
case ACLK_RKVENC:
case ACLK_VPU_PRE:
diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig
index 0247d93..8012833 100644
--- a/drivers/phy/rockchip/Kconfig
+++ b/drivers/phy/rockchip/Kconfig
@@ -12,6 +12,13 @@ config PHY_ROCKCHIP_INNO_DSIDPHY
help
Support for Rockchip MIPI DPHY with Innosilicon IP block.
+config PHY_ROCKCHIP_INNO_HDMI
+ bool "Rockchip INNO HDMI PHY Driver"
+ depends on ARCH_ROCKCHIP
+ select PHY
+ help
+ Enable this to support the Rockchip Innosilicon HDMI PHY.
+
config PHY_ROCKCHIP_INNO_USB2
bool "Rockchip INNO USB2PHY Driver"
depends on ARCH_ROCKCHIP
diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile
index 7fdbd10..0420017 100644
--- a/drivers/phy/rockchip/Makefile
+++ b/drivers/phy/rockchip/Makefile
@@ -3,6 +3,7 @@
# Copyright (C) 2020 Amarula Solutions(India)
#
+obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o
obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o
obj-$(CONFIG_PHY_ROCKCHIP_NANENG_COMBOPHY) += phy-rockchip-naneng-combphy.o
obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
new file mode 100644
index 0000000..3bb1a25
--- /dev/null
+++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c
@@ -0,0 +1,885 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Rockchip Innosilicon HDMI PHY
+ *
+ * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd.
+ * Copyright (c) 2017 Rockchip Electronics Co. Ltd.
+ */
+
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <generic-phy.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+
+#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
+
+/* REG: 0x01 */
+#define RK3328_BYPASS_RXSENSE_EN BIT(2)
+#define RK3328_BYPASS_POWERON_EN BIT(1)
+#define RK3328_BYPASS_PLLPD_EN BIT(0)
+/* REG: 0x02 */
+#define RK3328_INT_POL_HIGH BIT(7)
+#define RK3328_BYPASS_PDATA_EN BIT(4)
+#define RK3328_PDATA_EN BIT(0)
+/* REG:0x05 */
+#define RK3328_INT_TMDS_CLK(x) UPDATE(x, 7, 4)
+#define RK3328_INT_TMDS_D2(x) UPDATE(x, 3, 0)
+/* REG:0x07 */
+#define RK3328_INT_TMDS_D1(x) UPDATE(x, 7, 4)
+#define RK3328_INT_TMDS_D0(x) UPDATE(x, 3, 0)
+/* for all RK3328_INT_TMDS_*, ESD_DET as defined in 0xc8-0xcb */
+#define RK3328_INT_AGND_LOW_PULSE_LOCKED BIT(3)
+#define RK3328_INT_RXSENSE_LOW_PULSE_LOCKED BIT(2)
+#define RK3328_INT_VSS_AGND_ESD_DET BIT(1)
+#define RK3328_INT_AGND_VSS_ESD_DET BIT(0)
+/* REG: 0xa0 */
+#define RK3328_PCLK_VCO_DIV_5_MASK BIT(1)
+#define RK3328_PCLK_VCO_DIV_5(x) UPDATE(x, 1, 1)
+#define RK3328_PRE_PLL_POWER_DOWN BIT(0)
+/* REG: 0xa1 */
+#define RK3328_PRE_PLL_PRE_DIV_MASK GENMASK(5, 0)
+#define RK3328_PRE_PLL_PRE_DIV(x) UPDATE(x, 5, 0)
+/* REG: 0xa2 */
+/* unset means center spread */
+#define RK3328_SPREAD_SPECTRUM_MOD_DOWN BIT(7)
+#define RK3328_SPREAD_SPECTRUM_MOD_DISABLE BIT(6)
+#define RK3328_PRE_PLL_FRAC_DIV_DISABLE UPDATE(3, 5, 4)
+#define RK3328_PRE_PLL_FB_DIV_11_8_MASK GENMASK(3, 0)
+#define RK3328_PRE_PLL_FB_DIV_11_8(x) UPDATE((x) >> 8, 3, 0)
+/* REG: 0xa3 */
+#define RK3328_PRE_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0)
+/* REG: 0xa4*/
+#define RK3328_PRE_PLL_TMDSCLK_DIV_C_MASK GENMASK(1, 0)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_C(x) UPDATE(x, 1, 0)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_B_MASK GENMASK(3, 2)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_B(x) UPDATE(x, 3, 2)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_A_MASK GENMASK(5, 4)
+#define RK3328_PRE_PLL_TMDSCLK_DIV_A(x) UPDATE(x, 5, 4)
+/* REG: 0xa5 */
+#define RK3328_PRE_PLL_PCLK_DIV_B_SHIFT 5
+#define RK3328_PRE_PLL_PCLK_DIV_B_MASK GENMASK(6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_B(x) UPDATE(x, 6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_A_MASK GENMASK(4, 0)
+#define RK3328_PRE_PLL_PCLK_DIV_A(x) UPDATE(x, 4, 0)
+/* REG: 0xa6 */
+#define RK3328_PRE_PLL_PCLK_DIV_C_SHIFT 5
+#define RK3328_PRE_PLL_PCLK_DIV_C_MASK GENMASK(6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_C(x) UPDATE(x, 6, 5)
+#define RK3328_PRE_PLL_PCLK_DIV_D_MASK GENMASK(4, 0)
+#define RK3328_PRE_PLL_PCLK_DIV_D(x) UPDATE(x, 4, 0)
+/* REG: 0xa9 */
+#define RK3328_PRE_PLL_LOCK_STATUS BIT(0)
+/* REG: 0xaa */
+#define RK3328_POST_PLL_POST_DIV_ENABLE GENMASK(3, 2)
+#define RK3328_POST_PLL_REFCLK_SEL_TMDS BIT(1)
+#define RK3328_POST_PLL_POWER_DOWN BIT(0)
+/* REG:0xab */
+#define RK3328_POST_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7)
+#define RK3328_POST_PLL_PRE_DIV(x) UPDATE(x, 4, 0)
+/* REG: 0xac */
+#define RK3328_POST_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0)
+/* REG: 0xad */
+#define RK3328_POST_PLL_POST_DIV_MASK GENMASK(1, 0)
+#define RK3328_POST_PLL_POST_DIV_2 0x0
+#define RK3328_POST_PLL_POST_DIV_4 0x1
+#define RK3328_POST_PLL_POST_DIV_8 0x3
+/* REG: 0xaf */
+#define RK3328_POST_PLL_LOCK_STATUS BIT(0)
+/* REG: 0xb0 */
+#define RK3328_BANDGAP_ENABLE BIT(2)
+/* REG: 0xb2 */
+#define RK3328_TMDS_CLK_DRIVER_EN BIT(3)
+#define RK3328_TMDS_D2_DRIVER_EN BIT(2)
+#define RK3328_TMDS_D1_DRIVER_EN BIT(1)
+#define RK3328_TMDS_D0_DRIVER_EN BIT(0)
+#define RK3328_TMDS_DRIVER_ENABLE (RK3328_TMDS_CLK_DRIVER_EN | \
+ RK3328_TMDS_D2_DRIVER_EN | \
+ RK3328_TMDS_D1_DRIVER_EN | \
+ RK3328_TMDS_D0_DRIVER_EN)
+/* REG:0xc5 */
+#define RK3328_BYPASS_TERM_RESISTOR_CALIB BIT(7)
+#define RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(x) UPDATE((x) >> 8, 6, 0)
+/* REG:0xc6 */
+#define RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(x) UPDATE(x, 7, 0)
+/* REG:0xc7 */
+#define RK3328_TERM_RESISTOR_50 UPDATE(0, 2, 1)
+#define RK3328_TERM_RESISTOR_62_5 UPDATE(1, 2, 1)
+#define RK3328_TERM_RESISTOR_75 UPDATE(2, 2, 1)
+#define RK3328_TERM_RESISTOR_100 UPDATE(3, 2, 1)
+/* REG 0xc8 - 0xcb */
+#define RK3328_ESD_DETECT_MASK GENMASK(7, 6)
+#define RK3328_ESD_DETECT_340MV (0x0 << 6)
+#define RK3328_ESD_DETECT_280MV (0x1 << 6)
+#define RK3328_ESD_DETECT_260MV (0x2 << 6)
+#define RK3328_ESD_DETECT_240MV (0x3 << 6)
+/* resistors can be used in parallel */
+#define RK3328_TMDS_TERM_RESIST_MASK GENMASK(5, 0)
+#define RK3328_TMDS_TERM_RESIST_75 BIT(5)
+#define RK3328_TMDS_TERM_RESIST_150 BIT(4)
+#define RK3328_TMDS_TERM_RESIST_300 BIT(3)
+#define RK3328_TMDS_TERM_RESIST_600 BIT(2)
+#define RK3328_TMDS_TERM_RESIST_1000 BIT(1)
+#define RK3328_TMDS_TERM_RESIST_2000 BIT(0)
+/* REG: 0xd1 */
+#define RK3328_PRE_PLL_FRAC_DIV_23_16(x) UPDATE((x) >> 16, 7, 0)
+/* REG: 0xd2 */
+#define RK3328_PRE_PLL_FRAC_DIV_15_8(x) UPDATE((x) >> 8, 7, 0)
+/* REG: 0xd3 */
+#define RK3328_PRE_PLL_FRAC_DIV_7_0(x) UPDATE(x, 7, 0)
+
+struct phy_config {
+ unsigned long tmdsclock;
+ u8 regs[14];
+};
+
+struct pre_pll_config {
+ unsigned long pixclock;
+ unsigned long tmdsclock;
+ u8 prediv;
+ u16 fbdiv;
+ u8 tmds_div_a;
+ u8 tmds_div_b;
+ u8 tmds_div_c;
+ u8 pclk_div_a;
+ u8 pclk_div_b;
+ u8 pclk_div_c;
+ u8 pclk_div_d;
+ u8 vco_div_5_en;
+ u32 fracdiv;
+};
+
+struct post_pll_config {
+ unsigned long tmdsclock;
+ u8 prediv;
+ u16 fbdiv;
+ u8 postdiv;
+ u8 version;
+};
+
+struct inno_hdmi_phy_plat_ops {
+ void (*init)(struct phy *phy);
+ int (*power_on)(struct phy *phy, const struct post_pll_config *cfg,
+ const struct phy_config *phy_cfg);
+ void (*power_off)(struct phy *phy);
+ void (*clk_enable)(struct phy *phy);
+ void (*clk_disable)(struct phy *phy);
+ unsigned long (*clk_recalc_rate)(struct phy *phy,
+ unsigned long parent_rate);
+ long (*clk_round_rate)(struct phy *phy, unsigned long rate);
+ int (*clk_set_rate)(struct phy *phy, unsigned long rate,
+ unsigned long parent_rate);
+};
+
+enum inno_hdmi_phy_type {
+ INNO_HDMI_PHY_RK3328,
+};
+
+struct inno_hdmi_phy_data {
+ enum inno_hdmi_phy_type phy_type;
+ const struct inno_hdmi_phy_plat_ops *plat_ops;
+ const struct phy_config *phy_cfg_table;
+};
+
+struct inno_hdmi_phy {
+ struct udevice *dev;
+ ofnode node;
+ void *regs;
+
+ struct clk refoclk;
+ struct clk sysclk;
+ unsigned long tmdsclock;
+ unsigned long pixclock;
+ u32 bus_width;
+ struct phy_config *phy_cfg;
+ const struct inno_hdmi_phy_data *data;
+};
+
+static const struct pre_pll_config pre_pll_cfg_table[] = {
+ { 25175000, 25175000, 3, 125, 3, 1, 1, 1, 3, 3, 4, 0, 0xe00000},
+ { 25175000, 31468750, 1, 41, 0, 3, 3, 1, 3, 3, 4, 0, 0xf5554f},
+ { 27000000, 27000000, 1, 36, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 27000000, 33750000, 1, 45, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 31500000, 31500000, 1, 42, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 31500000, 39375000, 1, 105, 1, 3, 3, 10, 0, 3, 4, 0, 0x0},
+ { 33750000, 33750000, 1, 45, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 33750000, 42187500, 1, 169, 2, 3, 3, 15, 0, 3, 4, 0, 0x0},
+ { 35500000, 35500000, 1, 71, 2, 2, 2, 6, 0, 3, 4, 0, 0x0},
+ { 35500000, 44375000, 1, 74, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 36000000, 36000000, 1, 36, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 36000000, 45000000, 1, 45, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ { 40000000, 40000000, 1, 40, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 40000000, 50000000, 1, 50, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ { 49500000, 49500000, 1, 66, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 49500000, 61875000, 1, 165, 1, 3, 3, 10, 0, 3, 4, 0, 0x0},
+ { 50000000, 50000000, 1, 50, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 50000000, 62500000, 1, 125, 2, 2, 2, 15, 0, 2, 2, 0, 0x0},
+ { 54000000, 54000000, 1, 36, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 54000000, 67500000, 1, 45, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ { 56250000, 56250000, 1, 75, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 56250000, 70312500, 1, 117, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 59341000, 59341000, 1, 118, 2, 2, 2, 6, 0, 3, 4, 0, 0xae978d},
+ { 59341000, 74176250, 2, 148, 2, 1, 1, 15, 0, 1, 1, 0, 0x5a3d70},
+ { 59400000, 59400000, 1, 99, 3, 1, 1, 1, 3, 3, 4, 0, 0x0},
+ { 59400000, 74250000, 1, 99, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 65000000, 65000000, 1, 65, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 65000000, 81250000, 3, 325, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 68250000, 68250000, 1, 91, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 68250000, 85312500, 1, 142, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 71000000, 71000000, 1, 71, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 71000000, 88750000, 3, 355, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 72000000, 72000000, 1, 36, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ { 72000000, 90000000, 1, 60, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ { 73250000, 73250000, 3, 293, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 73250000, 91562500, 1, 61, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ { 74176000, 74176000, 1, 37, 2, 0, 0, 1, 1, 2, 2, 0, 0x16872b},
+ { 74176000, 92720000, 2, 185, 2, 1, 1, 15, 0, 1, 1, 0, 0x70a3d7},
+ { 74250000, 74250000, 1, 99, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 74250000, 92812500, 4, 495, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 75000000, 75000000, 1, 50, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 75000000, 93750000, 1, 125, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 78750000, 78750000, 1, 105, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 78750000, 98437500, 1, 164, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 79500000, 79500000, 1, 53, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 79500000, 99375000, 1, 199, 2, 2, 2, 15, 0, 2, 2, 0, 0x0},
+ { 83500000, 83500000, 2, 167, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ { 83500000, 104375000, 1, 104, 2, 1, 1, 15, 0, 1, 1, 0, 0x600000},
+ { 85500000, 85500000, 1, 57, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 85500000, 106875000, 1, 178, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ { 85750000, 85750000, 3, 343, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 85750000, 107187500, 1, 143, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ { 88750000, 88750000, 3, 355, 0, 3, 3, 1, 2, 3, 4, 0, 0x0},
+ { 88750000, 110937500, 1, 110, 2, 1, 1, 15, 0, 1, 1, 0, 0xf00000},
+ { 94500000, 94500000, 1, 63, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ { 94500000, 118125000, 1, 197, 3, 1, 1, 25, 0, 1, 1, 0, 0x0},
+ {101000000, 101000000, 1, 101, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {101000000, 126250000, 1, 42, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {102250000, 102250000, 4, 409, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {102250000, 127812500, 1, 128, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {106500000, 106500000, 1, 71, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {106500000, 133125000, 1, 133, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {108000000, 108000000, 1, 36, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {108000000, 135000000, 1, 45, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {115500000, 115500000, 1, 77, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {115500000, 144375000, 1, 48, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {117500000, 117500000, 2, 235, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {117500000, 146875000, 1, 49, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {119000000, 119000000, 1, 119, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {119000000, 148750000, 3, 148, 0, 1, 1, 1, 3, 1, 1, 0, 0xc00000},
+ {121750000, 121750000, 4, 487, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {121750000, 152187500, 1, 203, 0, 3, 3, 1, 3, 3, 4, 0, 0x0},
+ {122500000, 122500000, 2, 245, 2, 1, 1, 1, 1, 3, 4, 0, 0x0},
+ {122500000, 153125000, 1, 51, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {135000000, 135000000, 1, 45, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {135000000, 168750000, 1, 169, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {136750000, 136750000, 1, 68, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {136750000, 170937500, 1, 113, 0, 2, 2, 1, 3, 2, 2, 0, 0xf5554f},
+ {140250000, 140250000, 2, 187, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {140250000, 175312500, 1, 117, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {146250000, 146250000, 2, 195, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {146250000, 182812500, 1, 61, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {148250000, 148250000, 3, 222, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {148250000, 185312500, 1, 123, 0, 2, 2, 1, 3, 2, 2, 0, 0x8aaab0},
+ {148352000, 148352000, 2, 148, 2, 0, 0, 1, 1, 2, 2, 0, 0x5a1cac},
+ {148352000, 185440000, 3, 185, 0, 1, 1, 1, 3, 1, 1, 0, 0x70a3d7},
+ {148500000, 148500000, 1, 99, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {148500000, 185625000, 4, 495, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {154000000, 154000000, 1, 77, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {154000000, 192500000, 1, 64, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {156000000, 156000000, 1, 52, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {156000000, 195000000, 1, 65, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {156750000, 156750000, 2, 209, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {156750000, 195937500, 1, 196, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {157000000, 157000000, 2, 157, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {157000000, 196250000, 1, 131, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {157500000, 157500000, 1, 105, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {157500000, 196875000, 1, 197, 2, 1, 1, 15, 0, 1, 1, 0, 0x0},
+ {162000000, 162000000, 1, 54, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {162000000, 202500000, 2, 135, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {175500000, 175500000, 1, 117, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {175500000, 219375000, 1, 73, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {179500000, 179500000, 3, 359, 0, 2, 2, 1, 0, 3, 4, 0, 0x0},
+ {179500000, 224375000, 1, 75, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {182750000, 182750000, 1, 91, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {182750000, 228437500, 1, 152, 0, 2, 2, 1, 3, 2, 2, 0, 0x4aaab0},
+ {182750000, 228437500, 1, 152, 0, 2, 2, 1, 3, 2, 2, 0, 0x4aaab0},
+ {187000000, 187000000, 2, 187, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {187000000, 233750000, 1, 39, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {187250000, 187250000, 3, 280, 2, 0, 0, 1, 1, 2, 2, 0, 0xe00000},
+ {187250000, 234062500, 1, 156, 0, 2, 2, 1, 3, 2, 2, 0, 0xaaab0},
+ {189000000, 189000000, 1, 63, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {189000000, 236250000, 1, 79, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {193250000, 193250000, 3, 289, 2, 0, 0, 1, 1, 2, 2, 0, 0xe00000},
+ {193250000, 241562500, 1, 161, 0, 2, 2, 1, 3, 2, 2, 0, 0xaaab0},
+ {202500000, 202500000, 2, 135, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {202500000, 253125000, 1, 169, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {204750000, 204750000, 4, 273, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {204750000, 255937500, 1, 171, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {208000000, 208000000, 1, 104, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {208000000, 260000000, 1, 173, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {214750000, 214750000, 1, 107, 2, 0, 0, 1, 1, 2, 2, 0, 0x600000},
+ {214750000, 268437500, 1, 178, 0, 2, 2, 1, 3, 2, 2, 0, 0xf5554f},
+ {218250000, 218250000, 4, 291, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {218250000, 272812500, 1, 91, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {229500000, 229500000, 2, 153, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {229500000, 286875000, 1, 191, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {234000000, 234000000, 1, 39, 0, 0, 0, 1, 0, 1, 1, 0, 0x0},
+ {234000000, 292500000, 1, 195, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {241500000, 241500000, 2, 161, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {241500000, 301875000, 1, 201, 0, 2, 2, 1, 3, 2, 2, 0, 0x0},
+ {245250000, 245250000, 4, 327, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {245250000, 306562500, 1, 51, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {245500000, 245500000, 4, 491, 2, 0, 0, 1, 1, 2, 2, 0, 0x0},
+ {245500000, 306875000, 1, 51, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {261000000, 261000000, 1, 87, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {261000000, 326250000, 1, 109, 0, 1, 1, 1, 3, 1, 1, 0, 0x0},
+ {268250000, 268250000, 9, 402, 0, 0, 0, 1, 0, 1, 1, 0, 0x600000},
+ {268250000, 335312500, 1, 111, 0, 1, 1, 1, 3, 1, 1, 0, 0xc5554f},
+ {268500000, 268500000, 2, 179, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {268500000, 335625000, 1, 56, 0, 0, 0, 1, 3, 0, 0, 1, 0x0},
+ {281250000, 281250000, 4, 375, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {281250000, 351562500, 1, 117, 0, 3, 1, 1, 3, 1, 1, 0, 0x0},
+ {288000000, 288000000, 1, 48, 0, 0, 0, 1, 0, 1, 1, 0, 0x0},
+ {288000000, 360000000, 1, 60, 0, 2, 0, 1, 3, 0, 0, 1, 0x0},
+ {296703000, 296703000, 1, 49, 0, 0, 0, 1, 0, 1, 1, 0, 0x7353f7},
+ {296703000, 370878750, 1, 123, 0, 3, 1, 1, 3, 1, 1, 0, 0xa051eb},
+ {297000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {297000000, 371250000, 4, 495, 0, 3, 1, 1, 3, 1, 1, 0, 0x0},
+ {312250000, 312250000, 9, 468, 0, 0, 0, 1, 0, 1, 1, 0, 0x600000},
+ {312250000, 390312500, 1, 130, 0, 3, 1, 1, 3, 1, 1, 0, 0x1aaab0},
+ {317000000, 317000000, 3, 317, 0, 1, 1, 1, 0, 2, 2, 0, 0x0},
+ {317000000, 396250000, 1, 66, 0, 2, 0, 1, 3, 0, 0, 1, 0x0},
+ {319750000, 319750000, 3, 159, 0, 0, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {319750000, 399687500, 3, 199, 0, 2, 0, 1, 3, 0, 0, 1, 0xd80000},
+ {333250000, 333250000, 9, 499, 0, 0, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {333250000, 416562500, 1, 138, 0, 3, 1, 1, 3, 1, 1, 0, 0xdaaab0},
+ {348500000, 348500000, 9, 522, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000},
+ {348500000, 435625000, 1, 145, 0, 3, 1, 1, 3, 1, 1, 0, 0x35554f},
+ {356500000, 356500000, 9, 534, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000},
+ {356500000, 445625000, 1, 148, 0, 3, 1, 1, 3, 1, 1, 0, 0x8aaab0},
+ {380500000, 380500000, 9, 570, 0, 2, 0, 1, 0, 1, 1, 0, 0xc00000},
+ {380500000, 475625000, 1, 158, 0, 3, 1, 1, 3, 1, 1, 0, 0x8aaab0},
+ {443250000, 443250000, 1, 73, 0, 2, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {443250000, 554062500, 1, 92, 0, 2, 0, 1, 3, 0, 0, 1, 0x580000},
+ {505250000, 505250000, 9, 757, 0, 2, 0, 1, 0, 1, 1, 0, 0xe00000},
+ {552750000, 552750000, 3, 276, 0, 2, 0, 1, 0, 1, 1, 0, 0x600000},
+ {593407000, 296703500, 3, 296, 0, 1, 1, 1, 0, 1, 1, 0, 0xb41893},
+ {593407000, 370879375, 4, 494, 0, 3, 1, 1, 3, 0, 0, 1, 0x817e4a},
+ {593407000, 593407000, 3, 296, 0, 2, 0, 1, 0, 1, 1, 0, 0xb41893},
+ {594000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 1, 1, 0, 0x0},
+ {594000000, 371250000, 4, 495, 0, 3, 1, 1, 3, 0, 0, 1, 0x0},
+ {594000000, 594000000, 1, 99, 0, 2, 0, 1, 0, 1, 1, 0, 0x0},
+ { /* sentinel */ }
+};
+
+static const struct post_pll_config post_pll_cfg_table[] = {
+ {33750000, 1, 40, 8, 1},
+ {33750000, 1, 80, 8, 2},
+ {74250000, 1, 40, 8, 1},
+ {74250000, 18, 80, 8, 2},
+ {148500000, 2, 40, 4, 3},
+ {297000000, 4, 40, 2, 3},
+ {594000000, 8, 40, 1, 3},
+ { /* sentinel */ }
+};
+
+/* phy tuning values for an undocumented set of registers */
+static const struct phy_config rk3328_phy_cfg[] = {
+ { 165000000, {
+ 0x07, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x08, 0x08, 0x08,
+ 0x00, 0xac, 0xcc, 0xcc, 0xcc,
+ },
+ }, {
+ 340000000, {
+ 0x0b, 0x0d, 0x0d, 0x0d, 0x07, 0x15, 0x08, 0x08, 0x08,
+ 0x3f, 0xac, 0xcc, 0xcd, 0xdd,
+ },
+ }, {
+ 594000000, {
+ 0x10, 0x1a, 0x1a, 0x1a, 0x07, 0x15, 0x08, 0x08, 0x08,
+ 0x00, 0xac, 0xcc, 0xcc, 0xcc,
+ },
+ }, { /* sentinel */ },
+};
+
+static inline void inno_write(struct inno_hdmi_phy *inno, u32 reg, u8 val)
+{
+ writel(val, inno->regs + (reg * 4));
+}
+
+static inline u8 inno_read(struct inno_hdmi_phy *inno, u32 reg)
+{
+ u32 val;
+
+ val = readl(inno->regs + (reg * 4));
+
+ return val;
+}
+
+static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg,
+ u8 mask, u8 val)
+{
+ u32 tmp, orig;
+
+ orig = inno_read(inno, reg);
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+ inno_write(inno, reg, tmp);
+}
+
+#define inno_poll(reg, val, cond, sleep_us, timeout_us) \
+ readl_poll_sleep_timeout((reg) * 4, val, cond, sleep_us, timeout_us)
+
+static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno,
+ unsigned long rate)
+{
+ int bus_width = inno->bus_width;
+
+ switch (bus_width) {
+ case 4:
+ case 5:
+ case 6:
+ case 10:
+ case 12:
+ case 16:
+ return (u64)rate * bus_width / 8;
+ default:
+ return rate;
+ }
+}
+
+static
+unsigned long inno_hdmi_phy_rk3328_clk_recalc_rate(struct phy *phy,
+ unsigned long parent_rate)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+ unsigned long frac;
+ u8 nd, no_a, no_b, no_d;
+ u64 vco;
+ u16 nf;
+
+ nd = inno_read(inno, 0xa1) & RK3328_PRE_PLL_PRE_DIV_MASK;
+ nf = ((inno_read(inno, 0xa2) & RK3328_PRE_PLL_FB_DIV_11_8_MASK) << 8);
+ nf |= inno_read(inno, 0xa3);
+ vco = parent_rate * nf;
+
+ if (!(inno_read(inno, 0xa2) & RK3328_PRE_PLL_FRAC_DIV_DISABLE)) {
+ frac = inno_read(inno, 0xd3) |
+ (inno_read(inno, 0xd2) << 8) |
+ (inno_read(inno, 0xd1) << 16);
+ vco += DIV_ROUND_CLOSEST(parent_rate * frac, (1 << 24));
+ }
+
+ if (inno_read(inno, 0xa0) & RK3328_PCLK_VCO_DIV_5_MASK) {
+ do_div(vco, nd * 5);
+ } else {
+ no_a = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_A_MASK;
+ no_b = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_B_MASK;
+ no_b >>= RK3328_PRE_PLL_PCLK_DIV_B_SHIFT;
+ no_b += 2;
+ no_d = inno_read(inno, 0xa6) & RK3328_PRE_PLL_PCLK_DIV_D_MASK;
+
+ do_div(vco, (nd * (no_a == 1 ? no_b : no_a) * no_d * 2));
+ }
+
+ inno->pixclock = DIV_ROUND_CLOSEST((unsigned long)vco, 1000) * 1000;
+
+ dev_info(phy->dev, "rate %lu vco %llu\n", inno->pixclock, vco);
+
+ return inno->pixclock;
+}
+
+static long inno_hdmi_phy_rk3328_clk_round_rate(struct phy *phy,
+ unsigned long rate)
+{
+ const struct pre_pll_config *cfg = pre_pll_cfg_table;
+
+ rate = (rate / 1000) * 1000;
+
+ for (; cfg->pixclock != 0; cfg++)
+ if (cfg->pixclock == rate)
+ break;
+
+ if (cfg->pixclock == 0)
+ return -EINVAL;
+
+ return cfg->pixclock;
+}
+
+static const
+struct pre_pll_config *inno_hdmi_phy_get_pre_pll_cfg(struct inno_hdmi_phy *inno,
+ unsigned long rate)
+{
+ const struct pre_pll_config *cfg = pre_pll_cfg_table;
+ unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate);
+
+ for (; cfg->pixclock != 0; cfg++)
+ if (cfg->pixclock == rate && cfg->tmdsclock == tmdsclock)
+ break;
+
+ if (cfg->pixclock == 0)
+ return ERR_PTR(-EINVAL);
+
+ return cfg;
+}
+
+static int
+inno_hdmi_phy_rk3328_clk_set_rate(struct phy *phy,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+ unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate);
+ const struct pre_pll_config *cfg;
+ u32 val;
+ int ret;
+
+ dev_info(phy->dev, "rate %lu tmdsclk %lu\n", rate, tmdsclock);
+
+ if (inno->pixclock == rate && inno->tmdsclock == tmdsclock)
+ return 0;
+
+ cfg = inno_hdmi_phy_get_pre_pll_cfg(inno, rate);
+ if (IS_ERR(cfg))
+ return PTR_ERR(cfg);
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN,
+ RK3328_PRE_PLL_POWER_DOWN);
+
+ /* Configure pre-pll */
+ inno_update_bits(inno, 0xa0, RK3328_PCLK_VCO_DIV_5_MASK,
+ RK3328_PCLK_VCO_DIV_5(cfg->vco_div_5_en));
+ inno_write(inno, 0xa1, RK3328_PRE_PLL_PRE_DIV(cfg->prediv));
+
+ val = RK3328_SPREAD_SPECTRUM_MOD_DISABLE;
+ if (!cfg->fracdiv)
+ val |= RK3328_PRE_PLL_FRAC_DIV_DISABLE;
+ inno_write(inno, 0xa2, RK3328_PRE_PLL_FB_DIV_11_8(cfg->fbdiv) | val);
+ inno_write(inno, 0xa3, RK3328_PRE_PLL_FB_DIV_7_0(cfg->fbdiv));
+ inno_write(inno, 0xa5, RK3328_PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a) |
+ RK3328_PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b));
+ inno_write(inno, 0xa6, RK3328_PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) |
+ RK3328_PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d));
+ inno_write(inno, 0xa4, RK3328_PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) |
+ RK3328_PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) |
+ RK3328_PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b));
+ inno_write(inno, 0xd3, RK3328_PRE_PLL_FRAC_DIV_7_0(cfg->fracdiv));
+ inno_write(inno, 0xd2, RK3328_PRE_PLL_FRAC_DIV_15_8(cfg->fracdiv));
+ inno_write(inno, 0xd1, RK3328_PRE_PLL_FRAC_DIV_23_16(cfg->fracdiv));
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0);
+
+ /* Wait for Pre-PLL lock */
+ ret = inno_poll(0xa9, val, val & RK3328_PRE_PLL_LOCK_STATUS,
+ 1000, 10000);
+ if (ret) {
+ dev_err(phy->dev, "Pre-PLL locking failed\n");
+ return ret;
+ }
+
+ inno->pixclock = rate;
+ inno->tmdsclock = tmdsclock;
+
+ return 0;
+}
+
+static void inno_hdmi_phy_rk3328_clk_enable(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0);
+}
+
+static void inno_hdmi_phy_rk3328_clk_disable(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+ inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN,
+ RK3328_PRE_PLL_POWER_DOWN);
+}
+
+static int
+inno_hdmi_phy_rk3328_power_on(struct phy *phy,
+ const struct post_pll_config *cfg,
+ const struct phy_config *phy_cfg)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+ int ret;
+ u32 v;
+
+ inno_update_bits(inno, 0x02, RK3328_PDATA_EN, 0);
+ inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN,
+ RK3328_POST_PLL_POWER_DOWN);
+
+ inno_write(inno, 0xac, RK3328_POST_PLL_FB_DIV_7_0(cfg->fbdiv));
+ if (cfg->postdiv == 1) {
+ inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) |
+ RK3328_POST_PLL_PRE_DIV(cfg->prediv));
+ inno_write(inno, 0xaa, RK3328_POST_PLL_REFCLK_SEL_TMDS |
+ RK3328_POST_PLL_POWER_DOWN);
+ } else {
+ v = (cfg->postdiv / 2) - 1;
+ v &= RK3328_POST_PLL_POST_DIV_MASK;
+ inno_write(inno, 0xad, v);
+ inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) |
+ RK3328_POST_PLL_PRE_DIV(cfg->prediv));
+ inno_write(inno, 0xaa, RK3328_POST_PLL_POST_DIV_ENABLE |
+ RK3328_POST_PLL_REFCLK_SEL_TMDS |
+ RK3328_POST_PLL_POWER_DOWN);
+ }
+
+ for (v = 0; v < 14; v++)
+ inno_write(inno, 0xb5 + v, phy_cfg->regs[v]);
+
+ /* set ESD detection threshold for TMDS CLK, D2, D1 and D0 */
+ for (v = 0; v < 4; v++)
+ inno_update_bits(inno, 0xc8 + v, RK3328_ESD_DETECT_MASK,
+ RK3328_ESD_DETECT_340MV);
+
+ if (phy_cfg->tmdsclock > 340000000) {
+ /* Set termination resistor to 100ohm */
+ v = clk_get_rate(&inno->sysclk) / 100000;
+ inno_write(inno, 0xc5, RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(v)
+ | RK3328_BYPASS_TERM_RESISTOR_CALIB);
+ inno_write(inno, 0xc6, RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(v));
+ inno_write(inno, 0xc7, RK3328_TERM_RESISTOR_100);
+ inno_update_bits(inno, 0xc5,
+ RK3328_BYPASS_TERM_RESISTOR_CALIB, 0);
+ } else {
+ inno_write(inno, 0xc5, RK3328_BYPASS_TERM_RESISTOR_CALIB);
+
+ /* clk termination resistor is 50ohm (parallel resistors) */
+ if (phy_cfg->tmdsclock > 165000000)
+ inno_update_bits(inno, 0xc8,
+ RK3328_TMDS_TERM_RESIST_MASK,
+ RK3328_TMDS_TERM_RESIST_75 |
+ RK3328_TMDS_TERM_RESIST_150);
+
+ /* data termination resistor for D2, D1 and D0 is 150ohm */
+ for (v = 0; v < 3; v++)
+ inno_update_bits(inno, 0xc9 + v,
+ RK3328_TMDS_TERM_RESIST_MASK,
+ RK3328_TMDS_TERM_RESIST_150);
+ }
+
+ inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, 0);
+ inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE,
+ RK3328_BANDGAP_ENABLE);
+ inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE,
+ RK3328_TMDS_DRIVER_ENABLE);
+
+ /* Wait for post PLL lock */
+ ret = inno_poll(0xaf, v, v & RK3328_POST_PLL_LOCK_STATUS,
+ 1000, 10000);
+ if (ret) {
+ dev_err(phy->dev, "Post-PLL locking failed\n");
+ return ret;
+ }
+
+ if (phy_cfg->tmdsclock > 340000000)
+ mdelay(100);
+
+ inno_update_bits(inno, 0x02, RK3328_PDATA_EN, RK3328_PDATA_EN);
+
+ /* Enable PHY IRQ */
+ inno_write(inno, 0x05, RK3328_INT_TMDS_CLK(RK3328_INT_VSS_AGND_ESD_DET)
+ | RK3328_INT_TMDS_D2(RK3328_INT_VSS_AGND_ESD_DET));
+ inno_write(inno, 0x07, RK3328_INT_TMDS_D1(RK3328_INT_VSS_AGND_ESD_DET)
+ | RK3328_INT_TMDS_D0(RK3328_INT_VSS_AGND_ESD_DET));
+
+ return 0;
+}
+
+static void inno_hdmi_phy_rk3328_power_off(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+ inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE, 0);
+ inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE, 0);
+ inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN,
+ RK3328_POST_PLL_POWER_DOWN);
+
+ /* Disable PHY IRQ */
+ inno_write(inno, 0x05, 0);
+ inno_write(inno, 0x07, 0);
+}
+
+static void inno_hdmi_phy_rk3328_init(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+ const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops;
+
+ /*
+ * Use phy internal register control
+ * rxsense/poweron/pllpd/pdataen signal.
+ */
+ inno_write(inno, 0x01, RK3328_BYPASS_RXSENSE_EN |
+ RK3328_BYPASS_POWERON_EN |
+ RK3328_BYPASS_PLLPD_EN);
+ inno_write(inno, 0x02, RK3328_INT_POL_HIGH | RK3328_BYPASS_PDATA_EN |
+ RK3328_PDATA_EN);
+
+ /* Disable phy irq */
+ inno_write(inno, 0x05, 0);
+ inno_write(inno, 0x07, 0);
+
+ if (plat_ops->clk_recalc_rate)
+ plat_ops->clk_recalc_rate(phy, clk_get_rate(&inno->refoclk));
+
+ if (plat_ops->clk_round_rate)
+ plat_ops->clk_round_rate(phy, inno->pixclock);
+}
+
+static const struct inno_hdmi_phy_plat_ops rk3328_hdmi_phy_plat_ops = {
+ .init = inno_hdmi_phy_rk3328_init,
+ .power_on = inno_hdmi_phy_rk3328_power_on,
+ .power_off = inno_hdmi_phy_rk3328_power_off,
+ .clk_enable = inno_hdmi_phy_rk3328_clk_enable,
+ .clk_disable = inno_hdmi_phy_rk3328_clk_disable,
+ .clk_recalc_rate = inno_hdmi_phy_rk3328_clk_recalc_rate,
+ .clk_round_rate = inno_hdmi_phy_rk3328_clk_round_rate,
+ .clk_set_rate = inno_hdmi_phy_rk3328_clk_set_rate,
+};
+
+static int inno_hdmi_phy_power_on(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+ const struct post_pll_config *cfg = post_pll_cfg_table;
+ const struct phy_config *phy_cfg = inno->data->phy_cfg_table;
+ u32 tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, inno->pixclock);
+ const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops;
+ int ret;
+
+ if (!tmdsclock) {
+ dev_err(phy->dev, "TMDS clock is zero!\n");
+ return -EINVAL;
+ }
+
+ if (!plat_ops->power_on)
+ return -EINVAL;
+
+ dev_info(phy->dev, "TMDS clock = %d\n", tmdsclock);
+
+ for (; cfg->tmdsclock != ~0UL; cfg++)
+ if (tmdsclock <= cfg->tmdsclock)
+ break;
+
+ for (; phy_cfg->tmdsclock != ~0UL; phy_cfg++)
+ if (tmdsclock <= phy_cfg->tmdsclock)
+ break;
+
+ if (cfg->tmdsclock == 0 || phy_cfg->tmdsclock == 0)
+ return -EINVAL;
+
+ if (plat_ops->clk_set_rate) {
+ ret = plat_ops->clk_set_rate(phy, inno->pixclock, 24000000);
+ if (ret)
+ return ret;
+ }
+
+ if (plat_ops->clk_enable)
+ plat_ops->clk_enable(phy);
+
+ if (plat_ops->power_on) {
+ ret = plat_ops->power_on(phy, cfg, phy_cfg);
+ if (ret) {
+ if (plat_ops->clk_disable)
+ plat_ops->clk_disable(phy);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int inno_hdmi_phy_power_off(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+ const struct inno_hdmi_phy_plat_ops *plat_ops = inno->data->plat_ops;
+
+ if (!plat_ops->power_off)
+ return -EINVAL;
+
+ plat_ops->power_off(phy);
+
+ if (plat_ops->clk_disable)
+ plat_ops->clk_disable(phy);
+
+ inno->tmdsclock = 0;
+
+ return 0;
+}
+
+static int inno_hdmi_phy_init(struct phy *phy)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(phy->dev);
+
+ if (inno->data->plat_ops->init)
+ inno->data->plat_ops->init(phy);
+
+ return 0;
+}
+
+static struct phy_ops inno_hdmi_phy_ops = {
+ .init = inno_hdmi_phy_init,
+ .power_on = inno_hdmi_phy_power_on,
+ .power_off = inno_hdmi_phy_power_off,
+};
+
+static int inno_hdmi_phy_probe(struct udevice *dev)
+{
+ struct inno_hdmi_phy *inno = dev_get_priv(dev);
+ int ret;
+
+ inno->regs = dev_read_addr_ptr(dev);
+ if (!inno->regs)
+ return -ENOMEM;
+
+ inno->data = (const struct inno_hdmi_phy_data *)dev_get_driver_data(dev);
+ if (!inno->data)
+ return -EINVAL;
+
+ inno->bus_width = 8;
+
+ ret = clk_get_by_name(dev, "refoclk", &inno->refoclk);
+ if (ret) {
+ dev_err(dev, "failed to get the refoclk (ret=%d)\n", ret);
+ return ret;
+ }
+
+ ret = clk_get_by_name(dev, "sysclk", &inno->sysclk);
+ if (ret) {
+ dev_err(dev, "failed to get the sysclk (ret=%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct inno_hdmi_phy_data rk3328_inno_hdmi_phy_drv_data = {
+ .phy_type = INNO_HDMI_PHY_RK3328,
+ .plat_ops = &rk3328_hdmi_phy_plat_ops,
+ .phy_cfg_table = rk3328_phy_cfg,
+};
+
+static const struct udevice_id inno_hdmi_phy_ids[] = {
+ {
+ .compatible = "rockchip,rk3328-hdmi-phy",
+ .data = (ulong)&rk3328_inno_hdmi_phy_drv_data,
+ },
+ { /* sentile */ }
+};
+
+U_BOOT_DRIVER(inno_hdmi_phy) = {
+ .name = "inno_hdmi_phy",
+ .id = UCLASS_PHY,
+ .of_match = inno_hdmi_phy_ids,
+ .ops = &inno_hdmi_phy_ops,
+ .probe = inno_hdmi_phy_probe,
+ .priv_auto = sizeof(struct inno_hdmi_phy),
+};
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 39c8252..7808ae7 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -522,6 +522,14 @@ config VIDEO_LCD_ORISETECH_OTM8009A
Say Y here if you want to enable support for Orise Technology
otm8009a 480x800 dsi 2dl panel.
+config VIDEO_LCD_LG_LD070WX3
+ bool "LD070WX3 DSI LCD panel support"
+ depends on PANEL && BACKLIGHT
+ select VIDEO_MIPI_DSI
+ help
+ Say Y here if you want to enable support for LG LD070WX3
+ 800x1280 DSI video mode panel.
+
config VIDEO_LCD_RAYDIUM_RM68200
bool "RM68200 DSI LCD panel support"
select VIDEO_MIPI_DSI
@@ -547,6 +555,15 @@ config VIDEO_LCD_RENESAS_R69328
IPS-LCD module with Renesas R69328 IC. The panel has a 720x1280
resolution and uses 24 bit RGB per pixel.
+config VIDEO_LCD_SAMSUNG_LTL106HL02
+ tristate "Samsung LTL106HL02 1920x1080 DSI video mode panel"
+ depends on PANEL && BACKLIGHT
+ select VIDEO_MIPI_DSI
+ help
+ Say Y here if you want to enable support for Samsung LTL106HL02
+ LCD module found in Microsoft Surface 2. The panel has a FullHD
+ resolution (1920x1080).
+
config VIDEO_LCD_SSD2828
bool "SSD2828 bridge chip"
---help---
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index fdc2937..f3f70cd 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -58,10 +58,12 @@ obj-$(CONFIG_VIDEO_LCD_ANX9804) += anx9804.o
obj-$(CONFIG_VIDEO_LCD_ENDEAVORU) += endeavoru-panel.o
obj-$(CONFIG_VIDEO_LCD_HIMAX_HX8394) += himax-hx8394.o
obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o
+obj-$(CONFIG_VIDEO_LCD_LG_LD070WX3) += lg-ld070wx3.o
obj-$(CONFIG_VIDEO_LCD_ORISETECH_OTM8009A) += orisetech_otm8009a.o
obj-$(CONFIG_VIDEO_LCD_RAYDIUM_RM68200) += raydium-rm68200.o
obj-$(CONFIG_VIDEO_LCD_RENESAS_R61307) += renesas-r61307.o
obj-$(CONFIG_VIDEO_LCD_RENESAS_R69328) += renesas-r69328.o
+obj-$(CONFIG_VIDEO_LCD_SAMSUNG_LTL106HL02) += samsung-ltl106hl02.o
obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
obj-$(CONFIG_VIDEO_LCD_TDO_TL070WSH30) += tdo-tl070wsh30.o
obj-$(CONFIG_VIDEO_MCDE_SIMPLE) += mcde_simple.o
diff --git a/drivers/video/bridge/Kconfig b/drivers/video/bridge/Kconfig
index 2311ca2..ab91727 100644
--- a/drivers/video/bridge/Kconfig
+++ b/drivers/video/bridge/Kconfig
@@ -7,6 +7,16 @@ config VIDEO_BRIDGE
requires LVDS, an eDP->LVDS bridge chip can be used to provide the
necessary conversion. This option enables support for these devices.
+config VIDEO_BRIDGE_PARADE_DP501
+ bool "Support Parade DP501 DP & DVI/HDMI dual mode transmitter"
+ depends on PANEL && DM_GPIO
+ select DM_I2C
+ help
+ The Parade DP501 is a DP & DVI/HDMI dual-mode transmitter. It
+ enables an RGB/Parallel SOC output to be converted, packed and
+ serialized into either DP or TMDS output device. Only DisplayPort
+ functionality of this transmitter has been implemented and tested.
+
config VIDEO_BRIDGE_PARADE_PS862X
bool "Support Parade PS862X DP->LVDS bridge"
depends on VIDEO_BRIDGE
@@ -40,3 +50,12 @@ config VIDEO_BRIDGE_SOLOMON_SSD2825
select VIDEO_MIPI_DSI
help
Solomon SSD2824 SPI RGB-DSI bridge driver wrapped into panel uClass.
+
+config VIDEO_BRIDGE_TOSHIBA_TC358768
+ bool "Support Toshiba TC358768 MIPI DSI bridge"
+ depends on PANEL && DM_GPIO
+ select VIDEO_MIPI_DSI
+ select DM_I2C
+ help
+ Toshiba TC358768AXBG/TC358778XBG DSI bridge chip driver.
+ Found in Asus Transformer Infinity TF700T.
diff --git a/drivers/video/bridge/Makefile b/drivers/video/bridge/Makefile
index 22625c8..58697e3 100644
--- a/drivers/video/bridge/Makefile
+++ b/drivers/video/bridge/Makefile
@@ -4,7 +4,9 @@
# Written by Simon Glass <sjg@chromium.org>
obj-$(CONFIG_VIDEO_BRIDGE) += video-bridge-uclass.o
+obj-$(CONFIG_VIDEO_BRIDGE_PARADE_DP501) += dp501.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
+obj-$(CONFIG_VIDEO_BRIDGE_TOSHIBA_TC358768) += tc358768.o
diff --git a/drivers/video/bridge/dp501.c b/drivers/video/bridge/dp501.c
new file mode 100644
index 0000000..095e3e7
--- /dev/null
+++ b/drivers/video/bridge/dp501.c
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Jonas Schwöbel <jonasschwoebel@yahoo.de>
+ * Copyright (C) 2024 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <log.h>
+#include <backlight.h>
+#include <panel.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <power/regulator.h>
+
+#include <asm/gpio.h>
+
+/* TOP */
+#define TOPCFG0 0x00
+#define ROMI2C_PRESCALE 0x01
+#define HDCPI2C_PRESCALE 0x02
+#define GPIO 0x03
+#define GPIO_OUT_ENB 0x04
+#define TESTI2C_CTL 0x05
+#define I2CMTIMEOUT 0x06
+#define TOPCFG1 0x07
+#define TOPCFG2 0x08
+#define TOPCFG3 0x09
+#define TOPCFG4 0x0A
+#define CLKSWRST 0x0B
+#define CADETB_CTL 0x0C
+
+/* Video Attribute */
+#define HTOTAL_L 0x10
+#define HTOTAL_H 0x11
+#define HSTART_L 0x12
+#define HSTART_H 0x13
+#define HWIDTH_L 0x14
+#define HWIDTH_H 0x15
+#define VTOTAL_L 0x16
+#define VTOTAL_H 0x17
+#define VSTART_L 0x18
+#define VSTART_H 0x19
+#define VHEIGHT_L 0x1A
+#define VHEIGHT_H 0x1B
+#define HSPHSW_L 0x1C
+#define HSPHSW_H 0x1D
+#define VSPVSW_L 0x1E
+#define VSPVSW_H 0x1F
+#define MISC0 0x20
+#define MISC1 0x21
+
+/* Video Capture */
+#define VCAPCTRL0 0x24
+#define VCAPCTRL1 0x25
+#define VCAPCTRL2 0x26
+#define VCAPCTRL3 0x27
+#define VCAPCTRL4 0x28
+#define VCAP_MEASURE 0x29
+
+/* Main Link Control */
+#define NVID_L 0x2C
+#define NVID_M 0x2D
+#define NVID_H 0x2E
+#define LINK_CTRL0 0x2F
+#define LINK_CTRL1 0x30
+#define LINK_DEBUG 0x31
+#define ERR_POS 0x32
+#define ERR_PAT 0x33
+#define LINK_DEB_SEL 0x34
+#define IDLE_PATTERN 0x35
+#define TU_SIZE 0x36
+#define CRC_CTRL 0x37
+#define CRC_OUT 0x38
+
+/* AVI-2 InfoFrame */
+#define SD_CTRL0 0x3A
+#define SD_CTRL1 0x3B
+#define SD_HB0 0x3C
+#define SD_HB1 0x3D
+#define SD_HB2 0x3E
+#define SD_HB3 0x3F
+#define SD_DB0 0x40
+#define SD_DB1 0x41
+#define SD_DB2 0x42
+#define SD_DB3 0x43
+#define SD_DB4 0x44
+#define SD_DB5 0x45
+#define SD_DB6 0x46
+#define SD_DB7 0x47
+#define SD_DB8 0x48
+#define SD_DB9 0x49
+#define SD_DB10 0x4A
+#define SD_DB11 0x4B
+#define SD_DB12 0x4C
+#define SD_DB13 0x4D
+#define SD_DB14 0x4E
+#define SD_DB15 0x4F
+
+/* Aux Channel and PCS */
+#define DPCD_REV 0X50
+#define MAX_LINK_RATE 0x51
+#define MAX_LANE_COUNT 0x52
+#define MAX_DOWNSPREAD 0x53
+#define NORP 0x54
+#define DOWNSTRMPORT_PRE 0x55
+#define MLINK_CH_CODING 0x56
+#define RCV_P0_CAP0 0x58
+#define RCV_P0_CAP1 0x59
+#define RCV_P1_CAP0 0x5A
+#define RCV_P1_CAP1 0x5B
+#define DOWNSPREAD_CTL 0x5C
+#define LINK_BW 0x5D
+#define LANE_CNT 0x5E
+#define TRAINING_CTL 0x5F
+#define QUALTEST_CTL 0x60
+#define SINK_COUNT 0x61
+#define DEV_SERVICE_IRQ 0x62
+#define LANE01_STATUS 0x63
+#define LANE23_STATUS 0x64
+#define LANE_STATUS_UPDATE 0x65
+#define SINK_STATUS 0x66
+#define AUX_NOISE 0x67
+#define TEST_MODE 0x69
+#define TEST_PATTERN0 0x6A
+#define TEST_PATTERN1 0x6B
+#define TEST_PATTERN2 0x6C
+#define SIGNATURE 0x6D
+#define PCSCFG 0x6E
+#define AUXCTRL0 0x6f
+#define AUXCTRL2 0x70
+#define AUXCTRL1 0x71
+#define HPDCTL0 0x72
+#define HPDCTL1 0x73
+#define LINK_STATE_CTRL 0x74
+#define SWRST 0x75
+#define LINK_IRQ 0x76
+#define AUXIRQ_CTRL 0x77
+#define HPD2_IRQ_CTRL 0x78
+#define SW_TRAIN_CTRL 0x79
+#define SW_DRV_SET 0x7A
+#define SW_PRE_SET 0x7B
+#define DPCD_ADDR_L 0x7D
+#define DPCD_ADDR_M 0x7E
+#define DPCD_ADDR_H 0x7F
+#define DPCD_LENGTH 0x80
+#define DPCD_WDATA 0x81
+#define DPCD_RDATA 0x82
+#define DPCD_CTL 0x83
+#define DPCD_STATUS 0x84
+#define AUX_STATUS 0x85
+#define I2CTOAUX_RELENGTH 0x86
+#define AUX_RETRY_CTRL 0x87
+#define TIMEOUT_CTRL 0x88
+#define I2CCMD_OPT1 0x89
+#define AUXCMD_ERR_IRQ 0x8A
+#define AUXCMD_OPT2 0x8B
+#define HDCP_Reserved 0x8C
+
+/* Audio InfoFrame */
+#define TX_MVID0 0x90
+#define TX_MVID1 0x91
+#define TX_MVID2 0x92
+#define TX_MVID_OFF 0x93
+#define TX_MAUD0 0x94
+#define TX_MAUD1 0x95
+#define TX_MAUD2 0x96
+#define TX_MAUD_OFF 0x97
+#define MN_CTRL 0x98
+#define MOUT0 0x99
+#define MOUT1 0x9A
+#define MOUT2 0x9B
+
+/* Audio Control */
+#define NAUD_L 0x9F
+#define NAUD_M 0xA0
+#define NAUD_H 0xA1
+#define AUD_CTRL0 0xA2
+#define AUD_CTRL1 0xA3
+#define LANE_POL 0xAA
+#define LANE_EN 0xAB
+#define LANE_MAP 0xAC
+#define SCR_POLY0 0xAD
+#define SCR_POLY1 0xAE
+#define PRBS7_POLY 0xAF
+
+/* Video Pre-process */
+#define MISC_SHDOW 0xB0
+#define VCAPCPCTL0 0xB1
+#define VCAPCPCTL1 0xB2
+#define VCAPCPCTL2 0xB3
+#define CSCPAR 0xB4
+#define I2CTODPCDSTATUS2 0xBA
+#define AUXCTL_REG 0xBB
+
+/* Page 2 */
+#define SEL_PIO1 0x24
+#define SEL_PIO2 0x25
+#define SEL_PIO3 0x26
+#define CHIP_VER_L 0x82
+
+struct dp501_priv {
+ struct udevice *panel;
+ struct display_timing timing;
+
+ struct udevice *chip2;
+
+ struct udevice *vdd;
+ struct gpio_desc reset_gpio;
+ struct gpio_desc enable_gpio;
+};
+
+static int dp501_sw_init(struct udevice *dev)
+{
+ struct dp501_priv *priv = dev_get_priv(dev);
+ int i;
+ u8 val;
+
+ dm_i2c_reg_write(dev, TOPCFG4, 0x30);
+ udelay(200);
+ dm_i2c_reg_write(dev, TOPCFG4, 0x0c);
+ dm_i2c_reg_write(dev, 0x8f, 0x02);
+
+ /* check for connected panel during 1 msec */
+ for (i = 0; i < 5; i++) {
+ val = dm_i2c_reg_read(dev, 0x8d);
+ val &= BIT(2);
+ if (val)
+ break;
+
+ udelay(200);
+ }
+
+ if (!val) {
+ log_debug("%s: panel is not connected!\n", __func__);
+ return -ENODEV;
+ }
+
+ dm_i2c_reg_write(priv->chip2, SEL_PIO1, 0x02);
+ dm_i2c_reg_write(priv->chip2, SEL_PIO2, 0x04);
+ dm_i2c_reg_write(priv->chip2, SEL_PIO3, 0x10);
+
+ dm_i2c_reg_write(dev, LINK_STATE_CTRL, 0xa0);
+ dm_i2c_reg_write(dev, 0x8f, 0x02);
+ dm_i2c_reg_write(dev, TOPCFG1, 0x16);
+ dm_i2c_reg_write(dev, TOPCFG0, 0x24);
+ dm_i2c_reg_write(dev, HPD2_IRQ_CTRL, 0x30);
+ dm_i2c_reg_write(dev, AUXIRQ_CTRL, 0xff);
+ dm_i2c_reg_write(dev, LINK_IRQ, 0xff);
+
+ /* auto detect DVO timing */
+ dm_i2c_reg_write(dev, VCAPCTRL3, 0x30);
+
+ /* reset tpfifo at v blank */
+ dm_i2c_reg_write(dev, LINK_CTRL0, 0x82);
+
+ dm_i2c_reg_write(dev, VCAPCTRL4, 0x07);
+ dm_i2c_reg_write(dev, AUX_RETRY_CTRL, 0x7f);
+ dm_i2c_reg_write(dev, TIMEOUT_CTRL, 0x1e);
+ dm_i2c_reg_write(dev, AUXCTL_REG, 0x06);
+
+ /* DPCD readable */
+ dm_i2c_reg_write(dev, HPDCTL0, 0xa9);
+
+ /* Scramble on */
+ dm_i2c_reg_write(dev, QUALTEST_CTL, 0x00);
+
+ dm_i2c_reg_write(dev, 0x8f, 0x02);
+
+ dm_i2c_reg_write(dev, VCAPCTRL0, 0xc4);
+
+ /* set color depth 8bit (0x00: 6bit; 0x20: 8bit; 0x40: 10bit) */
+ dm_i2c_reg_write(dev, MISC0, 0x20);
+
+ dm_i2c_reg_write(dev, VCAPCPCTL2, 0x01);
+
+ /* check if bridge returns ready status */
+ for (i = 0; i < 5; i++) {
+ val = dm_i2c_reg_read(dev, LINK_IRQ);
+ val &= BIT(0);
+ if (val)
+ break;
+
+ udelay(200);
+ }
+
+ if (!val) {
+ log_debug("%s: bridge is not ready\n", __func__);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void dpcd_configure(struct udevice *dev, u32 config, bool write)
+{
+ dm_i2c_reg_write(dev, DPCD_ADDR_L, (u8)(config >> 8));
+ dm_i2c_reg_write(dev, DPCD_ADDR_M, (u8)(config >> 16));
+ dm_i2c_reg_write(dev, DPCD_ADDR_H, (u8)((config >> 24) | BIT(7)));
+ dm_i2c_reg_write(dev, DPCD_LENGTH, 0x00);
+ dm_i2c_reg_write(dev, LINK_IRQ, 0x20);
+
+ if (write)
+ dm_i2c_reg_write(dev, DPCD_WDATA, (u8)(config & 0xff));
+
+ dm_i2c_reg_write(dev, DPCD_CTL, 0x01);
+
+ udelay(10);
+}
+
+static int dump_dpcd_data(struct udevice *dev, u32 config, u8 *data)
+{
+ int i;
+ u8 value;
+
+ dpcd_configure(dev, config, false);
+
+ value = dm_i2c_reg_read(dev, DPCD_CTL);
+ if (value)
+ return -ENODATA;
+
+ for (i = 0; i < 5; i++) {
+ value = dm_i2c_reg_read(dev, LINK_IRQ);
+ value &= BIT(5);
+ if (value)
+ break;
+
+ udelay(100);
+ }
+
+ if (!value)
+ return -ENODATA;
+
+ value = dm_i2c_reg_read(dev, DPCD_STATUS);
+ if (!(value & 0xe0))
+ *data = dm_i2c_reg_read(dev, DPCD_RDATA);
+ else
+ return -ENODATA;
+
+ return 0;
+}
+
+static int dp501_dpcd_dump(struct udevice *dev, u32 config, u8 *data)
+{
+ int i, ret;
+
+ for (i = 0; i < 5; i++) {
+ ret = dump_dpcd_data(dev, config, data);
+ if (!ret)
+ break;
+
+ udelay(100);
+ }
+
+ return ret;
+}
+
+static int dp501_reset_link(struct udevice *dev)
+{
+ dm_i2c_reg_write(dev, TRAINING_CTL, 0x00);
+ dm_i2c_reg_write(dev, SWRST, 0xf8);
+ dm_i2c_reg_write(dev, SWRST, 0x00);
+
+ return -ENODEV;
+}
+
+static int dp501_link_training(struct udevice *dev)
+{
+ int i, ret;
+ u8 lane, link, link_out;
+ u8 lane_cnt, lane01, lane23;
+
+ dpcd_configure(dev, 0x030000, true);
+ dpcd_configure(dev, 0x03011c, true);
+ dpcd_configure(dev, 0x0301f8, true);
+
+ ret = dp501_dpcd_dump(dev, 0x90000100, &link);
+ if (ret) {
+ log_debug("%s: link dump failed %d\n", __func__, ret);
+ return dp501_reset_link(dev);
+ }
+
+ ret = dp501_dpcd_dump(dev, 0x90000200, &lane);
+ if (ret) {
+ log_debug("%s: lane dump failed %d\n", __func__, ret);
+ return dp501_reset_link(dev);
+ }
+
+ /* Software trainig */
+ for (i = 10; i > 0; i--) {
+ dm_i2c_reg_write(dev, LINK_BW, link);
+ dm_i2c_reg_write(dev, LANE_CNT, lane | BIT(7));
+
+ link_out = dm_i2c_reg_read(dev, LINK_BW);
+ lane_cnt = dm_i2c_reg_read(dev, LANE_CNT);
+
+ if (link_out == link &&
+ (lane_cnt == (lane | BIT(7))))
+ break;
+
+ udelay(500);
+ }
+
+ if (!i)
+ return dp501_reset_link(dev);
+
+ dm_i2c_reg_write(dev, LINK_STATE_CTRL, 0x00);
+ dm_i2c_reg_write(dev, TRAINING_CTL, 0x0d);
+
+ /* check if bridge returns link ready status */
+ for (i = 0; i < 100; i++) {
+ link_out = dm_i2c_reg_read(dev, LINK_IRQ);
+ link_out &= BIT(1);
+ if (link_out) {
+ dm_i2c_reg_write(dev, LINK_IRQ, 0xff);
+ break;
+ }
+
+ udelay(100);
+ }
+
+ if (!link_out) {
+ log_debug("%s: link prepare failed %d\n",
+ __func__, link_out);
+ return dp501_reset_link(dev);
+ }
+
+ lane01 = dm_i2c_reg_read(dev, LANE01_STATUS);
+ lane23 = dm_i2c_reg_read(dev, LANE23_STATUS);
+
+ switch (lane_cnt & 0xf) {
+ case 4:
+ if (lane01 == 0x77 &&
+ lane23 == 0x77)
+ return 0;
+ break;
+
+ case 2:
+ if (lane01 == 0x77)
+ return 0;
+ break;
+
+ default:
+ if ((lane01 & 7) == 7)
+ return 0;
+ break;
+ }
+
+ return dp501_reset_link(dev);
+}
+
+static int dp501_attach(struct udevice *dev)
+{
+ struct dp501_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = dp501_sw_init(dev);
+ if (ret)
+ return ret;
+
+ mdelay(90);
+
+ ret = dp501_link_training(dev);
+ if (ret)
+ return ret;
+
+ /* Perform panel HW setup */
+ return panel_enable_backlight(priv->panel);
+}
+
+static int dp501_set_backlight(struct udevice *dev, int percent)
+{
+ struct dp501_priv *priv = dev_get_priv(dev);
+
+ return panel_set_backlight(priv->panel, percent);
+}
+
+static int dp501_panel_timings(struct udevice *dev,
+ struct display_timing *timing)
+{
+ struct dp501_priv *priv = dev_get_priv(dev);
+
+ memcpy(timing, &priv->timing, sizeof(*timing));
+ return 0;
+}
+
+static void dp501_hw_init(struct dp501_priv *priv)
+{
+ dm_gpio_set_value(&priv->reset_gpio, 1);
+
+ regulator_set_enable_if_allowed(priv->vdd, 1);
+ dm_gpio_set_value(&priv->enable_gpio, 1);
+
+ udelay(100);
+
+ dm_gpio_set_value(&priv->reset_gpio, 0);
+ mdelay(80);
+}
+
+static int dp501_setup(struct udevice *dev)
+{
+ struct dm_i2c_chip *chip = dev_get_parent_plat(dev);
+ struct dp501_priv *priv = dev_get_priv(dev);
+ struct udevice *bus = dev_get_parent(dev);
+ int ret;
+
+ /* get panel */
+ ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev,
+ "panel", &priv->panel);
+ if (ret) {
+ log_debug("%s: Cannot get panel: ret=%d\n", __func__, ret);
+ return log_ret(ret);
+ }
+
+ /* get regulators */
+ ret = device_get_supply_regulator(dev, "power-supply", &priv->vdd);
+ if (ret) {
+ log_debug("%s: vddc regulator error: %d\n", __func__, ret);
+ if (ret != -ENOENT)
+ return log_ret(ret);
+ }
+
+ /* get gpios */
+ ret = gpio_request_by_name(dev, "reset-gpios", 0,
+ &priv->reset_gpio, GPIOD_IS_OUT);
+ if (ret) {
+ log_debug("%s: Could not decode reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = gpio_request_by_name(dev, "enable-gpios", 0,
+ &priv->enable_gpio, GPIOD_IS_OUT);
+ if (ret) {
+ log_debug("%s: Could not decode enable-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = i2c_get_chip(bus, chip->chip_addr + 2, 1, &priv->chip2);
+ if (ret) {
+ log_debug("%s: cannot get second PMIC I2C chip (err %d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ dp501_hw_init(priv);
+
+ /* get EDID */
+ return panel_get_display_timing(priv->panel, &priv->timing);
+}
+
+static int dp501_probe(struct udevice *dev)
+{
+ if (device_get_uclass_id(dev->parent) != UCLASS_I2C)
+ return -EPROTONOSUPPORT;
+
+ return dp501_setup(dev);
+}
+
+struct panel_ops dp501_ops = {
+ .enable_backlight = dp501_attach,
+ .set_backlight = dp501_set_backlight,
+ .get_display_timing = dp501_panel_timings,
+};
+
+static const struct udevice_id dp501_ids[] = {
+ { .compatible = "parade,dp501" },
+ { }
+};
+
+U_BOOT_DRIVER(dp501) = {
+ .name = "dp501",
+ .id = UCLASS_PANEL,
+ .of_match = dp501_ids,
+ .ops = &dp501_ops,
+ .probe = dp501_probe,
+ .priv_auto = sizeof(struct dp501_priv),
+};
diff --git a/drivers/video/bridge/ssd2825.c b/drivers/video/bridge/ssd2825.c
index cea20dc..f0ef3da 100644
--- a/drivers/video/bridge/ssd2825.c
+++ b/drivers/video/bridge/ssd2825.c
@@ -349,39 +349,6 @@ 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);
@@ -417,17 +384,15 @@ static int ssd2825_bridge_enable_panel(struct udevice *dev)
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;
+ /* Perform panel setup */
+ return panel_enable_backlight(priv->panel);
}
static int ssd2825_bridge_set_panel(struct udevice *dev, int percent)
{
- return 0;
+ struct ssd2825_bridge_priv *priv = dev_get_priv(dev);
+
+ return panel_set_backlight(priv->panel, percent);
}
static int ssd2825_bridge_panel_timings(struct udevice *dev,
@@ -440,6 +405,45 @@ static int ssd2825_bridge_panel_timings(struct udevice *dev,
return 0;
}
+static int ssd2825_bridge_hw_init(struct udevice *dev)
+{
+ struct ssd2825_bridge_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->tx_clk);
+ if (ret) {
+ log_debug("%s: error enabling tx_clk (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = dm_gpio_set_value(&priv->power_gpio, 1);
+ if (ret) {
+ log_debug("%s: error changing power-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(10);
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(10);
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(10);
+
+ return 0;
+}
+
static int ssd2825_bridge_probe(struct udevice *dev)
{
struct ssd2825_bridge_priv *priv = dev_get_priv(dev);
@@ -496,7 +500,7 @@ static int ssd2825_bridge_probe(struct udevice *dev)
return PTR_ERR(priv->tx_clk);
}
- return 0;
+ return ssd2825_bridge_hw_init(dev);
}
static const struct panel_ops ssd2825_bridge_ops = {
diff --git a/drivers/video/bridge/tc358768.c b/drivers/video/bridge/tc358768.c
new file mode 100644
index 0000000..19b6ca2
--- /dev/null
+++ b/drivers/video/bridge/tc358768.c
@@ -0,0 +1,985 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Texas Instruments Incorporated
+ * Copyright (C) 2022 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <i2c.h>
+#include <log.h>
+#include <mipi_display.h>
+#include <mipi_dsi.h>
+#include <backlight.h>
+#include <panel.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <power/regulator.h>
+
+#include <asm/gpio.h>
+
+/* Global (16-bit addressable) */
+#define TC358768_CHIPID 0x0000
+#define TC358768_SYSCTL 0x0002
+#define TC358768_CONFCTL 0x0004
+#define TC358768_VSDLY 0x0006
+#define TC358768_DATAFMT 0x0008
+#define TC358768_GPIOEN 0x000E
+#define TC358768_GPIODIR 0x0010
+#define TC358768_GPIOIN 0x0012
+#define TC358768_GPIOOUT 0x0014
+#define TC358768_PLLCTL0 0x0016
+#define TC358768_PLLCTL1 0x0018
+#define TC358768_CMDBYTE 0x0022
+#define TC358768_PP_MISC 0x0032
+#define TC358768_DSITX_DT 0x0050
+#define TC358768_FIFOSTATUS 0x00F8
+
+/* Debug (16-bit addressable) */
+#define TC358768_VBUFCTRL 0x00E0
+#define TC358768_DBG_WIDTH 0x00E2
+#define TC358768_DBG_VBLANK 0x00E4
+#define TC358768_DBG_DATA 0x00E8
+
+/* TX PHY (32-bit addressable) */
+#define TC358768_CLW_DPHYCONTTX 0x0100
+#define TC358768_D0W_DPHYCONTTX 0x0104
+#define TC358768_D1W_DPHYCONTTX 0x0108
+#define TC358768_D2W_DPHYCONTTX 0x010C
+#define TC358768_D3W_DPHYCONTTX 0x0110
+#define TC358768_CLW_CNTRL 0x0140
+#define TC358768_D0W_CNTRL 0x0144
+#define TC358768_D1W_CNTRL 0x0148
+#define TC358768_D2W_CNTRL 0x014C
+#define TC358768_D3W_CNTRL 0x0150
+
+/* TX PPI (32-bit addressable) */
+#define TC358768_STARTCNTRL 0x0204
+#define TC358768_DSITXSTATUS 0x0208
+#define TC358768_LINEINITCNT 0x0210
+#define TC358768_LPTXTIMECNT 0x0214
+#define TC358768_TCLK_HEADERCNT 0x0218
+#define TC358768_TCLK_TRAILCNT 0x021C
+#define TC358768_THS_HEADERCNT 0x0220
+#define TC358768_TWAKEUP 0x0224
+#define TC358768_TCLK_POSTCNT 0x0228
+#define TC358768_THS_TRAILCNT 0x022C
+#define TC358768_HSTXVREGCNT 0x0230
+#define TC358768_HSTXVREGEN 0x0234
+#define TC358768_TXOPTIONCNTRL 0x0238
+#define TC358768_BTACNTRL1 0x023C
+
+/* TX CTRL (32-bit addressable) */
+#define TC358768_DSI_CONTROL 0x040C
+#define TC358768_DSI_STATUS 0x0410
+#define TC358768_DSI_INT 0x0414
+#define TC358768_DSI_INT_ENA 0x0418
+#define TC358768_DSICMD_RDFIFO 0x0430
+#define TC358768_DSI_ACKERR 0x0434
+#define TC358768_DSI_ACKERR_INTENA 0x0438
+#define TC358768_DSI_ACKERR_HALT 0x043c
+#define TC358768_DSI_RXERR 0x0440
+#define TC358768_DSI_RXERR_INTENA 0x0444
+#define TC358768_DSI_RXERR_HALT 0x0448
+#define TC358768_DSI_ERR 0x044C
+#define TC358768_DSI_ERR_INTENA 0x0450
+#define TC358768_DSI_ERR_HALT 0x0454
+#define TC358768_DSI_CONFW 0x0500
+#define TC358768_DSI_LPCMD 0x0500
+#define TC358768_DSI_RESET 0x0504
+#define TC358768_DSI_INT_CLR 0x050C
+#define TC358768_DSI_START 0x0518
+
+/* DSITX CTRL (16-bit addressable) */
+#define TC358768_DSICMD_TX 0x0600
+#define TC358768_DSICMD_TYPE 0x0602
+#define TC358768_DSICMD_WC 0x0604
+#define TC358768_DSICMD_WD0 0x0610
+#define TC358768_DSICMD_WD1 0x0612
+#define TC358768_DSICMD_WD2 0x0614
+#define TC358768_DSICMD_WD3 0x0616
+#define TC358768_DSI_EVENT 0x0620
+#define TC358768_DSI_VSW 0x0622
+#define TC358768_DSI_VBPR 0x0624
+#define TC358768_DSI_VACT 0x0626
+#define TC358768_DSI_HSW 0x0628
+#define TC358768_DSI_HBPR 0x062A
+#define TC358768_DSI_HACT 0x062C
+
+/* TC358768_DSI_CONTROL (0x040C) register */
+#define TC358768_DSI_CONTROL_DIS_MODE BIT(15)
+#define TC358768_DSI_CONTROL_TXMD BIT(7)
+#define TC358768_DSI_CONTROL_HSCKMD BIT(5)
+#define TC358768_DSI_CONTROL_EOTDIS BIT(0)
+
+/* TC358768_DSI_CONFW (0x0500) register */
+#define TC358768_DSI_CONFW_MODE_SET (5 << 29)
+#define TC358768_DSI_CONFW_MODE_CLR (6 << 29)
+#define TC358768_DSI_CONFW_ADDR_DSI_CONTROL (3 << 24)
+
+#define NANO 1000000000UL
+#define PICO 1000000000000ULL
+
+struct tc358768_priv {
+ struct mipi_dsi_host host;
+ struct mipi_dsi_device device;
+
+ struct udevice *panel;
+ struct display_timing timing;
+
+ struct udevice *vddc;
+ struct udevice *vddmipi;
+ struct udevice *vddio;
+
+ struct clk *refclk;
+
+ struct gpio_desc reset_gpio;
+
+ u32 pd_lines; /* number of Parallel Port Input Data Lines */
+ u32 dsi_lanes; /* number of DSI Lanes */
+
+ /* Parameters for PLL programming */
+ u32 fbd; /* PLL feedback divider */
+ u32 prd; /* PLL input divider */
+ u32 frs; /* PLL Freqency range for HSCK (post divider) */
+
+ u32 dsiclk; /* pll_clk / 2 */
+};
+
+static void tc358768_read(struct udevice *dev, u32 reg, u32 *val)
+{
+ int count;
+ u8 buf[4] = { 0, 0, 0, 0 };
+
+ /* 16-bit register? */
+ if (reg < 0x100 || reg >= 0x600)
+ count = 2;
+ else
+ count = 4;
+
+ dm_i2c_read(dev, reg, buf, count);
+ *val = (buf[0] << 8) | (buf[1] & 0xff) |
+ (buf[2] << 24) | (buf[3] << 16);
+
+ log_debug("%s 0x%04x >> 0x%08x\n",
+ __func__, reg, *val);
+}
+
+static void tc358768_write(struct udevice *dev, u32 reg, u32 val)
+{
+ int count;
+ u8 buf[4];
+
+ /* 16-bit register? */
+ if (reg < 0x100 || reg >= 0x600)
+ count = 2;
+ else
+ count = 4;
+
+ buf[0] = val >> 8;
+ buf[1] = val & 0xff;
+ buf[2] = val >> 24;
+ buf[3] = val >> 16;
+
+ log_debug("%s 0x%04x << 0x%08x\n",
+ __func__, reg, val);
+
+ dm_i2c_write(dev, reg, buf, count);
+}
+
+static void tc358768_update_bits(struct udevice *dev, u32 reg, u32 mask,
+ u32 val)
+{
+ u32 tmp, orig;
+
+ tc358768_read(dev, reg, &orig);
+
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+ if (tmp != orig)
+ tc358768_write(dev, reg, tmp);
+}
+
+static ssize_t tc358768_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct udevice *dev = (struct udevice *)host->dev;
+ struct mipi_dsi_packet packet;
+ int ret;
+
+ if (msg->rx_len) {
+ log_debug("%s: MIPI rx is not supported\n", __func__);
+ return -EOPNOTSUPP;
+ }
+
+ if (msg->tx_len > 8) {
+ log_debug("%s: Maximum 8 byte MIPI tx is supported\n", __func__);
+ return -EOPNOTSUPP;
+ }
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret)
+ return ret;
+
+ if (mipi_dsi_packet_format_is_short(msg->type)) {
+ tc358768_write(dev, TC358768_DSICMD_TYPE,
+ (0x10 << 8) | (packet.header[0] & 0x3f));
+ tc358768_write(dev, TC358768_DSICMD_WC, 0);
+ tc358768_write(dev, TC358768_DSICMD_WD0,
+ (packet.header[2] << 8) | packet.header[1]);
+ } else {
+ int i;
+
+ tc358768_write(dev, TC358768_DSICMD_TYPE,
+ (0x40 << 8) | (packet.header[0] & 0x3f));
+ tc358768_write(dev, TC358768_DSICMD_WC, packet.payload_length);
+ for (i = 0; i < packet.payload_length; i += 2) {
+ u16 val = packet.payload[i];
+
+ if (i + 1 < packet.payload_length)
+ val |= packet.payload[i + 1] << 8;
+
+ tc358768_write(dev, TC358768_DSICMD_WD0 + i, val);
+ }
+ }
+
+ /* start transfer */
+ tc358768_write(dev, TC358768_DSICMD_TX, 1);
+
+ return packet.size;
+}
+
+static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = {
+ .transfer = tc358768_dsi_host_transfer,
+};
+
+static void tc358768_sw_reset(struct udevice *dev)
+{
+ /* Assert Reset */
+ tc358768_write(dev, TC358768_SYSCTL, 1);
+ mdelay(5);
+
+ /* Release Reset, Exit Sleep */
+ tc358768_write(dev, TC358768_SYSCTL, 0);
+}
+
+static void tc358768_hw_enable(struct tc358768_priv *priv)
+{
+ int ret;
+
+ ret = clk_prepare_enable(priv->refclk);
+ if (ret)
+ log_debug("%s: error enabling refclk (%d)\n", __func__, ret);
+
+ ret = regulator_set_enable_if_allowed(priv->vddc, true);
+ if (ret)
+ log_debug("%s: error enabling vddc (%d)\n", __func__, ret);
+
+ ret = regulator_set_enable_if_allowed(priv->vddmipi, true);
+ if (ret)
+ log_debug("%s: error enabling vddmipi (%d)\n", __func__, ret);
+
+ mdelay(10);
+
+ ret = regulator_set_enable_if_allowed(priv->vddio, true);
+ if (ret)
+ log_debug("%s: error enabling vddio (%d)\n", __func__, ret);
+
+ mdelay(2);
+
+ /*
+ * The RESX is active low (GPIO_ACTIVE_LOW).
+ * DEASSERT (value = 0) the reset_gpio to enable the chip
+ */
+ ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+ if (ret)
+ log_debug("%s: error changing reset-gpio (%d)\n", __func__, ret);
+
+ /* wait for encoder clocks to stabilize */
+ mdelay(2);
+}
+
+static u32 tc358768_pclk_to_pll(struct tc358768_priv *priv, u32 pclk)
+{
+ return (u32)div_u64((u64)pclk * priv->pd_lines, priv->dsi_lanes);
+}
+
+static int tc358768_calc_pll(struct tc358768_priv *priv,
+ struct display_timing *dt)
+{
+ static const u32 frs_limits[] = {
+ 1000000000,
+ 500000000,
+ 250000000,
+ 125000000,
+ 62500000
+ };
+ unsigned long refclk;
+ u32 prd, target_pll, i, max_pll, min_pll;
+ u32 frs, best_diff, best_pll, best_prd, best_fbd;
+
+ target_pll = tc358768_pclk_to_pll(priv, dt->pixelclock.typ);
+
+ /* pll_clk = RefClk * FBD / PRD * (1 / (2^FRS)) */
+
+ for (i = 0; i < ARRAY_SIZE(frs_limits); i++)
+ if (target_pll >= frs_limits[i])
+ break;
+
+ if (i == ARRAY_SIZE(frs_limits) || i == 0)
+ return -EINVAL;
+
+ frs = i - 1;
+ max_pll = frs_limits[i - 1];
+ min_pll = frs_limits[i];
+
+ refclk = clk_get_rate(priv->refclk);
+
+ best_diff = UINT_MAX;
+ best_pll = 0;
+ best_prd = 0;
+ best_fbd = 0;
+
+ for (prd = 1; prd <= 16; ++prd) {
+ u32 divisor = prd * (1 << frs);
+ u32 fbd;
+
+ for (fbd = 1; fbd <= 512; ++fbd) {
+ u32 pll, diff, pll_in;
+
+ pll = (u32)div_u64((u64)refclk * fbd, divisor);
+
+ if (pll >= max_pll || pll < min_pll)
+ continue;
+
+ pll_in = (u32)div_u64((u64)refclk, prd);
+ if (pll_in < 4000000)
+ continue;
+
+ diff = max(pll, target_pll) - min(pll, target_pll);
+
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_pll = pll;
+ best_prd = prd;
+ best_fbd = fbd;
+
+ if (best_diff == 0)
+ goto found;
+ }
+ }
+ }
+
+ if (best_diff == UINT_MAX) {
+ log_debug("%s: could not find suitable PLL setup\n", __func__);
+ return -EINVAL;
+ }
+
+found:
+ priv->fbd = best_fbd;
+ priv->prd = best_prd;
+ priv->frs = frs;
+ priv->dsiclk = best_pll / 2;
+
+ return 0;
+}
+
+static void tc358768_setup_pll(struct udevice *dev)
+{
+ struct tc358768_priv *priv = dev_get_priv(dev);
+ u32 fbd, prd, frs;
+ int ret;
+
+ ret = tc358768_calc_pll(priv, &priv->timing);
+ if (ret)
+ log_debug("%s: PLL calculation failed: %d\n", __func__, ret);
+
+ fbd = priv->fbd;
+ prd = priv->prd;
+ frs = priv->frs;
+
+ log_debug("%s: PLL: refclk %lu, fbd %u, prd %u, frs %u\n", __func__,
+ clk_get_rate(priv->refclk), fbd, prd, frs);
+ log_debug("%s: PLL: pll_clk: %u, DSIClk %u, HSByteClk %u\n", __func__,
+ priv->dsiclk * 2, priv->dsiclk, priv->dsiclk / 4);
+
+ /* PRD[15:12] FBD[8:0] */
+ tc358768_write(dev, TC358768_PLLCTL0, ((prd - 1) << 12) | (fbd - 1));
+
+ /* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */
+ tc358768_write(dev, TC358768_PLLCTL1,
+ (frs << 10) | (0x2 << 8) | BIT(1) | BIT(0));
+
+ /* wait for lock */
+ mdelay(5);
+
+ /* FRS[11:10] LBWS[9:8] CKEN[4] PLL_CKEN[4] RESETB[1] EN[0] */
+ tc358768_write(dev, TC358768_PLLCTL1,
+ (frs << 10) | (0x2 << 8) | BIT(4) | BIT(1) | BIT(0));
+}
+
+static u32 tc358768_ns_to_cnt(u32 ns, u32 period_ps)
+{
+ return DIV_ROUND_UP(ns * 1000, period_ps);
+}
+
+static u32 tc358768_ps_to_ns(u32 ps)
+{
+ return ps / 1000;
+}
+
+static u32 tc358768_dpi_to_ns(u32 val, u32 pclk)
+{
+ return (u32)div_u64((u64)val * NANO, pclk);
+}
+
+/* Convert value in DPI pixel clock units to DSI byte count */
+static u32 tc358768_dpi_to_dsi_bytes(struct tc358768_priv *priv, u32 val)
+{
+ u64 m = (u64)val * priv->dsiclk / 4 * priv->dsi_lanes;
+ u64 n = priv->timing.pixelclock.typ;
+
+ return (u32)div_u64(m + n - 1, n);
+}
+
+static u32 tc358768_dsi_bytes_to_ns(struct tc358768_priv *priv, u32 val)
+{
+ u64 m = (u64)val * NANO;
+ u64 n = priv->dsiclk / 4 * priv->dsi_lanes;
+
+ return (u32)div_u64(m, n);
+}
+
+static int tc358768_attach(struct udevice *dev)
+{
+ struct tc358768_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct display_timing *dt = &priv->timing;
+ u32 val, val2, lptxcnt, hact, data_type;
+ s32 raw_val;
+ u32 hsbyteclk_ps, dsiclk_ps, ui_ps;
+ u32 dsiclk, hsbyteclk;
+ int i;
+ /* In pixelclock units */
+ u32 dpi_htot, dpi_data_start;
+ /* In byte units */
+ u32 dsi_dpi_htot, dsi_dpi_data_start;
+ u32 dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp;
+ const u32 dsi_hss = 4; /* HSS is a short packet (4 bytes) */
+ /* In hsbyteclk units */
+ u32 dsi_vsdly;
+ const u32 internal_dly = 40;
+
+ if (device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
+ debug("%s: Non-continuous mode unimplemented, falling back to continuous\n", __func__);
+ device->mode_flags &= ~MIPI_DSI_CLOCK_NON_CONTINUOUS;
+ }
+
+ tc358768_hw_enable(priv);
+ tc358768_sw_reset(dev);
+
+ tc358768_setup_pll(dev);
+
+ dsiclk = priv->dsiclk;
+ hsbyteclk = dsiclk / 4;
+
+ /* Data Format Control Register */
+ val = BIT(2) | BIT(1) | BIT(0); /* rdswap_en | dsitx_en | txdt_en */
+ switch (device->format) {
+ case MIPI_DSI_FMT_RGB888:
+ val |= (0x3 << 4);
+ hact = dt->hactive.typ * 3;
+ data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ val |= (0x4 << 4);
+ hact = dt->hactive.typ * 3;
+ data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ val |= (0x4 << 4) | BIT(3);
+ hact = dt->hactive.typ * 18 / 8;
+ data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ val |= (0x5 << 4);
+ hact = dt->hactive.typ * 2;
+ data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16;
+ break;
+ default:
+ log_debug("%s: Invalid data format (%u)\n",
+ __func__, device->format);
+ return -EINVAL;
+ }
+
+ /*
+ * There are three important things to make TC358768 work correctly,
+ * which are not trivial to manage:
+ *
+ * 1. Keep the DPI line-time and the DSI line-time as close to each
+ * other as possible.
+ * 2. TC358768 goes to LP mode after each line's active area. The DSI
+ * HFP period has to be long enough for entering and exiting LP mode.
+ * But it is not clear how to calculate this.
+ * 3. VSDly (video start delay) has to be long enough to ensure that the
+ * DSI TX does not start transmitting until we have started receiving
+ * pixel data from the DPI input. It is not clear how to calculate
+ * this either.
+ */
+
+ dpi_htot = dt->hactive.typ + dt->hfront_porch.typ +
+ dt->hsync_len.typ + dt->hback_porch.typ;
+ dpi_data_start = dt->hsync_len.typ + dt->hback_porch.typ;
+
+ log_debug("%s: dpi horiz timing (pclk): %u + %u + %u + %u = %u\n", __func__,
+ dt->hsync_len.typ, dt->hback_porch.typ, dt->hactive.typ,
+ dt->hfront_porch.typ, dpi_htot);
+
+ log_debug("%s: dpi horiz timing (ns): %u + %u + %u + %u = %u\n", __func__,
+ tc358768_dpi_to_ns(dt->hsync_len.typ, dt->pixelclock.typ),
+ tc358768_dpi_to_ns(dt->hback_porch.typ, dt->pixelclock.typ),
+ tc358768_dpi_to_ns(dt->hactive.typ, dt->pixelclock.typ),
+ tc358768_dpi_to_ns(dt->hfront_porch.typ, dt->pixelclock.typ),
+ tc358768_dpi_to_ns(dpi_htot, dt->pixelclock.typ));
+
+ log_debug("%s: dpi data start (ns): %u + %u = %u\n", __func__,
+ tc358768_dpi_to_ns(dt->hsync_len.typ, dt->pixelclock.typ),
+ tc358768_dpi_to_ns(dt->hback_porch.typ, dt->pixelclock.typ),
+ tc358768_dpi_to_ns(dpi_data_start, dt->pixelclock.typ));
+
+ dsi_dpi_htot = tc358768_dpi_to_dsi_bytes(priv, dpi_htot);
+ dsi_dpi_data_start = tc358768_dpi_to_dsi_bytes(priv, dpi_data_start);
+
+ if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+ dsi_hsw = tc358768_dpi_to_dsi_bytes(priv, dt->hsync_len.typ);
+ dsi_hbp = tc358768_dpi_to_dsi_bytes(priv, dt->hback_porch.typ);
+ } else {
+ /* HBP is included in HSW in event mode */
+ dsi_hbp = 0;
+ dsi_hsw = tc358768_dpi_to_dsi_bytes(priv,
+ dt->hsync_len.typ +
+ dt->hback_porch.typ);
+
+ /*
+ * The pixel packet includes the actual pixel data, and:
+ * DSI packet header = 4 bytes
+ * DCS code = 1 byte
+ * DSI packet footer = 2 bytes
+ */
+ dsi_hact = hact + 4 + 1 + 2;
+
+ dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss;
+
+ /*
+ * Here we should check if HFP is long enough for entering LP
+ * and exiting LP, but it's not clear how to calculate that.
+ * Instead, this is a naive algorithm that just adjusts the HFP
+ * and HSW so that HFP is (at least) roughly 2/3 of the total
+ * blanking time.
+ */
+ if (dsi_hfp < (dsi_hfp + dsi_hsw + dsi_hss) * 2 / 3) {
+ u32 old_hfp = dsi_hfp;
+ u32 old_hsw = dsi_hsw;
+ u32 tot = dsi_hfp + dsi_hsw + dsi_hss;
+
+ dsi_hsw = tot / 3;
+
+ /*
+ * Seems like sometimes HSW has to be divisible by num-lanes, but
+ * not always...
+ */
+ dsi_hsw = roundup(dsi_hsw, priv->dsi_lanes);
+
+ dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss;
+
+ log_debug("%s: hfp too short, adjusting dsi hfp and dsi hsw from %u, %u to %u, %u\n",
+ __func__, old_hfp, old_hsw, dsi_hfp, dsi_hsw);
+ }
+
+ log_debug("%s: dsi horiz timing (bytes): %u, %u + %u + %u + %u = %u\n", __func__,
+ dsi_hss, dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp,
+ dsi_hss + dsi_hsw + dsi_hbp + dsi_hact + dsi_hfp);
+
+ log_debug("%s: dsi horiz timing (ns): %u + %u + %u + %u + %u = %u\n", __func__,
+ tc358768_dsi_bytes_to_ns(priv, dsi_hss),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hsw),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hbp),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hact),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hfp),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hss + dsi_hsw +
+ dsi_hbp + dsi_hact + dsi_hfp));
+ }
+
+ /* VSDly calculation */
+
+ /* Start with the HW internal delay */
+ dsi_vsdly = internal_dly;
+
+ /* Convert to byte units as the other variables are in byte units */
+ dsi_vsdly *= priv->dsi_lanes;
+
+ /* Do we need more delay, in addition to the internal? */
+ if (dsi_dpi_data_start > dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp) {
+ dsi_vsdly = dsi_dpi_data_start - dsi_hss - dsi_hsw - dsi_hbp;
+ dsi_vsdly = roundup(dsi_vsdly, priv->dsi_lanes);
+ }
+
+ log_debug("%s: dsi data start (bytes) %u + %u + %u + %u = %u\n", __func__,
+ dsi_vsdly, dsi_hss, dsi_hsw, dsi_hbp,
+ dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp);
+
+ log_debug("%s: dsi data start (ns) %u + %u + %u + %u = %u\n", __func__,
+ tc358768_dsi_bytes_to_ns(priv, dsi_vsdly),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hss),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hsw),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hbp),
+ tc358768_dsi_bytes_to_ns(priv, dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp));
+
+ /* Convert back to hsbyteclk */
+ dsi_vsdly /= priv->dsi_lanes;
+
+ /*
+ * The docs say that there is an internal delay of 40 cycles.
+ * However, we get underflows if we follow that rule. If we
+ * instead ignore the internal delay, things work. So either
+ * the docs are wrong or the calculations are wrong.
+ *
+ * As a temporary fix, add the internal delay here, to counter
+ * the subtraction when writing the register.
+ */
+ dsi_vsdly += internal_dly;
+
+ /* Clamp to the register max */
+ if (dsi_vsdly - internal_dly > 0x3ff) {
+ log_warning("%s: VSDly too high, underflows likely\n", __func__);
+ dsi_vsdly = 0x3ff + internal_dly;
+ }
+
+ /* VSDly[9:0] */
+ tc358768_write(dev, TC358768_VSDLY, dsi_vsdly - internal_dly);
+
+ tc358768_write(dev, TC358768_DATAFMT, val);
+ tc358768_write(dev, TC358768_DSITX_DT, data_type);
+
+ /* Enable D-PHY (HiZ->LP11) */
+ tc358768_write(dev, TC358768_CLW_CNTRL, 0x0000);
+ /* Enable lanes */
+ for (i = 0; i < device->lanes; i++)
+ tc358768_write(dev, TC358768_D0W_CNTRL + i * 4, 0x0000);
+
+ /* Set up D-PHY CONTTX */
+ tc358768_write(dev, TC358768_CLW_DPHYCONTTX, 0x0203);
+ /* Adjust lanes */
+ for (i = 0; i < device->lanes; i++)
+ tc358768_write(dev, TC358768_D0W_DPHYCONTTX + i * 4, 0x0203);
+
+ /* DSI Timings */
+ hsbyteclk_ps = (u32)div_u64(PICO, hsbyteclk);
+ dsiclk_ps = (u32)div_u64(PICO, dsiclk);
+ ui_ps = dsiclk_ps / 2;
+ log_debug("%s: dsiclk: %u ps, ui %u ps, hsbyteclk %u ps\n",
+ __func__, dsiclk_ps, ui_ps, hsbyteclk_ps);
+
+ /* LP11 > 100us for D-PHY Rx Init */
+ val = tc358768_ns_to_cnt(100 * 1000, hsbyteclk_ps) - 1;
+ log_debug("%s: LINEINITCNT: 0x%x\n", __func__, val);
+ tc358768_write(dev, TC358768_LINEINITCNT, val);
+
+ /* LPTimeCnt > 50ns */
+ val = tc358768_ns_to_cnt(50, hsbyteclk_ps) - 1;
+ lptxcnt = val;
+ log_debug("%s: LPTXTIMECNT: 0x%x\n", __func__, val);
+ tc358768_write(dev, TC358768_LPTXTIMECNT, val);
+
+ /* 38ns < TCLK_PREPARE < 95ns */
+ val = tc358768_ns_to_cnt(65, hsbyteclk_ps) - 1;
+ log_debug("%s: TCLK_PREPARECNT: 0x%x\n", __func__, val);
+ /* TCLK_PREPARE + TCLK_ZERO > 300ns */
+ val2 = tc358768_ns_to_cnt(300 - tc358768_ps_to_ns(2 * ui_ps),
+ hsbyteclk_ps) - 2;
+ log_debug("%s: TCLK_ZEROCNT: 0x%x\n", __func__, val2);
+ val |= val2 << 8;
+ tc358768_write(dev, TC358768_TCLK_HEADERCNT, val);
+
+ /* TCLK_TRAIL > 60ns AND TEOT <= 105 ns + 12*UI */
+ raw_val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(2 * ui_ps),
+ hsbyteclk_ps) - 5;
+ val = clamp(raw_val, 0, 127);
+ log_debug("%s: TCLK_TRAILCNT: 0x%x\n", __func__, val);
+ tc358768_write(dev, TC358768_TCLK_TRAILCNT, val);
+
+ /* 40ns + 4*UI < THS_PREPARE < 85ns + 6*UI */
+ val = 50 + tc358768_ps_to_ns(4 * ui_ps);
+ val = tc358768_ns_to_cnt(val, hsbyteclk_ps) - 1;
+ log_debug("%s: THS_PREPARECNT: 0x%x\n", __func__, val);
+ /* THS_PREPARE + THS_ZERO > 145ns + 10*UI */
+ raw_val = tc358768_ns_to_cnt(145 - tc358768_ps_to_ns(3 * ui_ps),
+ hsbyteclk_ps) - 10;
+ val2 = clamp(raw_val, 0, 127);
+ log_debug("%s: THS_ZEROCNT: 0x%x\n", __func__, val2);
+ val |= val2 << 8;
+ tc358768_write(dev, TC358768_THS_HEADERCNT, val);
+
+ /* TWAKEUP > 1ms in lptxcnt steps */
+ val = tc358768_ns_to_cnt(1020000, hsbyteclk_ps);
+ val = val / (lptxcnt + 1) - 1;
+ log_debug("%s: TWAKEUP: 0x%x\n", __func__, val);
+ tc358768_write(dev, TC358768_TWAKEUP, val);
+
+ /* TCLK_POSTCNT > 60ns + 52*UI */
+ val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(52 * ui_ps),
+ hsbyteclk_ps) - 3;
+ log_debug("%s: TCLK_POSTCNT: 0x%x\n", __func__, val);
+ tc358768_write(dev, TC358768_TCLK_POSTCNT, val);
+
+ /* max(60ns + 4*UI, 8*UI) < THS_TRAILCNT < 105ns + 12*UI */
+ raw_val = tc358768_ns_to_cnt(60 + tc358768_ps_to_ns(18 * ui_ps),
+ hsbyteclk_ps) - 4;
+ val = clamp(raw_val, 0, 15);
+ log_debug("%s: THS_TRAILCNT: 0x%x\n", __func__, val);
+ tc358768_write(dev, TC358768_THS_TRAILCNT, val);
+
+ val = BIT(0);
+ for (i = 0; i < device->lanes; i++)
+ val |= BIT(i + 1);
+ tc358768_write(dev, TC358768_HSTXVREGEN, val);
+
+ tc358768_write(dev, TC358768_TXOPTIONCNTRL,
+ (device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) ? 0 : BIT(0));
+
+ /* TXTAGOCNT[26:16] RXTASURECNT[10:0] */
+ val = tc358768_ps_to_ns((lptxcnt + 1) * hsbyteclk_ps * 4);
+ val = tc358768_ns_to_cnt(val, hsbyteclk_ps) / 4 - 1;
+ log_debug("%s: TXTAGOCNT: 0x%x\n", __func__, val);
+ val2 = tc358768_ns_to_cnt(tc358768_ps_to_ns((lptxcnt + 1) * hsbyteclk_ps),
+ hsbyteclk_ps) - 2;
+ log_debug("%s: RXTASURECNT: 0x%x\n", __func__, val2);
+ val = val << 16 | val2;
+ tc358768_write(dev, TC358768_BTACNTRL1, val);
+
+ /* START[0] */
+ tc358768_write(dev, TC358768_STARTCNTRL, 1);
+
+ if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+ /* Set pulse mode */
+ tc358768_write(dev, TC358768_DSI_EVENT, 0);
+
+ /* vact */
+ tc358768_write(dev, TC358768_DSI_VACT, dt->vactive.typ);
+ /* vsw */
+ tc358768_write(dev, TC358768_DSI_VSW, dt->vsync_len.typ);
+ /* vbp */
+ tc358768_write(dev, TC358768_DSI_VBPR, dt->vback_porch.typ);
+ } else {
+ /* Set event mode */
+ tc358768_write(dev, TC358768_DSI_EVENT, 1);
+
+ /* vact */
+ tc358768_write(dev, TC358768_DSI_VACT, dt->vactive.typ);
+
+ /* vsw (+ vbp) */
+ tc358768_write(dev, TC358768_DSI_VSW,
+ dt->vsync_len.typ + dt->vback_porch.typ);
+ /* vbp (not used in event mode) */
+ tc358768_write(dev, TC358768_DSI_VBPR, 0);
+ }
+
+ /* hsw (bytes) */
+ tc358768_write(dev, TC358768_DSI_HSW, dsi_hsw);
+
+ /* hbp (bytes) */
+ tc358768_write(dev, TC358768_DSI_HBPR, dsi_hbp);
+
+ /* hact (bytes) */
+ tc358768_write(dev, TC358768_DSI_HACT, hact);
+
+ /* VSYNC polarity */
+ tc358768_update_bits(dev, TC358768_CONFCTL, BIT(5),
+ (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH) ? BIT(5) : 0);
+
+ /* HSYNC polarity */
+ tc358768_update_bits(dev, TC358768_PP_MISC, BIT(0),
+ (dt->flags & DISPLAY_FLAGS_HSYNC_LOW) ? BIT(0) : 0);
+
+ /* Start DSI Tx */
+ tc358768_write(dev, TC358768_DSI_START, 0x1);
+
+ /* Configure DSI_Control register */
+ val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL;
+ val |= TC358768_DSI_CONTROL_TXMD | TC358768_DSI_CONTROL_HSCKMD |
+ 0x3 << 1 | TC358768_DSI_CONTROL_EOTDIS;
+ tc358768_write(dev, TC358768_DSI_CONFW, val);
+
+ val = TC358768_DSI_CONFW_MODE_SET | TC358768_DSI_CONFW_ADDR_DSI_CONTROL;
+ val |= (device->lanes - 1) << 1;
+
+ val |= TC358768_DSI_CONTROL_TXMD;
+
+ if (!(device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+ val |= TC358768_DSI_CONTROL_HSCKMD;
+
+ /*
+ * TODO: Actually MIPI_DSI_MODE_NO_EOT_PACKET
+ *
+ * Many of the DSI flags have names opposite to their
+ * actual effects, e.g. MIPI_DSI_MODE_EOT_PACKET means
+ * that EoT packets will actually be disabled.
+ */
+ if (device->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
+ val |= TC358768_DSI_CONTROL_EOTDIS;
+
+ tc358768_write(dev, TC358768_DSI_CONFW, val);
+
+ val = TC358768_DSI_CONFW_MODE_CLR |
+ TC358768_DSI_CONFW_ADDR_DSI_CONTROL |
+ TC358768_DSI_CONTROL_DIS_MODE; /* DSI mode */
+ tc358768_write(dev, TC358768_DSI_CONFW, val);
+
+ /* clear FrmStop and RstPtr */
+ tc358768_update_bits(dev, TC358768_PP_MISC, 0x3 << 14, 0);
+
+ /* set PP_en */
+ tc358768_update_bits(dev, TC358768_CONFCTL, BIT(6), BIT(6));
+
+ /* Set up panel configuration */
+ return panel_enable_backlight(priv->panel);
+}
+
+static int tc358768_set_backlight(struct udevice *dev, int percent)
+{
+ struct tc358768_priv *priv = dev_get_priv(dev);
+
+ return panel_set_backlight(priv->panel, percent);
+}
+
+static int tc358768_panel_timings(struct udevice *dev,
+ struct display_timing *timing)
+{
+ struct tc358768_priv *priv = dev_get_priv(dev);
+
+ /* Default to positive sync */
+
+ if (!(priv->timing.flags &
+ (DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_HSYNC_HIGH)))
+ priv->timing.flags |= DISPLAY_FLAGS_HSYNC_HIGH;
+
+ if (!(priv->timing.flags &
+ (DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_VSYNC_HIGH)))
+ priv->timing.flags |= DISPLAY_FLAGS_VSYNC_HIGH;
+
+ memcpy(timing, &priv->timing, sizeof(*timing));
+
+ return 0;
+}
+
+static int tc358768_setup(struct udevice *dev)
+{
+ struct tc358768_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct mipi_dsi_panel_plat *mipi_plat;
+ int ret;
+
+ /* The bridge uses 16 bit registers */
+ ret = i2c_set_chip_offset_len(dev, 2);
+ if (ret) {
+ log_debug("%s: set_chip_offset_len failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev,
+ "panel", &priv->panel);
+ if (ret) {
+ log_debug("%s: Cannot get panel: ret=%d\n", __func__, ret);
+ return log_ret(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 = &tc358768_dsi_host_ops;
+
+ device->host = &priv->host;
+ device->lanes = mipi_plat->lanes;
+ device->format = mipi_plat->format;
+ device->mode_flags = mipi_plat->mode_flags;
+
+ priv->pd_lines = mipi_dsi_pixel_format_to_bpp(device->format);
+ priv->dsi_lanes = device->lanes;
+
+ /* get regulators */
+ ret = device_get_supply_regulator(dev, "vddc-supply", &priv->vddc);
+ if (ret) {
+ log_debug("%s: vddc regulator error: %d\n", __func__, ret);
+ if (ret != -ENOENT)
+ return log_ret(ret);
+ }
+
+ ret = device_get_supply_regulator(dev, "vddmipi-supply", &priv->vddmipi);
+ if (ret) {
+ log_debug("%s: vddmipi regulator error: %d\n", __func__, ret);
+ if (ret != -ENOENT)
+ return log_ret(ret);
+ }
+
+ ret = device_get_supply_regulator(dev, "vddio-supply", &priv->vddio);
+ if (ret) {
+ log_debug("%s: vddio regulator error: %d\n", __func__, ret);
+ if (ret != -ENOENT)
+ return log_ret(ret);
+ }
+
+ /* get clk */
+ priv->refclk = devm_clk_get(dev, "refclk");
+ if (IS_ERR(priv->refclk)) {
+ log_debug("%s: Could not get refclk: %ld\n",
+ __func__, PTR_ERR(priv->refclk));
+ return PTR_ERR(priv->refclk);
+ }
+
+ /* get gpios */
+ ret = gpio_request_by_name(dev, "reset-gpios", 0,
+ &priv->reset_gpio, GPIOD_IS_OUT);
+ if (ret) {
+ log_debug("%s: Could not decode reset-gpios (%d)\n", __func__, ret);
+ return ret;
+ }
+
+ dm_gpio_set_value(&priv->reset_gpio, 1);
+
+ return 0;
+}
+
+static int tc358768_probe(struct udevice *dev)
+{
+ if (device_get_uclass_id(dev->parent) != UCLASS_I2C)
+ return -EPROTONOSUPPORT;
+
+ return tc358768_setup(dev);
+}
+
+struct panel_ops tc358768_ops = {
+ .enable_backlight = tc358768_attach,
+ .set_backlight = tc358768_set_backlight,
+ .get_display_timing = tc358768_panel_timings,
+};
+
+static const struct udevice_id tc358768_ids[] = {
+ { .compatible = "toshiba,tc358768" },
+ { .compatible = "toshiba,tc358778" },
+ { }
+};
+
+U_BOOT_DRIVER(tc358768) = {
+ .name = "tc358768",
+ .id = UCLASS_PANEL,
+ .of_match = tc358768_ids,
+ .ops = &tc358768_ops,
+ .probe = tc358768_probe,
+ .priv_auto = sizeof(struct tc358768_priv),
+};
diff --git a/drivers/video/dw_hdmi.c b/drivers/video/dw_hdmi.c
index c4fbb18..c217af9 100644
--- a/drivers/video/dw_hdmi.c
+++ b/drivers/video/dw_hdmi.c
@@ -78,10 +78,10 @@ static void dw_hdmi_write(struct dw_hdmi *hdmi, u8 val, int offset)
{
switch (hdmi->reg_io_width) {
case 1:
- writeb(val, hdmi->ioaddr + offset);
+ writeb(val, (void *)(hdmi->ioaddr + offset));
break;
case 4:
- writel(val, hdmi->ioaddr + (offset << 2));
+ writel(val, (void *)(hdmi->ioaddr + (offset << 2)));
break;
default:
debug("reg_io_width has unsupported width!\n");
@@ -93,9 +93,9 @@ static u8 dw_hdmi_read(struct dw_hdmi *hdmi, int offset)
{
switch (hdmi->reg_io_width) {
case 1:
- return readb(hdmi->ioaddr + offset);
+ return readb((void *)(hdmi->ioaddr + offset));
case 4:
- return readl(hdmi->ioaddr + (offset << 2));
+ return readl((void *)(hdmi->ioaddr + (offset << 2)));
default:
debug("reg_io_width has unsupported width!\n");
break;
@@ -936,6 +936,22 @@ int dw_hdmi_phy_wait_for_hpd(struct dw_hdmi *hdmi)
return -1;
}
+int dw_hdmi_detect_hpd(struct dw_hdmi *hdmi)
+{
+ int ret;
+
+ ret = dw_hdmi_phy_wait_for_hpd(hdmi);
+ if (ret < 0) {
+ debug("hdmi can not get hpd signal\n");
+ return -ENODEV;
+ }
+
+ if (hdmi->ops && hdmi->ops->read_hpd)
+ hdmi->ops->read_hpd(hdmi, true);
+
+ return 0;
+}
+
void dw_hdmi_phy_init(struct dw_hdmi *hdmi)
{
/* enable phy i2cm done irq */
@@ -988,7 +1004,7 @@ int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid)
hdmi_av_composer(hdmi, edid);
- ret = hdmi->phy_set(hdmi, edid->pixelclock.typ);
+ ret = hdmi->ops->phy_set(hdmi, edid->pixelclock.typ);
if (ret)
return ret;
@@ -1009,10 +1025,18 @@ int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid)
return 0;
}
+static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = {
+ .phy_set = dw_hdmi_phy_cfg,
+};
+
void dw_hdmi_init(struct dw_hdmi *hdmi)
{
uint ih_mute;
+ /* hook Synopsys PHYs ops */
+ if (!hdmi->ops)
+ hdmi->ops = &dw_hdmi_synopsys_phy_ops;
+
/*
* boot up defaults are:
* hdmi_ih_mute = 0x03 (disabled)
@@ -1037,4 +1061,7 @@ void dw_hdmi_init(struct dw_hdmi *hdmi)
/* enable i2c client nack % arbitration error irq */
hdmi_write(hdmi, ~0x44, HDMI_I2CM_CTLINT);
+
+ if (hdmi->ops && hdmi->ops->setup_hpd)
+ hdmi->ops->setup_hpd(hdmi);
}
diff --git a/drivers/video/endeavoru-panel.c b/drivers/video/endeavoru-panel.c
index 79a2721..1bff641 100644
--- a/drivers/video/endeavoru-panel.c
+++ b/drivers/video/endeavoru-panel.c
@@ -57,61 +57,8 @@ static void dcs_write_one(struct mipi_dsi_device *dsi, u8 cmd, u8 data)
static int endeavoru_panel_enable_backlight(struct udevice *dev)
{
- struct endeavoru_panel_priv *priv = dev_get_priv(dev);
- int ret;
-
- ret = dm_gpio_set_value(&priv->reset_gpio, 1);
- if (ret) {
- log_err("error changing reset-gpios (%d)\n", ret);
- return ret;
- }
- mdelay(5);
-
- ret = regulator_set_enable_if_allowed(priv->vddio, 1);
- if (ret) {
- log_err("error enabling iovcc-supply (%d)\n", ret);
- return ret;
- }
- mdelay(1);
-
- ret = regulator_set_enable_if_allowed(priv->vdd, 1);
- if (ret) {
- log_err("error enabling vcc-supply (%d)\n", ret);
- return ret;
- }
- mdelay(20);
-
- ret = dm_gpio_set_value(&priv->reset_gpio, 0);
- if (ret) {
- log_err("error changing reset-gpios (%d)\n", ret);
- return ret;
- }
- mdelay(2);
-
- /* Reset panel */
- ret = dm_gpio_set_value(&priv->reset_gpio, 1);
- if (ret) {
- log_err("error changing reset-gpios (%d)\n", ret);
- return ret;
- }
- mdelay(1);
-
- ret = dm_gpio_set_value(&priv->reset_gpio, 0);
- if (ret) {
- log_err("error changing reset-gpios (%d)\n", ret);
- return ret;
- }
- mdelay(25);
-
- return 0;
-}
-
-static int endeavoru_panel_set_backlight(struct udevice *dev, int percent)
-{
- struct endeavoru_panel_priv *priv = dev_get_priv(dev);
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
struct mipi_dsi_device *dsi = plat->device;
- int ret;
dcs_write_one(dsi, 0xc2, 0x08);
@@ -160,18 +107,22 @@ static int endeavoru_panel_set_backlight(struct udevice *dev, int percent)
dcs_write_one(dsi, 0x55, 0x80);
dcs_write_one(dsi, 0x5e, 0x06);
- ret = backlight_enable(priv->backlight);
- if (ret)
- return ret;
-
/* Set backlight */
dcs_write_one(dsi, 0x51, 0x96);
- ret = backlight_set_brightness(priv->backlight, percent);
+ return 0;
+}
+
+static int endeavoru_panel_set_backlight(struct udevice *dev, int percent)
+{
+ struct endeavoru_panel_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = backlight_enable(priv->backlight);
if (ret)
return ret;
- return 0;
+ return backlight_set_brightness(priv->backlight, percent);
}
static int endeavoru_panel_timings(struct udevice *dev,
@@ -217,6 +168,63 @@ static int endeavoru_panel_of_to_plat(struct udevice *dev)
return 0;
}
+static int endeavoru_panel_hw_init(struct udevice *dev)
+{
+ struct endeavoru_panel_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(5);
+
+ ret = regulator_set_enable_if_allowed(priv->vddio, 1);
+ if (ret) {
+ log_debug("%s: error enabling iovcc-supply (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(1);
+
+ ret = regulator_set_enable_if_allowed(priv->vdd, 1);
+ if (ret) {
+ log_debug("%s: error enabling vcc-supply (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(20);
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(2);
+
+ /* Reset panel */
+ ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(1);
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(25);
+
+ return 0;
+}
+
static int endeavoru_panel_probe(struct udevice *dev)
{
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
@@ -226,7 +234,7 @@ static int endeavoru_panel_probe(struct udevice *dev)
plat->format = MIPI_DSI_FMT_RGB888;
plat->mode_flags = MIPI_DSI_MODE_VIDEO;
- return 0;
+ return endeavoru_panel_hw_init(dev);
}
static const struct panel_ops endeavoru_panel_ops = {
diff --git a/drivers/video/lg-ld070wx3.c b/drivers/video/lg-ld070wx3.c
new file mode 100644
index 0000000..610a06f
--- /dev/null
+++ b/drivers/video/lg-ld070wx3.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LG LD070WX3-SL01 DSI panel driver
+ *
+ * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <backlight.h>
+#include <dm.h>
+#include <panel.h>
+#include <log.h>
+#include <mipi_dsi.h>
+#include <linux/delay.h>
+#include <power/regulator.h>
+
+struct lg_ld070wx3_priv {
+ struct udevice *vdd;
+ struct udevice *vcc;
+
+ struct udevice *backlight;
+};
+
+static struct display_timing default_timing = {
+ .pixelclock.typ = 70000000,
+ .hactive.typ = 800,
+ .hfront_porch.typ = 32,
+ .hback_porch.typ = 48,
+ .hsync_len.typ = 8,
+ .vactive.typ = 1280,
+ .vfront_porch.typ = 5,
+ .vback_porch.typ = 3,
+ .vsync_len.typ = 1,
+};
+
+static void dcs_write_one(struct mipi_dsi_device *dsi, u8 cmd, u8 data)
+{
+ mipi_dsi_dcs_write(dsi, cmd, &data, 1);
+}
+
+static int lg_ld070wx3_enable_backlight(struct udevice *dev)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+ struct mipi_dsi_device *dsi = plat->device;
+ int ret;
+
+ ret = mipi_dsi_dcs_soft_reset(dsi);
+ if (ret < 0) {
+ log_debug("%s: failed to soft reset panel: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Delay before sending new command after soft reset */
+ mdelay(20);
+
+ /* Differential input impedance selection */
+ dcs_write_one(dsi, 0xAE, 0x0B);
+
+ /* Enter test mode 1 and 2*/
+ dcs_write_one(dsi, 0xEE, 0xEA);
+ dcs_write_one(dsi, 0xEF, 0x5F);
+
+ /* Increased MIPI CLK driving ability */
+ dcs_write_one(dsi, 0xF2, 0x68);
+
+ /* Exit test mode 1 and 2 */
+ dcs_write_one(dsi, 0xEE, 0x00);
+ dcs_write_one(dsi, 0xEF, 0x00);
+
+ return 0;
+}
+
+static int lg_ld070wx3_set_backlight(struct udevice *dev, int percent)
+{
+ struct lg_ld070wx3_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = backlight_enable(priv->backlight);
+ if (ret)
+ return ret;
+
+ return backlight_set_brightness(priv->backlight, percent);
+}
+
+static int lg_ld070wx3_timings(struct udevice *dev,
+ struct display_timing *timing)
+{
+ memcpy(timing, &default_timing, sizeof(*timing));
+ return 0;
+}
+
+static int lg_ld070wx3_of_to_plat(struct udevice *dev)
+{
+ struct lg_ld070wx3_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
+ "backlight", &priv->backlight);
+ if (ret) {
+ log_debug("%s: cannot get backlight: ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
+ "vdd-supply", &priv->vdd);
+ if (ret) {
+ log_debug("%s: cannot get vdd-supply: ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
+ "vcc-supply", &priv->vcc);
+ if (ret) {
+ log_debug("%s: cannot get vcc-supply: ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lg_ld070wx3_hw_init(struct udevice *dev)
+{
+ struct lg_ld070wx3_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = regulator_set_enable_if_allowed(priv->vcc, 1);
+ if (ret) {
+ log_debug("%s: enabling vcc-supply failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = regulator_set_enable_if_allowed(priv->vdd, 1);
+ if (ret) {
+ log_debug("%s: enabling vdd-supply failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /*
+ * According to spec delay between enabling supply is 0,
+ * for regulators to reach required voltage ~5ms needed.
+ * MIPI interface signal for setup requires additional
+ * 110ms which in total results in 115ms.
+ */
+ mdelay(115);
+
+ return 0;
+}
+
+static int lg_ld070wx3_probe(struct udevice *dev)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+
+ /* fill characteristics of DSI data link */
+ plat->lanes = 4;
+ plat->format = MIPI_DSI_FMT_RGB888;
+ plat->mode_flags = MIPI_DSI_MODE_VIDEO;
+
+ return lg_ld070wx3_hw_init(dev);
+}
+
+static const struct panel_ops lg_ld070wx3_ops = {
+ .enable_backlight = lg_ld070wx3_enable_backlight,
+ .set_backlight = lg_ld070wx3_set_backlight,
+ .get_display_timing = lg_ld070wx3_timings,
+};
+
+static const struct udevice_id lg_ld070wx3_ids[] = {
+ { .compatible = "lg,ld070wx3-sl01" },
+ { }
+};
+
+U_BOOT_DRIVER(lg_ld070wx3) = {
+ .name = "lg_ld070wx3",
+ .id = UCLASS_PANEL,
+ .of_match = lg_ld070wx3_ids,
+ .ops = &lg_ld070wx3_ops,
+ .of_to_plat = lg_ld070wx3_of_to_plat,
+ .probe = lg_ld070wx3_probe,
+ .plat_auto = sizeof(struct mipi_dsi_panel_plat),
+ .priv_auto = sizeof(struct lg_ld070wx3_priv),
+};
diff --git a/drivers/video/meson/meson_dw_hdmi.c b/drivers/video/meson/meson_dw_hdmi.c
index 5db0190..259af1b 100644
--- a/drivers/video/meson/meson_dw_hdmi.c
+++ b/drivers/video/meson/meson_dw_hdmi.c
@@ -375,6 +375,10 @@ static int meson_dw_hdmi_wait_hpd(struct dw_hdmi *hdmi)
return -ETIMEDOUT;
}
+static const struct dw_hdmi_phy_ops dw_hdmi_meson_phy_ops = {
+ .phy_set = meson_dw_hdmi_phy_init,
+};
+
static int meson_dw_hdmi_probe(struct udevice *dev)
{
struct meson_dw_hdmi *priv = dev_get_priv(dev);
@@ -397,7 +401,7 @@ static int meson_dw_hdmi_probe(struct udevice *dev)
priv->hdmi.hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
priv->hdmi.hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_YUV8_1X24;
- priv->hdmi.phy_set = meson_dw_hdmi_phy_init;
+ priv->hdmi.ops = &dw_hdmi_meson_phy_ops;
if (meson_hdmi_is_compatible(priv, HDMI_COMPATIBLE_G12A))
priv->hdmi.reg_io_width = 1;
else {
diff --git a/drivers/video/renesas-r61307.c b/drivers/video/renesas-r61307.c
index 426fdc6..1eccaf6 100644
--- a/drivers/video/renesas-r61307.c
+++ b/drivers/video/renesas-r61307.c
@@ -120,42 +120,6 @@ static struct display_timing default_timing = {
static int renesas_r61307_enable_backlight(struct udevice *dev)
{
struct renesas_r61307_priv *priv = dev_get_priv(dev);
- int ret;
-
- ret = regulator_set_enable_if_allowed(priv->vcc, 1);
- if (ret) {
- log_err("enabling vcc-supply failed (%d)\n", ret);
- return ret;
- }
- mdelay(5);
-
- ret = regulator_set_enable_if_allowed(priv->iovcc, 1);
- if (ret) {
- log_err("enabling iovcc-supply failed (%d)\n", ret);
- return ret;
- }
-
- ret = dm_gpio_set_value(&priv->reset_gpio, 0);
- if (ret) {
- log_err("changing reset-gpio failed (%d)\n", ret);
- return ret;
- }
- mdelay(5);
-
- ret = dm_gpio_set_value(&priv->reset_gpio, 1);
- if (ret) {
- log_err("changing reset-gpio failed (%d)\n", ret);
- return ret;
- }
-
- mdelay(5);
-
- return 0;
-}
-
-static int renesas_r61307_set_backlight(struct udevice *dev, int percent)
-{
- struct renesas_r61307_priv *priv = dev_get_priv(dev);
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
struct mipi_dsi_device *dsi = plat->device;
int ret;
@@ -205,18 +169,23 @@ static int renesas_r61307_set_backlight(struct udevice *dev, int percent)
log_err("failed to set display on: %d\n", ret);
return ret;
}
-
mdelay(50);
+ return 0;
+}
+
+static int renesas_r61307_set_backlight(struct udevice *dev, int percent)
+{
+ struct renesas_r61307_priv *priv = dev_get_priv(dev);
+ int ret;
+
ret = backlight_enable(priv->backlight);
if (ret)
return ret;
- ret = backlight_set_brightness(priv->backlight, percent);
- if (ret)
- return ret;
+ mdelay(5);
- return 0;
+ return backlight_set_brightness(priv->backlight, percent);
}
static int renesas_r61307_timings(struct udevice *dev,
@@ -266,6 +235,46 @@ static int renesas_r61307_of_to_plat(struct udevice *dev)
return 0;
}
+static int renesas_r61307_hw_init(struct udevice *dev)
+{
+ struct renesas_r61307_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = regulator_set_enable_if_allowed(priv->vcc, 1);
+ if (ret) {
+ log_debug("%s: enabling vcc-supply failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(5);
+
+ ret = regulator_set_enable_if_allowed(priv->iovcc, 1);
+ if (ret) {
+ log_debug("%s: enabling iovcc-supply failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+ if (ret) {
+ log_debug("%s: changing reset-gpio failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(5);
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+ if (ret) {
+ log_debug("%s: changing reset-gpio failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ mdelay(5);
+
+ return 0;
+}
+
static int renesas_r61307_probe(struct udevice *dev)
{
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
@@ -275,7 +284,7 @@ static int renesas_r61307_probe(struct udevice *dev)
plat->format = MIPI_DSI_FMT_RGB888;
plat->mode_flags = MIPI_DSI_MODE_VIDEO;
- return 0;
+ return renesas_r61307_hw_init(dev);
}
static const struct panel_ops renesas_r61307_ops = {
diff --git a/drivers/video/renesas-r69328.c b/drivers/video/renesas-r69328.c
index d2f7169..ecf89ec 100644
--- a/drivers/video/renesas-r69328.c
+++ b/drivers/video/renesas-r69328.c
@@ -65,37 +65,6 @@ static struct display_timing default_timing = {
static int renesas_r69328_enable_backlight(struct udevice *dev)
{
- struct renesas_r69328_priv *priv = dev_get_priv(dev);
- int ret;
-
- ret = dm_gpio_set_value(&priv->enable_gpio, 1);
- if (ret) {
- log_err("error changing enable-gpios (%d)\n", ret);
- return ret;
- }
- mdelay(5);
-
- ret = dm_gpio_set_value(&priv->reset_gpio, 0);
- if (ret) {
- log_err("error changing reset-gpios (%d)\n", ret);
- return ret;
- }
- mdelay(5);
-
- ret = dm_gpio_set_value(&priv->reset_gpio, 1);
- if (ret) {
- log_err("error changing reset-gpios (%d)\n", ret);
- return ret;
- }
-
- mdelay(5);
-
- return 0;
-}
-
-static int renesas_r69328_set_backlight(struct udevice *dev, int percent)
-{
- struct renesas_r69328_priv *priv = dev_get_priv(dev);
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
struct mipi_dsi_device *dsi = plat->device;
int ret;
@@ -153,18 +122,23 @@ static int renesas_r69328_set_backlight(struct udevice *dev, int percent)
log_err("failed to set display on: %d\n", ret);
return ret;
}
-
mdelay(50);
+ return 0;
+}
+
+static int renesas_r69328_set_backlight(struct udevice *dev, int percent)
+{
+ struct renesas_r69328_priv *priv = dev_get_priv(dev);
+ int ret;
+
ret = backlight_enable(priv->backlight);
if (ret)
return ret;
- ret = backlight_set_brightness(priv->backlight, percent);
- if (ret)
- return ret;
+ mdelay(5);
- return 0;
+ return backlight_set_brightness(priv->backlight, percent);
}
static int renesas_r69328_timings(struct udevice *dev,
@@ -203,6 +177,39 @@ static int renesas_r69328_of_to_plat(struct udevice *dev)
return 0;
}
+static int renesas_r69328_hw_init(struct udevice *dev)
+{
+ struct renesas_r69328_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = dm_gpio_set_value(&priv->enable_gpio, 1);
+ if (ret) {
+ log_debug("%s: error changing enable-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(5);
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 0);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(5);
+
+ ret = dm_gpio_set_value(&priv->reset_gpio, 1);
+ if (ret) {
+ log_debug("%s: error changing reset-gpios (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ mdelay(5);
+
+ return 0;
+}
+
static int renesas_r69328_probe(struct udevice *dev)
{
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
@@ -212,7 +219,7 @@ static int renesas_r69328_probe(struct udevice *dev)
plat->format = MIPI_DSI_FMT_RGB888;
plat->mode_flags = MIPI_DSI_MODE_VIDEO;
- return 0;
+ return renesas_r69328_hw_init(dev);
}
static const struct panel_ops renesas_r69328_ops = {
diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile
index 8128289..f55bece 100644
--- a/drivers/video/rockchip/Makefile
+++ b/drivers/video/rockchip/Makefile
@@ -6,10 +6,12 @@
ifdef CONFIG_VIDEO_ROCKCHIP
obj-y += rk_vop.o
obj-$(CONFIG_ROCKCHIP_RK3288) += rk3288_vop.o
+obj-$(CONFIG_ROCKCHIP_RK3328) += rk3328_vop.o
obj-$(CONFIG_ROCKCHIP_RK3399) += rk3399_vop.o
obj-$(CONFIG_DISPLAY_ROCKCHIP_EDP) += rk_edp.o
obj-$(CONFIG_DISPLAY_ROCKCHIP_LVDS) += rk_lvds.o
obj-hdmi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_hdmi.o
+obj-hdmi-$(CONFIG_ROCKCHIP_RK3328) += rk3328_hdmi.o
obj-hdmi-$(CONFIG_ROCKCHIP_RK3399) += rk3399_hdmi.o
obj-$(CONFIG_DISPLAY_ROCKCHIP_HDMI) += rk_hdmi.o $(obj-hdmi-y)
obj-mipi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_mipi.o
diff --git a/drivers/video/rockchip/rk3328_hdmi.c b/drivers/video/rockchip/rk3328_hdmi.c
new file mode 100644
index 0000000..763669c
--- /dev/null
+++ b/drivers/video/rockchip/rk3328_hdmi.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd.
+ */
+
+#include <clk.h>
+#include <display.h>
+#include <dm.h>
+#include <dw_hdmi.h>
+#include <asm/io.h>
+#include <asm/arch-rockchip/grf_rk3328.h>
+#include "rk_hdmi.h"
+
+#define RK3328_IO_3V_DOMAIN (7 << (9 + 16))
+#define RK3328_IO_5V_DOMAIN ((7 << 9) | (3 << (9 + 16)))
+#define RK3328_IO_DDC_IN_MSK ((3 << 10) | (3 << (10 + 16)))
+#define RK3328_IO_CTRL_BY_HDMI ((1 << 13) | (1 << (13 + 16)))
+
+static int rk3328_hdmi_enable(struct udevice *dev, int panel_bpp,
+ const struct display_timing *edid)
+{
+ struct rk_hdmi_priv *priv = dev_get_priv(dev);
+
+ return dw_hdmi_enable(&priv->hdmi, edid);
+}
+
+static int rk3328_dw_hdmi_phy_cfg(struct dw_hdmi *hdmi, uint pixclock)
+{
+ struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi);
+ int ret;
+
+ ret = generic_phy_init(&priv->phy);
+ if (ret) {
+ printf("failed to init phy (ret=%d)\n", ret);
+ return ret;
+ }
+
+ ret = generic_phy_power_on(&priv->phy);
+ if (ret) {
+ printf("failed to power on phy (ret=%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void rk3328_dw_hdmi_setup_hpd(struct dw_hdmi *hdmi)
+{
+ struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi);
+ struct rk3328_grf_regs *grf = priv->grf;
+
+ writel(RK3328_IO_DDC_IN_MSK, &grf->soc_con[2]);
+ writel(RK3328_IO_CTRL_BY_HDMI, &grf->soc_con[3]);
+}
+
+static void rk3328_dw_hdmi_read_hpd(struct dw_hdmi *hdmi, bool hpd_status)
+{
+ struct rk_hdmi_priv *priv = container_of(hdmi, struct rk_hdmi_priv, hdmi);
+ struct rk3328_grf_regs *grf = priv->grf;
+
+ if (hpd_status)
+ writel(RK3328_IO_5V_DOMAIN, &grf->soc_con[4]);
+ else
+ writel(RK3328_IO_3V_DOMAIN, &grf->soc_con[4]);
+}
+
+static const struct dw_hdmi_phy_ops dw_hdmi_rk3328_phy_ops = {
+ .phy_set = rk3328_dw_hdmi_phy_cfg,
+ .setup_hpd = rk3328_dw_hdmi_setup_hpd,
+ .read_hpd = rk3328_dw_hdmi_read_hpd,
+};
+
+static int rk3328_hdmi_of_to_plat(struct udevice *dev)
+{
+ struct rk_hdmi_priv *priv = dev_get_priv(dev);
+ struct dw_hdmi *hdmi = &priv->hdmi;
+
+ hdmi->i2c_clk_high = 0x71;
+ hdmi->i2c_clk_low = 0x76;
+
+ rk_hdmi_of_to_plat(dev);
+
+ hdmi->ops = &dw_hdmi_rk3328_phy_ops;
+
+ return 0;
+}
+
+static int rk3328_hdmi_probe(struct udevice *dev)
+{
+ struct rk_hdmi_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = generic_phy_get_by_name(dev, "hdmi", &priv->phy);
+ if (ret) {
+ printf("failed to get hdmi phy\n");
+ return ret;
+ };
+
+ ret = rk_hdmi_probe(dev);
+ if (ret) {
+ printf("failed to probe rk hdmi\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dm_display_ops rk3328_hdmi_ops = {
+ .read_edid = rk_hdmi_read_edid,
+ .enable = rk3328_hdmi_enable,
+};
+
+static const struct udevice_id rk3328_hdmi_ids[] = {
+ { .compatible = "rockchip,rk3328-dw-hdmi" },
+ { }
+};
+
+U_BOOT_DRIVER(rk3328_hdmi_rockchip) = {
+ .name = "rk3328_hdmi_rockchip",
+ .id = UCLASS_DISPLAY,
+ .of_match = rk3328_hdmi_ids,
+ .ops = &rk3328_hdmi_ops,
+ .of_to_plat = rk3328_hdmi_of_to_plat,
+ .probe = rk3328_hdmi_probe,
+ .priv_auto = sizeof(struct rk_hdmi_priv),
+};
diff --git a/drivers/video/rockchip/rk3328_vop.c b/drivers/video/rockchip/rk3328_vop.c
new file mode 100644
index 0000000..55233f1
--- /dev/null
+++ b/drivers/video/rockchip/rk3328_vop.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd.
+ */
+
+#include <dm.h>
+#include <video.h>
+#include <asm/io.h>
+#include "rk_vop.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static void rk3328_set_pin_polarity(struct udevice *dev,
+ enum vop_modes mode, u32 polarity)
+{
+ struct rk_vop_priv *priv = dev_get_priv(dev);
+ struct rk3288_vop *regs = priv->regs;
+
+ switch (mode) {
+ case VOP_MODE_HDMI:
+ clrsetbits_le32(&regs->dsp_ctrl1,
+ M_RK3399_DSP_HDMI_POL,
+ V_RK3399_DSP_HDMI_POL(polarity));
+ break;
+ default:
+ debug("%s: unsupported output mode %x\n", __func__, mode);
+ }
+}
+
+static int rk3328_vop_probe(struct udevice *dev)
+{
+ /* Before relocation we don't need to do anything */
+ if (!(gd->flags & GD_FLG_RELOC))
+ return 0;
+
+ return rk_vop_probe(dev);
+}
+
+static int rk3328_vop_remove(struct udevice *dev)
+{
+ struct rk_vop_priv *priv = dev_get_priv(dev);
+ struct rk3288_vop *regs = priv->regs;
+ struct rk3288_vop *win_regs = priv->regs + priv->win_offset;
+
+ /* FIXME: Explicit disabling of WIN0 is needed to avoid iommu
+ * page-fault in Linux, better handling of iommu-address in
+ * Linux might drop this.
+ */
+ clrbits_le32(&win_regs->win0_ctrl0, M_WIN0_EN);
+ writel(0x01, &regs->reg_cfg_done);
+
+ return 0;
+}
+
+struct rkvop_driverdata rk3328_driverdata = {
+ .dsp_offset = 0x490,
+ .win_offset = 0xd0,
+ .features = VOP_FEATURE_OUTPUT_10BIT,
+ .set_pin_polarity = rk3328_set_pin_polarity,
+};
+
+static const struct udevice_id rk3328_vop_ids[] = {
+ {
+ .compatible = "rockchip,rk3328-vop",
+ .data = (ulong)&rk3328_driverdata
+ },
+ { /* sentile */ }
+};
+
+static const struct video_ops rk3328_vop_ops = {
+};
+
+U_BOOT_DRIVER(rk3328_vop) = {
+ .name = "rk3328_vop",
+ .id = UCLASS_VIDEO,
+ .of_match = rk3328_vop_ids,
+ .ops = &rk3328_vop_ops,
+ .bind = rk_vop_bind,
+ .probe = rk3328_vop_probe,
+ .remove = rk3328_vop_remove,
+ .priv_auto = sizeof(struct rk_vop_priv),
+ .flags = DM_FLAG_PRE_RELOC | DM_FLAG_OS_PREPARE,
+};
diff --git a/drivers/video/rockchip/rk_hdmi.c b/drivers/video/rockchip/rk_hdmi.c
index 044a29e..d31f6a4 100644
--- a/drivers/video/rockchip/rk_hdmi.c
+++ b/drivers/video/rockchip/rk_hdmi.c
@@ -89,7 +89,6 @@ int rk_hdmi_of_to_plat(struct udevice *dev)
/* hdmi->i2c_clk_{high,low} are set up by the SoC driver */
hdmi->reg_io_width = 4;
- hdmi->phy_set = dw_hdmi_phy_cfg;
priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
@@ -111,14 +110,12 @@ int rk_hdmi_probe(struct udevice *dev)
struct dw_hdmi *hdmi = &priv->hdmi;
int ret;
- ret = dw_hdmi_phy_wait_for_hpd(hdmi);
- if (ret < 0) {
- debug("hdmi can not get hpd signal\n");
- return -1;
- }
-
dw_hdmi_init(hdmi);
dw_hdmi_phy_init(hdmi);
+ ret = dw_hdmi_detect_hpd(hdmi);
+ if (ret < 0)
+ return ret;
+
return 0;
}
diff --git a/drivers/video/rockchip/rk_hdmi.h b/drivers/video/rockchip/rk_hdmi.h
index 200dbae..dcfba3d 100644
--- a/drivers/video/rockchip/rk_hdmi.h
+++ b/drivers/video/rockchip/rk_hdmi.h
@@ -6,6 +6,8 @@
#ifndef __RK_HDMI_H__
#define __RK_HDMI_H__
+#include <generic-phy.h>
+
struct rkhdmi_driverdata {
/* configuration */
u8 i2c_clk_high;
@@ -19,6 +21,7 @@ struct rkhdmi_driverdata {
struct rk_hdmi_priv {
struct dw_hdmi hdmi;
+ struct phy phy;
void *grf;
};
diff --git a/drivers/video/rockchip/rk_vop.c b/drivers/video/rockchip/rk_vop.c
index c514e2a..acc02e5 100644
--- a/drivers/video/rockchip/rk_vop.c
+++ b/drivers/video/rockchip/rk_vop.c
@@ -39,11 +39,14 @@ enum vop_pol {
DCLK_INVERT = 3
};
-static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbbase,
+static void rkvop_enable(struct udevice *dev, ulong fbbase,
int fb_bits_per_pixel,
const struct display_timing *edid,
struct reset_ctl *dclk_rst)
{
+ struct rk_vop_priv *priv = dev_get_priv(dev);
+ struct rk3288_vop *regs = priv->regs;
+ struct rk3288_vop *win_regs = priv->regs + priv->win_offset;
u32 lb_mode;
u32 rgb_mode;
u32 hactive = edid->hactive.typ;
@@ -51,32 +54,32 @@ static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbb
int ret;
writel(V_ACT_WIDTH(hactive - 1) | V_ACT_HEIGHT(vactive - 1),
- &regs->win0_act_info);
+ &win_regs->win0_act_info);
writel(V_DSP_XST(edid->hsync_len.typ + edid->hback_porch.typ) |
V_DSP_YST(edid->vsync_len.typ + edid->vback_porch.typ),
- &regs->win0_dsp_st);
+ &win_regs->win0_dsp_st);
writel(V_DSP_WIDTH(hactive - 1) |
V_DSP_HEIGHT(vactive - 1),
- &regs->win0_dsp_info);
+ &win_regs->win0_dsp_info);
- clrsetbits_le32(&regs->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR,
+ clrsetbits_le32(&win_regs->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR,
V_WIN0_KEY_EN(0) | V_WIN0_KEY_COLOR(0));
switch (fb_bits_per_pixel) {
case 16:
rgb_mode = RGB565;
- writel(V_RGB565_VIRWIDTH(hactive), &regs->win0_vir);
+ writel(V_RGB565_VIRWIDTH(hactive), &win_regs->win0_vir);
break;
case 24:
rgb_mode = RGB888;
- writel(V_RGB888_VIRWIDTH(hactive), &regs->win0_vir);
+ writel(V_RGB888_VIRWIDTH(hactive), &win_regs->win0_vir);
break;
case 32:
default:
rgb_mode = ARGB8888;
- writel(V_ARGB888_VIRWIDTH(hactive), &regs->win0_vir);
+ writel(V_ARGB888_VIRWIDTH(hactive), &win_regs->win0_vir);
break;
}
@@ -89,12 +92,12 @@ static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbb
else
lb_mode = LB_RGB_1280X8;
- clrsetbits_le32(&regs->win0_ctrl0,
+ clrsetbits_le32(&win_regs->win0_ctrl0,
M_WIN0_LB_MODE | M_WIN0_DATA_FMT | M_WIN0_EN,
V_WIN0_LB_MODE(lb_mode) | V_WIN0_DATA_FMT(rgb_mode) |
V_WIN0_EN(1));
- writel(fbbase, &regs->win0_yrgb_mst);
+ writel(fbbase, &win_regs->win0_yrgb_mst);
writel(0x01, &regs->reg_cfg_done); /* enable reg config */
ret = reset_assert(dclk_rst);
@@ -162,6 +165,7 @@ static void rkvop_mode_set(struct udevice *dev,
{
struct rk_vop_priv *priv = dev_get_priv(dev);
struct rk3288_vop *regs = priv->regs;
+ struct rk3288_vop *dsp_regs = priv->regs + priv->dsp_offset;
struct rkvop_driverdata *data =
(struct rkvop_driverdata *)dev_get_driver_data(dev);
@@ -195,27 +199,27 @@ static void rkvop_mode_set(struct udevice *dev,
writel(V_HSYNC(hsync_len) |
V_HORPRD(hsync_len + hback_porch + hactive + hfront_porch),
- &regs->dsp_htotal_hs_end);
+ &dsp_regs->dsp_htotal_hs_end);
writel(V_HEAP(hsync_len + hback_porch + hactive) |
V_HASP(hsync_len + hback_porch),
- &regs->dsp_hact_st_end);
+ &dsp_regs->dsp_hact_st_end);
writel(V_VSYNC(vsync_len) |
V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch),
- &regs->dsp_vtotal_vs_end);
+ &dsp_regs->dsp_vtotal_vs_end);
writel(V_VAEP(vsync_len + vback_porch + vactive)|
V_VASP(vsync_len + vback_porch),
- &regs->dsp_vact_st_end);
+ &dsp_regs->dsp_vact_st_end);
writel(V_HEAP(hsync_len + hback_porch + hactive) |
V_HASP(hsync_len + hback_porch),
- &regs->post_dsp_hact_info);
+ &dsp_regs->post_dsp_hact_info);
writel(V_VAEP(vsync_len + vback_porch + vactive)|
V_VASP(vsync_len + vback_porch),
- &regs->post_dsp_vact_info);
+ &dsp_regs->post_dsp_vact_info);
writel(0x01, &regs->reg_cfg_done); /* enable reg config */
}
@@ -243,9 +247,7 @@ static void rkvop_mode_set(struct udevice *dev,
static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode ep_node)
{
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
- struct rk_vop_priv *priv = dev_get_priv(dev);
int vop_id, remote_vop_id;
- struct rk3288_vop *regs = priv->regs;
struct display_timing timing;
struct udevice *disp;
int ret;
@@ -380,7 +382,7 @@ static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode ep_node)
return ret;
}
- rkvop_enable(dev, regs, fbbase, 1 << l2bpp, &timing, &dclk_rst);
+ rkvop_enable(dev, fbbase, 1 << l2bpp, &timing, &dclk_rst);
ret = display_enable(disp, 1 << l2bpp, &timing);
if (ret)
@@ -415,6 +417,8 @@ int rk_vop_probe(struct udevice *dev)
{
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
struct rk_vop_priv *priv = dev_get_priv(dev);
+ struct rkvop_driverdata *ops =
+ (struct rkvop_driverdata *)dev_get_driver_data(dev);
int ret = 0;
ofnode port, node;
struct reset_ctl ahb_rst;
@@ -448,6 +452,8 @@ int rk_vop_probe(struct udevice *dev)
#endif
priv->regs = dev_read_addr_ptr(dev);
+ priv->win_offset = ops->win_offset;
+ priv->dsp_offset = ops->dsp_offset;
/*
* Try all the ports until we find one that works. In practice this
diff --git a/drivers/video/rockchip/rk_vop.h b/drivers/video/rockchip/rk_vop.h
index 0528fb2..eba68d8 100644
--- a/drivers/video/rockchip/rk_vop.h
+++ b/drivers/video/rockchip/rk_vop.h
@@ -11,6 +11,8 @@
struct rk_vop_priv {
void *grf;
void *regs;
+ int win_offset;
+ int dsp_offset;
};
enum vop_features {
@@ -18,6 +20,8 @@ enum vop_features {
};
struct rkvop_driverdata {
+ int win_offset;
+ int dsp_offset;
/* configuration */
u32 features;
/* block-specific setters/getters */
diff --git a/drivers/video/samsung-ltl106hl02.c b/drivers/video/samsung-ltl106hl02.c
new file mode 100644
index 0000000..5e6c11c
--- /dev/null
+++ b/drivers/video/samsung-ltl106hl02.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Samsung LTL106HL02-001 DSI panel driver
+ *
+ * Copyright (c) 2020 Anton Bambura <jenneron@protonmail.com>
+ * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ * Copyright (c) 2024 Jonas Schwöbel <jonasschwoebel@yahoo.de>
+ */
+
+#include <backlight.h>
+#include <dm.h>
+#include <panel.h>
+#include <log.h>
+#include <mipi_dsi.h>
+#include <asm/gpio.h>
+#include <linux/delay.h>
+#include <power/regulator.h>
+
+struct samsung_ltl106hl02_priv {
+ struct udevice *vdd;
+ struct udevice *backlight;
+
+ struct gpio_desc reset_gpio;
+};
+
+static struct display_timing default_timing = {
+ .pixelclock.typ = 137000000,
+ .hactive.typ = 1920,
+ .hfront_porch.typ = 32,
+ .hback_porch.typ = 64,
+ .hsync_len.typ = 32,
+ .vactive.typ = 1080,
+ .vfront_porch.typ = 2,
+ .vback_porch.typ = 26,
+ .vsync_len.typ = 3,
+};
+
+static int samsung_ltl106hl02_enable_backlight(struct udevice *dev)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+ struct mipi_dsi_device *dsi = plat->device;
+ int ret;
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ log_debug("%s: failed to exit sleep mode: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(70);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ log_debug("%s: failed to enable display: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ mdelay(5);
+
+ return 0;
+}
+
+static int samsung_ltl106hl02_set_backlight(struct udevice *dev, int percent)
+{
+ struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = backlight_enable(priv->backlight);
+ if (ret)
+ return ret;
+
+ return backlight_set_brightness(priv->backlight, percent);
+}
+
+static int samsung_ltl106hl02_timings(struct udevice *dev,
+ struct display_timing *timing)
+{
+ memcpy(timing, &default_timing, sizeof(*timing));
+ return 0;
+}
+
+static int samsung_ltl106hl02_of_to_plat(struct udevice *dev)
+{
+ struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
+ "backlight", &priv->backlight);
+ if (ret) {
+ log_debug("%s: cannot get backlight: ret = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
+ "vdd-supply", &priv->vdd);
+ if (ret)
+ log_debug("%s: cannot get vdd-supply: error %d\n",
+ __func__, ret);
+
+ ret = gpio_request_by_name(dev, "reset-gpios", 0,
+ &priv->reset_gpio, GPIOD_IS_OUT);
+ if (ret)
+ log_debug("%s: cannot get reset-gpios: error %d\n",
+ __func__, ret);
+
+ return 0;
+}
+
+static int samsung_ltl106hl02_hw_init(struct udevice *dev)
+{
+ struct samsung_ltl106hl02_priv *priv = dev_get_priv(dev);
+
+ dm_gpio_set_value(&priv->reset_gpio, 1);
+ regulator_set_enable_if_allowed(priv->vdd, 1);
+
+ /* Dataheets states at least 8.5 msec for vdd stabilization */
+ mdelay(10);
+
+ dm_gpio_set_value(&priv->reset_gpio, 0);
+
+ return 0;
+}
+
+static int samsung_ltl106hl02_probe(struct udevice *dev)
+{
+ struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
+
+ /* fill characteristics of DSI data link */
+ plat->lanes = 4;
+ plat->format = MIPI_DSI_FMT_RGB888;
+ plat->mode_flags = MIPI_DSI_MODE_VIDEO;
+
+ return samsung_ltl106hl02_hw_init(dev);
+}
+
+static const struct panel_ops samsung_ltl106hl02_ops = {
+ .enable_backlight = samsung_ltl106hl02_enable_backlight,
+ .set_backlight = samsung_ltl106hl02_set_backlight,
+ .get_display_timing = samsung_ltl106hl02_timings,
+};
+
+static const struct udevice_id samsung_ltl106hl02_ids[] = {
+ { .compatible = "samsung,ltl106hl02-001" },
+ { }
+};
+
+U_BOOT_DRIVER(samsung_ltl106hl02) = {
+ .name = "samsung_ltl106hl02",
+ .id = UCLASS_PANEL,
+ .of_match = samsung_ltl106hl02_ids,
+ .ops = &samsung_ltl106hl02_ops,
+ .of_to_plat = samsung_ltl106hl02_of_to_plat,
+ .probe = samsung_ltl106hl02_probe,
+ .plat_auto = sizeof(struct mipi_dsi_panel_plat),
+ .priv_auto = sizeof(struct samsung_ltl106hl02_priv),
+};
diff --git a/drivers/video/simple_panel.c b/drivers/video/simple_panel.c
index efb122b..76a3042 100644
--- a/drivers/video/simple_panel.c
+++ b/drivers/video/simple_panel.c
@@ -7,31 +7,22 @@
#include <common.h>
#include <backlight.h>
#include <dm.h>
+#include <edid.h>
+#include <i2c.h>
#include <log.h>
#include <mipi_dsi.h>
#include <panel.h>
#include <asm/gpio.h>
#include <power/regulator.h>
+#define EDID_I2C_ADDR 0x50
+
struct simple_panel_priv {
struct udevice *reg;
struct udevice *backlight;
struct gpio_desc enable;
};
-/* List of supported DSI panels */
-enum {
- PANEL_NON_DSI,
- PANASONIC_VVX10F004B00,
-};
-
-static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = {
- .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
- MIPI_DSI_CLOCK_NON_CONTINUOUS,
- .format = MIPI_DSI_FMT_RGB888,
- .lanes = 4,
-};
-
static int simple_panel_enable_backlight(struct udevice *dev)
{
struct simple_panel_priv *priv = dev_get_priv(dev);
@@ -62,13 +53,71 @@ static int simple_panel_set_backlight(struct udevice *dev, int percent)
return 0;
}
+#if CONFIG_IS_ENABLED(I2C_EDID) && CONFIG_IS_ENABLED(DM_I2C)
+static int simple_panel_get_edid_timing(struct udevice *dev,
+ struct display_timing *timings)
+{
+ struct udevice *panel_ddc, *panel_edid;
+ struct display_timing edid_timing;
+ u8 edid_buf[EDID_SIZE] = { 0 };
+ int ret, bpc;
+ /* Check for DDC i2c if no timings are provided */
+ ret = uclass_get_device_by_phandle(UCLASS_I2C, dev,
+ "ddc-i2c-bus",
+ &panel_ddc);
+ if (ret) {
+ log_debug("%s: cannot get DDC i2c bus: error %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = dm_i2c_probe(panel_ddc, EDID_I2C_ADDR, 0, &panel_edid);
+ if (ret) {
+ log_debug("%s: cannot probe EDID: error %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = dm_i2c_read(panel_edid, 0, edid_buf, sizeof(edid_buf));
+ if (ret) {
+ log_debug("%s: cannot dump EDID buffer: error %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = edid_get_timing(edid_buf, sizeof(edid_buf),
+ &edid_timing, &bpc);
+ if (ret) {
+ log_debug("%s: cannot decode EDID info: error %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ memcpy(timings, &edid_timing, sizeof(*timings));
+
+ return 0;
+}
+#else
+static int simple_panel_get_edid_timing(struct udevice *dev,
+ struct display_timing *timings)
+{
+ return -ENOTSUPP;
+}
+#endif
+
static int simple_panel_get_display_timing(struct udevice *dev,
struct display_timing *timings)
{
const void *blob = gd->fdt_blob;
+ int ret;
+
+ /* Check for timing subnode if panel node first */
+ ret = fdtdec_decode_display_timing(blob, dev_of_offset(dev),
+ 0, timings);
+ if (!ret)
+ return ret;
- return fdtdec_decode_display_timing(blob, dev_of_offset(dev),
- 0, timings);
+ return simple_panel_get_edid_timing(dev, timings);
}
static int simple_panel_of_to_plat(struct udevice *dev)
@@ -111,7 +160,8 @@ static int simple_panel_probe(struct udevice *dev)
{
struct simple_panel_priv *priv = dev_get_priv(dev);
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
- const u32 dsi_data = dev_get_driver_data(dev);
+ struct mipi_dsi_panel_plat *dsi_data =
+ (struct mipi_dsi_panel_plat *)dev_get_driver_data(dev);
int ret;
ret = regulator_set_enable_if_allowed(priv->reg, true);
@@ -121,15 +171,8 @@ static int simple_panel_probe(struct udevice *dev)
return ret;
}
- switch (dsi_data) {
- case PANASONIC_VVX10F004B00:
- memcpy(plat, &panasonic_vvx10f004b00,
- sizeof(panasonic_vvx10f004b00));
- break;
- case PANEL_NON_DSI:
- default:
- break;
- }
+ if (dsi_data)
+ memcpy(plat, dsi_data, sizeof(struct mipi_dsi_panel_plat));
return 0;
}
@@ -140,6 +183,13 @@ static const struct panel_ops simple_panel_ops = {
.get_display_timing = simple_panel_get_display_timing,
};
+static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = {
+ .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_CLOCK_NON_CONTINUOUS,
+ .format = MIPI_DSI_FMT_RGB888,
+ .lanes = 4,
+};
+
static const struct udevice_id simple_panel_ids[] = {
{ .compatible = "simple-panel" },
{ .compatible = "auo,b133xtn01" },
@@ -150,7 +200,7 @@ static const struct udevice_id simple_panel_ids[] = {
{ .compatible = "sharp,lq123p1jx31" },
{ .compatible = "boe,nv101wxmn51" },
{ .compatible = "panasonic,vvx10f004b00",
- .data = PANASONIC_VVX10F004B00 },
+ .data = (ulong)&panasonic_vvx10f004b00 },
{ }
};
diff --git a/drivers/video/simplefb.c b/drivers/video/simplefb.c
index 235ec76..33bb78b 100644
--- a/drivers/video/simplefb.c
+++ b/drivers/video/simplefb.c
@@ -15,14 +15,14 @@ static int simple_video_probe(struct udevice *dev)
{
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
- const void *blob = gd->fdt_blob;
- const int node = dev_of_offset(dev);
+ ofnode node = dev_ofnode(dev);
const char *format;
+ int ret;
fdt_addr_t base;
fdt_size_t size;
+ u32 width, height, rot;
- base = fdtdec_get_addr_size_auto_parent(blob, dev_of_offset(dev->parent),
- node, "reg", 0, &size, false);
+ base = dev_read_addr_size(dev, &size);
if (base == FDT_ADDR_T_NONE) {
debug("%s: Failed to decode memory region\n", __func__);
return -EINVAL;
@@ -41,17 +41,25 @@ static int simple_video_probe(struct udevice *dev)
debug("%s: Query resolution...\n", __func__);
- uc_priv->xsize = fdtdec_get_uint(blob, node, "width", 0);
- uc_priv->ysize = fdtdec_get_uint(blob, node, "height", 0);
- uc_priv->rot = fdtdec_get_uint(blob, node, "rot", 0);
- if (uc_priv->rot > 3) {
- log_debug("%s: invalid rot\n", __func__);
- return log_msg_ret("rot", -EINVAL);
+ ret = ofnode_read_u32(node, "width", &width);
+ ret = ret ?: ofnode_read_u32(node, "height", &height);
+ if (ret || !width || !height) {
+ log_err("%s: invalid width or height: %d\n", __func__, ret);
+ return ret ?: -EINVAL;
}
+ ofnode_read_u32(node, "rot", &rot);
+ uc_priv->rot = rot;
+ uc_priv->xsize = width;
+ uc_priv->ysize = height;
- format = fdt_getprop(blob, node, "format", NULL);
+ format = ofnode_read_string(node, "format");
debug("%s: %dx%d@%s\n", __func__, uc_priv->xsize, uc_priv->ysize, format);
+ if (!format) {
+ log_err("%s: please add required property \"format\"\n", __func__);
+ return -EINVAL;
+ }
+
if (strcmp(format, "r5g6b5") == 0) {
uc_priv->bpix = VIDEO_BPP16;
} else if (strcmp(format, "a8b8g8r8") == 0 ||
@@ -67,7 +75,7 @@ static int simple_video_probe(struct udevice *dev)
uc_priv->bpix = VIDEO_BPP32;
uc_priv->format = VIDEO_X2R10G10B10;
} else {
- printf("%s: invalid format: %s\n", __func__, format);
+ log_err("%s: invalid format: %s\n", __func__, format);
return -EINVAL;
}
diff --git a/drivers/video/sunxi/sunxi_dw_hdmi.c b/drivers/video/sunxi/sunxi_dw_hdmi.c
index 0324a05..a5e8d39 100644
--- a/drivers/video/sunxi/sunxi_dw_hdmi.c
+++ b/drivers/video/sunxi/sunxi_dw_hdmi.c
@@ -358,17 +358,19 @@ static int sunxi_dw_hdmi_probe(struct udevice *dev)
sunxi_dw_hdmi_phy_init(&priv->hdmi);
- ret = dw_hdmi_phy_wait_for_hpd(&priv->hdmi);
- if (ret < 0) {
- debug("hdmi can not get hpd signal\n");
- return -1;
- }
+ ret = dw_hdmi_detect_hpd(&priv->hdmi);
+ if (ret < 0)
+ return ret;
dw_hdmi_init(&priv->hdmi);
return 0;
}
+static const struct dw_hdmi_phy_ops dw_hdmi_sunxi_phy_ops = {
+ .phy_set = sunxi_dw_hdmi_phy_cfg,
+};
+
static int sunxi_dw_hdmi_of_to_plat(struct udevice *dev)
{
struct sunxi_dw_hdmi_priv *priv = dev_get_priv(dev);
@@ -379,7 +381,7 @@ static int sunxi_dw_hdmi_of_to_plat(struct udevice *dev)
hdmi->i2c_clk_high = 0xd8;
hdmi->i2c_clk_low = 0xfe;
hdmi->reg_io_width = 1;
- hdmi->phy_set = sunxi_dw_hdmi_phy_cfg;
+ hdmi->ops = &dw_hdmi_sunxi_phy_ops;
ret = reset_get_bulk(dev, &priv->resets);
if (ret)
diff --git a/drivers/video/tegra20/Makefile b/drivers/video/tegra20/Makefile
index f0b534c..a75aea2 100644
--- a/drivers/video/tegra20/Makefile
+++ b/drivers/video/tegra20/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0+
obj-$(CONFIG_VIDEO_TEGRA20) += tegra-dc.o
-obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o mipi-phy.o
+obj-$(CONFIG_VIDEO_DSI_TEGRA30) += tegra-dsi.o tegra-mipi.o mipi-phy.o
obj-$(CONFIG_TEGRA_BACKLIGHT_PWM) += tegra-pwm-backlight.o
diff --git a/drivers/video/tegra20/tegra-dc.c b/drivers/video/tegra20/tegra-dc.c
index f53ad46..d073da7 100644
--- a/drivers/video/tegra20/tegra-dc.c
+++ b/drivers/video/tegra20/tegra-dc.c
@@ -3,8 +3,8 @@
* Copyright (c) 2011 The Chromium OS Authors.
*/
-#include <common.h>
#include <backlight.h>
+#include <cpu_func.h>
#include <dm.h>
#include <fdtdec.h>
#include <log.h>
@@ -21,12 +21,20 @@
#include <asm/arch/clock.h>
#include <asm/arch/funcmux.h>
#include <asm/arch/pinmux.h>
+#include <asm/arch/powergate.h>
#include <asm/arch/pwm.h>
-#include <asm/arch/display.h>
-#include <asm/arch-tegra/timer.h>
+
+#include "tegra-dc.h"
DECLARE_GLOBAL_DATA_PTR;
+/* Holder of Tegra per-SOC DC differences */
+struct tegra_dc_soc_info {
+ bool has_timer;
+ bool has_rgb;
+ bool has_pgate;
+};
+
/* Information about the display controller */
struct tegra_lcd_priv {
int width; /* width in pixels */
@@ -35,16 +43,19 @@ struct tegra_lcd_priv {
struct display_timing timing;
struct udevice *panel;
struct dc_ctlr *dc; /* Display controller regmap */
+ const struct tegra_dc_soc_info *soc;
fdt_addr_t frame_buffer; /* Address of frame buffer */
unsigned pixel_clock; /* Pixel clock in Hz */
int dc_clk[2]; /* Contains clk and its parent */
+ ulong scdiv; /* Clock divider used by disp_clk_ctrl */
bool rotation; /* 180 degree panel turn */
+ bool pipe; /* DC controller: 0 for A, 1 for B */
};
enum {
/* Maximum LCD size we support */
- LCD_MAX_WIDTH = 1920,
- LCD_MAX_HEIGHT = 1200,
+ LCD_MAX_WIDTH = 2560,
+ LCD_MAX_HEIGHT = 1600,
LCD_MAX_LOG2_BPP = VIDEO_BPP16,
};
@@ -110,13 +121,11 @@ static void update_window(struct tegra_lcd_priv *priv,
writel(val, &dc->cmd.state_ctrl);
}
-static int update_display_mode(struct dc_disp_reg *disp,
- struct tegra_lcd_priv *priv)
+static int update_display_mode(struct tegra_lcd_priv *priv)
{
+ struct dc_disp_reg *disp = &priv->dc->disp;
struct display_timing *dt = &priv->timing;
unsigned long val;
- unsigned long rate;
- unsigned long div;
writel(0x0, &disp->disp_timing_opt);
@@ -128,29 +137,22 @@ static int update_display_mode(struct dc_disp_reg *disp,
&disp->front_porch);
writel(dt->hactive.typ | (dt->vactive.typ << 16), &disp->disp_active);
- val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT;
- val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT;
- writel(val, &disp->data_enable_opt);
-
- val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT;
- val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT;
- val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT;
- writel(val, &disp->disp_interface_ctrl);
+ if (priv->soc->has_rgb) {
+ val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT;
+ val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT;
+ writel(val, &disp->data_enable_opt);
- /*
- * The pixel clock divider is in 7.1 format (where the bottom bit
- * represents 0.5). Here we calculate the divider needed to get from
- * the display clock (typically 600MHz) to the pixel clock. We round
- * up or down as requried.
- */
- rate = clock_get_periph_rate(priv->dc_clk[0], priv->dc_clk[1]);
- div = ((rate * 2 + priv->pixel_clock / 2) / priv->pixel_clock) - 2;
- debug("Display clock %lu, divider %lu\n", rate, div);
+ val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT;
+ val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT;
+ val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT;
+ writel(val, &disp->disp_interface_ctrl);
+ }
- writel(0x00010001, &disp->shift_clk_opt);
+ if (priv->soc->has_rgb)
+ writel(0x00010001, &disp->shift_clk_opt);
val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT;
- val |= div << SHIFT_CLK_DIVIDER_SHIFT;
+ val |= priv->scdiv << SHIFT_CLK_DIVIDER_SHIFT;
writel(val, &disp->disp_clk_ctrl);
return 0;
@@ -174,6 +176,7 @@ static void basic_init(struct dc_cmd_reg *cmd)
writel(val, &cmd->disp_pow_ctrl);
val = readl(&cmd->disp_cmd);
+ val &= ~CTRL_MODE_MASK;
val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT;
writel(val, &cmd->disp_cmd);
}
@@ -215,8 +218,11 @@ static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = {
0x00020000,
};
-static void rgb_enable(struct dc_com_reg *com)
+static void rgb_enable(struct tegra_lcd_priv *priv)
{
+ struct dc_com_reg *com = &priv->dc->com;
+ struct display_timing *dt = &priv->timing;
+ u32 value;
int i;
for (i = 0; i < PIN_REG_COUNT; i++) {
@@ -225,16 +231,31 @@ static void rgb_enable(struct dc_com_reg *com)
writel(rgb_data_tab[i], &com->pin_output_data[i]);
}
+ /* configure H- and V-sync signal polarities */
+ value = readl(&com->pin_output_polarity[1]);
+
+ if (dt->flags & DISPLAY_FLAGS_HSYNC_LOW)
+ value |= LHS_OUTPUT_POLARITY_LOW;
+ else
+ value &= ~LHS_OUTPUT_POLARITY_LOW;
+
+ if (dt->flags & DISPLAY_FLAGS_VSYNC_LOW)
+ value |= LVS_OUTPUT_POLARITY_LOW;
+ else
+ value &= ~LVS_OUTPUT_POLARITY_LOW;
+
+ writel(value, &com->pin_output_polarity[1]);
+
for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++)
writel(rgb_sel_tab[i], &com->pin_output_sel[i]);
}
-static int setup_window(struct disp_ctl_win *win,
- struct tegra_lcd_priv *priv)
+static int setup_window(struct tegra_lcd_priv *priv,
+ struct disp_ctl_win *win)
{
if (priv->rotation) {
- win->x = priv->width * 2;
- win->y = priv->height;
+ win->x = priv->width * 2 - 1;
+ win->y = priv->height - 1;
} else {
win->x = 0;
win->y = 0;
@@ -274,12 +295,11 @@ static int setup_window(struct disp_ctl_win *win,
* You should pass in the U-Boot address here, and check the contents of
* struct tegra_lcd_priv to see what was actually chosen.
*
- * @param blob Device tree blob
* @param priv Driver's private data
* @param default_lcd_base Default address of LCD frame buffer
* Return: 0 if ok, -1 on error (unsupported bits per pixel)
*/
-static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv,
+static int tegra_display_probe(struct tegra_lcd_priv *priv,
void *default_lcd_base)
{
struct disp_ctl_win window;
@@ -288,12 +308,29 @@ static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv,
priv->frame_buffer = (u32)default_lcd_base;
/*
- * We halve the rate if DISP1 paret is PLLD, since actual parent
+ * We halve the rate if DISP1 parent is PLLD, since actual parent
* is plld_out0 which is PLLD divided by 2.
*/
if (priv->dc_clk[1] == CLOCK_ID_DISPLAY)
rate /= 2;
+#ifndef CONFIG_TEGRA20
+ /* PLLD2 obeys same rules as PLLD but it is present only on T30+ */
+ if (priv->dc_clk[1] == CLOCK_ID_DISPLAY2)
+ rate /= 2;
+#endif
+
+ /*
+ * The pixel clock divider is in 7.1 format (where the bottom bit
+ * represents 0.5). Here we calculate the divider needed to get from
+ * the display clock (typically 600MHz) to the pixel clock. We round
+ * up or down as required.
+ */
+ if (!priv->scdiv)
+ priv->scdiv = ((rate * 2 + priv->pixel_clock / 2)
+ / priv->pixel_clock) - 2;
+ debug("Display clock %lu, divider %lu\n", rate, priv->scdiv);
+
/*
* HOST1X is init by default at 150MHz with PLLC as parent
*/
@@ -303,13 +340,17 @@ static int tegra_display_probe(const void *blob, struct tegra_lcd_priv *priv,
rate);
basic_init(&priv->dc->cmd);
- basic_init_timer(&priv->dc->disp);
- rgb_enable(&priv->dc->com);
+
+ if (priv->soc->has_timer)
+ basic_init_timer(&priv->dc->disp);
+
+ if (priv->soc->has_rgb)
+ rgb_enable(priv);
if (priv->pixel_clock)
- update_display_mode(&priv->dc->disp, priv);
+ update_display_mode(priv);
- if (setup_window(&window, priv))
+ if (setup_window(priv, &window))
return -1;
update_window(priv, &window);
@@ -322,7 +363,6 @@ static int tegra_lcd_probe(struct udevice *dev)
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
struct tegra_lcd_priv *priv = dev_get_priv(dev);
- const void *blob = gd->fdt_blob;
int ret;
/* Initialize the Tegra display controller */
@@ -330,8 +370,42 @@ static int tegra_lcd_probe(struct udevice *dev)
funcmux_select(PERIPH_ID_DISP1, FUNCMUX_DEFAULT);
#endif
- if (tegra_display_probe(blob, priv, (void *)plat->base)) {
- printf("%s: Failed to probe display driver\n", __func__);
+ if (priv->soc->has_pgate) {
+ uint powergate;
+
+ if (priv->pipe)
+ powergate = TEGRA_POWERGATE_DISB;
+ else
+ powergate = TEGRA_POWERGATE_DIS;
+
+ ret = tegra_powergate_power_off(powergate);
+ if (ret < 0) {
+ log_err("failed to power off DISP gate: %d", ret);
+ return ret;
+ }
+
+ ret = tegra_powergate_sequence_power_up(powergate,
+ priv->dc_clk[0]);
+ if (ret < 0) {
+ log_err("failed to power up DISP gate: %d", ret);
+ return ret;
+ }
+ }
+
+ /* Get shift clock divider from Tegra DSI if used */
+ if (!strcmp(priv->panel->name, TEGRA_DSI_A) ||
+ !strcmp(priv->panel->name, TEGRA_DSI_B)) {
+ struct tegra_dc_plat *dc_plat = dev_get_plat(priv->panel);
+
+ priv->scdiv = dc_plat->scdiv;
+ }
+
+ /* Clean the framebuffer area */
+ memset((u8 *)plat->base, 0, plat->size);
+ flush_dcache_all();
+
+ if (tegra_display_probe(priv, (void *)plat->base)) {
+ debug("%s: Failed to probe display driver\n", __func__);
return -1;
}
@@ -346,12 +420,6 @@ static int tegra_lcd_probe(struct udevice *dev)
return ret;
}
- ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
- if (ret) {
- debug("%s: Cannot set backlight to default, ret=%d\n", __func__, ret);
- return ret;
- }
-
mmu_set_region_dcache_behaviour(priv->frame_buffer, plat->size,
DCACHE_WRITETHROUGH);
@@ -361,10 +429,10 @@ static int tegra_lcd_probe(struct udevice *dev)
uc_priv->xsize = priv->width;
uc_priv->ysize = priv->height;
uc_priv->bpix = priv->log2_bpp;
- debug("LCD frame buffer at %pa, size %x\n", &priv->frame_buffer,
+ debug("LCD frame buffer at %08x, size %x\n", priv->frame_buffer,
plat->size);
- return 0;
+ return panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
}
static int tegra_lcd_of_to_plat(struct udevice *dev)
@@ -383,6 +451,8 @@ static int tegra_lcd_of_to_plat(struct udevice *dev)
return -EINVAL;
}
+ priv->soc = (struct tegra_dc_soc_info *)dev_get_driver_data(dev);
+
ret = clock_decode_pair(dev, priv->dc_clk);
if (ret < 0) {
debug("%s: Cannot decode clocks for '%s' (ret = %d)\n",
@@ -392,6 +462,9 @@ static int tegra_lcd_of_to_plat(struct udevice *dev)
priv->rotation = dev_read_bool(dev, "nvidia,180-rotation");
+ if (!strcmp(dev->name, TEGRA_DC_B))
+ priv->pipe = 1;
+
rgb = fdt_subnode_offset(blob, node, "rgb");
if (rgb < 0) {
debug("%s: Cannot find rgb subnode for '%s' (ret=%d)\n",
@@ -417,12 +490,14 @@ static int tegra_lcd_of_to_plat(struct udevice *dev)
return ret;
}
+ /* Fill the platform data for internal devices */
if (!strcmp(priv->panel->name, TEGRA_DSI_A) ||
!strcmp(priv->panel->name, TEGRA_DSI_B)) {
struct tegra_dc_plat *dc_plat = dev_get_plat(priv->panel);
dc_plat->dev = dev;
dc_plat->dc = priv->dc;
+ dc_plat->pipe = priv->pipe;
}
ret = panel_get_display_timing(priv->panel, &priv->timing);
@@ -464,19 +539,46 @@ static int tegra_lcd_bind(struct udevice *dev)
static const struct video_ops tegra_lcd_ops = {
};
+static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
+ .has_timer = true,
+ .has_rgb = true,
+ .has_pgate = false,
+};
+
+static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
+ .has_timer = false,
+ .has_rgb = true,
+ .has_pgate = false,
+};
+
+static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
+ .has_timer = false,
+ .has_rgb = false,
+ .has_pgate = true,
+};
+
static const struct udevice_id tegra_lcd_ids[] = {
- { .compatible = "nvidia,tegra20-dc" },
- { .compatible = "nvidia,tegra30-dc" },
- { }
+ {
+ .compatible = "nvidia,tegra20-dc",
+ .data = (ulong)&tegra20_dc_soc_info
+ }, {
+ .compatible = "nvidia,tegra30-dc",
+ .data = (ulong)&tegra30_dc_soc_info
+ }, {
+ .compatible = "nvidia,tegra114-dc",
+ .data = (ulong)&tegra114_dc_soc_info
+ }, {
+ /* sentinel */
+ }
};
U_BOOT_DRIVER(tegra_lcd) = {
- .name = "tegra_lcd",
- .id = UCLASS_VIDEO,
- .of_match = tegra_lcd_ids,
- .ops = &tegra_lcd_ops,
- .bind = tegra_lcd_bind,
- .probe = tegra_lcd_probe,
+ .name = "tegra_lcd",
+ .id = UCLASS_VIDEO,
+ .of_match = tegra_lcd_ids,
+ .ops = &tegra_lcd_ops,
+ .bind = tegra_lcd_bind,
+ .probe = tegra_lcd_probe,
.of_to_plat = tegra_lcd_of_to_plat,
.priv_auto = sizeof(struct tegra_lcd_priv),
};
diff --git a/drivers/video/tegra20/tegra-dc.h b/drivers/video/tegra20/tegra-dc.h
new file mode 100644
index 0000000..05042da
--- /dev/null
+++ b/drivers/video/tegra20/tegra-dc.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2010
+ * NVIDIA Corporation <www.nvidia.com>
+ */
+
+#ifndef _TEGRA_DC_H
+#define _TEGRA_DC_H
+
+#ifndef __ASSEMBLY__
+#include <linux/bitops.h>
+#endif
+
+/* arch-tegra/dc exists only because T124 uses it */
+#include <asm/arch-tegra/dc.h>
+
+#define TEGRA_DC_A "dc@54200000"
+#define TEGRA_DC_B "dc@54240000"
+#define TEGRA_DSI_A "dsi@54300000"
+#define TEGRA_DSI_B "dsi@54400000"
+
+struct tegra_dc_plat {
+ struct udevice *dev; /* Display controller device */
+ struct dc_ctlr *dc; /* Display controller regmap */
+ bool pipe; /* DC number: 0 for A, 1 for B */
+ ulong scdiv; /* Shift clock divider */
+};
+
+/* This holds information about a window which can be displayed */
+struct disp_ctl_win {
+ enum win_color_depth_id fmt; /* Color depth/format */
+ unsigned int bpp; /* Bits per pixel */
+ phys_addr_t phys_addr; /* Physical address in memory */
+ unsigned int x; /* Horizontal address offset (bytes) */
+ unsigned int y; /* Veritical address offset (bytes) */
+ unsigned int w; /* Width of source window */
+ unsigned int h; /* Height of source window */
+ unsigned int stride; /* Number of bytes per line */
+ unsigned int out_x; /* Left edge of output window (col) */
+ unsigned int out_y; /* Top edge of output window (row) */
+ unsigned int out_w; /* Width of output window in pixels */
+ unsigned int out_h; /* Height of output window in pixels */
+};
+
+#endif /* _TEGRA_DC_H */
diff --git a/drivers/video/tegra20/tegra-dsi.c b/drivers/video/tegra20/tegra-dsi.c
index a48f9c8..13dae37 100644
--- a/drivers/video/tegra20/tegra-dsi.c
+++ b/drivers/video/tegra20/tegra-dsi.c
@@ -12,6 +12,7 @@
#include <mipi_dsi.h>
#include <backlight.h>
#include <panel.h>
+#include <reset.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/time.h>
@@ -20,17 +21,24 @@
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
-#include <asm/arch/display.h>
-#include <asm/arch-tegra30/dsi.h>
+#include "tegra-dc.h"
+#include "tegra-dsi.h"
#include "mipi-phy.h"
+/* List of supported DSI bridges */
+enum {
+ DSI_V0,
+ DSI_V1,
+};
+
struct tegra_dsi_priv {
struct mipi_dsi_host host;
struct mipi_dsi_device device;
struct mipi_dphy_timing dphy_timing;
struct udevice *panel;
+ struct udevice *mipi;
struct display_timing timing;
struct dsi_ctlr *dsi;
@@ -41,6 +49,8 @@ struct tegra_dsi_priv {
int dsi_clk;
int video_fifo_depth;
int host_fifo_depth;
+
+ u32 version;
};
static void tegra_dc_enable_controller(struct udevice *dev)
@@ -501,6 +511,41 @@ static void tegra_dsi_pad_calibrate(struct dsi_pad_ctrl_reg *pad)
writel(value, TEGRA_VI_BASE + (CSI_CIL_PAD_CONFIG << 2));
}
+static void tegra_dsi_mipi_calibrate(struct tegra_dsi_priv *priv)
+{
+ struct dsi_pad_ctrl_reg *pad = &priv->dsi->pad;
+ u32 value;
+ int ret;
+
+ ret = misc_set_enabled(priv->mipi, true);
+ if (ret)
+ log_debug("%s: failed to enable MIPI calibration: %d\n",
+ __func__, ret);
+
+ writel(0, &pad->pad_ctrl);
+ writel(0, &pad->pad_ctrl_1);
+ writel(0, &pad->pad_ctrl_2);
+ writel(0, &pad->pad_ctrl_3);
+ writel(0, &pad->pad_ctrl_4);
+
+ /* DSI pad enable */
+ value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
+ writel(value, &pad->pad_ctrl);
+
+ value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
+ DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
+ DSI_PAD_OUT_CLK(0x0);
+ writel(value, &pad->pad_ctrl_2);
+
+ value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
+ DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
+ writel(value, &pad->pad_ctrl_3);
+
+ ret = misc_write(priv->mipi, 0, NULL, 0);
+ if (ret)
+ log_debug("%s: MIPI calibration failed %d\n", __func__, ret);
+}
+
static void tegra_dsi_set_timeout(struct dsi_timeout_reg *rtimeout,
unsigned long bclk,
unsigned int vrefresh)
@@ -664,10 +709,25 @@ static int tegra_dsi_encoder_enable(struct udevice *dev)
u32 value;
int ret;
+ /* If for some reasone DSI is enabled then it needs to
+ * be disabled in order for the panel initialization
+ * commands to be properly sent.
+ */
+ value = readl(&misc->dsi_pwr_ctrl);
+
+ if (value & DSI_POWER_CONTROL_ENABLE) {
+ value = readl(&misc->dsi_pwr_ctrl);
+ value &= ~DSI_POWER_CONTROL_ENABLE;
+ writel(value, &misc->dsi_pwr_ctrl);
+ }
+
/* Disable interrupt */
writel(0, &misc->int_enable);
- tegra_dsi_pad_calibrate(&priv->dsi->pad);
+ if (priv->version)
+ tegra_dsi_mipi_calibrate(priv);
+ else
+ tegra_dsi_pad_calibrate(&priv->dsi->pad);
tegra_dsi_get_muldiv(device->format, &mul, &div);
@@ -706,12 +766,6 @@ static int tegra_dsi_encoder_enable(struct udevice *dev)
if (ret)
return ret;
- tegra_dsi_configure(dev, 0);
-
- ret = panel_set_backlight(priv->panel, BACKLIGHT_DEFAULT);
- if (ret)
- return ret;
-
tegra_dsi_configure(dev, device->mode_flags);
tegra_dc_enable_controller(dev);
@@ -726,8 +780,10 @@ static int tegra_dsi_encoder_enable(struct udevice *dev)
static int tegra_dsi_bridge_set_panel(struct udevice *dev, int percent)
{
- /* Is not used in tegra dc */
- return 0;
+ struct tegra_dsi_priv *priv = dev_get_priv(dev);
+
+ /* Turn on/off backlight */
+ return panel_set_backlight(priv->panel, percent);
}
static int tegra_dsi_panel_timings(struct udevice *dev,
@@ -743,6 +799,7 @@ static int tegra_dsi_panel_timings(struct udevice *dev,
static void tegra_dsi_init_clocks(struct udevice *dev)
{
struct tegra_dsi_priv *priv = dev_get_priv(dev);
+ struct tegra_dc_plat *dc_plat = dev_get_plat(dev);
struct mipi_dsi_device *device = &priv->device;
unsigned int mul, div;
unsigned long bclk, plld;
@@ -754,6 +811,19 @@ static void tegra_dsi_init_clocks(struct udevice *dev)
plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC);
+ dc_plat->scdiv = ((plld * USEC_PER_SEC +
+ priv->timing.pixelclock.typ / 2) /
+ priv->timing.pixelclock.typ) - 2;
+
+ /*
+ * BUG: If DISP1 is a PLLD/D2 child, it cannot go over 370MHz. The
+ * cause of this is not quite clear. This can be overcomed by
+ * halving the PLLD/D2 if the target rate is > 800MHz. This way
+ * DISP1 and DSI clocks will be equal.
+ */
+ if (plld > 800)
+ plld /= 2;
+
switch (clock_get_osc_freq()) {
case CLOCK_OSC_FREQ_12_0: /* OSC is 12Mhz */
case CLOCK_OSC_FREQ_48_0: /* OSC is 48Mhz */
@@ -790,17 +860,27 @@ static int tegra_dsi_bridge_probe(struct udevice *dev)
struct tegra_dsi_priv *priv = dev_get_priv(dev);
struct mipi_dsi_device *device = &priv->device;
struct mipi_dsi_panel_plat *mipi_plat;
+ struct reset_ctl reset_ctl;
int ret;
+ priv->version = dev_get_driver_data(dev);
+
priv->dsi = (struct dsi_ctlr *)dev_read_addr_ptr(dev);
if (!priv->dsi) {
printf("%s: No display controller address\n", __func__);
return -EINVAL;
}
- priv->video_fifo_depth = 480;
+ priv->video_fifo_depth = 1920;
priv->host_fifo_depth = 64;
+ ret = reset_get_by_name(dev, "dsi", &reset_ctl);
+ if (ret) {
+ log_debug("%s: reset_get_by_name() failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
"avdd-dsi-csi-supply", &priv->avdd);
if (ret)
@@ -814,6 +894,16 @@ static int tegra_dsi_bridge_probe(struct udevice *dev)
return log_ret(ret);
}
+ if (priv->version) {
+ ret = uclass_get_device_by_phandle(UCLASS_MISC, dev,
+ "nvidia,mipi-calibrate",
+ &priv->mipi);
+ if (ret) {
+ log_debug("%s: cannot get MIPI: error %d\n", __func__, ret);
+ return ret;
+ }
+ }
+
panel_get_display_timing(priv->panel, &priv->timing);
mipi_plat = dev_get_plat(priv->panel);
@@ -829,12 +919,17 @@ static int tegra_dsi_bridge_probe(struct udevice *dev)
tegra_dsi_get_format(device->format, &priv->format);
+ reset_assert(&reset_ctl);
+
ret = regulator_set_enable_if_allowed(priv->avdd, true);
if (ret && ret != -ENOSYS)
return ret;
tegra_dsi_init_clocks(dev);
+ mdelay(2);
+ reset_deassert(&reset_ctl);
+
return 0;
}
@@ -845,7 +940,8 @@ static const struct panel_ops tegra_dsi_bridge_ops = {
};
static const struct udevice_id tegra_dsi_bridge_ids[] = {
- { .compatible = "nvidia,tegra30-dsi" },
+ { .compatible = "nvidia,tegra30-dsi", .data = DSI_V0 },
+ { .compatible = "nvidia,tegra114-dsi", .data = DSI_V1 },
{ }
};
diff --git a/arch/arm/include/asm/arch-tegra30/dsi.h b/drivers/video/tegra20/tegra-dsi.h
index 7ade132..69dac4b 100644
--- a/arch/arm/include/asm/arch-tegra30/dsi.h
+++ b/drivers/video/tegra20/tegra-dsi.h
@@ -4,8 +4,8 @@
* NVIDIA Corporation <www.nvidia.com>
*/
-#ifndef __ASM_ARCH_TEGRA_DSI_H
-#define __ASM_ARCH_TEGRA_DSI_H
+#ifndef _TEGRA_DSI_H
+#define _TEGRA_DSI_H
#ifndef __ASSEMBLY__
#include <linux/bitops.h>
@@ -105,6 +105,10 @@ struct dsi_pad_ctrl_reg {
uint pad_ctrl_cd; /* _PAD_CONTROL_CD_0 */
uint pad_cd_status; /* _PAD_CD_STATUS_0 */
uint dsi_vid_mode_control; /* _DSI_VID_MODE_CONTROL_0 */
+ uint pad_ctrl_1; /* _PAD_CONTROL_1 */
+ uint pad_ctrl_2; /* _PAD_CONTROL_2 */
+ uint pad_ctrl_3; /* _PAD_CONTROL_3 */
+ uint pad_ctrl_4; /* _PAD_CONTROL_4 */
};
/* Display Serial Interface (DSI_) regs */
@@ -184,6 +188,20 @@ struct dsi_ctlr {
#define DSI_PAD_CONTROL_PAD_LPUPADJ(x) (((x) & 0x3) << 14)
#define DSI_PAD_CONTROL_PAD_LPDNADJ(x) (((x) & 0x3) << 12)
+#define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0)
+#define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16)
+
+#define DSI_PAD_OUT_CLK(x) (((x) & 0x7) << 0)
+#define DSI_PAD_LP_DN(x) (((x) & 0x7) << 4)
+#define DSI_PAD_LP_UP(x) (((x) & 0x7) << 8)
+#define DSI_PAD_SLEW_DN(x) (((x) & 0x7) << 12)
+#define DSI_PAD_SLEW_UP(x) (((x) & 0x7) << 16)
+
+#define DSI_PAD_PREEMP_PD_CLK(x) (((x) & 0x3) << 12)
+#define DSI_PAD_PREEMP_PU_CLK(x) (((x) & 0x3) << 8)
+#define DSI_PAD_PREEMP_PD(x) (((x) & 0x3) << 4)
+#define DSI_PAD_PREEMP_PU(x) (((x) & 0x3) << 0)
+
/*
* pixel format as used in the DSI_CONTROL_FORMAT field
*/
@@ -214,4 +232,4 @@ enum tegra_dsi_format {
#define PAD_DRIV_DN_REF(x) (((x) & 0x7) << 16)
#define PAD_DRIV_UP_REF(x) (((x) & 0x7) << 8)
-#endif /* __ASM_ARCH_TEGRA_DSI_H */
+#endif /* _TEGRA_DSI_H */
diff --git a/drivers/video/tegra20/tegra-mipi.c b/drivers/video/tegra20/tegra-mipi.c
new file mode 100644
index 0000000..2df3c1a
--- /dev/null
+++ b/drivers/video/tegra20/tegra-mipi.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 NVIDIA Corporation
+ * Copyright (c) 2023 Svyatoslav Ryhel <clamor95@gmail.com>
+ */
+
+#include <dm.h>
+#include <clk.h>
+#include <misc.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+
+#include <asm/io.h>
+
+/* MIPI control registers 0x00 ~ 0x60 */
+struct mipi_ctlr {
+ uint mipi_cal_ctrl;
+ uint mipi_cal_autocal_ctrl;
+ uint mipi_cal_status;
+
+ uint unused1[2];
+
+ uint mipi_cal_config_csia;
+ uint mipi_cal_config_csib;
+ uint mipi_cal_config_csic;
+ uint mipi_cal_config_csid;
+ uint mipi_cal_config_csie;
+
+ uint unused2[4];
+
+ uint mipi_cal_config_dsia;
+ uint mipi_cal_config_dsib;
+ uint mipi_cal_config_dsic;
+ uint mipi_cal_config_dsid;
+
+ uint unused3[4];
+
+ uint mipi_cal_bias_pad_cfg0;
+ uint mipi_cal_bias_pad_cfg1;
+ uint mipi_cal_bias_pad_cfg2;
+};
+
+#define MIPI_CAL_CTRL_NOISE_FILTER(x) (((x) & 0xf) << 26)
+#define MIPI_CAL_CTRL_PRESCALE(x) (((x) & 0x3) << 24)
+#define MIPI_CAL_CTRL_CLKEN_OVR BIT(4)
+#define MIPI_CAL_CTRL_START BIT(0)
+
+#define MIPI_CAL_STATUS_DONE BIT(16)
+#define MIPI_CAL_STATUS_ACTIVE BIT(0)
+
+#define MIPI_CAL_OVERIDE(x) (((x) & 0x1) << 30)
+#define MIPI_CAL_SEL(x) (((x) & 0x1) << 21)
+#define MIPI_CAL_HSPDOS(x) (((x) & 0x1f) << 16)
+#define MIPI_CAL_HSPUOS(x) (((x) & 0x1f) << 8)
+#define MIPI_CAL_TERMOS(x) (((x) & 0x1f) << 0)
+
+#define MIPI_CAL_BIAS_PAD_PDVCLAMP BIT(1)
+#define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF BIT(0)
+
+#define MIPI_CAL_BIAS_PAD_DRV_DN_REF(x) (((x) & 0x7) << 16)
+#define MIPI_CAL_BIAS_PAD_DRV_UP_REF(x) (((x) & 0x7) << 8)
+
+#define MIPI_CAL_BIAS_PAD_VCLAMP(x) (((x) & 0x7) << 16)
+#define MIPI_CAL_BIAS_PAD_VAUXP(x) (((x) & 0x7) << 4)
+#define MIPI_CAL_BIAS_PAD_PDVREG BIT(1)
+
+struct tegra_mipi_priv {
+ struct mipi_ctlr *mipi;
+ struct clk *mipi_cal;
+};
+
+static int tegra_mipi_calibrate(struct udevice *dev, int offset, const void *buf,
+ int size)
+{
+ struct tegra_mipi_priv *priv = dev_get_priv(dev);
+ u32 value;
+
+ value = MIPI_CAL_BIAS_PAD_DRV_DN_REF(0x2) |
+ MIPI_CAL_BIAS_PAD_DRV_UP_REF(0x0);
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg1);
+
+ value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2);
+ value &= ~MIPI_CAL_BIAS_PAD_VCLAMP(0x7);
+ value &= ~MIPI_CAL_BIAS_PAD_VAUXP(0x7);
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2);
+
+ value = MIPI_CAL_OVERIDE(0x0) | MIPI_CAL_SEL(0x1) |
+ MIPI_CAL_HSPDOS(0x0) | MIPI_CAL_HSPUOS(0x4) |
+ MIPI_CAL_TERMOS(0x5);
+ writel(value, &priv->mipi->mipi_cal_config_dsia);
+ writel(value, &priv->mipi->mipi_cal_config_dsib);
+
+ /* Deselect PAD C */
+ value = readl(&priv->mipi->mipi_cal_config_dsic);
+ value &= ~(MIPI_CAL_SEL(0x1));
+ writel(value, &priv->mipi->mipi_cal_config_dsic);
+
+ /* Deselect PAD D */
+ value = readl(&priv->mipi->mipi_cal_config_dsid);
+ value &= ~(MIPI_CAL_SEL(0x1));
+ writel(value, &priv->mipi->mipi_cal_config_dsid);
+
+ value = readl(&priv->mipi->mipi_cal_ctrl);
+ value &= ~MIPI_CAL_CTRL_NOISE_FILTER(0xf);
+ value &= ~MIPI_CAL_CTRL_PRESCALE(0x3);
+ value |= MIPI_CAL_CTRL_NOISE_FILTER(0xa) |
+ MIPI_CAL_CTRL_PRESCALE(0x2) |
+ MIPI_CAL_CTRL_CLKEN_OVR;
+ writel(value, &priv->mipi->mipi_cal_ctrl);
+
+ /* clear any pending status bits */
+ value = readl(&priv->mipi->mipi_cal_status);
+ writel(value, &priv->mipi->mipi_cal_status);
+
+ value = readl(&priv->mipi->mipi_cal_ctrl);
+ value |= MIPI_CAL_CTRL_START;
+ writel(value, &priv->mipi->mipi_cal_ctrl);
+
+ /*
+ * Wait for min 72uS to let calibration logic finish calibration
+ * sequence codes before waiting for pads idle state to apply the
+ * results.
+ */
+ udelay(80);
+
+ return readl_poll_sleep_timeout(&priv->mipi->mipi_cal_status, value,
+ !(value & MIPI_CAL_STATUS_ACTIVE) &&
+ (value & MIPI_CAL_STATUS_DONE), 100,
+ 250000);
+}
+
+static int tegra_mipi_enable(struct udevice *dev, bool val)
+{
+ struct tegra_mipi_priv *priv = dev_get_priv(dev);
+ u32 value;
+
+ clk_enable(priv->mipi_cal);
+
+ value = readl(&priv->mipi->mipi_cal_bias_pad_cfg0);
+ value &= ~MIPI_CAL_BIAS_PAD_PDVCLAMP;
+ value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF;
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg0);
+
+ value = readl(&priv->mipi->mipi_cal_bias_pad_cfg2);
+ value &= ~MIPI_CAL_BIAS_PAD_PDVREG;
+ writel(value, &priv->mipi->mipi_cal_bias_pad_cfg2);
+
+ return 0;
+}
+
+static const struct misc_ops tegra_mipi_ops = {
+ .write = tegra_mipi_calibrate,
+ .set_enabled = tegra_mipi_enable,
+};
+
+static int tegra_mipi_probe(struct udevice *dev)
+{
+ struct tegra_mipi_priv *priv = dev_get_priv(dev);
+
+ priv->mipi = (struct mipi_ctlr *)dev_read_addr_ptr(dev);
+ if (!priv->mipi) {
+ log_debug("%s: no MIPI controller address\n", __func__);
+ return -EINVAL;
+ }
+
+ priv->mipi_cal = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->mipi_cal)) {
+ log_debug("%s: Could not get MIPI clock: %ld\n",
+ __func__, PTR_ERR(priv->mipi_cal));
+ return PTR_ERR(priv->mipi_cal);
+ }
+
+ return 0;
+}
+
+static const struct udevice_id tegra_mipi_ids[] = {
+ { .compatible = "nvidia,tegra114-mipi" },
+ { }
+};
+
+U_BOOT_DRIVER(tegra_mipi) = {
+ .name = "tegra_mipi",
+ .id = UCLASS_MISC,
+ .ops = &tegra_mipi_ops,
+ .of_match = tegra_mipi_ids,
+ .probe = tegra_mipi_probe,
+ .priv_auto = sizeof(struct tegra_mipi_priv),
+};
diff --git a/drivers/video/tegra20/tegra-pwm-backlight.c b/drivers/video/tegra20/tegra-pwm-backlight.c
index bb677da..5f93f57 100644
--- a/drivers/video/tegra20/tegra-pwm-backlight.c
+++ b/drivers/video/tegra20/tegra-pwm-backlight.c
@@ -15,7 +15,8 @@
#include <asm/io.h>
#include <asm/gpio.h>
-#include <asm/arch/display.h>
+
+#include "tegra-dc.h"
#define TEGRA_DISPLAY_A_BASE 0x54200000
#define TEGRA_DISPLAY_B_BASE 0x54240000
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 3571e62..7b5d1df 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -404,6 +404,10 @@ bool video_is_active(void)
{
struct udevice *dev;
+ /* Assume video to be active if SPL passed video hand-off to U-boot */
+ if (IS_ENABLED(CONFIG_SPL_VIDEO_HANDOFF) && spl_phase() > PHASE_SPL)
+ return true;
+
for (uclass_find_first_device(UCLASS_VIDEO, &dev);
dev;
uclass_find_next_device(&dev)) {
diff --git a/include/configs/evb_rk3328.h b/include/configs/evb_rk3328.h
index d10e5b1..c985080 100644
--- a/include/configs/evb_rk3328.h
+++ b/include/configs/evb_rk3328.h
@@ -6,6 +6,11 @@
#ifndef __EVB_RK3328_H
#define __EVB_RK3328_H
+#define ROCKCHIP_DEVICE_SETTINGS \
+ "stdin=serial,usbkbd\0" \
+ "stdout=serial,vidconsole\0" \
+ "stderr=serial,vidconsole\0"
+
#include <configs/rk3328_common.h>
#endif
diff --git a/include/configs/rk3328_common.h b/include/configs/rk3328_common.h
index e920ec7..2c40674 100644
--- a/include/configs/rk3328_common.h
+++ b/include/configs/rk3328_common.h
@@ -26,6 +26,7 @@
ENV_MEM_LAYOUT_SETTINGS \
"fdtfile=" CONFIG_DEFAULT_FDT_FILE "\0" \
"partitions=" PARTS_DEFAULT \
+ ROCKCHIP_DEVICE_SETTINGS \
"boot_targets=" BOOT_TARGETS "\0"
#endif
diff --git a/include/dw_hdmi.h b/include/dw_hdmi.h
index 8acae38..f4d66ed 100644
--- a/include/dw_hdmi.h
+++ b/include/dw_hdmi.h
@@ -534,6 +534,14 @@ struct hdmi_data_info {
struct hdmi_vmode video_mode;
};
+struct dw_hdmi;
+
+struct dw_hdmi_phy_ops {
+ int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock);
+ void (*read_hpd)(struct dw_hdmi *hdmi, bool hdp_status);
+ void (*setup_hpd)(struct dw_hdmi *hdmi);
+};
+
struct dw_hdmi {
ulong ioaddr;
const struct hdmi_mpll_config *mpll_cfg;
@@ -543,8 +551,8 @@ struct dw_hdmi {
u8 reg_io_width;
struct hdmi_data_info hdmi_data;
struct udevice *ddc_bus;
+ const struct dw_hdmi_phy_ops *ops;
- int (*phy_set)(struct dw_hdmi *hdmi, uint mpixelclock);
void (*write_reg)(struct dw_hdmi *hdmi, u8 val, int offset);
u8 (*read_reg)(struct dw_hdmi *hdmi, int offset);
};
@@ -556,5 +564,6 @@ void dw_hdmi_phy_init(struct dw_hdmi *hdmi);
int dw_hdmi_enable(struct dw_hdmi *hdmi, const struct display_timing *edid);
int dw_hdmi_read_edid(struct dw_hdmi *hdmi, u8 *buf, int buf_size);
void dw_hdmi_init(struct dw_hdmi *hdmi);
+int dw_hdmi_detect_hpd(struct dw_hdmi *hdmi);
#endif
diff --git a/include/fdt_support.h b/include/fdt_support.h
index 25600d6..4b71b89 100644
--- a/include/fdt_support.h
+++ b/include/fdt_support.h
@@ -423,6 +423,8 @@ int arch_fixup_memory_node(void *blob);
int fdt_setup_simplefb_node(void *fdt, int node, u64 base_address, u32 width,
u32 height, u32 stride, const char *format);
+int fdt_add_fb_mem_rsv(void *blob);
+
int fdt_overlay_apply_verbose(void *fdt, void *fdto);
int fdt_valid(struct fdt_header **blobp);