[U-Boot] [PATCH 0/5] sunxi: display: add support for eDP panels connected via an anx9804 bridge chip

Hi Ian and Anatolij,
Here is a patch series adding support for eDP panels connected via an anx9804 bridge chip.
Most of this series touches sunxi only files, except for the commit adding the anx9804 bridge driver, which is a generic driver which should hopefully be useful elsewhere too.
Anatolij, can we have your ack for merging this new video bridge chip driver via the sunxi tree ?
Regards,
Hans

Add a helper function to get the pll3 clock rate.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- arch/arm/cpu/armv7/sunxi/clock_sun4i.c | 9 +++++++++ arch/arm/cpu/armv7/sunxi/clock_sun6i.c | 12 ++++++++++++ arch/arm/include/asm/arch-sunxi/clock_sun4i.h | 3 +++ arch/arm/include/asm/arch-sunxi/clock_sun6i.h | 5 +++++ 4 files changed, 29 insertions(+)
diff --git a/arch/arm/cpu/armv7/sunxi/clock_sun4i.c b/arch/arm/cpu/armv7/sunxi/clock_sun4i.c index c3e04af..7c8eff9 100644 --- a/arch/arm/cpu/armv7/sunxi/clock_sun4i.c +++ b/arch/arm/cpu/armv7/sunxi/clock_sun4i.c @@ -198,6 +198,15 @@ void clock_set_pll3(unsigned int clk) CCM_PLL3_CTRL_M(clk / 3000000), &ccm->pll3_cfg); }
+unsigned int clock_get_pll3(void) +{ + struct sunxi_ccm_reg *const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + uint32_t rval = readl(&ccm->pll3_cfg); + int m = ((rval & CCM_PLL3_CTRL_M_MASK) >> CCM_PLL3_CTRL_M_SHIFT); + return 3000000 * m; +} + unsigned int clock_get_pll5p(void) { struct sunxi_ccm_reg *const ccm = diff --git a/arch/arm/cpu/armv7/sunxi/clock_sun6i.c b/arch/arm/cpu/armv7/sunxi/clock_sun6i.c index 3bfa122..1d6f839 100644 --- a/arch/arm/cpu/armv7/sunxi/clock_sun6i.c +++ b/arch/arm/cpu/armv7/sunxi/clock_sun6i.c @@ -188,6 +188,18 @@ void clock_set_pll11(unsigned int clk, bool sigma_delta_enable) } #endif
+unsigned int clock_get_pll3(void) +{ + struct sunxi_ccm_reg *const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + uint32_t rval = readl(&ccm->pll3_cfg); + int n = ((rval & CCM_PLL3_CTRL_N_MASK) >> CCM_PLL3_CTRL_N_SHIFT) + 1; + int m = ((rval & CCM_PLL3_CTRL_M_MASK) >> CCM_PLL3_CTRL_M_SHIFT) + 1; + + /* Multiply by 1000 after dividing by m to avoid integer overflows */ + return (24000 * n / m) * 1000; +} + unsigned int clock_get_pll6(void) { struct sunxi_ccm_reg *const ccm = diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun4i.h b/arch/arm/include/asm/arch-sunxi/clock_sun4i.h index b397809..1a0b525 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun4i.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun4i.h @@ -208,6 +208,8 @@ struct sunxi_ccm_reg { #define CCM_AHB_GATE_DLL (0x1 << 15) #define CCM_AHB_GATE_ACE (0x1 << 16)
+#define CCM_PLL3_CTRL_M_SHIFT 0 +#define CCM_PLL3_CTRL_M_MASK (0x7f << CCM_PLL3_CTRL_M_SHIFT) #define CCM_PLL3_CTRL_M(n) (((n) & 0x7f) << 0) #define CCM_PLL3_CTRL_INTEGER_MODE (0x1 << 15) #define CCM_PLL3_CTRL_EN (0x1 << 31) @@ -347,6 +349,7 @@ struct sunxi_ccm_reg { #ifndef __ASSEMBLY__ void clock_set_pll1(unsigned int hz); void clock_set_pll3(unsigned int hz); +unsigned int clock_get_pll3(void); unsigned int clock_get_pll5p(void); unsigned int clock_get_pll6(void); #endif diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun6i.h b/arch/arm/include/asm/arch-sunxi/clock_sun6i.h index 06c6feb..a197345 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun6i.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun6i.h @@ -179,7 +179,11 @@ struct sunxi_ccm_reg { #define CCM_PLL1_CTRL_P(n) (((n) & 0x3) << 16) #define CCM_PLL1_CTRL_EN (0x1 << 31)
+#define CCM_PLL3_CTRL_M_SHIFT 0 +#define CCM_PLL3_CTRL_M_MASK (0xf << CCM_PLL3_CTRL_M_SHIFT) #define CCM_PLL3_CTRL_M(n) ((((n) - 1) & 0xf) << 0) +#define CCM_PLL3_CTRL_N_SHIFT 8 +#define CCM_PLL3_CTRL_N_MASK (0x7f << CCM_PLL3_CTRL_N_SHIFT) #define CCM_PLL3_CTRL_N(n) ((((n) - 1) & 0x7f) << 8) #define CCM_PLL3_CTRL_INTEGER_MODE (0x1 << 24) #define CCM_PLL3_CTRL_EN (0x1 << 31) @@ -360,6 +364,7 @@ void clock_set_pll1(unsigned int hz); void clock_set_pll3(unsigned int hz); void clock_set_pll5(unsigned int clk, bool sigma_delta_enable); void clock_set_pll11(unsigned int clk, bool sigma_delta_enable); +unsigned int clock_get_pll3(void); unsigned int clock_get_pll6(void); #endif

On Sat, 2015-08-08 at 16:25 +0200, Hans de Goede wrote:
Add a helper function to get the pll3 clock rate.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Ian Campbell ijc@hellion.org.uk

Add support for the mipi pll, this is necessary for getting higher dotclocks with lcd panels.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- arch/arm/cpu/armv7/sunxi/clock_sun6i.c | 55 +++++++++++++++++++++++++++ arch/arm/include/asm/arch-sunxi/clock_sun4i.h | 1 + arch/arm/include/asm/arch-sunxi/clock_sun6i.h | 14 +++++++ 3 files changed, 70 insertions(+)
diff --git a/arch/arm/cpu/armv7/sunxi/clock_sun6i.c b/arch/arm/cpu/armv7/sunxi/clock_sun6i.c index 1d6f839..3ab3b31 100644 --- a/arch/arm/cpu/armv7/sunxi/clock_sun6i.c +++ b/arch/arm/cpu/armv7/sunxi/clock_sun6i.c @@ -170,6 +170,47 @@ void clock_set_pll5(unsigned int clk, bool sigma_delta_enable) udelay(5500); }
+#ifdef CONFIG_MACH_SUN6I +void clock_set_mipi_pll(unsigned int clk) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + unsigned int k, m, n, value, diff; + unsigned best_k = 0, best_m = 0, best_n = 0, best_diff = 0xffffffff; + unsigned int src = clock_get_pll3(); + + /* All calculations are in KHz to avoid overflows */ + clk /= 1000; + src /= 1000; + + /* Pick the closest lower clock */ + for (k = 1; k <= 4; k++) { + for (m = 1; m <= 16; m++) { + for (n = 1; n <= 16; n++) { + value = src * n * k / m; + if (value > clk) + continue; + + diff = clk - value; + if (diff < best_diff) { + best_diff = diff; + best_k = k; + best_m = m; + best_n = n; + } + if (diff == 0) + goto done; + } + } + } + +done: + writel(CCM_MIPI_PLL_CTRL_EN | CCM_MIPI_PLL_CTRL_LDO_EN | + CCM_MIPI_PLL_CTRL_N(best_n) | CCM_MIPI_PLL_CTRL_K(best_k) | + CCM_MIPI_PLL_CTRL_M(best_m), &ccm->mipi_pll_cfg); +} +#endif + #ifdef CONFIG_MACH_SUN8I_A33 void clock_set_pll11(unsigned int clk, bool sigma_delta_enable) { @@ -210,6 +251,20 @@ unsigned int clock_get_pll6(void) return 24000000 * n * k / 2; }
+unsigned int clock_get_mipi_pll(void) +{ + struct sunxi_ccm_reg *const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + uint32_t rval = readl(&ccm->mipi_pll_cfg); + unsigned int n = ((rval & CCM_MIPI_PLL_CTRL_N_MASK) >> CCM_MIPI_PLL_CTRL_N_SHIFT) + 1; + unsigned int k = ((rval & CCM_MIPI_PLL_CTRL_K_MASK) >> CCM_MIPI_PLL_CTRL_K_SHIFT) + 1; + unsigned int m = ((rval & CCM_MIPI_PLL_CTRL_M_MASK) >> CCM_MIPI_PLL_CTRL_M_SHIFT) + 1; + unsigned int src = clock_get_pll3(); + + /* Multiply by 1000 after dividing by m to avoid integer overflows */ + return ((src / 1000) * n * k / m) * 1000; +} + void clock_set_de_mod_clock(u32 *clk_cfg, unsigned int hz) { int pll = clock_get_pll6() * 2; diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun4i.h b/arch/arm/include/asm/arch-sunxi/clock_sun4i.h index 1a0b525..0088bb9 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun4i.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun4i.h @@ -289,6 +289,7 @@ struct sunxi_ccm_reg { #define CCM_LCD_CH0_CTRL_PLL7 (1 << 24) #define CCM_LCD_CH0_CTRL_PLL3_2X (2 << 24) #define CCM_LCD_CH0_CTRL_PLL7_2X (3 << 24) +#define CCM_LCD_CH0_CTRL_MIPI_PLL 0 /* No mipi pll on sun4i/5i/7i */ #ifdef CONFIG_MACH_SUN5I #define CCM_LCD_CH0_CTRL_TVE_RST (0x1 << 29) #else diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun6i.h b/arch/arm/include/asm/arch-sunxi/clock_sun6i.h index a197345..9b7b90c 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun6i.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun6i.h @@ -202,6 +202,18 @@ struct sunxi_ccm_reg { #define CCM_PLL6_CTRL_K_SHIFT 4 #define CCM_PLL6_CTRL_K_MASK (0x3 << CCM_PLL6_CTRL_K_SHIFT)
+#define CCM_MIPI_PLL_CTRL_M_SHIFT 0 +#define CCM_MIPI_PLL_CTRL_M_MASK (0xf << CCM_MIPI_PLL_CTRL_M_SHIFT) +#define CCM_MIPI_PLL_CTRL_M(n) ((((n) - 1) & 0xf) << 0) +#define CCM_MIPI_PLL_CTRL_K_SHIFT 4 +#define CCM_MIPI_PLL_CTRL_K_MASK (0x3 << CCM_MIPI_PLL_CTRL_K_SHIFT) +#define CCM_MIPI_PLL_CTRL_K(n) ((((n) - 1) & 0x3) << 4) +#define CCM_MIPI_PLL_CTRL_N_SHIFT 8 +#define CCM_MIPI_PLL_CTRL_N_MASK (0xf << CCM_MIPI_PLL_CTRL_N_SHIFT) +#define CCM_MIPI_PLL_CTRL_N(n) ((((n) - 1) & 0xf) << 8) +#define CCM_MIPI_PLL_CTRL_LDO_EN (0x3 << 22) +#define CCM_MIPI_PLL_CTRL_EN (0x1 << 31) + #define CCM_PLL11_CTRL_N(n) ((((n) - 1) & 0x3f) << 8) #define CCM_PLL11_CTRL_SIGMA_DELTA_EN (0x1 << 24) #define CCM_PLL11_CTRL_UPD (0x1 << 30) @@ -364,8 +376,10 @@ void clock_set_pll1(unsigned int hz); void clock_set_pll3(unsigned int hz); void clock_set_pll5(unsigned int clk, bool sigma_delta_enable); void clock_set_pll11(unsigned int clk, bool sigma_delta_enable); +void clock_set_mipi_pll(unsigned int hz); unsigned int clock_get_pll3(void); unsigned int clock_get_pll6(void); +unsigned int clock_get_mipi_pll(void); #endif
#endif /* _SUNXI_CLOCK_SUN6I_H */

On Sat, 2015-08-08 at 16:25 +0200, Hans de Goede wrote:
Add support for the mipi pll, this is necessary for getting higher dotclocks with lcd panels.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Ian Campbell ijc@hellion.org.uk

On Sat, Aug 8, 2015 at 10:25 PM, Hans de Goede hdegoede@redhat.com wrote:
Add support for the mipi pll, this is necessary for getting higher dotclocks with lcd panels.
Do you plan on adding this to the kernel (both the clock driver and reference in the simplefb node) as well?
ChenYu
Signed-off-by: Hans de Goede hdegoede@redhat.com
arch/arm/cpu/armv7/sunxi/clock_sun6i.c | 55 +++++++++++++++++++++++++++ arch/arm/include/asm/arch-sunxi/clock_sun4i.h | 1 + arch/arm/include/asm/arch-sunxi/clock_sun6i.h | 14 +++++++ 3 files changed, 70 insertions(+)

Hi,
On 11-08-15 09:57, Chen-Yu Tsai wrote:
On Sat, Aug 8, 2015 at 10:25 PM, Hans de Goede hdegoede@redhat.com wrote:
Add support for the mipi pll, this is necessary for getting higher dotclocks with lcd panels.
Do you plan on adding this to the kernel (both the clock driver and reference in the simplefb node) as well?
Not at this time, once we have a kms driver in the kernel, and figured out how to add support for bridge chips like these, we are going to need support for this to support high res lcd panels though.
Regards,
Hans

Add support for using the mipi pll as lcd clock source, this is necessary for getting higher dotclocks with lcd panels.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- drivers/video/sunxi_display.c | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-)
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c index 75acf84..aad870e 100644 --- a/drivers/video/sunxi_display.c +++ b/drivers/video/sunxi_display.c @@ -521,6 +521,7 @@ static void sunxi_lcdc_pll_set(int tcon, int dotclock, int value, n, m, min_m, max_m, diff; int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF; int best_double = 0; + bool use_mipi_pll = false;
if (tcon == 0) { #ifdef CONFIG_VIDEO_LCD_IF_PARALLEL @@ -571,16 +572,42 @@ static void sunxi_lcdc_pll_set(int tcon, int dotclock, } }
- debug("dotclock: %dkHz = %dkHz: (%d * 3MHz * %d) / %d\n", - dotclock, (best_double + 1) * 3000 * best_n / best_m, - best_double + 1, best_n, best_m); +#ifdef CONFIG_MACH_SUN6I + /* + * Use the MIPI pll if we've been unable to find any matching setting + * for PLL3, this happens with high dotclocks because of min_m = 6. + */ + if (tcon == 0 && best_n == 0) { + use_mipi_pll = true; + best_m = 6; /* Minimum m for tcon0 */ + }
- clock_set_pll3(best_n * 3000000); + if (use_mipi_pll) { + clock_set_pll3(297000000); /* Fix the video pll at 297 MHz */ + clock_set_mipi_pll(best_m * dotclock * 1000); + debug("dotclock: %dkHz = %dkHz via mipi pll\n", + dotclock, clock_get_mipi_pll() / best_m / 1000); + } else +#endif + { + clock_set_pll3(best_n * 3000000); + debug("dotclock: %dkHz = %dkHz: (%d * 3MHz * %d) / %d\n", + dotclock, + (best_double + 1) * clock_get_pll3() / best_m / 1000, + best_double + 1, best_n, best_m); + }
if (tcon == 0) { - writel(CCM_LCD_CH0_CTRL_GATE | CCM_LCD_CH0_CTRL_RST | - (best_double ? CCM_LCD_CH0_CTRL_PLL3_2X : - CCM_LCD_CH0_CTRL_PLL3), + u32 pll; + + if (use_mipi_pll) + pll = CCM_LCD_CH0_CTRL_MIPI_PLL; + else if (best_double) + pll = CCM_LCD_CH0_CTRL_PLL3_2X; + else + pll = CCM_LCD_CH0_CTRL_PLL3; + + writel(CCM_LCD_CH0_CTRL_GATE | CCM_LCD_CH0_CTRL_RST | pll, &ccm->lcd0_ch0_clk_cfg); } else { writel(CCM_LCD_CH1_CTRL_GATE |

On Sat, 2015-08-08 at 16:25 +0200, Hans de Goede wrote:
Add support for using the mipi pll as lcd clock source, this is necessary for getting higher dotclocks with lcd panels.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Ian Campbell ijc@hellion.org.uk

On Sat, 8 Aug 2015 16:25:02 +0200 Hans de Goede hdegoede@redhat.com wrote:
Add support for using the mipi pll as lcd clock source, this is necessary for getting higher dotclocks with lcd panels.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Anatolij Gustschin agust@denx.de

Add support for the ANX9804 bridge chip, which can take pixel data coming from a parallel LCD interface and translate it on the flight into a DP interface for driving eDP TFT displays. It uses I2C for configuration.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- drivers/video/Kconfig | 8 +++ drivers/video/Makefile | 1 + drivers/video/anx9804.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/video/anx9804.h | 25 +++++++ 4 files changed, 222 insertions(+) create mode 100755 drivers/video/anx9804.c create mode 100644 drivers/video/anx9804.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 9ae23e8..71cbff9 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -145,6 +145,14 @@ config FRAMEBUFFER_VESA_MODE default 0x11B if FRAMEBUFFER_VESA_MODE_11B default 0x117 if FRAMEBUFFER_VESA_MODE_USER
+config VIDEO_LCD_ANX9804 + bool "ANX9804 bridge chip" + default n + ---help--- + Support for the ANX9804 bridge chip, which can take pixel data coming + from a parallel LCD interface and translate it on the flight into a DP + interface for driving eDP TFT displays. It uses I2C for configuration. + config VIDEO_LCD_SSD2828 bool "SSD2828 bridge chip" default n diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 2ead7f1..e414e8c 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_VIDEO_COREBOOT) += coreboot_fb.o obj-$(CONFIG_VIDEO_CT69000) += ct69000.o videomodes.o obj-$(CONFIG_VIDEO_DA8XX) += da8xx-fb.o videomodes.o obj-$(CONFIG_VIDEO_IMX25LCDC) += imx25lcdc.o videomodes.o +obj-$(CONFIG_VIDEO_LCD_ANX9804) += anx9804.o obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o obj-$(CONFIG_VIDEO_MB862xx) += mb862xx.o videomodes.o diff --git a/drivers/video/anx9804.c b/drivers/video/anx9804.c new file mode 100755 index 0000000..83d60d6 --- /dev/null +++ b/drivers/video/anx9804.c @@ -0,0 +1,188 @@ +/* + * (C) 2015 Hans de Goede hdegoede@redhat.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/* + * Support for the ANX9804 bridge chip, which can take pixel data coming + * from a parallel LCD interface and translate it on the flight into a DP + * interface for driving eDP TFT displays. + */ + +#include <common.h> +#include <i2c.h> +#include "anx9804.h" + +#define BIT(x) (1 << (x)) + +/* Registers at i2c address 0x38 */ + +#define ANX9804_HDCP_CONTROL_0_REG 0x01 + +#define ANX9804_SYS_CTRL2_REG 0x81 +#define ANX9804_SYS_CTRL2_CHA_STA 0x04 + +#define ANX9804_SYS_CTRL3_REG 0x82 +#define ANX9804_SYS_CTRL3_VALID_CTRL BIT(0) +#define ANX9804_SYS_CTRL3_F_VALID BIT(1) +#define ANX9804_SYS_CTRL3_HPD_CTRL BIT(4) +#define ANX9804_SYS_CTRL3_F_HPD BIT(5) + +#define ANX9804_LINK_BW_SET_REG 0xa0 +#define ANX9804_LANE_COUNT_SET_REG 0xa1 +#define ANX9804_TRAINING_PTN_SET_REG 0xa2 +#define ANX9804_TRAINING_LANE0_SET_REG 0xa3 +#define ANX9804_TRAINING_LANE1_SET_REG 0xa4 +#define ANX9804_TRAINING_LANE2_SET_REG 0xa5 +#define ANX9804_TRAINING_LANE3_SET_REG 0xa6 + +#define ANX9804_LINK_TRAINING_CTRL_REG 0xa8 +#define ANX9804_LINK_TRAINING_CTRL_EN BIT(0) + +#define ANX9804_LINK_DEBUG_REG 0xb8 +#define ANX9804_PLL_CTRL_REG 0xc7 +#define ANX9804_ANALOG_POWER_DOWN_REG 0xc8 + +/* Registers at i2c address 0x39 */ + +#define ANX9804_DEV_IDH_REG 0x03 + +#define ANX9804_POWERD_CTRL_REG 0x05 +#define ANX9804_POWERD_AUDIO BIT(4) + +#define ANX9804_RST_CTRL_REG 0x06 + +#define ANX9804_RST_CTRL2_REG 0x07 +#define ANX9804_RST_CTRL2_AUX BIT(2) +#define ANX9804_RST_CTRL2_AC_MODE BIT(6) + +#define ANX9804_VID_CTRL1_REG 0x08 +#define ANX9804_VID_CTRL1_VID_EN BIT(7) +#define ANX9804_VID_CTRL1_EDGE BIT(0) + +#define ANX9804_VID_CTRL2_REG 0x09 +#define ANX9804_ANALOG_DEBUG_REG1 0xdc +#define ANX9804_ANALOG_DEBUG_REG3 0xde +#define ANX9804_PLL_FILTER_CTRL1 0xdf +#define ANX9804_PLL_FILTER_CTRL3 0xe1 +#define ANX9804_PLL_FILTER_CTRL 0xe2 +#define ANX9804_PLL_CTRL3 0xe6 + +/** + * anx9804_init() - Init anx9804 parallel lcd to edp bridge chip + * + * This function will init an anx9804 parallel lcd to dp bridge chip + * using the passed in parameters. + * + * @i2c_bus: Number of the i2c bus to which the anx9804 is connected. + * @lanes: Number of displayport lanes to use + * @data_rate: Register value for the bandwidth reg 0x06: 1.62G, 0x0a: 2.7G + * @bpp: Bits per pixel, must be 18 or 24 + */ +void anx9804_init(unsigned int i2c_bus, u8 lanes, u8 data_rate, int bpp) +{ + unsigned int orig_i2c_bus = i2c_get_bus_num(); + u8 c, colordepth; + int i; + + i2c_set_bus_num(i2c_bus); + + if (bpp == 18) + colordepth = 0x00; /* 6 bit */ + else + colordepth = 0x10; /* 8 bit */ + + /* Reset */ + i2c_reg_write(0x39, ANX9804_RST_CTRL_REG, 1); + mdelay(100); + i2c_reg_write(0x39, ANX9804_RST_CTRL_REG, 0); + + /* Write 0 to the powerdown reg (powerup everything) */ + i2c_reg_write(0x39, ANX9804_POWERD_CTRL_REG, 0); + + c = i2c_reg_read(0x39, ANX9804_DEV_IDH_REG); + if (c != 0x98) { + printf("Error anx9804 chipid mismatch\n"); + i2c_set_bus_num(orig_i2c_bus); + return; + } + + for (i = 0; i < 100; i++) { + c = i2c_reg_read(0x38, ANX9804_SYS_CTRL2_REG); + i2c_reg_write(0x38, ANX9804_SYS_CTRL2_REG, c); + c = i2c_reg_read(0x38, ANX9804_SYS_CTRL2_REG); + if ((c & ANX9804_SYS_CTRL2_CHA_STA) == 0) + break; + + mdelay(5); + } + if (i == 100) + printf("Error anx9804 clock is not stable\n"); + + i2c_reg_write(0x39, ANX9804_VID_CTRL2_REG, colordepth); + + /* Set a bunch of analog related register values */ + i2c_reg_write(0x38, ANX9804_PLL_CTRL_REG, 0x07); + i2c_reg_write(0x39, ANX9804_PLL_FILTER_CTRL3, 0x19); + i2c_reg_write(0x39, ANX9804_PLL_CTRL3, 0xd9); + i2c_reg_write(0x39, ANX9804_RST_CTRL2_REG, ANX9804_RST_CTRL2_AC_MODE); + i2c_reg_write(0x39, ANX9804_ANALOG_DEBUG_REG1, 0xf0); + i2c_reg_write(0x39, ANX9804_ANALOG_DEBUG_REG3, 0x99); + i2c_reg_write(0x39, ANX9804_PLL_FILTER_CTRL1, 0x7b); + i2c_reg_write(0x38, ANX9804_LINK_DEBUG_REG, 0x30); + i2c_reg_write(0x39, ANX9804_PLL_FILTER_CTRL, 0x06); + + /* Force HPD */ + i2c_reg_write(0x38, ANX9804_SYS_CTRL3_REG, + ANX9804_SYS_CTRL3_F_HPD | ANX9804_SYS_CTRL3_HPD_CTRL); + + /* Power up and configure lanes */ + i2c_reg_write(0x38, ANX9804_ANALOG_POWER_DOWN_REG, 0x00); + i2c_reg_write(0x38, ANX9804_TRAINING_LANE0_SET_REG, 0x00); + i2c_reg_write(0x38, ANX9804_TRAINING_LANE1_SET_REG, 0x00); + i2c_reg_write(0x38, ANX9804_TRAINING_LANE2_SET_REG, 0x00); + i2c_reg_write(0x38, ANX9804_TRAINING_LANE3_SET_REG, 0x00); + + /* Reset AUX CH */ + i2c_reg_write(0x39, ANX9804_RST_CTRL2_REG, + ANX9804_RST_CTRL2_AC_MODE | ANX9804_RST_CTRL2_AUX); + i2c_reg_write(0x39, ANX9804_RST_CTRL2_REG, + ANX9804_RST_CTRL2_AC_MODE); + + /* Powerdown audio and some other unused bits */ + i2c_reg_write(0x39, ANX9804_POWERD_CTRL_REG, ANX9804_POWERD_AUDIO); + i2c_reg_write(0x38, ANX9804_HDCP_CONTROL_0_REG, 0x00); + i2c_reg_write(0x38, 0xa7, 0x00); + + /* Set data-rate / lanes */ + i2c_reg_write(0x38, ANX9804_LINK_BW_SET_REG, data_rate); + i2c_reg_write(0x38, ANX9804_LANE_COUNT_SET_REG, lanes); + + /* Link training */ + i2c_reg_write(0x38, ANX9804_LINK_TRAINING_CTRL_REG, + ANX9804_LINK_TRAINING_CTRL_EN); + mdelay(5); + for (i = 0; i < 100; i++) { + c = i2c_reg_read(0x38, ANX9804_LINK_TRAINING_CTRL_REG); + if ((c & 0x01) == 0) + break; + + mdelay(5); + } + if(i == 100) { + printf("Error anx9804 link training timeout\n"); + i2c_set_bus_num(orig_i2c_bus); + return; + } + + /* Enable */ + i2c_reg_write(0x39, ANX9804_VID_CTRL1_REG, + ANX9804_VID_CTRL1_VID_EN | ANX9804_VID_CTRL1_EDGE); + /* Force stream valid */ + i2c_reg_write(0x38, ANX9804_SYS_CTRL3_REG, + ANX9804_SYS_CTRL3_F_HPD | ANX9804_SYS_CTRL3_HPD_CTRL | + ANX9804_SYS_CTRL3_F_VALID | ANX9804_SYS_CTRL3_VALID_CTRL); + + i2c_set_bus_num(orig_i2c_bus); +} diff --git a/drivers/video/anx9804.h b/drivers/video/anx9804.h new file mode 100644 index 0000000..6c5daf9 --- /dev/null +++ b/drivers/video/anx9804.h @@ -0,0 +1,25 @@ +/* + * (C) 2015 Hans de Goede hdegoede@redhat.com + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/* + * Support for the ANX9804 bridge chip, which can take pixel data coming + * from a parallel LCD interface and translate it on the flight into a DP + * interface for driving eDP TFT displays. + */ + +#ifndef _ANX9804_H +#define _ANX9804_H + +#define ANX9804_DATA_RATE_1620M 0x06 +#define ANX9804_DATA_RATE_2700M 0x0a + +#ifdef CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804 +void anx9804_init(unsigned int i2c_bus, u8 lanes, u8 data_rate, int bpp); +#else +static inline void anx9804_init(unsigned int i2c_bus, u8 lanes, u8 data_rate, + int bpp) {} +#endif +#endif

On Sat, 2015-08-08 at 16:25 +0200, Hans de Goede wrote:
Add support for the ANX9804 bridge chip, which can take pixel data coming from a parallel LCD interface and translate it on the flight into a DP
I think you meant "on the fly" (in the Kconfig too).
The driver itself looks pretty straight forward to me, but I'll leave it to Anatolij to formally ack.

Hi,
On 08/11/2015 09:53 AM, Ian Campbell wrote:
On Sat, 2015-08-08 at 16:25 +0200, Hans de Goede wrote:
Add support for the ANX9804 bridge chip, which can take pixel data coming from a parallel LCD interface and translate it on the flight into a DP
I think you meant "on the fly" (in the Kconfig too).
Thanks, fixed in my personal tree.
Regards,
Hans

On Sat, 8 Aug 2015 16:25:03 +0200 Hans de Goede hdegoede@redhat.com wrote:
Add support for the ANX9804 bridge chip, which can take pixel data coming from a parallel LCD interface and translate it on the flight into a DP interface for driving eDP TFT displays. It uses I2C for configuration.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Anatolij Gustschin agust@denx.de

Add support for 4 1.62G lane eDP panels connected via an anx9804 bridge, such as found on the Colombus devkit.
While at it also fix the wrong indentation of the SSD2828 Kconfig help text in board/sunxi/Kconfig.
Signed-off-by: Hans de Goede hdegoede@redhat.com --- board/sunxi/Kconfig | 11 ++++++++++- configs/Colombus_defconfig | 11 +++++++++++ drivers/video/sunxi_display.c | 19 ++++++++++++++++++- include/configs/sunxi-common.h | 4 ++++ 4 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/board/sunxi/Kconfig b/board/sunxi/Kconfig index 13f4537..fd6668f 100644 --- a/board/sunxi/Kconfig +++ b/board/sunxi/Kconfig @@ -526,7 +526,16 @@ config VIDEO_LCD_PANEL_MIPI_4_LANE_513_MBPS_VIA_SSD2828 select VIDEO_LCD_SSD2828 select VIDEO_LCD_IF_PARALLEL ---help--- - 7.85" 768x1024 LCD panels, such as LG LP079X01 or AUO B079XAN01.0 + 7.85" 768x1024 LCD panels, such as LG LP079X01 or AUO B079XAN01.0 + +config VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804 + bool "eDP 4-lane, 1.62G LCD panel via ANX9804 bridge chip" + select VIDEO_LCD_ANX9804 + select VIDEO_LCD_IF_PARALLEL + select VIDEO_LCD_PANEL_I2C + ---help--- + Select this for eDP LCD panels with 4 lanes running at 1.62G, + connected via an ANX9804 bridge chip.
config VIDEO_LCD_PANEL_HITACHI_TX18D42VM bool "Hitachi tx18d42vm LCD panel" diff --git a/configs/Colombus_defconfig b/configs/Colombus_defconfig index 7fa9ef1..35f644a 100644 --- a/configs/Colombus_defconfig +++ b/configs/Colombus_defconfig @@ -4,6 +4,16 @@ CONFIG_MACH_SUN6I=y CONFIG_DRAM_CLK=240 CONFIG_DRAM_ZQ=251 CONFIG_USB1_VBUS_PIN="" +CONFIG_I2C0_ENABLE=y +CONFIG_AXP_GPIO=y +CONFIG_VIDEO_LCD_MODE="x:2048,y:1536,depth:24,pclk_khz:208000,le:5,ri:150,up:9,lo:24,hs:5,vs:1,sync:3,vmode:0" +CONFIG_VIDEO_LCD_DCLK_PHASE=0 +CONFIG_VIDEO_LCD_POWER="PH27" +CONFIG_VIDEO_LCD_BL_EN="PM1" +CONFIG_VIDEO_LCD_BL_PWM="PH13" +CONFIG_VIDEO_LCD_PANEL_I2C_SDA="PA23" +CONFIG_VIDEO_LCD_PANEL_I2C_SCL="PA24" +CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804=y CONFIG_DEFAULT_DEVICE_TREE="sun6i-a31-colombus" # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set CONFIG_SPL=y @@ -13,4 +23,5 @@ CONFIG_SYS_EXTRA_OPTIONS="SUNXI_GMAC,RGMII" # CONFIG_CMD_FPGA is not set CONFIG_ETH_DESIGNWARE=y CONFIG_AXP221_ALDO1_VOLT=3300 +CONFIG_AXP221_ELDO3_VOLT=1800 CONFIG_USB_EHCI_HCD=y diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c index aad870e..62cef87 100644 --- a/drivers/video/sunxi_display.c +++ b/drivers/video/sunxi_display.c @@ -15,6 +15,7 @@ #include <asm/global_data.h> #include <asm/gpio.h> #include <asm/io.h> +#include <axp221.h> #include <errno.h> #include <fdtdec.h> #include <fdt_support.h> @@ -22,6 +23,7 @@ #include <malloc.h> #include <video_fb.h> #include "videomodes.h" +#include "anx9804.h" #include "hitachi_tx18d42vm_lcd.h" #include "ssd2828.h"
@@ -765,13 +767,17 @@ static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode, (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE; int bp, clk_delay, clk_div, clk_double, pin, total, val;
- for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(27); pin++) + for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(27); pin++) { #ifdef CONFIG_VIDEO_LCD_IF_PARALLEL sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0); #endif #ifdef CONFIG_VIDEO_LCD_IF_LVDS sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LVDS0); #endif +#ifdef CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804 + sunxi_gpio_set_drv(pin, 3); +#endif + }
sunxi_lcdc_pll_set(0, mode->pixclock_khz, &clk_div, &clk_double);
@@ -1208,6 +1214,17 @@ static void sunxi_mode_set(const struct ctfb_res_modes *mode, break; case sunxi_monitor_lcd: sunxi_lcdc_panel_enable(); + if (IS_ENABLED(CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804)) { + /* + * The anx9804 needs 1.8V from eldo3, we do this here + * and not via CONFIG_AXP221_ELDO3 from board_init() + * to avoid turning this on when using hdmi output. + */ + axp221_set_eldo(3, 1800); + anx9804_init(CONFIG_VIDEO_LCD_I2C_BUS, 4, + ANX9804_DATA_RATE_1620M, + sunxi_display.depth); + } if (IS_ENABLED(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM)) { mdelay(50); /* Wait for lcd controller power on */ hitachi_tx18d42vm_init(); diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h index 83b6556..436c249 100644 --- a/include/configs/sunxi-common.h +++ b/include/configs/sunxi-common.h @@ -290,7 +290,11 @@ extern int soft_i2c_gpio_scl; * The amount of RAM to keep free at the top of RAM when relocating u-boot, * to use as framebuffer. This must be a multiple of 4096. */ +#ifdef CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804 +#define CONFIG_SUNXI_MAX_FB_SIZE (12 << 20) +#else #define CONFIG_SUNXI_MAX_FB_SIZE (9 << 20) +#endif
/* Do we want to initialize a simple FB? */ #define CONFIG_VIDEO_DT_SIMPLEFB

On Sat, 2015-08-08 at 16:25 +0200, Hans de Goede wrote:
Add support for 4 1.62G lane eDP panels connected via an anx9804 bridge, such as found on the Colombus devkit.
While at it also fix the wrong indentation of the SSD2828 Kconfig help text in board/sunxi/Kconfig.
Signed-off-by: Hans de Goede hdegoede@redhat.com
Acked-by: Ian Campbell ijc@hellion.org.uk

Hi Hans,
On Sat, 8 Aug 2015 16:24:59 +0200 Hans de Goede hdegoede@redhat.com wrote: ...
Anatolij, can we have your ack for merging this new video bridge chip driver via the sunxi tree ?
just replied with ack, please merge.
Thanks,
Anatolij
participants (4)
-
Anatolij Gustschin
-
Chen-Yu Tsai
-
Hans de Goede
-
Ian Campbell