From 35ac89dd8fe7e9373d8c4336e7fbe93ae3fd59d7 Mon Sep 17 00:00:00 2001 From: Jacob Chen Date: Mon, 14 Mar 2016 11:20:16 +0800 Subject: [PATCH] rockchip: video: Add a display driver for rockchip LVDS Some Rockchip SoCs support LVDS output. Add a display driver for this so that these displays can be used on supported boards. Signed-off-by: Jacob Chen Acked-by: Simon Glass --- .../include/asm/arch-rockchip/lvds_rk3288.h | 97 +++++++ drivers/video/rockchip/Makefile | 2 +- drivers/video/rockchip/rk_lvds.c | 254 ++++++++++++++++++ 3 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 arch/arm/include/asm/arch-rockchip/lvds_rk3288.h create mode 100644 drivers/video/rockchip/rk_lvds.c diff --git a/arch/arm/include/asm/arch-rockchip/lvds_rk3288.h b/arch/arm/include/asm/arch-rockchip/lvds_rk3288.h new file mode 100644 index 0000000000..121a8985d1 --- /dev/null +++ b/arch/arm/include/asm/arch-rockchip/lvds_rk3288.h @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Rockchip Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _ASM_ARCH_LVDS_RK3288_H +#define _ASM_ARCH_LVDS_RK3288_H + +#define RK3288_LVDS_CH0_REG0 0x00 +#define RK3288_LVDS_CH0_REG0_LVDS_EN BIT(7) +#define RK3288_LVDS_CH0_REG0_TTL_EN BIT(6) +#define RK3288_LVDS_CH0_REG0_LANECK_EN BIT(5) +#define RK3288_LVDS_CH0_REG0_LANE4_EN BIT(4) +#define RK3288_LVDS_CH0_REG0_LANE3_EN BIT(3) +#define RK3288_LVDS_CH0_REG0_LANE2_EN BIT(2) +#define RK3288_LVDS_CH0_REG0_LANE1_EN BIT(1) +#define RK3288_LVDS_CH0_REG0_LANE0_EN BIT(0) + +#define RK3288_LVDS_CH0_REG1 0x04 +#define RK3288_LVDS_CH0_REG1_LANECK_BIAS BIT(5) +#define RK3288_LVDS_CH0_REG1_LANE4_BIAS BIT(4) +#define RK3288_LVDS_CH0_REG1_LANE3_BIAS BIT(3) +#define RK3288_LVDS_CH0_REG1_LANE2_BIAS BIT(2) +#define RK3288_LVDS_CH0_REG1_LANE1_BIAS BIT(1) +#define RK3288_LVDS_CH0_REG1_LANE0_BIAS BIT(0) + +#define RK3288_LVDS_CH0_REG2 0x08 +#define RK3288_LVDS_CH0_REG2_RESERVE_ON BIT(7) +#define RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE BIT(6) +#define RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE BIT(5) +#define RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE BIT(4) +#define RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE BIT(3) +#define RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE BIT(2) +#define RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE BIT(1) +#define RK3288_LVDS_CH0_REG2_PLL_FBDIV8 BIT(0) + +#define RK3288_LVDS_CH0_REG3 0x0c +#define RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK 0xff + +#define RK3288_LVDS_CH0_REG4 0x10 +#define RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE BIT(5) +#define RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE BIT(4) +#define RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE BIT(3) +#define RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE BIT(2) +#define RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE BIT(1) +#define RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE BIT(0) + +#define RK3288_LVDS_CH0_REG5 0x14 +#define RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA BIT(5) +#define RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA BIT(4) +#define RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA BIT(3) +#define RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA BIT(2) +#define RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA BIT(1) +#define RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA BIT(0) + +#define RK3288_LVDS_CFG_REGC 0x30 +#define RK3288_LVDS_CFG_REGC_PLL_ENABLE 0x00 +#define RK3288_LVDS_CFG_REGC_PLL_DISABLE 0xff + +#define RK3288_LVDS_CH0_REGD 0x34 +#define RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK 0x1f + +#define RK3288_LVDS_CH0_REG20 0x80 +#define RK3288_LVDS_CH0_REG20_MSB 0x45 +#define RK3288_LVDS_CH0_REG20_LSB 0x44 + +#define RK3288_LVDS_CFG_REG21 0x84 +#define RK3288_LVDS_CFG_REG21_TX_ENABLE 0x92 +#define RK3288_LVDS_CFG_REG21_TX_DISABLE 0x00 + +/* fbdiv value is split over 2 registers, with bit8 in reg2 */ +#define RK3288_LVDS_PLL_FBDIV_REG2(_fbd) \ + (_fbd & BIT(8) ? RK3288_LVDS_CH0_REG2_PLL_FBDIV8 : 0) +#define RK3288_LVDS_PLL_FBDIV_REG3(_fbd) \ + (_fbd & RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK) +#define RK3288_LVDS_PLL_PREDIV_REGD(_pd) \ + (_pd & RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK) + +#define RK3288_LVDS_SOC_CON6_SEL_VOP_LIT BIT(3) + +#define LVDS_FMT_MASK (7 << 16) +#define LVDS_MSB (1 << 3) +#define LVDS_DUAL (1 << 4) +#define LVDS_FMT_1 (1 << 5) +#define LVDS_TTL_EN (1 << 6) +#define LVDS_START_PHASE_RST_1 (1 << 7) +#define LVDS_DCLK_INV (1 << 8) +#define LVDS_CH0_EN (1 << 11) +#define LVDS_CH1_EN (1 << 12) +#define LVDS_PWRDN (1 << 15) + +#define LVDS_24BIT (0 << 1) +#define LVDS_18BIT (1 << 1) + + +#endif diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile index 0e9a8acf69..7962f8611e 100644 --- a/drivers/video/rockchip/Makefile +++ b/drivers/video/rockchip/Makefile @@ -5,4 +5,4 @@ # SPDX-License-Identifier: GPL-2.0+ # -obj-y += rk_edp.o rk_hdmi.o rk_vop.o +obj-y += rk_edp.o rk_hdmi.o rk_vop.o rk_lvds.o diff --git a/drivers/video/rockchip/rk_lvds.c b/drivers/video/rockchip/rk_lvds.c new file mode 100644 index 0000000000..dc10b866c9 --- /dev/null +++ b/drivers/video/rockchip/rk_lvds.c @@ -0,0 +1,254 @@ +/* + * Copyright 2016 Rockchip Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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_platdata(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 (fdtdec_decode_display_timing + (gd->fdt_blob, dev->of_offset, 0, timing)) { + debug("%s: Failed to decode display timing\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int rk_lvds_ofdata_to_platdata(struct udevice *dev) +{ + struct rk_lvds_priv *priv = dev_get_priv(dev); + const void *blob = gd->fdt_blob; + int node = dev->of_offset; + int ret; + priv->regs = (void *)dev_get_addr(dev); + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + + ret = fdtdec_get_int(blob, node, "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 = fdtdec_get_int(blob, node, "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 = fdtdec_get_int(blob, node, "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, + .ofdata_to_platdata = rk_lvds_ofdata_to_platdata, + .probe = rk_lvds_probe, + .priv_auto_alloc_size = sizeof(struct rk_lvds_priv), +}; -- 2.25.1