CI: https://source.denx.de/u-boot/custodians/u-boot-video/-/pipelines/20466

- simple_panel: support timing parsing from EDID
- dw_hdmi: fix gcc-14 compiler warnings
- dw_hdmi: support vendor PHY for HDMI
- rockchip: add Rockchip INNO HDMI PHY driver
- rockchip: RK3328 HDMI and VOP support
- evb-rk3328: enable vidconsole support
- Tegra DC and DSI improvements and Tegra 114 support
- add LG LG070WX3 MIPI DSI panel driver
- add Samsung LTL106HL02 MIPI DSI panel driver
- add Toshiba TC358768 RGB to DSI bridge support
- add basic support for the Parade DP501 transmitter
- Tegra 3 panel and bridge driver improvements
- simplefb: modernise DT parsing
- fdt_simplefb: Enumerate framebuffer info from video handoff
- preserve framebuffer if SPL is passing video hand-off
- fdt_support: allow reserving FB region without simplefb
This commit is contained in:
Tom Rini
2024-04-21 08:54:20 -06:00
55 changed files with 4221 additions and 427 deletions

View File

@@ -130,6 +130,10 @@
bootph-all;
};
&vop {
bootph-all;
};
#ifdef CONFIG_ROCKCHIP_SPI_IMAGE
&binman {
simple-bin-spi {

View File

@@ -1,3 +1,16 @@
#include <config.h>
#include "tegra-u-boot.dtsi"
/ {
host1x@50000000 {
bootph-all;
dc@54200000 {
bootph-all;
};
dc@54240000 {
bootph-all;
};
};
};

View File

@@ -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>,

View File

@@ -8,5 +8,9 @@
dc@54200000 {
bootph-all;
};
dc@54240000 {
bootph-all;
};
};
};

View File

@@ -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>,

View File

@@ -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,

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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*/

View File

@@ -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*/

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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),
};

View File

@@ -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---

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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),
};

View File

@@ -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 = {

View File

@@ -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),
};

View File

@@ -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);
}

View File

@@ -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);
/* Set backlight */
dcs_write_one(dsi, 0x51, 0x96);
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;
/* Set backlight */
dcs_write_one(dsi, 0x51, 0x96);
ret = backlight_set_brightness(priv->backlight, percent);
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 = {

186
drivers/video/lg-ld070wx3.c Normal file
View File

@@ -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),
};

View File

