diff options
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(®s->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, ®s->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), - ®s->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), - ®s->win0_dsp_st); + &win_regs->win0_dsp_st); writel(V_DSP_WIDTH(hactive - 1) | V_DSP_HEIGHT(vactive - 1), - ®s->win0_dsp_info); + &win_regs->win0_dsp_info); - clrsetbits_le32(®s->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), ®s->win0_vir); + writel(V_RGB565_VIRWIDTH(hactive), &win_regs->win0_vir); break; case 24: rgb_mode = RGB888; - writel(V_RGB888_VIRWIDTH(hactive), ®s->win0_vir); + writel(V_RGB888_VIRWIDTH(hactive), &win_regs->win0_vir); break; case 32: default: rgb_mode = ARGB8888; - writel(V_ARGB888_VIRWIDTH(hactive), ®s->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(®s->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, ®s->win0_yrgb_mst); + writel(fbbase, &win_regs->win0_yrgb_mst); writel(0x01, ®s->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), - ®s->dsp_htotal_hs_end); + &dsp_regs->dsp_htotal_hs_end); writel(V_HEAP(hsync_len + hback_porch + hactive) | V_HASP(hsync_len + hback_porch), - ®s->dsp_hact_st_end); + &dsp_regs->dsp_hact_st_end); writel(V_VSYNC(vsync_len) | V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch), - ®s->dsp_vtotal_vs_end); + &dsp_regs->dsp_vtotal_vs_end); writel(V_VAEP(vsync_len + vback_porch + vactive)| V_VASP(vsync_len + vback_porch), - ®s->dsp_vact_st_end); + &dsp_regs->dsp_vact_st_end); writel(V_HEAP(hsync_len + hback_porch + hactive) | V_HASP(hsync_len + hback_porch), - ®s->post_dsp_hact_info); + &dsp_regs->post_dsp_hact_info); writel(V_VAEP(vsync_len + vback_porch + vactive)| V_VASP(vsync_len + vback_porch), - ®s->post_dsp_vact_info); + &dsp_regs->post_dsp_vact_info); writel(0x01, ®s->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); |