diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/u-boot/drivers/video/rockchip | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/u-boot/drivers/video/rockchip')
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/Kconfig | 72 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/Makefile | 18 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk3288_hdmi.c | 117 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk3288_mipi.c | 189 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk3288_vop.c | 110 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk3399_hdmi.c | 80 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk3399_mipi.c | 180 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk3399_vop.c | 104 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_edp.c | 1173 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_hdmi.c | 125 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_hdmi.h | 75 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_lvds.c | 252 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_mipi.c | 331 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_mipi.h | 32 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_vop.c | 488 | ||||
-rw-r--r-- | roms/u-boot/drivers/video/rockchip/rk_vop.h | 66 |
16 files changed, 3412 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/video/rockchip/Kconfig b/roms/u-boot/drivers/video/rockchip/Kconfig new file mode 100644 index 000000000..0ade631bd --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/Kconfig @@ -0,0 +1,72 @@ +# +# Video drivers selection for rockchip soc. These configs only impact the +# compile process. You can surely check all the options. In this case, all the +# display driver will be compiled, but which drivers finally will be used is +# decided by device tree configuration. What's more, enable needed power for +# display by configure the device tree, and the vop driver will do the rest. +# +# Author: Eric Gao <eric.gao@rock-chips.com> +# + +menuconfig VIDEO_ROCKCHIP + bool "Enable Rockchip Video Support" + depends on DM_VIDEO + help + Rockchip SoCs provide video output capabilities for High-Definition + Multimedia Interface (HDMI), Low-voltage Differential Signalling + (LVDS), embedded DisplayPort (eDP) and Display Serial Interface (DSI). + + This driver supports the on-chip video output device, and targets the + Rockchip RK3288 and RK3399. + +config VIDEO_ROCKCHIP_MAX_XRES + int "Maximum horizontal resolution (for memory allocation purposes)" + depends on VIDEO_ROCKCHIP + default 3840 if DISPLAY_ROCKCHIP_HDMI + default 1920 + help + The maximum horizontal resolution to support for the framebuffer. + This configuration is used for reserving/allocating memory for the + framebuffer during device-model binding/probing. + +config VIDEO_ROCKCHIP_MAX_YRES + int "Maximum vertical resolution (for memory allocation purposes)" + depends on VIDEO_ROCKCHIP + default 2160 if DISPLAY_ROCKCHIP_HDMI + default 1080 + help + The maximum vertical resolution to support for the framebuffer. + This configuration is used for reserving/allocating memory for the + framebuffer during device-model binding/probing. + +if VIDEO_ROCKCHIP + +config DISPLAY_ROCKCHIP_EDP + bool "EDP Port" + depends on VIDEO_ROCKCHIP + help + This enables Embedded DisplayPort(EDP) display support. + +config DISPLAY_ROCKCHIP_LVDS + bool "LVDS Port" + depends on VIDEO_ROCKCHIP + help + This enables Low-voltage Differential Signaling(LVDS) display + support. + +config DISPLAY_ROCKCHIP_HDMI + bool "HDMI port" + select VIDEO_DW_HDMI + depends on VIDEO_ROCKCHIP + help + This enables High-Definition Multimedia Interface display support. + +config DISPLAY_ROCKCHIP_MIPI + bool "MIPI Port" + depends on VIDEO_ROCKCHIP + help + This enables Mobile Industry Processor Interface(MIPI) display + support. The mipi controller and dphy on rk3288& rk3399 support + 16,18, 24 bits per pixel with up to 2k resolution ratio. + +endif diff --git a/roms/u-boot/drivers/video/rockchip/Makefile b/roms/u-boot/drivers/video/rockchip/Makefile new file mode 100644 index 000000000..9aced5ef3 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2000-2007 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. + +ifdef CONFIG_VIDEO_ROCKCHIP +obj-y += rk_vop.o +obj-$(CONFIG_ROCKCHIP_RK3288) += rk3288_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_RK3399) += rk3399_hdmi.o +obj-$(CONFIG_DISPLAY_ROCKCHIP_HDMI) += rk_hdmi.o $(obj-hdmi-y) +obj-mipi-$(CONFIG_ROCKCHIP_RK3288) += rk3288_mipi.o +obj-mipi-$(CONFIG_ROCKCHIP_RK3399) += rk3399_mipi.o +obj-$(CONFIG_DISPLAY_ROCKCHIP_MIPI) += rk_mipi.o $(obj-mipi-y) +endif diff --git a/roms/u-boot/drivers/video/rockchip/rk3288_hdmi.c b/roms/u-boot/drivers/video/rockchip/rk3288_hdmi.c new file mode 100644 index 000000000..327ae7871 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk3288_hdmi.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <dw_hdmi.h> +#include <edid.h> +#include <log.h> +#include <malloc.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/hardware.h> +#include <asm/arch-rockchip/grf_rk3288.h> +#include <power/regulator.h> +#include "rk_hdmi.h" + +static int rk3288_hdmi_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *edid) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + struct display_plat *uc_plat = dev_get_uclass_plat(dev); + int vop_id = uc_plat->source_id; + struct rk3288_grf *grf = priv->grf; + + /* hdmi source select hdmi controller */ + rk_setreg(&grf->soc_con6, 1 << 15); + + /* hdmi data from vop id */ + rk_clrsetreg(&grf->soc_con6, 1 << 4, (vop_id == 1) ? (1 << 4) : 0); + + return dw_hdmi_enable(&priv->hdmi, edid); +} + +static int rk3288_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 = 0x7a; + hdmi->i2c_clk_low = 0x8d; + + /* + * TODO(sjg@chromium.org): The above values don't work - these + * ones work better, but generate lots of errors in the data. + */ + hdmi->i2c_clk_high = 0x0d; + hdmi->i2c_clk_low = 0x0d; + + return rk_hdmi_of_to_plat(dev); +} + +static int rk3288_clk_config(struct udevice *dev) +{ + struct display_plat *uc_plat = dev_get_uclass_plat(dev); + struct clk clk; + int ret; + + /* + * Configure the maximum clock to permit whatever resolution the + * monitor wants + */ + ret = clk_get_by_index(uc_plat->src_dev, 0, &clk); + if (ret >= 0) { + ret = clk_set_rate(&clk, 384000000); + clk_free(&clk); + } + if (ret < 0) { + debug("%s: Failed to set clock in source device '%s': ret=%d\n", + __func__, uc_plat->src_dev->name, ret); + return ret; + } + + return 0; +} + +static const char * const rk3288_regulator_names[] = { + "vcc50_hdmi" +}; + +static int rk3288_hdmi_probe(struct udevice *dev) +{ + /* Enable VOP clock for RK3288 */ + rk3288_clk_config(dev); + + /* Enable regulators required for HDMI */ + rk_hdmi_probe_regulators(dev, rk3288_regulator_names, + ARRAY_SIZE(rk3288_regulator_names)); + + return rk_hdmi_probe(dev); +} + +static const struct dm_display_ops rk3288_hdmi_ops = { + .read_edid = rk_hdmi_read_edid, + .enable = rk3288_hdmi_enable, +}; + +static const struct udevice_id rk3288_hdmi_ids[] = { + { .compatible = "rockchip,rk3288-dw-hdmi" }, + { } +}; + +U_BOOT_DRIVER(rk3288_hdmi_rockchip) = { + .name = "rk3288_hdmi_rockchip", + .id = UCLASS_DISPLAY, + .of_match = rk3288_hdmi_ids, + .ops = &rk3288_hdmi_ops, + .of_to_plat = rk3288_hdmi_of_to_plat, + .probe = rk3288_hdmi_probe, + .priv_auto = sizeof(struct rk_hdmi_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk3288_mipi.c b/roms/u-boot/drivers/video/rockchip/rk3288_mipi.c new file mode 100644 index 000000000..7e48dd834 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk3288_mipi.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd + * Author: Eric Gao <eric.gao@rock-chips.com> + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <log.h> +#include <panel.h> +#include <regmap.h> +#include "rk_mipi.h" +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <dm/uclass-internal.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/cru.h> +#include <asm/arch-rockchip/grf_rk3288.h> +#include <asm/arch-rockchip/hardware.h> +#include <asm/arch-rockchip/rockchip_mipi_dsi.h> + +#define MHz 1000000 + +/* Select mipi dsi source, big or little vop */ +static int rk_mipi_dsi_source_select(struct udevice *dev) +{ + struct rk_mipi_priv *priv = dev_get_priv(dev); + struct rk3288_grf *grf = priv->grf; + struct display_plat *disp_uc_plat = dev_get_uclass_plat(dev); + + /* Select the video source */ + switch (disp_uc_plat->source_id) { + case VOP_B: + rk_clrsetreg(&grf->soc_con6, RK3288_DSI0_LCDC_SEL_MASK, + RK3288_DSI0_LCDC_SEL_BIG + << RK3288_DSI0_LCDC_SEL_SHIFT); + break; + case VOP_L: + rk_clrsetreg(&grf->soc_con6, RK3288_DSI0_LCDC_SEL_MASK, + RK3288_DSI0_LCDC_SEL_LIT + << RK3288_DSI0_LCDC_SEL_SHIFT); + break; + default: + debug("%s: Invalid VOP id\n", __func__); + return -EINVAL; + } + + return 0; +} + +/* Setup mipi dphy working mode */ +static void rk_mipi_dphy_mode_set(struct udevice *dev) +{ + struct rk_mipi_priv *priv = dev_get_priv(dev); + struct rk3288_grf *grf = priv->grf; + int val; + + /* Set Controller as TX mode */ + val = RK3288_DPHY_TX0_RXMODE_DIS << RK3288_DPHY_TX0_RXMODE_SHIFT; + rk_clrsetreg(&grf->soc_con8, RK3288_DPHY_TX0_RXMODE_MASK, val); + + /* Exit tx stop mode */ + val |= RK3288_DPHY_TX0_TXSTOPMODE_EN + << RK3288_DPHY_TX0_TXSTOPMODE_SHIFT; + rk_clrsetreg(&grf->soc_con8, + RK3288_DPHY_TX0_TXSTOPMODE_MASK, val); + + /* Disable turnequest */ + val |= RK3288_DPHY_TX0_TURNREQUEST_EN + << RK3288_DPHY_TX0_TURNREQUEST_SHIFT; + rk_clrsetreg(&grf->soc_con8, + RK3288_DPHY_TX0_TURNREQUEST_MASK, val); +} + +/* + * This function is called by rk_display_init() using rk_mipi_dsi_enable() and + * rk_mipi_phy_enable() to initialize mipi controller and dphy. If success, + * enable backlight. + */ +static int rk_mipi_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *timing) +{ + int ret; + struct rk_mipi_priv *priv = dev_get_priv(dev); + + /* Fill the mipi controller parameter */ + priv->ref_clk = 24 * MHz; + priv->sys_clk = priv->ref_clk; + priv->pix_clk = timing->pixelclock.typ; + priv->phy_clk = priv->pix_clk * 6; + priv->txbyte_clk = priv->phy_clk / 8; + priv->txesc_clk = 20 * MHz; + + /* Select vop port, big or little */ + rk_mipi_dsi_source_select(dev); + + /* Set mipi dphy work mode */ + rk_mipi_dphy_mode_set(dev); + + /* Config and enable mipi dsi according to timing */ + ret = rk_mipi_dsi_enable(dev, timing); + if (ret) { + debug("%s: rk_mipi_dsi_enable() failed (err=%d)\n", + __func__, ret); + return ret; + } + + /* Config and enable mipi phy */ + ret = rk_mipi_phy_enable(dev); + if (ret) { + debug("%s: rk_mipi_phy_enable() failed (err=%d)\n", + __func__, ret); + return ret; + } + + /* Enable backlight */ + ret = panel_enable_backlight(priv->panel); + if (ret) { + debug("%s: panel_enable_backlight() failed (err=%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int rk_mipi_of_to_plat(struct udevice *dev) +{ + struct rk_mipi_priv *priv = dev_get_priv(dev); + + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + if (IS_ERR_OR_NULL(priv->grf)) { + debug("%s: Get syscon grf failed (ret=%p)\n", + __func__, priv->grf); + return -ENXIO; + } + priv->regs = dev_read_addr(dev); + if (priv->regs == FDT_ADDR_T_NONE) { + debug("%s: Get MIPI dsi address failed (ret=%lu)\n", __func__, + priv->regs); + return -ENXIO; + } + + return 0; +} + +/* + * Probe function: check panel existence and readingit's timing. Then config + * mipi dsi controller and enable it according to the timing parameter. + */ +static int rk_mipi_probe(struct udevice *dev) +{ + int ret; + struct rk_mipi_priv *priv = dev_get_priv(dev); + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, "rockchip,panel", + &priv->panel); + if (ret) { + debug("%s: Can not find panel (err=%d)\n", __func__, ret); + return ret; + } + + return 0; +} + +static const struct dm_display_ops rk_mipi_dsi_ops = { + .read_timing = rk_mipi_read_timing, + .enable = rk_mipi_enable, +}; + +static const struct udevice_id rk_mipi_dsi_ids[] = { + { .compatible = "rockchip,rk3288_mipi_dsi" }, + { } +}; + +U_BOOT_DRIVER(rk_mipi_dsi) = { + .name = "rk_mipi_dsi", + .id = UCLASS_DISPLAY, + .of_match = rk_mipi_dsi_ids, + .of_to_plat = rk_mipi_of_to_plat, + .probe = rk_mipi_probe, + .ops = &rk_mipi_dsi_ops, + .priv_auto = sizeof(struct rk_mipi_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk3288_vop.c b/roms/u-boot/drivers/video/rockchip/rk3288_vop.c new file mode 100644 index 000000000..44f32bb5f --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk3288_vop.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + */ + +#include <common.h> +#include <display.h> +#include <dm.h> +#include <regmap.h> +#include <syscon.h> +#include <video.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/grf_rk3288.h> +#include <asm/arch-rockchip/hardware.h> +#include <linux/delay.h> +#include "rk_vop.h" + +DECLARE_GLOBAL_DATA_PTR; + +static void rk3288_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; + + /* The RK3328 VOP (v3.1) has its polarity configuration in ctrl0 */ + clrsetbits_le32(®s->dsp_ctrl0, + M_DSP_DCLK_POL | M_DSP_DEN_POL | + M_DSP_VSYNC_POL | M_DSP_HSYNC_POL, + V_DSP_PIN_POL(polarity)); +} + +static void rk3288_set_io_vsel(struct udevice *dev) +{ + struct rk3288_grf *grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + + /* lcdc(vop) iodomain select 1.8V */ + rk_setreg(&grf->io_vsel, 1 << 0); +} + +/* + * Try some common regulators. We should really get these from the + * device tree somehow. + */ +static const char * const rk3288_regulator_names[] = { + "vcc18_lcd", + "VCC18_LCD", + "vdd10_lcd_pwren_h", + "vdd10_lcd", + "VDD10_LCD", + "vcc33_lcd" +}; + +static int rk3288_vop_probe(struct udevice *dev) +{ + /* Before relocation we don't need to do anything */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + /* Set the LCDC(vop) iodomain to 1.8V */ + rk3288_set_io_vsel(dev); + + /* Probe regulators required for the RK3288 VOP */ + rk_vop_probe_regulators(dev, rk3288_regulator_names, + ARRAY_SIZE(rk3288_regulator_names)); + + return rk_vop_probe(dev); +} + +static int rk_vop_remove(struct udevice *dev) +{ + struct rk_vop_priv *priv = dev_get_priv(dev); + struct rk3288_vop *regs = priv->regs; + + setbits_le32(®s->sys_ctrl, V_STANDBY_EN(1)); + + /* wait frame complete (60Hz) to enter standby */ + mdelay(17); + + return 0; +} + +struct rkvop_driverdata rk3288_driverdata = { + .features = VOP_FEATURE_OUTPUT_10BIT, + .set_pin_polarity = rk3288_set_pin_polarity, +}; + +static const struct udevice_id rk3288_vop_ids[] = { + { .compatible = "rockchip,rk3288-vop", + .data = (ulong)&rk3288_driverdata }, + { } +}; + +static const struct video_ops rk3288_vop_ops = { +}; + +U_BOOT_DRIVER(rockchip_rk3288_vop) = { + .name = "rockchip_rk3288_vop", + .id = UCLASS_VIDEO, + .of_match = rk3288_vop_ids, + .ops = &rk3288_vop_ops, + .bind = rk_vop_bind, + .probe = rk3288_vop_probe, + .remove = rk_vop_remove, + .priv_auto = sizeof(struct rk_vop_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk3399_hdmi.c b/roms/u-boot/drivers/video/rockchip/rk3399_hdmi.c new file mode 100644 index 000000000..3041360c6 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk3399_hdmi.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <dw_hdmi.h> +#include <edid.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/hardware.h> +#include <asm/arch-rockchip/grf_rk3399.h> +#include <power/regulator.h> +#include "rk_hdmi.h" + +static int rk3399_hdmi_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *edid) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + struct display_plat *uc_plat = dev_get_uclass_plat(dev); + int vop_id = uc_plat->source_id; + struct rk3399_grf_regs *grf = priv->grf; + + /* select the hdmi encoder input data from our source_id */ + rk_clrsetreg(&grf->soc_con20, GRF_RK3399_HDMI_VOP_SEL_MASK, + (vop_id == 1) ? GRF_RK3399_HDMI_VOP_SEL_L : 0); + + return dw_hdmi_enable(&priv->hdmi, edid); +} + +static int rk3399_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 = 0x7a; + hdmi->i2c_clk_low = 0x8d; + + return rk_hdmi_of_to_plat(dev); +} + +static const char * const rk3399_regulator_names[] = { + "vcc1v8_hdmi", + "vcc0v9_hdmi" +}; + +static int rk3399_hdmi_probe(struct udevice *dev) +{ + /* Enable regulators required for HDMI */ + rk_hdmi_probe_regulators(dev, rk3399_regulator_names, + ARRAY_SIZE(rk3399_regulator_names)); + + return rk_hdmi_probe(dev); +} + +static const struct dm_display_ops rk3399_hdmi_ops = { + .read_edid = rk_hdmi_read_edid, + .enable = rk3399_hdmi_enable, +}; + +static const struct udevice_id rk3399_hdmi_ids[] = { + { .compatible = "rockchip,rk3399-dw-hdmi" }, + { } +}; + +U_BOOT_DRIVER(rk3399_hdmi_rockchip) = { + .name = "rk3399_hdmi_rockchip", + .id = UCLASS_DISPLAY, + .of_match = rk3399_hdmi_ids, + .ops = &rk3399_hdmi_ops, + .of_to_plat = rk3399_hdmi_of_to_plat, + .probe = rk3399_hdmi_probe, + .priv_auto = sizeof(struct rk_hdmi_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk3399_mipi.c b/roms/u-boot/drivers/video/rockchip/rk3399_mipi.c new file mode 100644 index 000000000..917335048 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk3399_mipi.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd + * Author: Eric Gao <eric.gao@rock-chips.com> + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <log.h> +#include <panel.h> +#include <regmap.h> +#include "rk_mipi.h" +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <dm/uclass-internal.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/cru.h> +#include <asm/arch-rockchip/grf_rk3399.h> +#include <asm/arch-rockchip/hardware.h> +#include <asm/arch-rockchip/rockchip_mipi_dsi.h> + +/* Select mipi dsi source, big or little vop */ +static int rk_mipi_dsi_source_select(struct udevice *dev) +{ + struct rk_mipi_priv *priv = dev_get_priv(dev); + struct rk3399_grf_regs *grf = priv->grf; + struct display_plat *disp_uc_plat = dev_get_uclass_plat(dev); + + /* Select the video source */ + switch (disp_uc_plat->source_id) { + case VOP_B: + rk_clrsetreg(&grf->soc_con20, GRF_DSI0_VOP_SEL_MASK, + GRF_DSI0_VOP_SEL_B << GRF_DSI0_VOP_SEL_SHIFT); + break; + case VOP_L: + rk_clrsetreg(&grf->soc_con20, GRF_DSI0_VOP_SEL_MASK, + GRF_DSI0_VOP_SEL_L << GRF_DSI0_VOP_SEL_SHIFT); + break; + default: + debug("%s: Invalid VOP id\n", __func__); + return -EINVAL; + } + + return 0; +} + +/* Setup mipi dphy working mode */ +static void rk_mipi_dphy_mode_set(struct udevice *dev) +{ + struct rk_mipi_priv *priv = dev_get_priv(dev); + struct rk3399_grf_regs *grf = priv->grf; + int val; + + /* Set Controller as TX mode */ + val = GRF_DPHY_TX0_RXMODE_DIS << GRF_DPHY_TX0_RXMODE_SHIFT; + rk_clrsetreg(&grf->soc_con22, GRF_DPHY_TX0_RXMODE_MASK, val); + + /* Exit tx stop mode */ + val |= GRF_DPHY_TX0_TXSTOPMODE_DIS << GRF_DPHY_TX0_TXSTOPMODE_SHIFT; + rk_clrsetreg(&grf->soc_con22, GRF_DPHY_TX0_TXSTOPMODE_MASK, val); + + /* Disable turnequest */ + val |= GRF_DPHY_TX0_TURNREQUEST_DIS << GRF_DPHY_TX0_TURNREQUEST_SHIFT; + rk_clrsetreg(&grf->soc_con22, GRF_DPHY_TX0_TURNREQUEST_MASK, val); +} + +/* + * This function is called by rk_display_init() using rk_mipi_dsi_enable() and + * rk_mipi_phy_enable() to initialize mipi controller and dphy. If success, + * enable backlight. + */ +static int rk_display_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *timing) +{ + int ret; + struct rk_mipi_priv *priv = dev_get_priv(dev); + + /* Fill the mipi controller parameter */ + priv->ref_clk = 24 * MHz; + priv->sys_clk = priv->ref_clk; + priv->pix_clk = timing->pixelclock.typ; + priv->phy_clk = priv->pix_clk * 6; + priv->txbyte_clk = priv->phy_clk / 8; + priv->txesc_clk = 20 * MHz; + + /* Select vop port, big or little */ + rk_mipi_dsi_source_select(dev); + + /* Set mipi dphy work mode */ + rk_mipi_dphy_mode_set(dev); + + /* Config and enable mipi dsi according to timing */ + ret = rk_mipi_dsi_enable(dev, timing); + if (ret) { + debug("%s: rk_mipi_dsi_enable() failed (err=%d)\n", + __func__, ret); + return ret; + } + + /* Config and enable mipi phy */ + ret = rk_mipi_phy_enable(dev); + if (ret) { + debug("%s: rk_mipi_phy_enable() failed (err=%d)\n", + __func__, ret); + return ret; + } + + /* Enable backlight */ + ret = panel_enable_backlight(priv->panel); + if (ret) { + debug("%s: panel_enable_backlight() failed (err=%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int rk_mipi_of_to_plat(struct udevice *dev) +{ + struct rk_mipi_priv *priv = dev_get_priv(dev); + + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + if (IS_ERR_OR_NULL(priv->grf)) { + debug("%s: Get syscon grf failed (ret=%p)\n", + __func__, priv->grf); + return -ENXIO; + } + priv->regs = dev_read_addr(dev); + if (priv->regs == FDT_ADDR_T_NONE) { + debug("%s: Get MIPI dsi address failed\n", __func__); + return -ENXIO; + } + + return 0; +} + +/* + * Probe function: check panel existence and readingit's timing. Then config + * mipi dsi controller and enable it according to the timing parameter. + */ +static int rk_mipi_probe(struct udevice *dev) +{ + int ret; + struct rk_mipi_priv *priv = dev_get_priv(dev); + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, "rockchip,panel", + &priv->panel); + if (ret) { + debug("%s: Can not find panel (err=%d)\n", __func__, ret); + return ret; + } + + return 0; +} + +static const struct dm_display_ops rk_mipi_dsi_ops = { + .read_timing = rk_mipi_read_timing, + .enable = rk_display_enable, +}; + +static const struct udevice_id rk_mipi_dsi_ids[] = { + { .compatible = "rockchip,rk3399_mipi_dsi" }, + { } +}; + +U_BOOT_DRIVER(rk_mipi_dsi) = { + .name = "rk_mipi_dsi", + .id = UCLASS_DISPLAY, + .of_match = rk_mipi_dsi_ids, + .of_to_plat = rk_mipi_of_to_plat, + .probe = rk_mipi_probe, + .ops = &rk_mipi_dsi_ops, + .priv_auto = sizeof(struct rk_mipi_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk3399_vop.c b/roms/u-boot/drivers/video/rockchip/rk3399_vop.c new file mode 100644 index 000000000..a34b49105 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk3399_vop.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + */ + +#include <common.h> +#include <display.h> +#include <dm.h> +#include <log.h> +#include <regmap.h> +#include <video.h> +#include <asm/arch-rockchip/hardware.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include "rk_vop.h" + +DECLARE_GLOBAL_DATA_PTR; + +static void rk3399_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; + + /* + * The RK3399 VOPs (v3.5 and v3.6) require a per-mode setting of + * the polarity configuration (in ctrl1). + */ + switch (mode) { + case VOP_MODE_HDMI: + clrsetbits_le32(®s->dsp_ctrl1, + M_RK3399_DSP_HDMI_POL, + V_RK3399_DSP_HDMI_POL(polarity)); + break; + + case VOP_MODE_EDP: + clrsetbits_le32(®s->dsp_ctrl1, + M_RK3399_DSP_EDP_POL, + V_RK3399_DSP_EDP_POL(polarity)); + break; + + case VOP_MODE_MIPI: + clrsetbits_le32(®s->dsp_ctrl1, + M_RK3399_DSP_MIPI_POL, + V_RK3399_DSP_MIPI_POL(polarity)); + break; + + default: + debug("%s: unsupported output mode %x\n", __func__, mode); + } +} + +/* + * Try some common regulators. We should really get these from the + * device tree somehow. + */ +static const char * const rk3399_regulator_names[] = { + "vcc33_lcd" +}; + +static int rk3399_vop_probe(struct udevice *dev) +{ + /* Before relocation we don't need to do anything */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + /* Probe regulators required for the RK3399 VOP */ + rk_vop_probe_regulators(dev, rk3399_regulator_names, + ARRAY_SIZE(rk3399_regulator_names)); + + return rk_vop_probe(dev); +} + +struct rkvop_driverdata rk3399_lit_driverdata = { + .set_pin_polarity = rk3399_set_pin_polarity, +}; + +struct rkvop_driverdata rk3399_big_driverdata = { + .features = VOP_FEATURE_OUTPUT_10BIT, + .set_pin_polarity = rk3399_set_pin_polarity, +}; + +static const struct udevice_id rk3399_vop_ids[] = { + { .compatible = "rockchip,rk3399-vop-big", + .data = (ulong)&rk3399_big_driverdata }, + { .compatible = "rockchip,rk3399-vop-lit", + .data = (ulong)&rk3399_lit_driverdata }, + { } +}; + +static const struct video_ops rk3399_vop_ops = { +}; + +U_BOOT_DRIVER(rk3399_vop) = { + .name = "rk3399_vop", + .id = UCLASS_VIDEO, + .of_match = rk3399_vop_ids, + .ops = &rk3399_vop_ops, + .bind = rk_vop_bind, + .probe = rk3399_vop_probe, + .priv_auto = sizeof(struct rk_vop_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk_edp.c b/roms/u-boot/drivers/video/rockchip/rk_edp.c new file mode 100644 index 000000000..0ddf5e02d --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_edp.c @@ -0,0 +1,1173 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <edid.h> +#include <log.h> +#include <malloc.h> +#include <panel.h> +#include <regmap.h> +#include <reset.h> +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/hardware.h> +#include <asm/arch-rockchip/edp_rk3288.h> +#include <asm/arch-rockchip/grf_rk3288.h> +#include <asm/arch-rockchip/grf_rk3399.h> + +#define MAX_CR_LOOP 5 +#define MAX_EQ_LOOP 5 +#define DP_LINK_STATUS_SIZE 6 + +static const char * const voltage_names[] = { + "0.4V", "0.6V", "0.8V", "1.2V" +}; +static const char * const pre_emph_names[] = { + "0dB", "3.5dB", "6dB", "9.5dB" +}; + +#define DP_VOLTAGE_MAX DP_TRAIN_VOLTAGE_SWING_1200 +#define DP_PRE_EMPHASIS_MAX DP_TRAIN_PRE_EMPHASIS_9_5 + +#define RK3288_GRF_SOC_CON6 0x025c +#define RK3288_GRF_SOC_CON12 0x0274 +#define RK3399_GRF_SOC_CON20 0x6250 +#define RK3399_GRF_SOC_CON25 0x6264 + +enum rockchip_dp_types { + RK3288_DP = 0, + RK3399_EDP +}; + +struct rockchip_dp_data { + unsigned long reg_vop_big_little; + unsigned long reg_vop_big_little_sel; + unsigned long reg_ref_clk_sel; + unsigned long ref_clk_sel_bit; + enum rockchip_dp_types chip_type; +}; + +struct rk_edp_priv { + struct rk3288_edp *regs; + void *grf; + struct udevice *panel; + struct link_train link_train; + u8 train_set[4]; +}; + +static void rk_edp_init_refclk(struct rk3288_edp *regs, enum rockchip_dp_types chip_type) +{ + writel(SEL_24M, ®s->analog_ctl_2); + u32 reg; + + reg = REF_CLK_24M; + if (chip_type == RK3288_DP) + reg ^= REF_CLK_MASK; + writel(reg, ®s->pll_reg_1); + + + writel(LDO_OUTPUT_V_SEL_145 | KVCO_DEFALUT | CHG_PUMP_CUR_SEL_5US | + V2L_CUR_SEL_1MA, ®s->pll_reg_2); + + writel(LOCK_DET_CNT_SEL_256 | LOOP_FILTER_RESET | PALL_SSC_RESET | + LOCK_DET_BYPASS | PLL_LOCK_DET_MODE | PLL_LOCK_DET_FORCE, + ®s->pll_reg_3); + + writel(REGULATOR_V_SEL_950MV | STANDBY_CUR_SEL | + CHG_PUMP_INOUT_CTRL_1200MV | CHG_PUMP_INPUT_CTRL_OP, + ®s->pll_reg_5); + + writel(SSC_OFFSET | SSC_MODE | SSC_DEPTH, ®s->ssc_reg); + + writel(TX_SWING_PRE_EMP_MODE | PRE_DRIVER_PW_CTRL1 | + LP_MODE_CLK_REGULATOR | RESISTOR_MSB_CTRL | RESISTOR_CTRL, + ®s->tx_common); + + writel(DP_AUX_COMMON_MODE | DP_AUX_EN | AUX_TERM_50OHM, + ®s->dp_aux); + + writel(DP_BG_OUT_SEL | DP_DB_CUR_CTRL | DP_BG_SEL | DP_RESISTOR_TUNE_BG, + ®s->dp_bias); + + writel(CH1_CH3_SWING_EMP_CTRL | CH0_CH2_SWING_EMP_CTRL, + ®s->dp_reserv2); +} + +static void rk_edp_init_interrupt(struct rk3288_edp *regs) +{ + /* Set interrupt pin assertion polarity as high */ + writel(INT_POL, ®s->int_ctl); + + /* Clear pending registers */ + writel(0xff, ®s->common_int_sta_1); + writel(0x4f, ®s->common_int_sta_2); + writel(0xff, ®s->common_int_sta_3); + writel(0x27, ®s->common_int_sta_4); + writel(0x7f, ®s->dp_int_sta); + + /* 0:mask,1: unmask */ + writel(0x00, ®s->common_int_mask_1); + writel(0x00, ®s->common_int_mask_2); + writel(0x00, ®s->common_int_mask_3); + writel(0x00, ®s->common_int_mask_4); + writel(0x00, ®s->int_sta_mask); +} + +static void rk_edp_enable_sw_function(struct rk3288_edp *regs) +{ + clrbits_le32(®s->func_en_1, SW_FUNC_EN_N); +} + +static bool rk_edp_get_pll_locked(struct rk3288_edp *regs) +{ + u32 val; + + val = readl(®s->dp_debug_ctl); + + return val & PLL_LOCK; +} + +static int rk_edp_init_analog_func(struct rk3288_edp *regs) +{ + ulong start; + + writel(0x00, ®s->dp_pd); + writel(PLL_LOCK_CHG, ®s->common_int_sta_1); + + clrbits_le32(®s->dp_debug_ctl, F_PLL_LOCK | PLL_LOCK_CTRL); + + start = get_timer(0); + while (!rk_edp_get_pll_locked(regs)) { + if (get_timer(start) > PLL_LOCK_TIMEOUT) { + printf("%s: PLL is not locked\n", __func__); + return -ETIMEDOUT; + } + } + + /* Enable Serdes FIFO function and Link symbol clock domain module */ + clrbits_le32(®s->func_en_2, SERDES_FIFO_FUNC_EN_N | + LS_CLK_DOMAIN_FUNC_EN_N | AUX_FUNC_EN_N | + SSC_FUNC_EN_N); + + return 0; +} + +static void rk_edp_init_aux(struct rk3288_edp *regs) +{ + /* Clear inerrupts related to AUX channel */ + writel(AUX_FUNC_EN_N, ®s->dp_int_sta); + + /* Disable AUX channel module */ + setbits_le32(®s->func_en_2, AUX_FUNC_EN_N); + + /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ + writel(DEFER_CTRL_EN | DEFER_COUNT(1), ®s->aux_ch_defer_dtl); + + /* Enable AUX channel module */ + clrbits_le32(®s->func_en_2, AUX_FUNC_EN_N); +} + +static int rk_edp_aux_enable(struct rk3288_edp *regs) +{ + ulong start; + + setbits_le32(®s->aux_ch_ctl_2, AUX_EN); + start = get_timer(0); + do { + if (!(readl(®s->aux_ch_ctl_2) & AUX_EN)) + return 0; + } while (get_timer(start) < 20); + + return -ETIMEDOUT; +} + +static int rk_edp_is_aux_reply(struct rk3288_edp *regs) +{ + ulong start; + + start = get_timer(0); + while (!(readl(®s->dp_int_sta) & RPLY_RECEIV)) { + if (get_timer(start) > 10) + return -ETIMEDOUT; + } + + writel(RPLY_RECEIV, ®s->dp_int_sta); + + return 0; +} + +static int rk_edp_start_aux_transaction(struct rk3288_edp *regs) +{ + int val, ret; + + /* Enable AUX CH operation */ + ret = rk_edp_aux_enable(regs); + if (ret) { + debug("AUX CH enable timeout!\n"); + return ret; + } + + /* Is AUX CH command reply received? */ + if (rk_edp_is_aux_reply(regs)) { + debug("AUX CH command reply failed!\n"); + return ret; + } + + /* Clear interrupt source for AUX CH access error */ + val = readl(®s->dp_int_sta); + if (val & AUX_ERR) { + writel(AUX_ERR, ®s->dp_int_sta); + return -EIO; + } + + /* Check AUX CH error access status */ + val = readl(®s->dp_int_sta); + if (val & AUX_STATUS_MASK) { + debug("AUX CH error happens: %d\n\n", val & AUX_STATUS_MASK); + return -EIO; + } + + return 0; +} + +static int rk_edp_dpcd_transfer(struct rk3288_edp *regs, + unsigned int val_addr, u8 *in_data, + unsigned int length, + enum dpcd_request request) +{ + int val; + int i, try_times; + u8 *data; + int ret = 0; + u32 len = 0; + + while (length) { + len = min(length, 16U); + for (try_times = 0; try_times < 10; try_times++) { + data = in_data; + /* Clear AUX CH data buffer */ + writel(BUF_CLR, ®s->buf_data_ctl); + + /* Select DPCD device address */ + writel(AUX_ADDR_7_0(val_addr), ®s->aux_addr_7_0); + writel(AUX_ADDR_15_8(val_addr), ®s->aux_addr_15_8); + writel(AUX_ADDR_19_16(val_addr), ®s->aux_addr_19_16); + + /* + * Set DisplayPort transaction and read 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + if (request == DPCD_WRITE) { + val = AUX_LENGTH(len) | + AUX_TX_COMM_DP_TRANSACTION | + AUX_TX_COMM_WRITE; + for (i = 0; i < len; i++) + writel(*data++, ®s->buf_data[i]); + } else + val = AUX_LENGTH(len) | + AUX_TX_COMM_DP_TRANSACTION | + AUX_TX_COMM_READ; + + writel(val, ®s->aux_ch_ctl_1); + + /* Start AUX transaction */ + ret = rk_edp_start_aux_transaction(regs); + if (ret == 0) + break; + else + printf("read dpcd Aux Transaction fail!\n"); + } + + if (ret) + return ret; + + if (request == DPCD_READ) { + for (i = 0; i < len; i++) + *data++ = (u8)readl(®s->buf_data[i]); + } + + length -= len; + val_addr += len; + in_data += len; + } + + return 0; +} + +static int rk_edp_dpcd_read(struct rk3288_edp *regs, u32 addr, u8 *values, + size_t size) +{ + return rk_edp_dpcd_transfer(regs, addr, values, size, DPCD_READ); +} + +static int rk_edp_dpcd_write(struct rk3288_edp *regs, u32 addr, u8 *values, + size_t size) +{ + return rk_edp_dpcd_transfer(regs, addr, values, size, DPCD_WRITE); +} + + +static int rk_edp_link_power_up(struct rk_edp_priv *edp) +{ + u8 value; + int ret; + + /* DP_SET_POWER register is only available on DPCD v1.1 and later */ + if (edp->link_train.revision < 0x11) + return 0; + + ret = rk_edp_dpcd_read(edp->regs, DPCD_LINK_POWER_STATE, &value, 1); + if (ret) + return ret; + + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D0; + + ret = rk_edp_dpcd_write(edp->regs, DPCD_LINK_POWER_STATE, &value, 1); + if (ret) + return ret; + + /* + * According to the DP 1.1 specification, a "Sink Device must exit the + * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink + * Control Field" (register 0x600). + */ + mdelay(1); + + return 0; +} + +static int rk_edp_link_configure(struct rk_edp_priv *edp) +{ + u8 values[2]; + + values[0] = edp->link_train.link_rate; + values[1] = edp->link_train.lane_count; + + return rk_edp_dpcd_write(edp->regs, DPCD_LINK_BW_SET, values, + sizeof(values)); +} + +static void rk_edp_set_link_training(struct rk_edp_priv *edp, + const u8 *training_values) +{ + int i; + + for (i = 0; i < edp->link_train.lane_count; i++) + writel(training_values[i], &edp->regs->ln_link_trn_ctl[i]); +} + +static u8 edp_link_status(const u8 *link_status, int r) +{ + return link_status[r - DPCD_LANE0_1_STATUS]; +} + +static int rk_edp_dpcd_read_link_status(struct rk_edp_priv *edp, + u8 *link_status) +{ + return rk_edp_dpcd_read(edp->regs, DPCD_LANE0_1_STATUS, link_status, + DP_LINK_STATUS_SIZE); +} + +static u8 edp_get_lane_status(const u8 *link_status, int lane) +{ + int i = DPCD_LANE0_1_STATUS + (lane >> 1); + int s = (lane & 1) * 4; + u8 l = edp_link_status(link_status, i); + + return (l >> s) & 0xf; +} + +static int rk_edp_clock_recovery(const u8 *link_status, int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = edp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return -EIO; + } + + return 0; +} + +static int rk_edp_channel_eq(const u8 *link_status, int lane_count) +{ + u8 lane_align; + u8 lane_status; + int lane; + + lane_align = edp_link_status(link_status, + DPCD_LANE_ALIGN_STATUS_UPDATED); + if (!(lane_align & DP_INTERLANE_ALIGN_DONE)) + return -EIO; + for (lane = 0; lane < lane_count; lane++) { + lane_status = edp_get_lane_status(link_status, lane); + if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) + return -EIO; + } + + return 0; +} + +static uint rk_edp_get_adjust_request_voltage(const u8 *link_status, int lane) +{ + int i = DPCD_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : + DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); + u8 l = edp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; +} + +static uint rk_edp_get_adjust_request_pre_emphasis(const u8 *link_status, + int lane) +{ + int i = DPCD_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); + u8 l = edp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; +} + +static void edp_get_adjust_train(const u8 *link_status, int lane_count, + u8 train_set[]) +{ + uint v = 0; + uint p = 0; + int lane; + + for (lane = 0; lane < lane_count; lane++) { + uint this_v, this_p; + + this_v = rk_edp_get_adjust_request_voltage(link_status, lane); + this_p = rk_edp_get_adjust_request_pre_emphasis(link_status, + lane); + + debug("requested signal parameters: lane %d voltage %s pre_emph %s\n", + lane, + voltage_names[this_v >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[this_p >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + if (this_v > v) + v = this_v; + if (this_p > p) + p = this_p; + } + + if (v >= DP_VOLTAGE_MAX) + v |= DP_TRAIN_MAX_SWING_REACHED; + + if (p >= DP_PRE_EMPHASIS_MAX) + p |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + debug("using signal parameters: voltage %s pre_emph %s\n", + voltage_names[(v & DP_TRAIN_VOLTAGE_SWING_MASK) + >> DP_TRAIN_VOLTAGE_SWING_SHIFT], + pre_emph_names[(p & DP_TRAIN_PRE_EMPHASIS_MASK) + >> DP_TRAIN_PRE_EMPHASIS_SHIFT]); + + for (lane = 0; lane < 4; lane++) + train_set[lane] = v | p; +} + +static int rk_edp_link_train_cr(struct rk_edp_priv *edp) +{ + struct rk3288_edp *regs = edp->regs; + int clock_recovery; + uint voltage, tries = 0; + u8 status[DP_LINK_STATUS_SIZE]; + int i, ret; + u8 value; + + value = DP_TRAINING_PATTERN_1; + writel(value, ®s->dp_training_ptn_set); + ret = rk_edp_dpcd_write(regs, DPCD_TRAINING_PATTERN_SET, &value, 1); + if (ret) + return ret; + memset(edp->train_set, '\0', sizeof(edp->train_set)); + + /* clock recovery loop */ + clock_recovery = 0; + tries = 0; + voltage = 0xff; + + while (1) { + rk_edp_set_link_training(edp, edp->train_set); + ret = rk_edp_dpcd_write(regs, DPCD_TRAINING_LANE0_SET, + edp->train_set, + edp->link_train.lane_count); + if (ret) + return ret; + + mdelay(1); + + ret = rk_edp_dpcd_read_link_status(edp, status); + if (ret) { + printf("displayport link status failed, ret=%d\n", ret); + break; + } + + clock_recovery = rk_edp_clock_recovery(status, + edp->link_train.lane_count); + if (!clock_recovery) + break; + + for (i = 0; i < edp->link_train.lane_count; i++) { + if ((edp->train_set[i] & + DP_TRAIN_MAX_SWING_REACHED) == 0) + break; + } + if (i == edp->link_train.lane_count) { + printf("clock recovery reached max voltage\n"); + break; + } + + if ((edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == + voltage) { + if (++tries == MAX_CR_LOOP) { + printf("clock recovery tried 5 times\n"); + break; + } + } else { + tries = 0; + } + + voltage = edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK; + + /* Compute new train_set as requested by sink */ + edp_get_adjust_train(status, edp->link_train.lane_count, + edp->train_set); + } + if (clock_recovery) { + printf("clock recovery failed: %d\n", clock_recovery); + return clock_recovery; + } else { + debug("clock recovery at voltage %d pre-emphasis %d\n", + edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (edp->train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT); + return 0; + } +} + +static int rk_edp_link_train_ce(struct rk_edp_priv *edp) +{ + struct rk3288_edp *regs = edp->regs; + int channel_eq; + u8 value; + int tries; + u8 status[DP_LINK_STATUS_SIZE]; + int ret; + + value = DP_TRAINING_PATTERN_2; + writel(value, ®s->dp_training_ptn_set); + ret = rk_edp_dpcd_write(regs, DPCD_TRAINING_PATTERN_SET, &value, 1); + if (ret) + return ret; + + /* channel equalization loop */ + channel_eq = 0; + for (tries = 0; tries < 5; tries++) { + rk_edp_set_link_training(edp, edp->train_set); + ret = rk_edp_dpcd_write(regs, DPCD_TRAINING_LANE0_SET, + edp->train_set, + edp->link_train.lane_count); + if (ret) + return ret; + + udelay(400); + + if (rk_edp_dpcd_read_link_status(edp, status) < 0) { + printf("displayport link status failed\n"); + return -1; + } + + channel_eq = rk_edp_channel_eq(status, + edp->link_train.lane_count); + if (!channel_eq) + break; + edp_get_adjust_train(status, edp->link_train.lane_count, + edp->train_set); + } + + if (channel_eq) { + printf("channel eq failed, ret=%d\n", channel_eq); + return channel_eq; + } + + debug("channel eq at voltage %d pre-emphasis %d\n", + edp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK, + (edp->train_set[0] & DP_TRAIN_PRE_EMPHASIS_MASK) + >> DP_TRAIN_PRE_EMPHASIS_SHIFT); + + return 0; +} + +static int rk_edp_init_training(struct rk_edp_priv *edp) +{ + u8 values[3]; + int ret; + + ret = rk_edp_dpcd_read(edp->regs, DPCD_DPCD_REV, values, + sizeof(values)); + if (ret < 0) + return ret; + + edp->link_train.revision = values[0]; + edp->link_train.link_rate = values[1]; + edp->link_train.lane_count = values[2] & DP_MAX_LANE_COUNT_MASK; + + debug("max link rate:%d.%dGps max number of lanes:%d\n", + edp->link_train.link_rate * 27 / 100, + edp->link_train.link_rate * 27 % 100, + edp->link_train.lane_count); + + if ((edp->link_train.link_rate != LINK_RATE_1_62GBPS) && + (edp->link_train.link_rate != LINK_RATE_2_70GBPS)) { + debug("Rx Max Link Rate is abnormal :%x\n", + edp->link_train.link_rate); + return -EPERM; + } + + if (edp->link_train.lane_count == 0) { + debug("Rx Max Lane count is abnormal :%x\n", + edp->link_train.lane_count); + return -EPERM; + } + + ret = rk_edp_link_power_up(edp); + if (ret) + return ret; + + return rk_edp_link_configure(edp); +} + +static int rk_edp_hw_link_training(struct rk_edp_priv *edp) +{ + ulong start; + u32 val; + int ret; + + /* Set link rate and count as you want to establish */ + writel(edp->link_train.link_rate, &edp->regs->link_bw_set); + writel(edp->link_train.lane_count, &edp->regs->lane_count_set); + + ret = rk_edp_link_train_cr(edp); + if (ret) + return ret; + ret = rk_edp_link_train_ce(edp); + if (ret) + return ret; + + writel(HW_LT_EN, &edp->regs->dp_hw_link_training); + start = get_timer(0); + do { + val = readl(&edp->regs->dp_hw_link_training); + if (!(val & HW_LT_EN)) + break; + } while (get_timer(start) < 10); + + if (val & HW_LT_ERR_CODE_MASK) { + printf("edp hw link training error: %d\n", + val >> HW_LT_ERR_CODE_SHIFT); + return -EIO; + } + + return 0; +} + +static int rk_edp_select_i2c_device(struct rk3288_edp *regs, + unsigned int device_addr, + unsigned int val_addr) +{ + int ret; + + /* Set EDID device address */ + writel(device_addr, ®s->aux_addr_7_0); + writel(0x0, ®s->aux_addr_15_8); + writel(0x0, ®s->aux_addr_19_16); + + /* Set offset from base address of EDID device */ + writel(val_addr, ®s->buf_data[0]); + + /* + * Set I2C transaction and write address + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + writel(AUX_TX_COMM_I2C_TRANSACTION | AUX_TX_COMM_MOT | + AUX_TX_COMM_WRITE, ®s->aux_ch_ctl_1); + + /* Start AUX transaction */ + ret = rk_edp_start_aux_transaction(regs); + if (ret != 0) { + debug("select_i2c_device Aux Transaction fail!\n"); + return ret; + } + + return 0; +} + +static int rk_edp_i2c_read(struct rk3288_edp *regs, unsigned int device_addr, + unsigned int val_addr, unsigned int count, u8 edid[]) +{ + u32 val; + unsigned int i, j; + unsigned int cur_data_idx; + unsigned int defer = 0; + int ret = 0; + + for (i = 0; i < count; i += 16) { + for (j = 0; j < 10; j++) { /* try 10 times */ + /* Clear AUX CH data buffer */ + writel(BUF_CLR, ®s->buf_data_ctl); + + /* Set normal AUX CH command */ + clrbits_le32(®s->aux_ch_ctl_2, ADDR_ONLY); + + /* + * If Rx sends defer, Tx sends only reads + * request without sending addres + */ + if (!defer) { + ret = rk_edp_select_i2c_device(regs, + device_addr, + val_addr + i); + } else { + defer = 0; + } + + /* + * Set I2C transaction and write data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + writel(AUX_LENGTH(16) | AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ, ®s->aux_ch_ctl_1); + + /* Start AUX transaction */ + ret = rk_edp_start_aux_transaction(regs); + if (ret == 0) { + break; + } else { + debug("Aux Transaction fail!\n"); + continue; + } + + /* Check if Rx sends defer */ + val = readl(®s->aux_rx_comm); + if (val == AUX_RX_COMM_AUX_DEFER || + val == AUX_RX_COMM_I2C_DEFER) { + debug("Defer: %d\n\n", val); + defer = 1; + } + } + + if (ret) + return ret; + + for (cur_data_idx = 0; cur_data_idx < 16; cur_data_idx++) { + val = readl(®s->buf_data[cur_data_idx]); + edid[i + cur_data_idx] = (u8)val; + } + } + + return 0; +} + +static int rk_edp_set_link_train(struct rk_edp_priv *edp) +{ + int ret; + + ret = rk_edp_init_training(edp); + if (ret) { + printf("DP LT init failed!\n"); + return ret; + } + + ret = rk_edp_hw_link_training(edp); + if (ret) + return ret; + + return 0; +} + +static void rk_edp_init_video(struct rk3288_edp *regs) +{ + writel(VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG, + ®s->common_int_sta_1); + writel(CHA_CRI(4) | CHA_CTRL, ®s->sys_ctl_2); + writel(VID_HRES_TH(2) | VID_VRES_TH(0), ®s->video_ctl_8); +} + +static void rk_edp_config_video_slave_mode(struct rk3288_edp *regs) +{ + clrbits_le32(®s->func_en_1, VID_FIFO_FUNC_EN_N | VID_CAP_FUNC_EN_N); +} + +static void rk_edp_set_video_cr_mn(struct rk3288_edp *regs, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value) +{ + if (type == REGISTER_M) { + setbits_le32(®s->sys_ctl_4, FIX_M_VID); + writel(m_value & 0xff, ®s->m_vid_0); + writel((m_value >> 8) & 0xff, ®s->m_vid_1); + writel((m_value >> 16) & 0xff, ®s->m_vid_2); + + writel(n_value & 0xf, ®s->n_vid_0); + writel((n_value >> 8) & 0xff, ®s->n_vid_1); + writel((n_value >> 16) & 0xff, ®s->n_vid_2); + } else { + clrbits_le32(®s->sys_ctl_4, FIX_M_VID); + + writel(0x00, ®s->n_vid_0); + writel(0x80, ®s->n_vid_1); + writel(0x00, ®s->n_vid_2); + } +} + +static int rk_edp_is_video_stream_clock_on(struct rk3288_edp *regs) +{ + ulong start; + u32 val; + + start = get_timer(0); + do { + val = readl(®s->sys_ctl_1); + + /* must write value to update DET_STA bit status */ + writel(val, ®s->sys_ctl_1); + val = readl(®s->sys_ctl_1); + if (!(val & DET_STA)) + continue; + + val = readl(®s->sys_ctl_2); + + /* must write value to update CHA_STA bit status */ + writel(val, ®s->sys_ctl_2); + val = readl(®s->sys_ctl_2); + if (!(val & CHA_STA)) + return 0; + + } while (get_timer(start) < 100); + + return -ETIMEDOUT; +} + +static int rk_edp_is_video_stream_on(struct rk_edp_priv *edp) +{ + ulong start; + u32 val; + + start = get_timer(0); + do { + val = readl(&edp->regs->sys_ctl_3); + + /* must write value to update STRM_VALID bit status */ + writel(val, &edp->regs->sys_ctl_3); + + val = readl(&edp->regs->sys_ctl_3); + if (!(val & STRM_VALID)) + return 0; + } while (get_timer(start) < 100); + + return -ETIMEDOUT; +} + +static int rk_edp_config_video(struct rk_edp_priv *edp) +{ + int ret; + + rk_edp_config_video_slave_mode(edp->regs); + + if (!rk_edp_get_pll_locked(edp->regs)) { + debug("PLL is not locked yet.\n"); + return -ETIMEDOUT; + } + + ret = rk_edp_is_video_stream_clock_on(edp->regs); + if (ret) + return ret; + + /* Set to use the register calculated M/N video */ + rk_edp_set_video_cr_mn(edp->regs, CALCULATED_M, 0, 0); + + /* For video bist, Video timing must be generated by register */ + clrbits_le32(&edp->regs->video_ctl_10, F_SEL); + + /* Disable video mute */ + clrbits_le32(&edp->regs->video_ctl_1, VIDEO_MUTE); + + /* Enable video at next frame */ + setbits_le32(&edp->regs->video_ctl_1, VIDEO_EN); + + return rk_edp_is_video_stream_on(edp); +} + +static void rockchip_edp_force_hpd(struct rk_edp_priv *edp) +{ + setbits_le32(&edp->regs->sys_ctl_3, F_HPD | HPD_CTRL); +} + +static int rockchip_edp_get_plug_in_status(struct rk_edp_priv *edp) +{ + u32 val; + + val = readl(&edp->regs->sys_ctl_3); + if (val & HPD_STATUS) + return 1; + + return 0; +} + +/* + * support edp HPD function + * some hardware version do not support edp hdp, + * we use 200ms to try to get the hpd single now, + * if we can not get edp hpd single, it will delay 200ms, + * also meet the edp power timing request, to compatible + * all of the hardware version + */ +static void rockchip_edp_wait_hpd(struct rk_edp_priv *edp) +{ + ulong start; + + start = get_timer(0); + do { + if (rockchip_edp_get_plug_in_status(edp)) + return; + udelay(100); + } while (get_timer(start) < 200); + + debug("do not get hpd single, force hpd\n"); + rockchip_edp_force_hpd(edp); +} + +static int rk_edp_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *edid) +{ + struct rk_edp_priv *priv = dev_get_priv(dev); + int ret = 0; + + ret = rk_edp_set_link_train(priv); + if (ret) { + printf("link train failed!\n"); + return ret; + } + + rk_edp_init_video(priv->regs); + ret = rk_edp_config_video(priv); + if (ret) { + printf("config video failed\n"); + return ret; + } + ret = panel_enable_backlight(priv->panel); + if (ret) { + debug("%s: backlight error: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int rk_edp_read_edid(struct udevice *dev, u8 *buf, int buf_size) +{ + struct rk_edp_priv *priv = dev_get_priv(dev); + u32 edid_size = EDID_LENGTH; + int ret; + int i; + + for (i = 0; i < 3; i++) { + ret = rk_edp_i2c_read(priv->regs, EDID_ADDR, EDID_HEADER, + EDID_LENGTH, &buf[EDID_HEADER]); + if (ret) { + debug("EDID read failed\n"); + continue; + } + + /* + * check if the EDID has an extension flag, and read additional + * EDID data if needed + */ + if (buf[EDID_EXTENSION_FLAG]) { + edid_size += EDID_LENGTH; + ret = rk_edp_i2c_read(priv->regs, EDID_ADDR, + EDID_LENGTH, EDID_LENGTH, + &buf[EDID_LENGTH]); + if (ret) { + debug("EDID Read failed!\n"); + continue; + } + } + goto done; + } + + /* After 3 attempts, give up */ + return ret; + +done: + return edid_size; +} + +static int rk_edp_of_to_plat(struct udevice *dev) +{ + struct rk_edp_priv *priv = dev_get_priv(dev); + + priv->regs = dev_read_addr_ptr(dev); + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + + return 0; +} + +static int rk_edp_remove(struct udevice *dev) +{ + struct rk_edp_priv *priv = dev_get_priv(dev); + struct rk3288_edp *regs = priv->regs; + + setbits_le32(®s->video_ctl_1, VIDEO_MUTE); + clrbits_le32(®s->video_ctl_1, VIDEO_EN); + clrbits_le32(®s->sys_ctl_3, F_HPD | HPD_CTRL); + setbits_le32(®s->func_en_1, SW_FUNC_EN_N); + + return 0; +} + +static int rk_edp_probe(struct udevice *dev) +{ + struct display_plat *uc_plat = dev_get_uclass_plat(dev); + struct rk_edp_priv *priv = dev_get_priv(dev); + struct rk3288_edp *regs = priv->regs; + struct rockchip_dp_data *edp_data = (struct rockchip_dp_data *)dev_get_driver_data(dev); + struct reset_ctl dp_rst; + + struct clk clk; + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, "rockchip,panel", + &priv->panel); + if (ret) { + debug("%s: Cannot find panel for '%s' (ret=%d)\n", __func__, + dev->name, ret); + return ret; + } + + ret = reset_get_by_name(dev, "dp", &dp_rst); + if (ret) { + dev_err(dev, "failed to get dp reset (ret=%d)\n", ret); + return ret; + } + + ret = reset_assert(&dp_rst); + if (ret) { + dev_err(dev, "failed to assert dp reset (ret=%d)\n", ret); + return ret; + } + udelay(20); + + ret = reset_deassert(&dp_rst); + if (ret) { + dev_err(dev, "failed to deassert dp reset (ret=%d)\n", ret); + return ret; + } + + int vop_id = uc_plat->source_id; + debug("%s, uc_plat=%p, vop_id=%u\n", __func__, uc_plat, vop_id); + + if (edp_data->chip_type == RK3288_DP) { + ret = clk_get_by_index(dev, 1, &clk); + if (ret >= 0) { + ret = clk_set_rate(&clk, 0); + clk_free(&clk); + } + if (ret) { + debug("%s: Failed to set EDP clock: ret=%d\n", __func__, ret); + return ret; + } + } + ret = clk_get_by_index(uc_plat->src_dev, 0, &clk); + if (ret >= 0) { + ret = clk_set_rate(&clk, 192000000); + clk_free(&clk); + } + if (ret < 0) { + debug("%s: Failed to set clock in source device '%s': ret=%d\n", + __func__, uc_plat->src_dev->name, ret); + return ret; + } + + /* grf_edp_ref_clk_sel: from internal 24MHz or 27MHz clock */ + rk_setreg(priv->grf + edp_data->reg_ref_clk_sel, + edp_data->ref_clk_sel_bit); + + /* select epd signal from vop0 or vop1 */ + rk_clrsetreg(priv->grf + edp_data->reg_vop_big_little, + edp_data->reg_vop_big_little_sel, + (vop_id == 1) ? edp_data->reg_vop_big_little_sel : 0); + + rockchip_edp_wait_hpd(priv); + + rk_edp_init_refclk(regs, edp_data->chip_type); + rk_edp_init_interrupt(regs); + rk_edp_enable_sw_function(regs); + ret = rk_edp_init_analog_func(regs); + if (ret) + return ret; + rk_edp_init_aux(regs); + + return 0; +} + +static const struct dm_display_ops dp_rockchip_ops = { + .read_edid = rk_edp_read_edid, + .enable = rk_edp_enable, +}; + +static const struct rockchip_dp_data rk3399_edp = { + .reg_vop_big_little = RK3399_GRF_SOC_CON20, + .reg_vop_big_little_sel = BIT(5), + .reg_ref_clk_sel = RK3399_GRF_SOC_CON25, + .ref_clk_sel_bit = BIT(11), + .chip_type = RK3399_EDP, +}; + +static const struct rockchip_dp_data rk3288_dp = { + .reg_vop_big_little = RK3288_GRF_SOC_CON6, + .reg_vop_big_little_sel = BIT(5), + .reg_ref_clk_sel = RK3288_GRF_SOC_CON12, + .ref_clk_sel_bit = BIT(4), + .chip_type = RK3288_DP, +}; + +static const struct udevice_id rockchip_dp_ids[] = { + { .compatible = "rockchip,rk3288-edp", .data = (ulong)&rk3288_dp }, + { .compatible = "rockchip,rk3399-edp", .data = (ulong)&rk3399_edp }, + { } +}; + +U_BOOT_DRIVER(dp_rockchip) = { + .name = "edp_rockchip", + .id = UCLASS_DISPLAY, + .of_match = rockchip_dp_ids, + .ops = &dp_rockchip_ops, + .of_to_plat = rk_edp_of_to_plat, + .probe = rk_edp_probe, + .remove = rk_edp_remove, + .priv_auto = sizeof(struct rk_edp_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk_hdmi.c b/roms/u-boot/drivers/video/rockchip/rk_hdmi.c new file mode 100644 index 000000000..8dcd4d596 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_hdmi.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <dw_hdmi.h> +#include <edid.h> +#include <log.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/hardware.h> +#include "rk_hdmi.h" +#include "rk_vop.h" /* for rk_vop_probe_regulators */ + +static const struct hdmi_phy_config rockchip_phy_config[] = { + { + .mpixelclock = 74250000, + .sym_ctr = 0x8009, .term = 0x0004, .vlev_ctr = 0x0272, + }, { + .mpixelclock = 148500000, + .sym_ctr = 0x802b, .term = 0x0004, .vlev_ctr = 0x028d, + }, { + .mpixelclock = 297000000, + .sym_ctr = 0x8039, .term = 0x0005, .vlev_ctr = 0x028d, + }, { + .mpixelclock = 584000000, + .sym_ctr = 0x8039, .term = 0x0000, .vlev_ctr = 0x019d, + }, { + .mpixelclock = ~0ul, + .sym_ctr = 0x0000, .term = 0x0000, .vlev_ctr = 0x0000, + } +}; + +static const struct hdmi_mpll_config rockchip_mpll_cfg[] = { + { + .mpixelclock = 40000000, + .cpce = 0x00b3, .gmp = 0x0000, .curr = 0x0018, + }, { + .mpixelclock = 65000000, + .cpce = 0x0072, .gmp = 0x0001, .curr = 0x0028, + }, { + .mpixelclock = 66000000, + .cpce = 0x013e, .gmp = 0x0003, .curr = 0x0038, + }, { + .mpixelclock = 83500000, + .cpce = 0x0072, .gmp = 0x0001, .curr = 0x0028, + }, { + .mpixelclock = 146250000, + .cpce = 0x0051, .gmp = 0x0002, .curr = 0x0038, + }, { + .mpixelclock = 148500000, + .cpce = 0x0051, .gmp = 0x0003, .curr = 0x0000, + }, { + .mpixelclock = 272000000, + .cpce = 0x0040, .gmp = 0x0003, .curr = 0x0000, + }, { + .mpixelclock = 340000000, + .cpce = 0x0040, .gmp = 0x0003, .curr = 0x0000, + }, { + .mpixelclock = ~0ul, + .cpce = 0x0051, .gmp = 0x0003, .curr = 0x0000, + } +}; + +int rk_hdmi_read_edid(struct udevice *dev, u8 *buf, int buf_size) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + + return dw_hdmi_read_edid(&priv->hdmi, buf, buf_size); +} + +int rk_hdmi_of_to_plat(struct udevice *dev) +{ + struct rk_hdmi_priv *priv = dev_get_priv(dev); + struct dw_hdmi *hdmi = &priv->hdmi; + + hdmi->ioaddr = (ulong)dev_read_addr(dev); + hdmi->mpll_cfg = rockchip_mpll_cfg; + hdmi->phy_cfg = rockchip_phy_config; + + /* 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); + + uclass_get_device_by_phandle(UCLASS_I2C, dev, "ddc-i2c-bus", + &hdmi->ddc_bus); + + return 0; +} + +void rk_hdmi_probe_regulators(struct udevice *dev, + const char * const *names, int cnt) +{ + rk_vop_probe_regulators(dev, names, cnt); +} + +int rk_hdmi_probe(struct udevice *dev) +{ + struct rk_hdmi_priv *priv = dev_get_priv(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); + + return 0; +} diff --git a/roms/u-boot/drivers/video/rockchip/rk_hdmi.h b/roms/u-boot/drivers/video/rockchip/rk_hdmi.h new file mode 100644 index 000000000..859a0b9ff --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_hdmi.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH + */ + +#ifndef __RK_HDMI_H__ +#define __RK_HDMI_H__ + +struct rkhdmi_driverdata { + /* configuration */ + u8 i2c_clk_high; + u8 i2c_clk_low; + const char * const *regulator_names; + u32 regulator_names_cnt; + /* setters/getters */ + int (*set_input_vop)(struct udevice *dev); + int (*clk_config)(struct udevice *dev); +}; + +struct rk_hdmi_priv { + struct dw_hdmi hdmi; + void *grf; +}; + +/** + * rk_hdmi_read_edid() - read the attached HDMI/DVI monitor's EDID + * + * N.B.: The buffer should be large enough to hold 2 EDID blocks, as + * this function calls dw_hdmi_read_edid, which ignores buf_size + * argument and assumes that there's always enough space for 2 + * EDID blocks. + * + * @dev: device + * @buf: output buffer for the EDID + * @buf_size: number of bytes in the buffer + * @return number of bytes read if OK, -ve if something went wrong + */ +int rk_hdmi_read_edid(struct udevice *dev, u8 *buf, int buf_size); + +/** + * rk_hdmi_probe_regulators() - probe (autoset + enable) regulators + * + * Probes a list of regulators by performing autoset and enable + * operations on them. The list of regulators is an array of string + * pointers and any individual regulator-probe may fail without + * counting as an error. + * + * @dev: device + * @names: array of string-pointers to regulator names to probe + * @cnt: number of elements in the 'names' array + */ +void rk_hdmi_probe_regulators(struct udevice *dev, + const char * const *names, int cnt); +/** + * rk_hdmi_of_to_plat() - common of_to_plat implementation + * + * @dev: device + * @return 0 if OK, -ve if something went wrong + */ +int rk_hdmi_of_to_plat(struct udevice *dev); + +/** + * rk_hdmi_probe() - common probe implementation + * + * Performs the following, common initialisation steps: + * 1. checks for HPD (i.e. a HDMI monitor being attached) + * 2. initialises the Designware HDMI core + * 3. initialises the Designware HDMI PHY + * + * @dev: device + * @return 0 if OK, -ve if something went wrong + */ +int rk_hdmi_probe(struct udevice *dev); + +#endif diff --git a/roms/u-boot/drivers/video/rockchip/rk_lvds.c b/roms/u-boot/drivers/video/rockchip/rk_lvds.c new file mode 100644 index 000000000..9cf3e3ca7 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_lvds.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2016 Rockchip Inc. + */ + +#include <common.h> +#include <display.h> +#include <dm.h> +#include <edid.h> +#include <log.h> +#include <panel.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/global_data.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/grf_rk3288.h> +#include <asm/arch-rockchip/hardware.h> +#include <asm/arch-rockchip/lvds_rk3288.h> +#include <dt-bindings/clock/rk3288-cru.h> +#include <dt-bindings/video/rk3288.h> + +DECLARE_GLOBAL_DATA_PTR; + +/** + * struct rk_lvds_priv - private rockchip lvds display driver info + * + * @reg: LVDS register address + * @grf: GRF register + * @panel: Panel device that is used in driver + * + * @output: Output mode, decided single or double channel, + * LVDS or LVTLL + * @format: Data format that RGB data will packing as + */ +struct rk_lvds_priv { + void __iomem *regs; + struct rk3288_grf *grf; + struct udevice *panel; + + int output; + int format; +}; + +static inline void lvds_writel(struct rk_lvds_priv *lvds, u32 offset, u32 val) +{ + writel(val, lvds->regs + offset); + + writel(val, lvds->regs + offset + 0x100); +} + +int rk_lvds_enable(struct udevice *dev, int panel_bpp, + const struct display_timing *edid) +{ + struct rk_lvds_priv *priv = dev_get_priv(dev); + struct display_plat *uc_plat = dev_get_uclass_plat(dev); + int ret = 0; + unsigned int val = 0; + + ret = panel_enable_backlight(priv->panel); + if (ret) { + debug("%s: backlight error: %d\n", __func__, ret); + return ret; + } + + /* Select the video source */ + if (uc_plat->source_id) + val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT | + (RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16); + else + val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16; + rk_setreg(&priv->grf->soc_con6, val); + + /* Select data transfer format */ + val = priv->format; + if (priv->output == LVDS_OUTPUT_DUAL) + val |= LVDS_DUAL | LVDS_CH0_EN | LVDS_CH1_EN; + else if (priv->output == LVDS_OUTPUT_SINGLE) + val |= LVDS_CH0_EN; + else if (priv->output == LVDS_OUTPUT_RGB) + val |= LVDS_TTL_EN | LVDS_CH0_EN | LVDS_CH1_EN; + val |= (0xffff << 16); + rk_setreg(&priv->grf->soc_con7, val); + + /* Enable LVDS PHY */ + if (priv->output == LVDS_OUTPUT_RGB) { + lvds_writel(priv, RK3288_LVDS_CH0_REG0, + RK3288_LVDS_CH0_REG0_TTL_EN | + RK3288_LVDS_CH0_REG0_LANECK_EN | + RK3288_LVDS_CH0_REG0_LANE4_EN | + RK3288_LVDS_CH0_REG0_LANE3_EN | + RK3288_LVDS_CH0_REG0_LANE2_EN | + RK3288_LVDS_CH0_REG0_LANE1_EN | + RK3288_LVDS_CH0_REG0_LANE0_EN); + lvds_writel(priv, RK3288_LVDS_CH0_REG2, + RK3288_LVDS_PLL_FBDIV_REG2(0x46)); + + lvds_writel(priv, RK3288_LVDS_CH0_REG3, + RK3288_LVDS_PLL_FBDIV_REG3(0x46)); + lvds_writel(priv, RK3288_LVDS_CH0_REG4, + RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE | + RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE | + RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE | + RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE | + RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE | + RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE); + lvds_writel(priv, RK3288_LVDS_CH0_REG5, + RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA | + RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA | + RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA | + RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA | + RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA | + RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA); + lvds_writel(priv, RK3288_LVDS_CH0_REGD, + RK3288_LVDS_PLL_PREDIV_REGD(0x0a)); + lvds_writel(priv, RK3288_LVDS_CH0_REG20, + RK3288_LVDS_CH0_REG20_LSB); + } else { + lvds_writel(priv, RK3288_LVDS_CH0_REG0, + RK3288_LVDS_CH0_REG0_LVDS_EN | + RK3288_LVDS_CH0_REG0_LANECK_EN | + RK3288_LVDS_CH0_REG0_LANE4_EN | + RK3288_LVDS_CH0_REG0_LANE3_EN | + RK3288_LVDS_CH0_REG0_LANE2_EN | + RK3288_LVDS_CH0_REG0_LANE1_EN | + RK3288_LVDS_CH0_REG0_LANE0_EN); + lvds_writel(priv, RK3288_LVDS_CH0_REG1, + RK3288_LVDS_CH0_REG1_LANECK_BIAS | + RK3288_LVDS_CH0_REG1_LANE4_BIAS | + RK3288_LVDS_CH0_REG1_LANE3_BIAS | + RK3288_LVDS_CH0_REG1_LANE2_BIAS | + RK3288_LVDS_CH0_REG1_LANE1_BIAS | + RK3288_LVDS_CH0_REG1_LANE0_BIAS); + lvds_writel(priv, RK3288_LVDS_CH0_REG2, + RK3288_LVDS_CH0_REG2_RESERVE_ON | + RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE | + RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE | + RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE | + RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE | + RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE | + RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE | + RK3288_LVDS_PLL_FBDIV_REG2(0x46)); + lvds_writel(priv, RK3288_LVDS_CH0_REG3, + RK3288_LVDS_PLL_FBDIV_REG3(0x46)); + lvds_writel(priv, RK3288_LVDS_CH0_REG4, 0x00); + lvds_writel(priv, RK3288_LVDS_CH0_REG5, 0x00); + lvds_writel(priv, RK3288_LVDS_CH0_REGD, + RK3288_LVDS_PLL_PREDIV_REGD(0x0a)); + lvds_writel(priv, RK3288_LVDS_CH0_REG20, + RK3288_LVDS_CH0_REG20_LSB); + } + + /* Power on */ + writel(RK3288_LVDS_CFG_REGC_PLL_ENABLE, + priv->regs + RK3288_LVDS_CFG_REGC); + + writel(RK3288_LVDS_CFG_REG21_TX_ENABLE, + priv->regs + RK3288_LVDS_CFG_REG21); + + return 0; +} + +int rk_lvds_read_timing(struct udevice *dev, struct display_timing *timing) +{ + if (ofnode_decode_display_timing(dev_ofnode(dev), 0, timing)) { + debug("%s: Failed to decode display timing\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int rk_lvds_of_to_plat(struct udevice *dev) +{ + struct rk_lvds_priv *priv = dev_get_priv(dev); + int ret; + priv->regs = dev_read_addr_ptr(dev); + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + + ret = dev_read_s32_default(dev, "rockchip,output", -1); + if (ret != -1) { + priv->output = ret; + debug("LVDS output : %d\n", ret); + } else { + /* default set it as output rgb */ + priv->output = LVDS_OUTPUT_RGB; + } + + ret = dev_read_s32_default(dev, "rockchip,data-mapping", -1); + if (ret != -1) { + priv->format = ret; + debug("LVDS data-mapping : %d\n", ret); + } else { + /* default set it as format jeida */ + priv->format = LVDS_FORMAT_JEIDA; + } + + ret = dev_read_s32_default(dev, "rockchip,data-width", -1); + if (ret != -1) { + debug("LVDS data-width : %d\n", ret); + if (ret == 24) { + priv->format |= LVDS_24BIT; + } else if (ret == 18) { + priv->format |= LVDS_18BIT; + } else { + debug("rockchip-lvds unsupport data-width[%d]\n", ret); + ret = -EINVAL; + return ret; + } + } else { + priv->format |= LVDS_24BIT; + } + + return 0; +} + +int rk_lvds_probe(struct udevice *dev) +{ + struct rk_lvds_priv *priv = dev_get_priv(dev); + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, "rockchip,panel", + &priv->panel); + if (ret) { + debug("%s: Cannot find panel for '%s' (ret=%d)\n", __func__, + dev->name, ret); + return ret; + } + + return 0; +} + +static const struct dm_display_ops lvds_rockchip_ops = { + .read_timing = rk_lvds_read_timing, + .enable = rk_lvds_enable, +}; + +static const struct udevice_id rockchip_lvds_ids[] = { + {.compatible = "rockchip,rk3288-lvds"}, + {} +}; + +U_BOOT_DRIVER(lvds_rockchip) = { + .name = "lvds_rockchip", + .id = UCLASS_DISPLAY, + .of_match = rockchip_lvds_ids, + .ops = &lvds_rockchip_ops, + .of_to_plat = rk_lvds_of_to_plat, + .probe = rk_lvds_probe, + .priv_auto = sizeof(struct rk_lvds_priv), +}; diff --git a/roms/u-boot/drivers/video/rockchip/rk_mipi.c b/roms/u-boot/drivers/video/rockchip/rk_mipi.c new file mode 100644 index 000000000..881322067 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_mipi.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd + * Author: Eric Gao <eric.gao@rock-chips.com> + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <log.h> +#include <panel.h> +#include <regmap.h> +#include <asm/global_data.h> +#include "rk_mipi.h" +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <dm/uclass-internal.h> +#include <linux/kernel.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/cru.h> +#include <asm/arch-rockchip/grf_rk3399.h> +#include <asm/arch-rockchip/rockchip_mipi_dsi.h> + +DECLARE_GLOBAL_DATA_PTR; + +int rk_mipi_read_timing(struct udevice *dev, + struct display_timing *timing) +{ + int ret; + + ret = ofnode_decode_display_timing(dev_ofnode(dev), 0, timing); + if (ret) { + debug("%s: Failed to decode display timing (ret=%d)\n", + __func__, ret); + return -EINVAL; + } + + return 0; +} + +/* + * Register write function used only for mipi dsi controller. + * Parameter: + * @regs: mipi controller address + * @reg: combination of regaddr(16bit)|bitswidth(8bit)|offset(8bit) you can + * use define in rk_mipi.h directly for this parameter + * @val: value that will be write to specified bits of register + */ +static void rk_mipi_dsi_write(uintptr_t regs, u32 reg, u32 val) +{ + u32 dat; + u32 mask; + u32 offset = (reg >> OFFSET_SHIFT) & 0xff; + u32 bits = (reg >> BITS_SHIFT) & 0xff; + uintptr_t addr = (reg >> ADDR_SHIFT) + regs; + + /* Mask for specifiled bits,the corresponding bits will be clear */ + mask = ~((0xffffffff << offset) & (0xffffffff >> (32 - offset - bits))); + + /* Make sure val in the available range */ + val &= ~(0xffffffff << bits); + + /* Get register's original val */ + dat = readl(addr); + + /* Clear specified bits */ + dat &= mask; + + /* Fill specified bits */ + dat |= val << offset; + + writel(dat, addr); +} + +int rk_mipi_dsi_enable(struct udevice *dev, + const struct display_timing *timing) +{ + ofnode node, timing_node; + int val; + struct rk_mipi_priv *priv = dev_get_priv(dev); + uintptr_t regs = priv->regs; + u32 txbyte_clk = priv->txbyte_clk; + u32 txesc_clk = priv->txesc_clk; + + txesc_clk = txbyte_clk/(txbyte_clk/txesc_clk + 1); + + /* Set Display timing parameter */ + rk_mipi_dsi_write(regs, VID_HSA_TIME, timing->hsync_len.typ); + rk_mipi_dsi_write(regs, VID_HBP_TIME, timing->hback_porch.typ); + rk_mipi_dsi_write(regs, VID_HLINE_TIME, (timing->hsync_len.typ + + timing->hback_porch.typ + timing->hactive.typ + + timing->hfront_porch.typ)); + rk_mipi_dsi_write(regs, VID_VSA_LINES, timing->vsync_len.typ); + rk_mipi_dsi_write(regs, VID_VBP_LINES, timing->vback_porch.typ); + rk_mipi_dsi_write(regs, VID_VFP_LINES, timing->vfront_porch.typ); + rk_mipi_dsi_write(regs, VID_ACTIVE_LINES, timing->vactive.typ); + + /* Set Signal Polarity */ + val = (timing->flags & DISPLAY_FLAGS_HSYNC_LOW) ? 1 : 0; + rk_mipi_dsi_write(regs, HSYNC_ACTIVE_LOW, val); + + val = (timing->flags & DISPLAY_FLAGS_VSYNC_LOW) ? 1 : 0; + rk_mipi_dsi_write(regs, VSYNC_ACTIVE_LOW, val); + + val = (timing->flags & DISPLAY_FLAGS_DE_LOW) ? 1 : 0; + rk_mipi_dsi_write(regs, DATAEN_ACTIVE_LOW, val); + + val = (timing->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) ? 1 : 0; + rk_mipi_dsi_write(regs, COLORM_ACTIVE_LOW, val); + + /* Set video mode */ + rk_mipi_dsi_write(regs, CMD_VIDEO_MODE, VIDEO_MODE); + + /* Set video mode transmission type as burst mode */ + rk_mipi_dsi_write(regs, VID_MODE_TYPE, BURST_MODE); + + /* Set pix num in a video package */ + rk_mipi_dsi_write(regs, VID_PKT_SIZE, 0x4b0); + + /* Set dpi color coding depth 24 bit */ + timing_node = ofnode_find_subnode(dev_ofnode(dev), "display-timings"); + node = ofnode_first_subnode(timing_node); + + val = ofnode_read_u32_default(node, "bits-per-pixel", -1); + switch (val) { + case 16: + rk_mipi_dsi_write(regs, DPI_COLOR_CODING, DPI_16BIT_CFG_1); + break; + case 24: + rk_mipi_dsi_write(regs, DPI_COLOR_CODING, DPI_24BIT); + break; + case 30: + rk_mipi_dsi_write(regs, DPI_COLOR_CODING, DPI_30BIT); + break; + default: + rk_mipi_dsi_write(regs, DPI_COLOR_CODING, DPI_24BIT); + } + /* Enable low power mode */ + rk_mipi_dsi_write(regs, LP_CMD_EN, 1); + rk_mipi_dsi_write(regs, LP_HFP_EN, 1); + rk_mipi_dsi_write(regs, LP_VACT_EN, 1); + rk_mipi_dsi_write(regs, LP_VFP_EN, 1); + rk_mipi_dsi_write(regs, LP_VBP_EN, 1); + rk_mipi_dsi_write(regs, LP_VSA_EN, 1); + + /* Division for timeout counter clk */ + rk_mipi_dsi_write(regs, TO_CLK_DIVISION, 0x0a); + + /* Tx esc clk division from txbyte clk */ + rk_mipi_dsi_write(regs, TX_ESC_CLK_DIVISION, txbyte_clk/txesc_clk); + + /* Timeout count for hs<->lp transation between Line period */ + rk_mipi_dsi_write(regs, HSTX_TO_CNT, 0x3e8); + + /* Phy State transfer timing */ + rk_mipi_dsi_write(regs, PHY_STOP_WAIT_TIME, 32); + rk_mipi_dsi_write(regs, PHY_TXREQUESTCLKHS, 1); + rk_mipi_dsi_write(regs, PHY_HS2LP_TIME, 0x14); + rk_mipi_dsi_write(regs, PHY_LP2HS_TIME, 0x10); + rk_mipi_dsi_write(regs, MAX_RD_TIME, 0x2710); + + /* Power on */ + rk_mipi_dsi_write(regs, SHUTDOWNZ, 1); + + return 0; +} + +/* rk mipi dphy write function. It is used to write test data to dphy */ +static void rk_mipi_phy_write(uintptr_t regs, unsigned char test_code, + unsigned char *test_data, unsigned char size) +{ + int i = 0; + + /* Write Test code */ + rk_mipi_dsi_write(regs, PHY_TESTCLK, 1); + rk_mipi_dsi_write(regs, PHY_TESTDIN, test_code); + rk_mipi_dsi_write(regs, PHY_TESTEN, 1); + rk_mipi_dsi_write(regs, PHY_TESTCLK, 0); + rk_mipi_dsi_write(regs, PHY_TESTEN, 0); + + /* Write Test data */ + for (i = 0; i < size; i++) { + rk_mipi_dsi_write(regs, PHY_TESTCLK, 0); + rk_mipi_dsi_write(regs, PHY_TESTDIN, test_data[i]); + rk_mipi_dsi_write(regs, PHY_TESTCLK, 1); + } +} + +/* + * Mipi dphy config function. Calculate the suitable prediv, feedback div, + * fsfreqrang value ,cap ,lpf and so on according to the given pix clk rate, + * and then enable phy. + */ +int rk_mipi_phy_enable(struct udevice *dev) +{ + int i; + struct rk_mipi_priv *priv = dev_get_priv(dev); + uintptr_t regs = priv->regs; + u64 fbdiv; + u64 prediv = 1; + u32 max_fbdiv = 512; + u32 max_prediv, min_prediv; + u64 ddr_clk = priv->phy_clk; + u32 refclk = priv->ref_clk; + u32 remain = refclk; + unsigned char test_data[2] = {0}; + + int freq_rang[][2] = { + {90, 0x01}, {100, 0x10}, {110, 0x20}, {130, 0x01}, + {140, 0x11}, {150, 0x21}, {170, 0x02}, {180, 0x12}, + {200, 0x22}, {220, 0x03}, {240, 0x13}, {250, 0x23}, + {270, 0x04}, {300, 0x14}, {330, 0x05}, {360, 0x15}, + {400, 0x25}, {450, 0x06}, {500, 0x16}, {550, 0x07}, + {600, 0x17}, {650, 0x08}, {700, 0x18}, {750, 0x09}, + {800, 0x19}, {850, 0x29}, {900, 0x39}, {950, 0x0a}, + {1000, 0x1a}, {1050, 0x2a}, {1100, 0x3a}, {1150, 0x0b}, + {1200, 0x1b}, {1250, 0x2b}, {1300, 0x3b}, {1350, 0x0c}, + {1400, 0x1c}, {1450, 0x2c}, {1500, 0x3c} + }; + + /* Shutdown mode */ + rk_mipi_dsi_write(regs, PHY_SHUTDOWNZ, 0); + rk_mipi_dsi_write(regs, PHY_RSTZ, 0); + rk_mipi_dsi_write(regs, PHY_TESTCLR, 1); + + /* Pll locking */ + rk_mipi_dsi_write(regs, PHY_TESTCLR, 0); + + /* config cp and lfp */ + test_data[0] = 0x80 | (ddr_clk / (200 * MHz)) << 3 | 0x3; + rk_mipi_phy_write(regs, CODE_PLL_VCORANGE_VCOCAP, test_data, 1); + + test_data[0] = 0x8; + rk_mipi_phy_write(regs, CODE_PLL_CPCTRL, test_data, 1); + + test_data[0] = 0x80 | 0x40; + rk_mipi_phy_write(regs, CODE_PLL_LPF_CP, test_data, 1); + + /* select the suitable value for fsfreqrang reg */ + for (i = 0; i < ARRAY_SIZE(freq_rang); i++) { + if (ddr_clk / (MHz) <= freq_rang[i][0]) + break; + } + if (i == ARRAY_SIZE(freq_rang)) { + debug("%s: Dphy freq out of range!\n", __func__); + return -EINVAL; + } + test_data[0] = freq_rang[i][1] << 1; + rk_mipi_phy_write(regs, CODE_HS_RX_LANE0, test_data, 1); + + /* + * Calculate the best ddrclk and it's corresponding div value. If the + * given pixelclock is great than 250M, ddrclk will be fix 1500M. + * Otherwise, + * it's equal to ddr_clk= pixclk * 6. 40MHz >= refclk / prediv >= 5MHz + * according to spec. + */ + max_prediv = (refclk / (5 * MHz)); + min_prediv = ((refclk / (40 * MHz)) ? (refclk / (40 * MHz) + 1) : 1); + + debug("%s: DEBUG: max_prediv=%u, min_prediv=%u\n", __func__, max_prediv, + min_prediv); + + if (max_prediv < min_prediv) { + debug("%s: Invalid refclk value\n", __func__); + return -EINVAL; + } + + /* Calculate the best refclk and feedback division value for dphy pll */ + for (i = min_prediv; i < max_prediv; i++) { + if ((ddr_clk * i % refclk < remain) && + (ddr_clk * i / refclk) < max_fbdiv) { + prediv = i; + remain = ddr_clk * i % refclk; + } + } + fbdiv = ddr_clk * prediv / refclk; + ddr_clk = refclk * fbdiv / prediv; + priv->phy_clk = ddr_clk; + + debug("%s: DEBUG: refclk=%u, refclk=%llu, fbdiv=%llu, phyclk=%llu\n", + __func__, refclk, prediv, fbdiv, ddr_clk); + + /* config prediv and feedback reg */ + test_data[0] = prediv - 1; + rk_mipi_phy_write(regs, CODE_PLL_INPUT_DIV_RAT, test_data, 1); + test_data[0] = (fbdiv - 1) & 0x1f; + rk_mipi_phy_write(regs, CODE_PLL_LOOP_DIV_RAT, test_data, 1); + test_data[0] = (fbdiv - 1) >> 5 | 0x80; + rk_mipi_phy_write(regs, CODE_PLL_LOOP_DIV_RAT, test_data, 1); + test_data[0] = 0x30; + rk_mipi_phy_write(regs, CODE_PLL_INPUT_LOOP_DIV_RAT, test_data, 1); + + /* rest config */ + test_data[0] = 0x4d; + rk_mipi_phy_write(regs, CODE_BANDGAP_BIAS_CTRL, test_data, 1); + + test_data[0] = 0x3d; + rk_mipi_phy_write(regs, CODE_TERMINATION_CTRL, test_data, 1); + + test_data[0] = 0xdf; + rk_mipi_phy_write(regs, CODE_TERMINATION_CTRL, test_data, 1); + + test_data[0] = 0x7; + rk_mipi_phy_write(regs, CODE_AFE_BIAS_BANDGAP_ANOLOG, test_data, 1); + + test_data[0] = 0x80 | 0x7; + rk_mipi_phy_write(regs, CODE_AFE_BIAS_BANDGAP_ANOLOG, test_data, 1); + + test_data[0] = 0x80 | 15; + rk_mipi_phy_write(regs, CODE_HSTXDATALANEREQUSETSTATETIME, + test_data, 1); + test_data[0] = 0x80 | 85; + rk_mipi_phy_write(regs, CODE_HSTXDATALANEPREPARESTATETIME, + test_data, 1); + test_data[0] = 0x40 | 10; + rk_mipi_phy_write(regs, CODE_HSTXDATALANEHSZEROSTATETIME, + test_data, 1); + + /* enter into stop mode */ + rk_mipi_dsi_write(regs, N_LANES, 0x03); + rk_mipi_dsi_write(regs, PHY_ENABLECLK, 1); + rk_mipi_dsi_write(regs, PHY_FORCEPLL, 1); + rk_mipi_dsi_write(regs, PHY_SHUTDOWNZ, 1); + rk_mipi_dsi_write(regs, PHY_RSTZ, 1); + + return 0; +} + diff --git a/roms/u-boot/drivers/video/rockchip/rk_mipi.h b/roms/u-boot/drivers/video/rockchip/rk_mipi.h new file mode 100644 index 000000000..3d1e440b0 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_mipi.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd + * Author: Eric Gao <eric.gao@rock-chips.com> + */ + +#ifndef __RK_MIPI_H +#define __RK_MIPI_H + +struct rk_mipi_priv { + uintptr_t regs; + void *grf; + struct udevice *panel; + struct mipi_dsi *dsi; + u32 ref_clk; + u32 sys_clk; + u32 pix_clk; + u32 phy_clk; + u32 txbyte_clk; + u32 txesc_clk; +}; + +int rk_mipi_read_timing(struct udevice *dev, + struct display_timing *timing); + +int rk_mipi_dsi_enable(struct udevice *dev, + const struct display_timing *timing); + +int rk_mipi_phy_enable(struct udevice *dev); + + +#endif diff --git a/roms/u-boot/drivers/video/rockchip/rk_vop.c b/roms/u-boot/drivers/video/rockchip/rk_vop.c new file mode 100644 index 000000000..fe0574870 --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_vop.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + */ + +#include <common.h> +#include <clk.h> +#include <display.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <edid.h> +#include <log.h> +#include <regmap.h> +#include <reset.h> +#include <syscon.h> +#include <video.h> +#include <asm/global_data.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/edp_rk3288.h> +#include <asm/arch-rockchip/vop_rk3288.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <efi.h> +#include <efi_loader.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <power/regulator.h> +#include "rk_vop.h" + +DECLARE_GLOBAL_DATA_PTR; + +enum vop_pol { + HSYNC_POSITIVE = 0, + VSYNC_POSITIVE = 1, + DEN_NEGATIVE = 2, + DCLK_INVERT = 3 +}; + +static void rkvop_enable(struct udevice *dev, struct rk3288_vop *regs, ulong fbbase, + int fb_bits_per_pixel, + const struct display_timing *edid, + struct reset_ctl *dclk_rst) +{ + u32 lb_mode; + u32 rgb_mode; + u32 hactive = edid->hactive.typ; + u32 vactive = edid->vactive.typ; + int ret; + + writel(V_ACT_WIDTH(hactive - 1) | V_ACT_HEIGHT(vactive - 1), + ®s->win0_act_info); + + writel(V_DSP_XST(edid->hsync_len.typ + edid->hback_porch.typ) | + V_DSP_YST(edid->vsync_len.typ + edid->vback_porch.typ), + ®s->win0_dsp_st); + + writel(V_DSP_WIDTH(hactive - 1) | + V_DSP_HEIGHT(vactive - 1), + ®s->win0_dsp_info); + + clrsetbits_le32(®s->win0_color_key, M_WIN0_KEY_EN | M_WIN0_KEY_COLOR, + V_WIN0_KEY_EN(0) | V_WIN0_KEY_COLOR(0)); + + switch (fb_bits_per_pixel) { + case 16: + rgb_mode = RGB565; + writel(V_RGB565_VIRWIDTH(hactive), ®s->win0_vir); + break; + case 24: + rgb_mode = RGB888; + writel(V_RGB888_VIRWIDTH(hactive), ®s->win0_vir); + break; + case 32: + default: + rgb_mode = ARGB8888; + writel(V_ARGB888_VIRWIDTH(hactive), ®s->win0_vir); + break; + } + + if (hactive > 2560) + lb_mode = LB_RGB_3840X2; + else if (hactive > 1920) + lb_mode = LB_RGB_2560X4; + else if (hactive > 1280) + lb_mode = LB_RGB_1920X5; + else + lb_mode = LB_RGB_1280X8; + + clrsetbits_le32(®s->win0_ctrl0, + M_WIN0_LB_MODE | M_WIN0_DATA_FMT | M_WIN0_EN, + V_WIN0_LB_MODE(lb_mode) | V_WIN0_DATA_FMT(rgb_mode) | + V_WIN0_EN(1)); + + writel(fbbase, ®s->win0_yrgb_mst); + writel(0x01, ®s->reg_cfg_done); /* enable reg config */ + + ret = reset_assert(dclk_rst); + if (ret) { + dev_warn(dev, "failed to assert dclk reset (ret=%d)\n", ret); + return; + } + udelay(20); + + ret = reset_deassert(dclk_rst); + if (ret) + dev_warn(dev, "failed to deassert dclk reset (ret=%d)\n", ret); + +} + +static void rkvop_set_pin_polarity(struct udevice *dev, + enum vop_modes mode, u32 polarity) +{ + struct rkvop_driverdata *ops = + (struct rkvop_driverdata *)dev_get_driver_data(dev); + + if (ops->set_pin_polarity) + ops->set_pin_polarity(dev, mode, polarity); +} + +static void rkvop_enable_output(struct udevice *dev, enum vop_modes mode) +{ + struct rk_vop_priv *priv = dev_get_priv(dev); + struct rk3288_vop *regs = priv->regs; + + /* remove from standby */ + clrbits_le32(®s->sys_ctrl, V_STANDBY_EN(1)); + + switch (mode) { + case VOP_MODE_HDMI: + clrsetbits_le32(®s->sys_ctrl, M_ALL_OUT_EN, + V_HDMI_OUT_EN(1)); + break; + + case VOP_MODE_EDP: + clrsetbits_le32(®s->sys_ctrl, M_ALL_OUT_EN, + V_EDP_OUT_EN(1)); + break; + +#if defined(CONFIG_ROCKCHIP_RK3288) + case VOP_MODE_LVDS: + clrsetbits_le32(®s->sys_ctrl, M_ALL_OUT_EN, + V_RGB_OUT_EN(1)); + break; +#endif + + case VOP_MODE_MIPI: + clrsetbits_le32(®s->sys_ctrl, M_ALL_OUT_EN, + V_MIPI_OUT_EN(1)); + break; + + default: + debug("%s: unsupported output mode %x\n", __func__, mode); + } +} + +static void rkvop_mode_set(struct udevice *dev, + const struct display_timing *edid, + enum vop_modes mode) +{ + struct rk_vop_priv *priv = dev_get_priv(dev); + struct rk3288_vop *regs = priv->regs; + struct rkvop_driverdata *data = + (struct rkvop_driverdata *)dev_get_driver_data(dev); + + u32 hactive = edid->hactive.typ; + u32 vactive = edid->vactive.typ; + u32 hsync_len = edid->hsync_len.typ; + u32 hback_porch = edid->hback_porch.typ; + u32 vsync_len = edid->vsync_len.typ; + u32 vback_porch = edid->vback_porch.typ; + u32 hfront_porch = edid->hfront_porch.typ; + u32 vfront_porch = edid->vfront_porch.typ; + int mode_flags; + u32 pin_polarity; + + pin_polarity = BIT(DCLK_INVERT); + if (edid->flags & DISPLAY_FLAGS_HSYNC_HIGH) + pin_polarity |= BIT(HSYNC_POSITIVE); + if (edid->flags & DISPLAY_FLAGS_VSYNC_HIGH) + pin_polarity |= BIT(VSYNC_POSITIVE); + + rkvop_set_pin_polarity(dev, mode, pin_polarity); + rkvop_enable_output(dev, mode); + + mode_flags = 0; /* RGB888 */ + if ((data->features & VOP_FEATURE_OUTPUT_10BIT) && + (mode == VOP_MODE_HDMI || mode == VOP_MODE_EDP)) + mode_flags = 15; /* RGBaaa */ + + clrsetbits_le32(®s->dsp_ctrl0, M_DSP_OUT_MODE, + V_DSP_OUT_MODE(mode_flags)); + + writel(V_HSYNC(hsync_len) | + V_HORPRD(hsync_len + hback_porch + hactive + hfront_porch), + ®s->dsp_htotal_hs_end); + + writel(V_HEAP(hsync_len + hback_porch + hactive) | + V_HASP(hsync_len + hback_porch), + ®s->dsp_hact_st_end); + + writel(V_VSYNC(vsync_len) | + V_VERPRD(vsync_len + vback_porch + vactive + vfront_porch), + ®s->dsp_vtotal_vs_end); + + writel(V_VAEP(vsync_len + vback_porch + vactive)| + V_VASP(vsync_len + vback_porch), + ®s->dsp_vact_st_end); + + writel(V_HEAP(hsync_len + hback_porch + hactive) | + V_HASP(hsync_len + hback_porch), + ®s->post_dsp_hact_info); + + writel(V_VAEP(vsync_len + vback_porch + vactive)| + V_VASP(vsync_len + vback_porch), + ®s->post_dsp_vact_info); + + writel(0x01, ®s->reg_cfg_done); /* enable reg config */ +} + +/** + * rk_display_init() - Try to enable the given display device + * + * This function performs many steps: + * - Finds the display device being referenced by @ep_node + * - Puts the VOP's ID into its uclass platform data + * - Probes the device to set it up + * - Reads the EDID timing information + * - Sets up the VOP clocks, etc. for the selected pixel clock and display mode + * - Enables the display (the display device handles this and will do different + * things depending on the display type) + * - Tells the uclass about the display resolution so that the console will + * appear correctly + * + * @dev: VOP device that we want to connect to the display + * @fbbase: Frame buffer address + * @ep_node: Device tree node to process - this is the offset of an endpoint + * node within the VOP's 'port' list. + * @return 0 if OK, -ve if something went wrong + */ +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; + u32 remote_phandle; + struct display_plat *disp_uc_plat; + struct clk clk; + enum video_log2_bpp l2bpp; + ofnode remote; + const char *compat; + struct reset_ctl dclk_rst; + + debug("%s(%s, 0x%lx, %s)\n", __func__, + dev_read_name(dev), fbbase, ofnode_get_name(ep_node)); + + ret = ofnode_read_u32(ep_node, "remote-endpoint", &remote_phandle); + if (ret) + return ret; + + remote = ofnode_get_by_phandle(remote_phandle); + if (!ofnode_valid(remote)) + return -EINVAL; + remote_vop_id = ofnode_read_u32_default(remote, "reg", -1); + debug("remote vop_id=%d\n", remote_vop_id); + + /* + * The remote-endpoint references into a subnode of the encoder + * (i.e. HDMI, MIPI, etc.) with the DTS looking something like + * the following (assume 'hdmi_in_vopl' to be referenced): + * + * hdmi: hdmi@ff940000 { + * ports { + * hdmi_in: port { + * hdmi_in_vopb: endpoint@0 { ... }; + * hdmi_in_vopl: endpoint@1 { ... }; + * } + * } + * } + * + * The original code had 3 steps of "walking the parent", but + * a much better (as in: less likely to break if the DTS + * changes) way of doing this is to "find the enclosing device + * of UCLASS_DISPLAY". + */ + while (ofnode_valid(remote)) { + remote = ofnode_get_parent(remote); + if (!ofnode_valid(remote)) { + debug("%s(%s): no UCLASS_DISPLAY for remote-endpoint\n", + __func__, dev_read_name(dev)); + return -EINVAL; + } + + uclass_find_device_by_ofnode(UCLASS_DISPLAY, remote, &disp); + if (disp) + break; + }; + compat = ofnode_get_property(remote, "compatible", NULL); + if (!compat) { + debug("%s(%s): Failed to find compatible property\n", + __func__, dev_read_name(dev)); + return -EINVAL; + } + if (strstr(compat, "edp")) { + vop_id = VOP_MODE_EDP; + } else if (strstr(compat, "mipi")) { + vop_id = VOP_MODE_MIPI; + } else if (strstr(compat, "hdmi")) { + vop_id = VOP_MODE_HDMI; + } else if (strstr(compat, "cdn-dp")) { + vop_id = VOP_MODE_DP; + } else if (strstr(compat, "lvds")) { + vop_id = VOP_MODE_LVDS; + } else { + debug("%s(%s): Failed to find vop mode for %s\n", + __func__, dev_read_name(dev), compat); + return -EINVAL; + } + debug("vop_id=%d\n", vop_id); + + disp_uc_plat = dev_get_uclass_plat(disp); + debug("Found device '%s', disp_uc_priv=%p\n", disp->name, disp_uc_plat); + if (display_in_use(disp)) { + debug(" - device in use\n"); + return -EBUSY; + } + + disp_uc_plat->source_id = remote_vop_id; + disp_uc_plat->src_dev = dev; + + ret = device_probe(disp); + if (ret) { + debug("%s: device '%s' display won't probe (ret=%d)\n", + __func__, dev->name, ret); + return ret; + } + + ret = display_read_timing(disp, &timing); + if (ret) { + debug("%s: Failed to read timings\n", __func__); + return ret; + } + + ret = clk_get_by_index(dev, 1, &clk); + if (!ret) + ret = clk_set_rate(&clk, timing.pixelclock.typ); + if (IS_ERR_VALUE(ret)) { + debug("%s: Failed to set pixel clock: ret=%d\n", __func__, ret); + return ret; + } + + /* Set bitwidth for vop display according to vop mode */ + switch (vop_id) { + case VOP_MODE_EDP: +#if defined(CONFIG_ROCKCHIP_RK3288) + case VOP_MODE_LVDS: +#endif + l2bpp = VIDEO_BPP16; + break; + case VOP_MODE_HDMI: + case VOP_MODE_MIPI: + l2bpp = VIDEO_BPP32; + break; + default: + l2bpp = VIDEO_BPP16; + } + + rkvop_mode_set(dev, &timing, vop_id); + + ret = reset_get_by_name(dev, "dclk", &dclk_rst); + if (ret) { + dev_err(dev, "failed to get dclk reset (ret=%d)\n", ret); + return ret; + } + + rkvop_enable(dev, regs, fbbase, 1 << l2bpp, &timing, &dclk_rst); + + ret = display_enable(disp, 1 << l2bpp, &timing); + if (ret) + return ret; + + uc_priv->xsize = timing.hactive.typ; + uc_priv->ysize = timing.vactive.typ; + uc_priv->bpix = l2bpp; + debug("fb=%lx, size=%d %d\n", fbbase, uc_priv->xsize, uc_priv->ysize); + + return 0; +} + +void rk_vop_probe_regulators(struct udevice *dev, + const char * const *names, int cnt) +{ + int i, ret; + const char *name; + struct udevice *reg; + + for (i = 0; i < cnt; ++i) { + name = names[i]; + debug("%s: probing regulator '%s'\n", dev->name, name); + + ret = regulator_autoset_by_name(name, ®); + if (!ret) + ret = regulator_set_enable(reg, true); + } +} + +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); + int ret = 0; + ofnode port, node; + struct reset_ctl ahb_rst; + + /* Before relocation we don't need to do anything */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + ret = reset_get_by_name(dev, "ahb", &ahb_rst); + if (ret) { + dev_err(dev, "failed to get ahb reset (ret=%d)\n", ret); + return ret; + } + + ret = reset_assert(&ahb_rst); + if (ret) { + dev_err(dev, "failed to assert ahb reset (ret=%d)\n", ret); + return ret; + } + udelay(20); + + ret = reset_deassert(&ahb_rst); + if (ret) { + dev_err(dev, "failed to deassert ahb reset (ret=%d)\n", ret); + return ret; + } + +#if defined(CONFIG_EFI_LOADER) + debug("Adding to EFI map %d @ %lx\n", plat->size, plat->base); + efi_add_memory_map(plat->base, plat->size, EFI_RESERVED_MEMORY_TYPE); +#endif + + priv->regs = (struct rk3288_vop *)dev_read_addr(dev); + + /* + * Try all the ports until we find one that works. In practice this + * tries EDP first if available, then HDMI. + * + * Note that rockchip_vop_set_clk() always uses NPLL as the source + * clock so it is currently not possible to use more than one display + * device simultaneously. + */ + port = dev_read_subnode(dev, "port"); + if (!ofnode_valid(port)) { + debug("%s(%s): 'port' subnode not found\n", + __func__, dev_read_name(dev)); + return -EINVAL; + } + + for (node = ofnode_first_subnode(port); + ofnode_valid(node); + node = dev_read_next_subnode(node)) { + ret = rk_display_init(dev, plat->base, node); + if (ret) + debug("Device failed: ret=%d\n", ret); + if (!ret) + break; + } + video_set_flush_dcache(dev, 1); + + return ret; +} + +int rk_vop_bind(struct udevice *dev) +{ + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->size = 4 * (CONFIG_VIDEO_ROCKCHIP_MAX_XRES * + CONFIG_VIDEO_ROCKCHIP_MAX_YRES); + + return 0; +} diff --git a/roms/u-boot/drivers/video/rockchip/rk_vop.h b/roms/u-boot/drivers/video/rockchip/rk_vop.h new file mode 100644 index 000000000..53a79c04b --- /dev/null +++ b/roms/u-boot/drivers/video/rockchip/rk_vop.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH + */ + +#ifndef __RK_VOP_H__ +#define __RK_VOP_H__ + +#include <asm/arch-rockchip/vop_rk3288.h> + +struct rk_vop_priv { + void *grf; + void *regs; +}; + +enum vop_features { + VOP_FEATURE_OUTPUT_10BIT = (1 << 0), +}; + +struct rkvop_driverdata { + /* configuration */ + u32 features; + /* block-specific setters/getters */ + void (*set_pin_polarity)(struct udevice *, enum vop_modes, u32); +}; + +/** + * rk_vop_probe() - common probe implementation + * + * Performs the rk_display_init on each port-subnode until finding a + * working port (or returning an error if none of the ports could be + * successfully initialised). + * + * @dev: device + * @return 0 if OK, -ve if something went wrong + */ +int rk_vop_probe(struct udevice *dev); + +/** + * rk_vop_bind() - common bind implementation + * + * Sets the plat->size field to the amount of memory to be reserved for + * the framebuffer: this is always + * (32 BPP) x VIDEO_ROCKCHIP_MAX_XRES x VIDEO_ROCKCHIP_MAX_YRES + * + * @dev: device + * @return 0 (always OK) + */ +int rk_vop_bind(struct udevice *dev); + +/** + * rk_vop_probe_regulators() - probe (autoset + enable) regulators + * + * Probes a list of regulators by performing autoset and enable + * operations on them. The list of regulators is an array of string + * pointers and any individual regulator-probe may fail without + * counting as an error. + * + * @dev: device + * @names: array of string-pointers to regulator names to probe + * @cnt: number of elements in the 'names' array + */ +void rk_vop_probe_regulators(struct udevice *dev, + const char * const *names, int cnt); + +#endif |