@@ -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 {

View File

@@ -118,42 +118,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);
@@ -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 = {

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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),
};

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2023 Edgeble AI Technologies Pvt. Ltd.
*/
#include <dm.h>
#include <video.h>
#include <asm/io.h>
#include "rk_vop.h"
DECLARE_GLOBAL_DATA_PTR;
static void rk3328_set_pin_polarity(struct udevice *dev,
enum vop_modes mode, u32 polarity)
{
struct rk_vop_priv *priv = dev_get_priv(dev);
struct rk3288_vop *regs = priv->regs;
switch (mode) {
case VOP_MODE_HDMI:
clrsetbits_le32(&regs->dsp_ctrl1,
M_RK3399_DSP_HDMI_POL,
V_RK3399_DSP_HDMI_POL(polarity));
break;
default:
debug("%s: unsupported output mode %x\n", __func__, mode);
}
}
static int rk3328_vop_probe(struct udevice *dev)
{
/* Before relocation we don't need to do anything */
if (!(gd->flags & GD_FLG_RELOC))
return 0;
return rk_vop_probe(dev);
}
static int rk3328_vop_remove(struct udevice *dev)
{
struct rk_vop_priv *priv = dev_get_priv(dev);
struct rk3288_vop *regs = priv->regs;
struct rk3288_vop *win_regs = priv->regs + priv->win_offset;
/* FIXME: Explicit disabling of WIN0 is needed to avoid iommu
* page-fault in Linux, better handling of iommu-address in
* Linux might drop this.
*/
clrbits_le32(&win_regs->win0_ctrl0, M_WIN0_EN);
writel(0x01, &regs->reg_cfg_done);
return 0;
}
struct rkvop_driverdata rk3328_driverdata = {
.dsp_offset = 0x490,
.win_offset = 0xd0,
.features = VOP_FEATURE_OUTPUT_10BIT,
.set_pin_polarity = rk3328_set_pin_polarity,
};
static const struct udevice_id rk3328_vop_ids[] = {
{
.compatible = "rockchip,rk3328-vop",
.data = (ulong)&rk3328_driverdata
},
{ /* sentile */ }
};
static const struct video_ops rk3328_vop_ops = {
};
U_BOOT_DRIVER(rk3328_vop) = {
.name = "rk3328_vop",
.id = UCLASS_VIDEO,
.of_match = rk3328_vop_ids,
.ops = &rk3328_vop_ops,
.bind = rk_vop_bind,
.probe = rk3328_vop_probe,
.remove = rk3328_vop_remove,
.priv_auto = sizeof(struct rk_vop_priv),
.flags = DM_FLAG_PRE_RELOC | DM_FLAG_OS_PREPARE,
};

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -39,11 +39,14 @@ enum vop_pol {
DCLK_INVERT = 3
};
static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbbase,
static void rkvop_enable(struct udevice *dev, ulong fbbase,
int fb_bits_per_pixel,
const struct display_timing *edid,
struct reset_ctl *dclk_rst)
{
struct rk_vop_priv *priv = dev_get_priv(dev);
struct rk3288_vop *regs = priv->regs;
struct rk3288_vop *win_regs = priv->regs + priv->win_offset;
u32 lb_mode;
u32 rgb_mode;
u32 hactive = edid->hactive.typ;
@@ -51,32 +54,32 @@ static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbb
int ret;
writel(V_ACT_WIDTH(hactive - 1) | V_ACT_HEIGHT(vactive - 1),
&regs->win0_act_info);
&win_regs->win0_act_info);
writel(V_DSP_XST(edid->hsync_len.typ + edid->hback_porch.typ) |
V_DSP_YST(edid->vsync_len.typ + edid->vback_porch.typ),
&regs->win0_dsp_st);
&win_regs->win0_dsp_st);
writel(V_DSP_WIDTH(hactive - 1) |
V_DSP_HEIGHT(vactive - 1),
&regs->win0_dsp_info);
&win_regs->win0_dsp_info);
clrsetbits_le32(&regs->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR,
clrsetbits_le32(&win_regs->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR,
V_WIN0_KEY_EN(0) | V_WIN0_KEY_COLOR(0));
switch (fb_bits_per_pixel) {
case 16:
rgb_mode = RGB565;
writel(V_RGB565_VIRWIDTH(hactive), &regs->win0_vir);
writel(V_RGB565_VIRWIDTH(hactive), &win_regs->win0_vir);
break;
case 24:
rgb_mode = RGB888;
writel(V_RGB888_VIRWIDTH(hactive), &regs->win0_vir);
writel(V_RGB888_VIRWIDTH(hactive), &win_regs->win0_vir);
break;
case 32:
default:
rgb_mode = ARGB8888;
writel(V_ARGB888_VIRWIDTH(hactive), &regs->win0_vir);
writel(V_ARGB888_VIRWIDTH(hactive), &win_regs->win0_vir);
break;
}
@@ -89,12 +92,12 @@ static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbb
else
lb_mode = LB_RGB_1280X8;
clrsetbits_le32(&regs->win0_ctrl0,
clrsetbits_le32(&win_regs->win0_ctrl0,
M_WIN0_LB_MODE | M_WIN0_DATA_FMT | M_WIN0_EN,
V_WIN0_LB_MODE(lb_mode) | V_WIN0_DATA_FMT(rgb_mode) |
V_WIN0_EN(1));
writel(fbbase, &regs->win0_yrgb_mst);
writel(fbbase, &win_regs->win0_yrgb_mst);
writel(0x01, &regs->reg_cfg_done); /* enable reg config */
ret = reset_assert(dclk_rst);
@@ -162,6 +165,7 @@ static void rkvop_mode_set(struct udevice *dev,
{
struct rk_vop_priv *priv = dev_get_priv(dev);
struct rk3288_vop *regs = priv->regs;
struct rk3288_vop *dsp_regs = priv->regs + priv->dsp_offset;
struct rkvop_driverdata *data =
(struct rkvop_driverdata *)dev_get_driver_data(dev);
@@ -195,27 +199,27 @@ static void rkvop_mode_set(struct udevice *dev,
writel(V_HSYNC(hsync_len) |
V_HORPRD(hsync_len + hback_porch + hactive + hfront_porch),
&regs->dsp_htotal_hs_end);
&dsp_regs->dsp_htotal_hs_end);
writel(V_HEAP(hsync_len + hback_porch + hactive) |
V_HASP(hsync_len + hback_porch),
&regs->dsp_hact_st_end);
&dsp_regs->dsp_hact_st_end);
writel(V_VSYNC(vsync_len) |
V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch),
&regs->dsp_vtotal_vs_end);
&dsp_regs->dsp_vtotal_vs_end);
writel(V_VAEP(vsync_len + vback_porch + vactive)|
V_VASP(vsync_len + vback_porch),
&regs->dsp_vact_st_end);
&dsp_regs->dsp_vact_st_end);
writel(V_HEAP(hsync_len + hback_porch + hactive) |
V_HASP(hsync_len + hback_porch),
&regs->post_dsp_hact_info);
&dsp_regs->post_dsp_hact_info);
writel(V_VAEP(vsync_len + vback_porch + vactive)|
V_VASP(vsync_len + vback_porch),
&regs->post_dsp_vact_info);
&dsp_regs->post_dsp_vact_info);
writel(0x01, &regs->reg_cfg_done); /* enable reg config */
}
@@ -243,9 +247,7 @@ static void rkvop_mode_set(struct udevice *dev,
static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode ep_node)
{
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
struct rk_vop_priv *priv = dev_get_priv(dev);
int vop_id, remote_vop_id;
struct rk3288_vop *regs = priv->regs;
struct display_timing timing;
struct udevice *disp;
int ret;
@@ -380,7 +382,7 @@ static int rk_display_init(struct udevice *dev, ulong fbbase, ofnode ep_node)
return ret;
}
rkvop_enable(dev, regs, fbbase, 1 << l2bpp, &timing, &dclk_rst);
rkvop_enable(dev, fbbase, 1 << l2bpp, &timing, &dclk_rst);
ret = display_enable(disp, 1 << l2bpp, &timing);
if (ret)
@@ -415,6 +417,8 @@ int rk_vop_probe(struct udevice *dev)
{
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
struct rk_vop_priv *priv = dev_get_priv(dev);
struct rkvop_driverdata *ops =
(struct rkvop_driverdata *)dev_get_driver_data(dev);
int ret = 0;
ofnode port, node;
struct reset_ctl ahb_rst;
@@ -448,6 +452,8 @@ int rk_vop_probe(struct udevice *dev)
#endif
priv->regs = dev_read_addr_ptr(dev);
priv->win_offset = ops->win_offset;
priv->dsp_offset = ops->dsp_offset;
/*
* Try all the ports until we find one that works. In practice this

View File

@@ -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 */

View File

@@ -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),
};

View File

@@ -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;
return fdtdec_decode_display_timing(blob, dev_of_offset(dev),
0, timings);
/* 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 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 },
{ }
};

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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);
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);
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);
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);
}
/*
* 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);
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),
};

View File

@@ -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 */

View File

@@ -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 },
{ }
};

View File

@@ -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 */

View File

@@ -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),
};

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);