xref: /rk3399_rockchip-uboot/drivers/video/sunxi/sunxi_display.c (revision d09ec7f81650425140776995fc9752189ddf7956)
15e023e7eSJernej Skrabec /*
25e023e7eSJernej Skrabec  * Display driver for Allwinner SoCs.
35e023e7eSJernej Skrabec  *
45e023e7eSJernej Skrabec  * (C) Copyright 2013-2014 Luc Verhaegen <libv@skynet.be>
55e023e7eSJernej Skrabec  * (C) Copyright 2014-2015 Hans de Goede <hdegoede@redhat.com>
65e023e7eSJernej Skrabec  *
75e023e7eSJernej Skrabec  * SPDX-License-Identifier:	GPL-2.0+
85e023e7eSJernej Skrabec  */
95e023e7eSJernej Skrabec 
105e023e7eSJernej Skrabec #include <common.h>
115e023e7eSJernej Skrabec 
125e023e7eSJernej Skrabec #include <asm/arch/clock.h>
135e023e7eSJernej Skrabec #include <asm/arch/display.h>
145e023e7eSJernej Skrabec #include <asm/arch/gpio.h>
155e023e7eSJernej Skrabec #include <asm/arch/lcdc.h>
165e023e7eSJernej Skrabec #include <asm/arch/pwm.h>
17*c1080626SJernej Skrabec #include <asm/arch/tve.h>
185e023e7eSJernej Skrabec #include <asm/global_data.h>
195e023e7eSJernej Skrabec #include <asm/gpio.h>
205e023e7eSJernej Skrabec #include <asm/io.h>
215e023e7eSJernej Skrabec #include <axp_pmic.h>
225e023e7eSJernej Skrabec #include <errno.h>
235e023e7eSJernej Skrabec #include <fdtdec.h>
245e023e7eSJernej Skrabec #include <fdt_support.h>
255e023e7eSJernej Skrabec #include <i2c.h>
265e023e7eSJernej Skrabec #include <malloc.h>
275e023e7eSJernej Skrabec #include <video_fb.h>
285e023e7eSJernej Skrabec #include "../videomodes.h"
295e023e7eSJernej Skrabec #include "../anx9804.h"
305e023e7eSJernej Skrabec #include "../hitachi_tx18d42vm_lcd.h"
315e023e7eSJernej Skrabec #include "../ssd2828.h"
325e023e7eSJernej Skrabec 
335e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_BL_PWM_ACTIVE_LOW
345e023e7eSJernej Skrabec #define PWM_ON 0
355e023e7eSJernej Skrabec #define PWM_OFF 1
365e023e7eSJernej Skrabec #else
375e023e7eSJernej Skrabec #define PWM_ON 1
385e023e7eSJernej Skrabec #define PWM_OFF 0
395e023e7eSJernej Skrabec #endif
405e023e7eSJernej Skrabec 
415e023e7eSJernej Skrabec DECLARE_GLOBAL_DATA_PTR;
425e023e7eSJernej Skrabec 
435e023e7eSJernej Skrabec enum sunxi_monitor {
445e023e7eSJernej Skrabec 	sunxi_monitor_none,
455e023e7eSJernej Skrabec 	sunxi_monitor_dvi,
465e023e7eSJernej Skrabec 	sunxi_monitor_hdmi,
475e023e7eSJernej Skrabec 	sunxi_monitor_lcd,
485e023e7eSJernej Skrabec 	sunxi_monitor_vga,
495e023e7eSJernej Skrabec 	sunxi_monitor_composite_pal,
505e023e7eSJernej Skrabec 	sunxi_monitor_composite_ntsc,
515e023e7eSJernej Skrabec 	sunxi_monitor_composite_pal_m,
525e023e7eSJernej Skrabec 	sunxi_monitor_composite_pal_nc,
535e023e7eSJernej Skrabec };
545e023e7eSJernej Skrabec #define SUNXI_MONITOR_LAST sunxi_monitor_composite_pal_nc
555e023e7eSJernej Skrabec 
565e023e7eSJernej Skrabec struct sunxi_display {
575e023e7eSJernej Skrabec 	GraphicDevice graphic_device;
585e023e7eSJernej Skrabec 	enum sunxi_monitor monitor;
595e023e7eSJernej Skrabec 	unsigned int depth;
605e023e7eSJernej Skrabec 	unsigned int fb_addr;
615e023e7eSJernej Skrabec 	unsigned int fb_size;
625e023e7eSJernej Skrabec } sunxi_display;
635e023e7eSJernej Skrabec 
645e023e7eSJernej Skrabec const struct ctfb_res_modes composite_video_modes[2] = {
655e023e7eSJernej Skrabec 	/*  x     y  hz  pixclk ps/kHz   le   ri  up  lo   hs vs  s  vmode */
665e023e7eSJernej Skrabec 	{ 720,  576, 50, 37037,  27000, 137,   5, 20, 27,   2, 2, 0, FB_VMODE_INTERLACED },
675e023e7eSJernej Skrabec 	{ 720,  480, 60, 37037,  27000, 116,  20, 16, 27,   2, 2, 0, FB_VMODE_INTERLACED },
685e023e7eSJernej Skrabec };
695e023e7eSJernej Skrabec 
705e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_HDMI
715e023e7eSJernej Skrabec 
725e023e7eSJernej Skrabec /*
735e023e7eSJernej Skrabec  * Wait up to 200ms for value to be set in given part of reg.
745e023e7eSJernej Skrabec  */
await_completion(u32 * reg,u32 mask,u32 val)755e023e7eSJernej Skrabec static int await_completion(u32 *reg, u32 mask, u32 val)
765e023e7eSJernej Skrabec {
775e023e7eSJernej Skrabec 	unsigned long tmo = timer_get_us() + 200000;
785e023e7eSJernej Skrabec 
795e023e7eSJernej Skrabec 	while ((readl(reg) & mask) != val) {
805e023e7eSJernej Skrabec 		if (timer_get_us() > tmo) {
815e023e7eSJernej Skrabec 			printf("DDC: timeout reading EDID\n");
825e023e7eSJernej Skrabec 			return -ETIME;
835e023e7eSJernej Skrabec 		}
845e023e7eSJernej Skrabec 	}
855e023e7eSJernej Skrabec 	return 0;
865e023e7eSJernej Skrabec }
875e023e7eSJernej Skrabec 
sunxi_hdmi_hpd_detect(int hpd_delay)885e023e7eSJernej Skrabec static int sunxi_hdmi_hpd_detect(int hpd_delay)
895e023e7eSJernej Skrabec {
905e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
915e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
925e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
935e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
945e023e7eSJernej Skrabec 	unsigned long tmo = timer_get_us() + hpd_delay * 1000;
955e023e7eSJernej Skrabec 
965e023e7eSJernej Skrabec 	/* Set pll3 to 300MHz */
975e023e7eSJernej Skrabec 	clock_set_pll3(300000000);
985e023e7eSJernej Skrabec 
995e023e7eSJernej Skrabec 	/* Set hdmi parent to pll3 */
1005e023e7eSJernej Skrabec 	clrsetbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_PLL_MASK,
1015e023e7eSJernej Skrabec 			CCM_HDMI_CTRL_PLL3);
1025e023e7eSJernej Skrabec 
1035e023e7eSJernej Skrabec 	/* Set ahb gating to pass */
1045e023e7eSJernej Skrabec #ifdef CONFIG_SUNXI_GEN_SUN6I
1055e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI);
1065e023e7eSJernej Skrabec #endif
1075e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI);
1085e023e7eSJernej Skrabec 
1095e023e7eSJernej Skrabec 	/* Clock on */
1105e023e7eSJernej Skrabec 	setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE);
1115e023e7eSJernej Skrabec 
1125e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_CTRL_ENABLE, &hdmi->ctrl);
1135e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PAD_CTRL0_HDP, &hdmi->pad_ctrl0);
1145e023e7eSJernej Skrabec 
1155e023e7eSJernej Skrabec 	while (timer_get_us() < tmo) {
1165e023e7eSJernej Skrabec 		if (readl(&hdmi->hpd) & SUNXI_HDMI_HPD_DETECT)
1175e023e7eSJernej Skrabec 			return 1;
1185e023e7eSJernej Skrabec 	}
1195e023e7eSJernej Skrabec 
1205e023e7eSJernej Skrabec 	return 0;
1215e023e7eSJernej Skrabec }
1225e023e7eSJernej Skrabec 
sunxi_hdmi_shutdown(void)1235e023e7eSJernej Skrabec static void sunxi_hdmi_shutdown(void)
1245e023e7eSJernej Skrabec {
1255e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
1265e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
1275e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
1285e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
1295e023e7eSJernej Skrabec 
1305e023e7eSJernej Skrabec 	clrbits_le32(&hdmi->ctrl, SUNXI_HDMI_CTRL_ENABLE);
1315e023e7eSJernej Skrabec 	clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE);
1325e023e7eSJernej Skrabec 	clrbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI);
1335e023e7eSJernej Skrabec #ifdef CONFIG_SUNXI_GEN_SUN6I
1345e023e7eSJernej Skrabec 	clrbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI);
1355e023e7eSJernej Skrabec #endif
1365e023e7eSJernej Skrabec 	clock_set_pll3(0);
1375e023e7eSJernej Skrabec }
1385e023e7eSJernej Skrabec 
sunxi_hdmi_ddc_do_command(u32 cmnd,int offset,int n)1395e023e7eSJernej Skrabec static int sunxi_hdmi_ddc_do_command(u32 cmnd, int offset, int n)
1405e023e7eSJernej Skrabec {
1415e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
1425e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
1435e023e7eSJernej Skrabec 
1445e023e7eSJernej Skrabec 	setbits_le32(&hdmi->ddc_fifo_ctrl, SUNXI_HDMI_DDC_FIFO_CTRL_CLEAR);
1455e023e7eSJernej Skrabec 	writel(SUNXI_HMDI_DDC_ADDR_EDDC_SEGMENT(offset >> 8) |
1465e023e7eSJernej Skrabec 	       SUNXI_HMDI_DDC_ADDR_EDDC_ADDR |
1475e023e7eSJernej Skrabec 	       SUNXI_HMDI_DDC_ADDR_OFFSET(offset) |
1485e023e7eSJernej Skrabec 	       SUNXI_HMDI_DDC_ADDR_SLAVE_ADDR, &hdmi->ddc_addr);
1495e023e7eSJernej Skrabec #ifndef CONFIG_MACH_SUN6I
1505e023e7eSJernej Skrabec 	writel(n, &hdmi->ddc_byte_count);
1515e023e7eSJernej Skrabec 	writel(cmnd, &hdmi->ddc_cmnd);
1525e023e7eSJernej Skrabec #else
1535e023e7eSJernej Skrabec 	writel(n << 16 | cmnd, &hdmi->ddc_cmnd);
1545e023e7eSJernej Skrabec #endif
1555e023e7eSJernej Skrabec 	setbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START);
1565e023e7eSJernej Skrabec 
1575e023e7eSJernej Skrabec 	return await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START, 0);
1585e023e7eSJernej Skrabec }
1595e023e7eSJernej Skrabec 
sunxi_hdmi_ddc_read(int offset,u8 * buf,int count)1605e023e7eSJernej Skrabec static int sunxi_hdmi_ddc_read(int offset, u8 *buf, int count)
1615e023e7eSJernej Skrabec {
1625e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
1635e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
1645e023e7eSJernej Skrabec 	int i, n;
1655e023e7eSJernej Skrabec 
1665e023e7eSJernej Skrabec 	while (count > 0) {
1675e023e7eSJernej Skrabec 		if (count > 16)
1685e023e7eSJernej Skrabec 			n = 16;
1695e023e7eSJernej Skrabec 		else
1705e023e7eSJernej Skrabec 			n = count;
1715e023e7eSJernej Skrabec 
1725e023e7eSJernej Skrabec 		if (sunxi_hdmi_ddc_do_command(
1735e023e7eSJernej Skrabec 				SUNXI_HDMI_DDC_CMND_EXPLICIT_EDDC_READ,
1745e023e7eSJernej Skrabec 				offset, n))
1755e023e7eSJernej Skrabec 			return -ETIME;
1765e023e7eSJernej Skrabec 
1775e023e7eSJernej Skrabec 		for (i = 0; i < n; i++)
1785e023e7eSJernej Skrabec 			*buf++ = readb(&hdmi->ddc_fifo_data);
1795e023e7eSJernej Skrabec 
1805e023e7eSJernej Skrabec 		offset += n;
1815e023e7eSJernej Skrabec 		count -= n;
1825e023e7eSJernej Skrabec 	}
1835e023e7eSJernej Skrabec 
1845e023e7eSJernej Skrabec 	return 0;
1855e023e7eSJernej Skrabec }
1865e023e7eSJernej Skrabec 
sunxi_hdmi_edid_get_block(int block,u8 * buf)1875e023e7eSJernej Skrabec static int sunxi_hdmi_edid_get_block(int block, u8 *buf)
1885e023e7eSJernej Skrabec {
1895e023e7eSJernej Skrabec 	int r, retries = 2;
1905e023e7eSJernej Skrabec 
1915e023e7eSJernej Skrabec 	do {
1925e023e7eSJernej Skrabec 		r = sunxi_hdmi_ddc_read(block * 128, buf, 128);
1935e023e7eSJernej Skrabec 		if (r)
1945e023e7eSJernej Skrabec 			continue;
1955e023e7eSJernej Skrabec 		r = edid_check_checksum(buf);
1965e023e7eSJernej Skrabec 		if (r) {
1975e023e7eSJernej Skrabec 			printf("EDID block %d: checksum error%s\n",
1985e023e7eSJernej Skrabec 			       block, retries ? ", retrying" : "");
1995e023e7eSJernej Skrabec 		}
2005e023e7eSJernej Skrabec 	} while (r && retries--);
2015e023e7eSJernej Skrabec 
2025e023e7eSJernej Skrabec 	return r;
2035e023e7eSJernej Skrabec }
2045e023e7eSJernej Skrabec 
sunxi_hdmi_edid_get_mode(struct ctfb_res_modes * mode)2055e023e7eSJernej Skrabec static int sunxi_hdmi_edid_get_mode(struct ctfb_res_modes *mode)
2065e023e7eSJernej Skrabec {
2075e023e7eSJernej Skrabec 	struct edid1_info edid1;
2085e023e7eSJernej Skrabec 	struct edid_cea861_info cea681[4];
2095e023e7eSJernej Skrabec 	struct edid_detailed_timing *t =
2105e023e7eSJernej Skrabec 		(struct edid_detailed_timing *)edid1.monitor_details.timing;
2115e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
2125e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
2135e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
2145e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
2155e023e7eSJernej Skrabec 	int i, r, ext_blocks = 0;
2165e023e7eSJernej Skrabec 
2175e023e7eSJernej Skrabec 	/* SUNXI_HDMI_CTRL_ENABLE & PAD_CTRL0 are already set by hpd_detect */
2185e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PAD_CTRL1 | SUNXI_HDMI_PAD_CTRL1_HALVE,
2195e023e7eSJernej Skrabec 	       &hdmi->pad_ctrl1);
2205e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PLL_CTRL | SUNXI_HDMI_PLL_CTRL_DIV(15),
2215e023e7eSJernej Skrabec 	       &hdmi->pll_ctrl);
2225e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PLL_DBG0_PLL3, &hdmi->pll_dbg0);
2235e023e7eSJernej Skrabec 
2245e023e7eSJernej Skrabec 	/* Reset i2c controller */
2255e023e7eSJernej Skrabec 	setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
2265e023e7eSJernej Skrabec 	writel(SUNXI_HMDI_DDC_CTRL_ENABLE |
2275e023e7eSJernej Skrabec 	       SUNXI_HMDI_DDC_CTRL_SDA_ENABLE |
2285e023e7eSJernej Skrabec 	       SUNXI_HMDI_DDC_CTRL_SCL_ENABLE |
2295e023e7eSJernej Skrabec 	       SUNXI_HMDI_DDC_CTRL_RESET, &hdmi->ddc_ctrl);
2305e023e7eSJernej Skrabec 	if (await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_RESET, 0))
2315e023e7eSJernej Skrabec 		return -EIO;
2325e023e7eSJernej Skrabec 
2335e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_DDC_CLOCK, &hdmi->ddc_clock);
2345e023e7eSJernej Skrabec #ifndef CONFIG_MACH_SUN6I
2355e023e7eSJernej Skrabec 	writel(SUNXI_HMDI_DDC_LINE_CTRL_SDA_ENABLE |
2365e023e7eSJernej Skrabec 	       SUNXI_HMDI_DDC_LINE_CTRL_SCL_ENABLE, &hdmi->ddc_line_ctrl);
2375e023e7eSJernej Skrabec #endif
2385e023e7eSJernej Skrabec 
2395e023e7eSJernej Skrabec 	r = sunxi_hdmi_edid_get_block(0, (u8 *)&edid1);
2405e023e7eSJernej Skrabec 	if (r == 0) {
2415e023e7eSJernej Skrabec 		r = edid_check_info(&edid1);
2425e023e7eSJernej Skrabec 		if (r) {
2435e023e7eSJernej Skrabec 			printf("EDID: invalid EDID data\n");
2445e023e7eSJernej Skrabec 			r = -EINVAL;
2455e023e7eSJernej Skrabec 		}
2465e023e7eSJernej Skrabec 	}
2475e023e7eSJernej Skrabec 	if (r == 0) {
2485e023e7eSJernej Skrabec 		ext_blocks = edid1.extension_flag;
2495e023e7eSJernej Skrabec 		if (ext_blocks > 4)
2505e023e7eSJernej Skrabec 			ext_blocks = 4;
2515e023e7eSJernej Skrabec 		for (i = 0; i < ext_blocks; i++) {
2525e023e7eSJernej Skrabec 			if (sunxi_hdmi_edid_get_block(1 + i,
2535e023e7eSJernej Skrabec 						(u8 *)&cea681[i]) != 0) {
2545e023e7eSJernej Skrabec 				ext_blocks = i;
2555e023e7eSJernej Skrabec 				break;
2565e023e7eSJernej Skrabec 			}
2575e023e7eSJernej Skrabec 		}
2585e023e7eSJernej Skrabec 	}
2595e023e7eSJernej Skrabec 
2605e023e7eSJernej Skrabec 	/* Disable DDC engine, no longer needed */
2615e023e7eSJernej Skrabec 	clrbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_ENABLE);
2625e023e7eSJernej Skrabec 	clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
2635e023e7eSJernej Skrabec 
2645e023e7eSJernej Skrabec 	if (r)
2655e023e7eSJernej Skrabec 		return r;
2665e023e7eSJernej Skrabec 
2675e023e7eSJernej Skrabec 	/* We want version 1.3 or 1.2 with detailed timing info */
2685e023e7eSJernej Skrabec 	if (edid1.version != 1 || (edid1.revision < 3 &&
2695e023e7eSJernej Skrabec 			!EDID1_INFO_FEATURE_PREFERRED_TIMING_MODE(edid1))) {
2705e023e7eSJernej Skrabec 		printf("EDID: unsupported version %d.%d\n",
2715e023e7eSJernej Skrabec 		       edid1.version, edid1.revision);
2725e023e7eSJernej Skrabec 		return -EINVAL;
2735e023e7eSJernej Skrabec 	}
2745e023e7eSJernej Skrabec 
2755e023e7eSJernej Skrabec 	/* Take the first usable detailed timing */
2765e023e7eSJernej Skrabec 	for (i = 0; i < 4; i++, t++) {
2775e023e7eSJernej Skrabec 		r = video_edid_dtd_to_ctfb_res_modes(t, mode);
2785e023e7eSJernej Skrabec 		if (r == 0)
2795e023e7eSJernej Skrabec 			break;
2805e023e7eSJernej Skrabec 	}
2815e023e7eSJernej Skrabec 	if (i == 4) {
2825e023e7eSJernej Skrabec 		printf("EDID: no usable detailed timing found\n");
2835e023e7eSJernej Skrabec 		return -ENOENT;
2845e023e7eSJernej Skrabec 	}
2855e023e7eSJernej Skrabec 
2865e023e7eSJernej Skrabec 	/* Check for basic audio support, if found enable hdmi output */
2875e023e7eSJernej Skrabec 	sunxi_display.monitor = sunxi_monitor_dvi;
2885e023e7eSJernej Skrabec 	for (i = 0; i < ext_blocks; i++) {
2895e023e7eSJernej Skrabec 		if (cea681[i].extension_tag != EDID_CEA861_EXTENSION_TAG ||
2905e023e7eSJernej Skrabec 		    cea681[i].revision < 2)
2915e023e7eSJernej Skrabec 			continue;
2925e023e7eSJernej Skrabec 
2935e023e7eSJernej Skrabec 		if (EDID_CEA861_SUPPORTS_BASIC_AUDIO(cea681[i]))
2945e023e7eSJernej Skrabec 			sunxi_display.monitor = sunxi_monitor_hdmi;
2955e023e7eSJernej Skrabec 	}
2965e023e7eSJernej Skrabec 
2975e023e7eSJernej Skrabec 	return 0;
2985e023e7eSJernej Skrabec }
2995e023e7eSJernej Skrabec 
3005e023e7eSJernej Skrabec #endif /* CONFIG_VIDEO_HDMI */
3015e023e7eSJernej Skrabec 
3025e023e7eSJernej Skrabec #ifdef CONFIG_MACH_SUN4I
3035e023e7eSJernej Skrabec /*
3045e023e7eSJernej Skrabec  * Testing has shown that on sun4i the display backend engine does not have
3055e023e7eSJernej Skrabec  * deep enough fifo-s causing flickering / tearing in full-hd mode due to
3065e023e7eSJernej Skrabec  * fifo underruns. So on sun4i we use the display frontend engine to do the
3075e023e7eSJernej Skrabec  * dma from memory, as the frontend does have deep enough fifo-s.
3085e023e7eSJernej Skrabec  */
3095e023e7eSJernej Skrabec 
3105e023e7eSJernej Skrabec static const u32 sun4i_vert_coef[32] = {
3115e023e7eSJernej Skrabec 	0x00004000, 0x000140ff, 0x00033ffe, 0x00043ffd,
3125e023e7eSJernej Skrabec 	0x00063efc, 0xff083dfc, 0x000a3bfb, 0xff0d39fb,
3135e023e7eSJernej Skrabec 	0xff0f37fb, 0xff1136fa, 0xfe1433fb, 0xfe1631fb,
3145e023e7eSJernej Skrabec 	0xfd192ffb, 0xfd1c2cfb, 0xfd1f29fb, 0xfc2127fc,
3155e023e7eSJernej Skrabec 	0xfc2424fc, 0xfc2721fc, 0xfb291ffd, 0xfb2c1cfd,
3165e023e7eSJernej Skrabec 	0xfb2f19fd, 0xfb3116fe, 0xfb3314fe, 0xfa3611ff,
3175e023e7eSJernej Skrabec 	0xfb370fff, 0xfb390dff, 0xfb3b0a00, 0xfc3d08ff,
3185e023e7eSJernej Skrabec 	0xfc3e0600, 0xfd3f0400, 0xfe3f0300, 0xff400100,
3195e023e7eSJernej Skrabec };
3205e023e7eSJernej Skrabec 
3215e023e7eSJernej Skrabec static const u32 sun4i_horz_coef[64] = {
3225e023e7eSJernej Skrabec 	0x40000000, 0x00000000, 0x40fe0000, 0x0000ff03,
3235e023e7eSJernej Skrabec 	0x3ffd0000, 0x0000ff05, 0x3ffc0000, 0x0000ff06,
3245e023e7eSJernej Skrabec 	0x3efb0000, 0x0000ff08, 0x3dfb0000, 0x0000ff09,
3255e023e7eSJernej Skrabec 	0x3bfa0000, 0x0000fe0d, 0x39fa0000, 0x0000fe0f,
3265e023e7eSJernej Skrabec 	0x38fa0000, 0x0000fe10, 0x36fa0000, 0x0000fe12,
3275e023e7eSJernej Skrabec 	0x33fa0000, 0x0000fd16, 0x31fa0000, 0x0000fd18,
3285e023e7eSJernej Skrabec 	0x2ffa0000, 0x0000fd1a, 0x2cfa0000, 0x0000fc1e,
3295e023e7eSJernej Skrabec 	0x29fa0000, 0x0000fc21, 0x27fb0000, 0x0000fb23,
3305e023e7eSJernej Skrabec 	0x24fb0000, 0x0000fb26, 0x21fb0000, 0x0000fb29,
3315e023e7eSJernej Skrabec 	0x1ffc0000, 0x0000fa2b, 0x1cfc0000, 0x0000fa2e,
3325e023e7eSJernej Skrabec 	0x19fd0000, 0x0000fa30, 0x16fd0000, 0x0000fa33,
3335e023e7eSJernej Skrabec 	0x14fd0000, 0x0000fa35, 0x11fe0000, 0x0000fa37,
3345e023e7eSJernej Skrabec 	0x0ffe0000, 0x0000fa39, 0x0dfe0000, 0x0000fa3b,
3355e023e7eSJernej Skrabec 	0x0afe0000, 0x0000fa3e, 0x08ff0000, 0x0000fb3e,
3365e023e7eSJernej Skrabec 	0x06ff0000, 0x0000fb40, 0x05ff0000, 0x0000fc40,
3375e023e7eSJernej Skrabec 	0x03ff0000, 0x0000fd41, 0x01ff0000, 0x0000fe42,
3385e023e7eSJernej Skrabec };
3395e023e7eSJernej Skrabec 
sunxi_frontend_init(void)3405e023e7eSJernej Skrabec static void sunxi_frontend_init(void)
3415e023e7eSJernej Skrabec {
3425e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
3435e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
3445e023e7eSJernej Skrabec 	struct sunxi_de_fe_reg * const de_fe =
3455e023e7eSJernej Skrabec 		(struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
3465e023e7eSJernej Skrabec 	int i;
3475e023e7eSJernej Skrabec 
3485e023e7eSJernej Skrabec 	/* Clocks on */
3495e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_FE0);
3505e023e7eSJernej Skrabec 	setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_FE0);
3515e023e7eSJernej Skrabec 	clock_set_de_mod_clock(&ccm->fe0_clk_cfg, 300000000);
3525e023e7eSJernej Skrabec 
3535e023e7eSJernej Skrabec 	setbits_le32(&de_fe->enable, SUNXI_DE_FE_ENABLE_EN);
3545e023e7eSJernej Skrabec 
3555e023e7eSJernej Skrabec 	for (i = 0; i < 32; i++) {
3565e023e7eSJernej Skrabec 		writel(sun4i_horz_coef[2 * i], &de_fe->ch0_horzcoef0[i]);
3575e023e7eSJernej Skrabec 		writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch0_horzcoef1[i]);
3585e023e7eSJernej Skrabec 		writel(sun4i_vert_coef[i], &de_fe->ch0_vertcoef[i]);
3595e023e7eSJernej Skrabec 		writel(sun4i_horz_coef[2 * i], &de_fe->ch1_horzcoef0[i]);
3605e023e7eSJernej Skrabec 		writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch1_horzcoef1[i]);
3615e023e7eSJernej Skrabec 		writel(sun4i_vert_coef[i], &de_fe->ch1_vertcoef[i]);
3625e023e7eSJernej Skrabec 	}
3635e023e7eSJernej Skrabec 
3645e023e7eSJernej Skrabec 	setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_COEF_RDY);
3655e023e7eSJernej Skrabec }
3665e023e7eSJernej Skrabec 
sunxi_frontend_mode_set(const struct ctfb_res_modes * mode,unsigned int address)3675e023e7eSJernej Skrabec static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
3685e023e7eSJernej Skrabec 				    unsigned int address)
3695e023e7eSJernej Skrabec {
3705e023e7eSJernej Skrabec 	struct sunxi_de_fe_reg * const de_fe =
3715e023e7eSJernej Skrabec 		(struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
3725e023e7eSJernej Skrabec 
3735e023e7eSJernej Skrabec 	setbits_le32(&de_fe->bypass, SUNXI_DE_FE_BYPASS_CSC_BYPASS);
3745e023e7eSJernej Skrabec 	writel(CONFIG_SYS_SDRAM_BASE + address, &de_fe->ch0_addr);
3755e023e7eSJernej Skrabec 	writel(mode->xres * 4, &de_fe->ch0_stride);
3765e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_INPUT_FMT_ARGB8888, &de_fe->input_fmt);
3775e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_OUTPUT_FMT_ARGB8888, &de_fe->output_fmt);
3785e023e7eSJernej Skrabec 
3795e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
3805e023e7eSJernej Skrabec 	       &de_fe->ch0_insize);
3815e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
3825e023e7eSJernej Skrabec 	       &de_fe->ch0_outsize);
3835e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_horzfact);
3845e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_vertfact);
3855e023e7eSJernej Skrabec 
3865e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
3875e023e7eSJernej Skrabec 	       &de_fe->ch1_insize);
3885e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
3895e023e7eSJernej Skrabec 	       &de_fe->ch1_outsize);
3905e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_horzfact);
3915e023e7eSJernej Skrabec 	writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_vertfact);
3925e023e7eSJernej Skrabec 
3935e023e7eSJernej Skrabec 	setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_REG_RDY);
3945e023e7eSJernej Skrabec }
3955e023e7eSJernej Skrabec 
sunxi_frontend_enable(void)3965e023e7eSJernej Skrabec static void sunxi_frontend_enable(void)
3975e023e7eSJernej Skrabec {
3985e023e7eSJernej Skrabec 	struct sunxi_de_fe_reg * const de_fe =
3995e023e7eSJernej Skrabec 		(struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
4005e023e7eSJernej Skrabec 
4015e023e7eSJernej Skrabec 	setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_FRM_START);
4025e023e7eSJernej Skrabec }
4035e023e7eSJernej Skrabec #else
sunxi_frontend_init(void)4045e023e7eSJernej Skrabec static void sunxi_frontend_init(void) {}
sunxi_frontend_mode_set(const struct ctfb_res_modes * mode,unsigned int address)4055e023e7eSJernej Skrabec static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
4065e023e7eSJernej Skrabec 				    unsigned int address) {}
sunxi_frontend_enable(void)4075e023e7eSJernej Skrabec static void sunxi_frontend_enable(void) {}
4085e023e7eSJernej Skrabec #endif
4095e023e7eSJernej Skrabec 
sunxi_is_composite(void)4105e023e7eSJernej Skrabec static bool sunxi_is_composite(void)
4115e023e7eSJernej Skrabec {
4125e023e7eSJernej Skrabec 	switch (sunxi_display.monitor) {
4135e023e7eSJernej Skrabec 	case sunxi_monitor_none:
4145e023e7eSJernej Skrabec 	case sunxi_monitor_dvi:
4155e023e7eSJernej Skrabec 	case sunxi_monitor_hdmi:
4165e023e7eSJernej Skrabec 	case sunxi_monitor_lcd:
4175e023e7eSJernej Skrabec 	case sunxi_monitor_vga:
4185e023e7eSJernej Skrabec 		return false;
4195e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal:
4205e023e7eSJernej Skrabec 	case sunxi_monitor_composite_ntsc:
4215e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_m:
4225e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_nc:
4235e023e7eSJernej Skrabec 		return true;
4245e023e7eSJernej Skrabec 	}
4255e023e7eSJernej Skrabec 
4265e023e7eSJernej Skrabec 	return false; /* Never reached */
4275e023e7eSJernej Skrabec }
4285e023e7eSJernej Skrabec 
4295e023e7eSJernej Skrabec /*
4305e023e7eSJernej Skrabec  * This is the entity that mixes and matches the different layers and inputs.
4315e023e7eSJernej Skrabec  * Allwinner calls it the back-end, but i like composer better.
4325e023e7eSJernej Skrabec  */
sunxi_composer_init(void)4335e023e7eSJernej Skrabec static void sunxi_composer_init(void)
4345e023e7eSJernej Skrabec {
4355e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
4365e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
4375e023e7eSJernej Skrabec 	struct sunxi_de_be_reg * const de_be =
4385e023e7eSJernej Skrabec 		(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
4395e023e7eSJernej Skrabec 	int i;
4405e023e7eSJernej Skrabec 
4415e023e7eSJernej Skrabec 	sunxi_frontend_init();
4425e023e7eSJernej Skrabec 
4435e023e7eSJernej Skrabec #ifdef CONFIG_SUNXI_GEN_SUN6I
4445e023e7eSJernej Skrabec 	/* Reset off */
4455e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DE_BE0);
4465e023e7eSJernej Skrabec #endif
4475e023e7eSJernej Skrabec 
4485e023e7eSJernej Skrabec 	/* Clocks on */
4495e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_BE0);
4505e023e7eSJernej Skrabec #ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
4515e023e7eSJernej Skrabec 	setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_BE0);
4525e023e7eSJernej Skrabec #endif
4535e023e7eSJernej Skrabec 	clock_set_de_mod_clock(&ccm->be0_clk_cfg, 300000000);
4545e023e7eSJernej Skrabec 
4555e023e7eSJernej Skrabec 	/* Engine bug, clear registers after reset */
4565e023e7eSJernej Skrabec 	for (i = 0x0800; i < 0x1000; i += 4)
4575e023e7eSJernej Skrabec 		writel(0, SUNXI_DE_BE0_BASE + i);
4585e023e7eSJernej Skrabec 
4595e023e7eSJernej Skrabec 	setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_ENABLE);
4605e023e7eSJernej Skrabec }
4615e023e7eSJernej Skrabec 
4625e023e7eSJernej Skrabec static u32 sunxi_rgb2yuv_coef[12] = {
4635e023e7eSJernej Skrabec 	0x00000107, 0x00000204, 0x00000064, 0x00000108,
4645e023e7eSJernej Skrabec 	0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808,
4655e023e7eSJernej Skrabec 	0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808
4665e023e7eSJernej Skrabec };
4675e023e7eSJernej Skrabec 
sunxi_composer_mode_set(const struct ctfb_res_modes * mode,unsigned int address)4685e023e7eSJernej Skrabec static void sunxi_composer_mode_set(const struct ctfb_res_modes *mode,
4695e023e7eSJernej Skrabec 				    unsigned int address)
4705e023e7eSJernej Skrabec {
4715e023e7eSJernej Skrabec 	struct sunxi_de_be_reg * const de_be =
4725e023e7eSJernej Skrabec 		(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
4735e023e7eSJernej Skrabec 	int i;
4745e023e7eSJernej Skrabec 
4755e023e7eSJernej Skrabec 	sunxi_frontend_mode_set(mode, address);
4765e023e7eSJernej Skrabec 
4775e023e7eSJernej Skrabec 	writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
4785e023e7eSJernej Skrabec 	       &de_be->disp_size);
4795e023e7eSJernej Skrabec 	writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
4805e023e7eSJernej Skrabec 	       &de_be->layer0_size);
4815e023e7eSJernej Skrabec #ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
4825e023e7eSJernej Skrabec 	writel(SUNXI_DE_BE_LAYER_STRIDE(mode->xres), &de_be->layer0_stride);
4835e023e7eSJernej Skrabec 	writel(address << 3, &de_be->layer0_addr_low32b);
4845e023e7eSJernej Skrabec 	writel(address >> 29, &de_be->layer0_addr_high4b);
4855e023e7eSJernej Skrabec #else
4865e023e7eSJernej Skrabec 	writel(SUNXI_DE_BE_LAYER_ATTR0_SRC_FE0, &de_be->layer0_attr0_ctrl);
4875e023e7eSJernej Skrabec #endif
4885e023e7eSJernej Skrabec 	writel(SUNXI_DE_BE_LAYER_ATTR1_FMT_XRGB8888, &de_be->layer0_attr1_ctrl);
4895e023e7eSJernej Skrabec 
4905e023e7eSJernej Skrabec 	setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_LAYER0_ENABLE);
4915e023e7eSJernej Skrabec 	if (mode->vmode == FB_VMODE_INTERLACED)
4925e023e7eSJernej Skrabec 		setbits_le32(&de_be->mode,
4935e023e7eSJernej Skrabec #ifndef CONFIG_MACH_SUN5I
4945e023e7eSJernej Skrabec 			     SUNXI_DE_BE_MODE_DEFLICKER_ENABLE |
4955e023e7eSJernej Skrabec #endif
4965e023e7eSJernej Skrabec 			     SUNXI_DE_BE_MODE_INTERLACE_ENABLE);
4975e023e7eSJernej Skrabec 
4985e023e7eSJernej Skrabec 	if (sunxi_is_composite()) {
4995e023e7eSJernej Skrabec 		writel(SUNXI_DE_BE_OUTPUT_COLOR_CTRL_ENABLE,
5005e023e7eSJernej Skrabec 		       &de_be->output_color_ctrl);
5015e023e7eSJernej Skrabec 		for (i = 0; i < 12; i++)
5025e023e7eSJernej Skrabec 			writel(sunxi_rgb2yuv_coef[i],
5035e023e7eSJernej Skrabec 			       &de_be->output_color_coef[i]);
5045e023e7eSJernej Skrabec 	}
5055e023e7eSJernej Skrabec }
5065e023e7eSJernej Skrabec 
sunxi_composer_enable(void)5075e023e7eSJernej Skrabec static void sunxi_composer_enable(void)
5085e023e7eSJernej Skrabec {
5095e023e7eSJernej Skrabec 	struct sunxi_de_be_reg * const de_be =
5105e023e7eSJernej Skrabec 		(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
5115e023e7eSJernej Skrabec 
5125e023e7eSJernej Skrabec 	sunxi_frontend_enable();
5135e023e7eSJernej Skrabec 
5145e023e7eSJernej Skrabec 	setbits_le32(&de_be->reg_ctrl, SUNXI_DE_BE_REG_CTRL_LOAD_REGS);
5155e023e7eSJernej Skrabec 	setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
5165e023e7eSJernej Skrabec }
5175e023e7eSJernej Skrabec 
5185e023e7eSJernej Skrabec /*
5195e023e7eSJernej Skrabec  * LCDC, what allwinner calls a CRTC, so timing controller and serializer.
5205e023e7eSJernej Skrabec  */
sunxi_lcdc_pll_set(int tcon,int dotclock,int * clk_div,int * clk_double)5215e023e7eSJernej Skrabec static void sunxi_lcdc_pll_set(int tcon, int dotclock,
5225e023e7eSJernej Skrabec 			       int *clk_div, int *clk_double)
5235e023e7eSJernej Skrabec {
5245e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
5255e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
5265e023e7eSJernej Skrabec 	int value, n, m, min_m, max_m, diff;
5275e023e7eSJernej Skrabec 	int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF;
5285e023e7eSJernej Skrabec 	int best_double = 0;
5295e023e7eSJernej Skrabec 	bool use_mipi_pll = false;
5305e023e7eSJernej Skrabec 
5315e023e7eSJernej Skrabec 	if (tcon == 0) {
5325e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
5335e023e7eSJernej Skrabec 		min_m = 6;
5345e023e7eSJernej Skrabec 		max_m = 127;
5355e023e7eSJernej Skrabec #endif
5365e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_IF_LVDS
5375e023e7eSJernej Skrabec 		min_m = max_m = 7;
5385e023e7eSJernej Skrabec #endif
5395e023e7eSJernej Skrabec 	} else {
5405e023e7eSJernej Skrabec 		min_m = 1;
5415e023e7eSJernej Skrabec 		max_m = 15;
5425e023e7eSJernej Skrabec 	}
5435e023e7eSJernej Skrabec 
5445e023e7eSJernej Skrabec 	/*
5455e023e7eSJernej Skrabec 	 * Find the lowest divider resulting in a matching clock, if there
5465e023e7eSJernej Skrabec 	 * is no match, pick the closest lower clock, as monitors tend to
5475e023e7eSJernej Skrabec 	 * not sync to higher frequencies.
5485e023e7eSJernej Skrabec 	 */
5495e023e7eSJernej Skrabec 	for (m = min_m; m <= max_m; m++) {
5505e023e7eSJernej Skrabec 		n = (m * dotclock) / 3000;
5515e023e7eSJernej Skrabec 
5525e023e7eSJernej Skrabec 		if ((n >= 9) && (n <= 127)) {
5535e023e7eSJernej Skrabec 			value = (3000 * n) / m;
5545e023e7eSJernej Skrabec 			diff = dotclock - value;
5555e023e7eSJernej Skrabec 			if (diff < best_diff) {
5565e023e7eSJernej Skrabec 				best_diff = diff;
5575e023e7eSJernej Skrabec 				best_m = m;
5585e023e7eSJernej Skrabec 				best_n = n;
5595e023e7eSJernej Skrabec 				best_double = 0;
5605e023e7eSJernej Skrabec 			}
5615e023e7eSJernej Skrabec 		}
5625e023e7eSJernej Skrabec 
5635e023e7eSJernej Skrabec 		/* These are just duplicates */
5645e023e7eSJernej Skrabec 		if (!(m & 1))
5655e023e7eSJernej Skrabec 			continue;
5665e023e7eSJernej Skrabec 
5675e023e7eSJernej Skrabec 		n = (m * dotclock) / 6000;
5685e023e7eSJernej Skrabec 		if ((n >= 9) && (n <= 127)) {
5695e023e7eSJernej Skrabec 			value = (6000 * n) / m;
5705e023e7eSJernej Skrabec 			diff = dotclock - value;
5715e023e7eSJernej Skrabec 			if (diff < best_diff) {
5725e023e7eSJernej Skrabec 				best_diff = diff;
5735e023e7eSJernej Skrabec 				best_m = m;
5745e023e7eSJernej Skrabec 				best_n = n;
5755e023e7eSJernej Skrabec 				best_double = 1;
5765e023e7eSJernej Skrabec 			}
5775e023e7eSJernej Skrabec 		}
5785e023e7eSJernej Skrabec 	}
5795e023e7eSJernej Skrabec 
5805e023e7eSJernej Skrabec #ifdef CONFIG_MACH_SUN6I
5815e023e7eSJernej Skrabec 	/*
5825e023e7eSJernej Skrabec 	 * Use the MIPI pll if we've been unable to find any matching setting
5835e023e7eSJernej Skrabec 	 * for PLL3, this happens with high dotclocks because of min_m = 6.
5845e023e7eSJernej Skrabec 	 */
5855e023e7eSJernej Skrabec 	if (tcon == 0 && best_n == 0) {
5865e023e7eSJernej Skrabec 		use_mipi_pll = true;
5875e023e7eSJernej Skrabec 		best_m = 6;  /* Minimum m for tcon0 */
5885e023e7eSJernej Skrabec 	}
5895e023e7eSJernej Skrabec 
5905e023e7eSJernej Skrabec 	if (use_mipi_pll) {
5915e023e7eSJernej Skrabec 		clock_set_pll3(297000000); /* Fix the video pll at 297 MHz */
5925e023e7eSJernej Skrabec 		clock_set_mipi_pll(best_m * dotclock * 1000);
5935e023e7eSJernej Skrabec 		debug("dotclock: %dkHz = %dkHz via mipi pll\n",
5945e023e7eSJernej Skrabec 		      dotclock, clock_get_mipi_pll() / best_m / 1000);
5955e023e7eSJernej Skrabec 	} else
5965e023e7eSJernej Skrabec #endif
5975e023e7eSJernej Skrabec 	{
5985e023e7eSJernej Skrabec 		clock_set_pll3(best_n * 3000000);
5995e023e7eSJernej Skrabec 		debug("dotclock: %dkHz = %dkHz: (%d * 3MHz * %d) / %d\n",
6005e023e7eSJernej Skrabec 		      dotclock,
6015e023e7eSJernej Skrabec 		      (best_double + 1) * clock_get_pll3() / best_m / 1000,
6025e023e7eSJernej Skrabec 		      best_double + 1, best_n, best_m);
6035e023e7eSJernej Skrabec 	}
6045e023e7eSJernej Skrabec 
6055e023e7eSJernej Skrabec 	if (tcon == 0) {
6065e023e7eSJernej Skrabec 		u32 pll;
6075e023e7eSJernej Skrabec 
6085e023e7eSJernej Skrabec 		if (use_mipi_pll)
6095e023e7eSJernej Skrabec 			pll = CCM_LCD_CH0_CTRL_MIPI_PLL;
6105e023e7eSJernej Skrabec 		else if (best_double)
6115e023e7eSJernej Skrabec 			pll = CCM_LCD_CH0_CTRL_PLL3_2X;
6125e023e7eSJernej Skrabec 		else
6135e023e7eSJernej Skrabec 			pll = CCM_LCD_CH0_CTRL_PLL3;
6145e023e7eSJernej Skrabec 
6155e023e7eSJernej Skrabec 		writel(CCM_LCD_CH0_CTRL_GATE | CCM_LCD_CH0_CTRL_RST | pll,
6165e023e7eSJernej Skrabec 		       &ccm->lcd0_ch0_clk_cfg);
6175e023e7eSJernej Skrabec 	} else {
6185e023e7eSJernej Skrabec 		writel(CCM_LCD_CH1_CTRL_GATE |
6195e023e7eSJernej Skrabec 		       (best_double ? CCM_LCD_CH1_CTRL_PLL3_2X :
6205e023e7eSJernej Skrabec 				      CCM_LCD_CH1_CTRL_PLL3) |
6215e023e7eSJernej Skrabec 		       CCM_LCD_CH1_CTRL_M(best_m), &ccm->lcd0_ch1_clk_cfg);
6225e023e7eSJernej Skrabec 		if (sunxi_is_composite())
6235e023e7eSJernej Skrabec 			setbits_le32(&ccm->lcd0_ch1_clk_cfg,
6245e023e7eSJernej Skrabec 				     CCM_LCD_CH1_CTRL_HALF_SCLK1);
6255e023e7eSJernej Skrabec 	}
6265e023e7eSJernej Skrabec 
6275e023e7eSJernej Skrabec 	*clk_div = best_m;
6285e023e7eSJernej Skrabec 	*clk_double = best_double;
6295e023e7eSJernej Skrabec }
6305e023e7eSJernej Skrabec 
sunxi_lcdc_init(void)6315e023e7eSJernej Skrabec static void sunxi_lcdc_init(void)
6325e023e7eSJernej Skrabec {
6335e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
6345e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
6355e023e7eSJernej Skrabec 	struct sunxi_lcdc_reg * const lcdc =
6365e023e7eSJernej Skrabec 		(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
6375e023e7eSJernej Skrabec 
6385e023e7eSJernej Skrabec 	/* Reset off */
6395e023e7eSJernej Skrabec #ifdef CONFIG_SUNXI_GEN_SUN6I
6405e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0);
6415e023e7eSJernej Skrabec #else
6425e023e7eSJernej Skrabec 	setbits_le32(&ccm->lcd0_ch0_clk_cfg, CCM_LCD_CH0_CTRL_RST);
6435e023e7eSJernej Skrabec #endif
6445e023e7eSJernej Skrabec 
6455e023e7eSJernej Skrabec 	/* Clock on */
6465e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
6475e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_IF_LVDS
6485e023e7eSJernej Skrabec #ifdef CONFIG_SUNXI_GEN_SUN6I
6495e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_reset2_cfg, 1 << AHB_RESET_OFFSET_LVDS);
6505e023e7eSJernej Skrabec #else
6515e023e7eSJernej Skrabec 	setbits_le32(&ccm->lvds_clk_cfg, CCM_LVDS_CTRL_RST);
6525e023e7eSJernej Skrabec #endif
6535e023e7eSJernej Skrabec #endif
6545e023e7eSJernej Skrabec 
6555e023e7eSJernej Skrabec 	lcdc_init(lcdc);
6565e023e7eSJernej Skrabec }
6575e023e7eSJernej Skrabec 
sunxi_lcdc_panel_enable(void)6585e023e7eSJernej Skrabec static void sunxi_lcdc_panel_enable(void)
6595e023e7eSJernej Skrabec {
6605e023e7eSJernej Skrabec 	int pin, reset_pin;
6615e023e7eSJernej Skrabec 
6625e023e7eSJernej Skrabec 	/*
6635e023e7eSJernej Skrabec 	 * Start with backlight disabled to avoid the screen flashing to
6645e023e7eSJernej Skrabec 	 * white while the lcd inits.
6655e023e7eSJernej Skrabec 	 */
6665e023e7eSJernej Skrabec 	pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN);
6675e023e7eSJernej Skrabec 	if (pin >= 0) {
6685e023e7eSJernej Skrabec 		gpio_request(pin, "lcd_backlight_enable");
6695e023e7eSJernej Skrabec 		gpio_direction_output(pin, 0);
6705e023e7eSJernej Skrabec 	}
6715e023e7eSJernej Skrabec 
6725e023e7eSJernej Skrabec 	pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
6735e023e7eSJernej Skrabec 	if (pin >= 0) {
6745e023e7eSJernej Skrabec 		gpio_request(pin, "lcd_backlight_pwm");
6755e023e7eSJernej Skrabec 		gpio_direction_output(pin, PWM_OFF);
6765e023e7eSJernej Skrabec 	}
6775e023e7eSJernej Skrabec 
6785e023e7eSJernej Skrabec 	reset_pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_RESET);
6795e023e7eSJernej Skrabec 	if (reset_pin >= 0) {
6805e023e7eSJernej Skrabec 		gpio_request(reset_pin, "lcd_reset");
6815e023e7eSJernej Skrabec 		gpio_direction_output(reset_pin, 0); /* Assert reset */
6825e023e7eSJernej Skrabec 	}
6835e023e7eSJernej Skrabec 
6845e023e7eSJernej Skrabec 	/* Give the backlight some time to turn off and power up the panel. */
6855e023e7eSJernej Skrabec 	mdelay(40);
6865e023e7eSJernej Skrabec 	pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_POWER);
6875e023e7eSJernej Skrabec 	if (pin >= 0) {
6885e023e7eSJernej Skrabec 		gpio_request(pin, "lcd_power");
6895e023e7eSJernej Skrabec 		gpio_direction_output(pin, 1);
6905e023e7eSJernej Skrabec 	}
6915e023e7eSJernej Skrabec 
6925e023e7eSJernej Skrabec 	if (reset_pin >= 0)
6935e023e7eSJernej Skrabec 		gpio_direction_output(reset_pin, 1); /* De-assert reset */
6945e023e7eSJernej Skrabec }
6955e023e7eSJernej Skrabec 
sunxi_lcdc_backlight_enable(void)6965e023e7eSJernej Skrabec static void sunxi_lcdc_backlight_enable(void)
6975e023e7eSJernej Skrabec {
6985e023e7eSJernej Skrabec 	int pin;
6995e023e7eSJernej Skrabec 
7005e023e7eSJernej Skrabec 	/*
7015e023e7eSJernej Skrabec 	 * We want to have scanned out at least one frame before enabling the
7025e023e7eSJernej Skrabec 	 * backlight to avoid the screen flashing to white when we enable it.
7035e023e7eSJernej Skrabec 	 */
7045e023e7eSJernej Skrabec 	mdelay(40);
7055e023e7eSJernej Skrabec 
7065e023e7eSJernej Skrabec 	pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN);
7075e023e7eSJernej Skrabec 	if (pin >= 0)
7085e023e7eSJernej Skrabec 		gpio_direction_output(pin, 1);
7095e023e7eSJernej Skrabec 
7105e023e7eSJernej Skrabec 	pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
7115e023e7eSJernej Skrabec #ifdef SUNXI_PWM_PIN0
7125e023e7eSJernej Skrabec 	if (pin == SUNXI_PWM_PIN0) {
7135e023e7eSJernej Skrabec 		writel(SUNXI_PWM_CTRL_POLARITY0(PWM_ON) |
7145e023e7eSJernej Skrabec 		       SUNXI_PWM_CTRL_ENABLE0 |
7155e023e7eSJernej Skrabec 		       SUNXI_PWM_CTRL_PRESCALE0(0xf), SUNXI_PWM_CTRL_REG);
7165e023e7eSJernej Skrabec 		writel(SUNXI_PWM_PERIOD_80PCT, SUNXI_PWM_CH0_PERIOD);
7175e023e7eSJernej Skrabec 		sunxi_gpio_set_cfgpin(pin, SUNXI_PWM_MUX);
7185e023e7eSJernej Skrabec 		return;
7195e023e7eSJernej Skrabec 	}
7205e023e7eSJernej Skrabec #endif
7215e023e7eSJernej Skrabec 	if (pin >= 0)
7225e023e7eSJernej Skrabec 		gpio_direction_output(pin, PWM_ON);
7235e023e7eSJernej Skrabec }
7245e023e7eSJernej Skrabec 
sunxi_ctfb_mode_to_display_timing(const struct ctfb_res_modes * mode,struct display_timing * timing)72530ca2023SJernej Skrabec static void sunxi_ctfb_mode_to_display_timing(const struct ctfb_res_modes *mode,
72630ca2023SJernej Skrabec 					      struct display_timing *timing)
72730ca2023SJernej Skrabec {
72830ca2023SJernej Skrabec 	timing->pixelclock.typ = mode->pixclock_khz * 1000;
72930ca2023SJernej Skrabec 
73030ca2023SJernej Skrabec 	timing->hactive.typ = mode->xres;
73130ca2023SJernej Skrabec 	timing->hfront_porch.typ = mode->right_margin;
73230ca2023SJernej Skrabec 	timing->hback_porch.typ = mode->left_margin;
73330ca2023SJernej Skrabec 	timing->hsync_len.typ = mode->hsync_len;
73430ca2023SJernej Skrabec 
73530ca2023SJernej Skrabec 	timing->vactive.typ = mode->yres;
73630ca2023SJernej Skrabec 	timing->vfront_porch.typ = mode->lower_margin;
73730ca2023SJernej Skrabec 	timing->vback_porch.typ = mode->upper_margin;
73830ca2023SJernej Skrabec 	timing->vsync_len.typ = mode->vsync_len;
73930ca2023SJernej Skrabec 
74030ca2023SJernej Skrabec 	if (mode->sync & FB_SYNC_HOR_HIGH_ACT)
74130ca2023SJernej Skrabec 		timing->flags |= DISPLAY_FLAGS_HSYNC_HIGH;
74230ca2023SJernej Skrabec 	else
74330ca2023SJernej Skrabec 		timing->flags |= DISPLAY_FLAGS_HSYNC_LOW;
74430ca2023SJernej Skrabec 	if (mode->sync & FB_SYNC_VERT_HIGH_ACT)
74530ca2023SJernej Skrabec 		timing->flags |= DISPLAY_FLAGS_VSYNC_HIGH;
74630ca2023SJernej Skrabec 	else
74730ca2023SJernej Skrabec 		timing->flags |= DISPLAY_FLAGS_VSYNC_LOW;
74830ca2023SJernej Skrabec 	if (mode->vmode == FB_VMODE_INTERLACED)
74930ca2023SJernej Skrabec 		timing->flags |= DISPLAY_FLAGS_INTERLACED;
75030ca2023SJernej Skrabec }
75130ca2023SJernej Skrabec 
sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes * mode,bool for_ext_vga_dac)7525e023e7eSJernej Skrabec static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode,
7535e023e7eSJernej Skrabec 				      bool for_ext_vga_dac)
7545e023e7eSJernej Skrabec {
7555e023e7eSJernej Skrabec 	struct sunxi_lcdc_reg * const lcdc =
7565e023e7eSJernej Skrabec 		(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
7575e023e7eSJernej Skrabec 	int clk_div, clk_double, pin;
75830ca2023SJernej Skrabec 	struct display_timing timing;
7595e023e7eSJernej Skrabec 
7605e023e7eSJernej Skrabec #if defined CONFIG_MACH_SUN8I && defined CONFIG_VIDEO_LCD_IF_LVDS
7615e023e7eSJernej Skrabec 	for (pin = SUNXI_GPD(18); pin <= SUNXI_GPD(27); pin++) {
7625e023e7eSJernej Skrabec #else
7635e023e7eSJernej Skrabec 	for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(27); pin++) {
7645e023e7eSJernej Skrabec #endif
7655e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
7665e023e7eSJernej Skrabec 		sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0);
7675e023e7eSJernej Skrabec #endif
7685e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_IF_LVDS
7695e023e7eSJernej Skrabec 		sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LVDS0);
7705e023e7eSJernej Skrabec #endif
7715e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804
7725e023e7eSJernej Skrabec 		sunxi_gpio_set_drv(pin, 3);
7735e023e7eSJernej Skrabec #endif
7745e023e7eSJernej Skrabec 	}
7755e023e7eSJernej Skrabec 
7765e023e7eSJernej Skrabec 	sunxi_lcdc_pll_set(0, mode->pixclock_khz, &clk_div, &clk_double);
7775e023e7eSJernej Skrabec 
77830ca2023SJernej Skrabec 	sunxi_ctfb_mode_to_display_timing(mode, &timing);
77930ca2023SJernej Skrabec 	lcdc_tcon0_mode_set(lcdc, &timing, clk_div, for_ext_vga_dac,
7805e023e7eSJernej Skrabec 			    sunxi_display.depth, CONFIG_VIDEO_LCD_DCLK_PHASE);
7815e023e7eSJernej Skrabec }
7825e023e7eSJernej Skrabec 
7835e023e7eSJernej Skrabec #if defined CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_COMPOSITE
7845e023e7eSJernej Skrabec static void sunxi_lcdc_tcon1_mode_set(const struct ctfb_res_modes *mode,
7855e023e7eSJernej Skrabec 				      int *clk_div, int *clk_double,
7865e023e7eSJernej Skrabec 				      bool use_portd_hvsync)
7875e023e7eSJernej Skrabec {
7885e023e7eSJernej Skrabec 	struct sunxi_lcdc_reg * const lcdc =
7895e023e7eSJernej Skrabec 		(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
79030ca2023SJernej Skrabec 	struct display_timing timing;
7915e023e7eSJernej Skrabec 
79230ca2023SJernej Skrabec 	sunxi_ctfb_mode_to_display_timing(mode, &timing);
79330ca2023SJernej Skrabec 	lcdc_tcon1_mode_set(lcdc, &timing, use_portd_hvsync,
7945e023e7eSJernej Skrabec 			    sunxi_is_composite());
7955e023e7eSJernej Skrabec 
7965e023e7eSJernej Skrabec 	if (use_portd_hvsync) {
7975e023e7eSJernej Skrabec 		sunxi_gpio_set_cfgpin(SUNXI_GPD(26), SUNXI_GPD_LCD0);
7985e023e7eSJernej Skrabec 		sunxi_gpio_set_cfgpin(SUNXI_GPD(27), SUNXI_GPD_LCD0);
7995e023e7eSJernej Skrabec 	}
8005e023e7eSJernej Skrabec 
8015e023e7eSJernej Skrabec 	sunxi_lcdc_pll_set(1, mode->pixclock_khz, clk_div, clk_double);
8025e023e7eSJernej Skrabec }
8035e023e7eSJernej Skrabec #endif /* CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA || CONFIG_VIDEO_COMPOSITE */
8045e023e7eSJernej Skrabec 
8055e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_HDMI
8065e023e7eSJernej Skrabec 
8075e023e7eSJernej Skrabec static void sunxi_hdmi_setup_info_frames(const struct ctfb_res_modes *mode)
8085e023e7eSJernej Skrabec {
8095e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
8105e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
8115e023e7eSJernej Skrabec 	u8 checksum = 0;
8125e023e7eSJernej Skrabec 	u8 avi_info_frame[17] = {
8135e023e7eSJernej Skrabec 		0x82, 0x02, 0x0d, 0x00, 0x12, 0x00, 0x88, 0x00,
8145e023e7eSJernej Skrabec 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
8155e023e7eSJernej Skrabec 		0x00
8165e023e7eSJernej Skrabec 	};
8175e023e7eSJernej Skrabec 	u8 vendor_info_frame[19] = {
8185e023e7eSJernej Skrabec 		0x81, 0x01, 0x06, 0x29, 0x03, 0x0c, 0x00, 0x40,
8195e023e7eSJernej Skrabec 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
8205e023e7eSJernej Skrabec 		0x00, 0x00, 0x00
8215e023e7eSJernej Skrabec 	};
8225e023e7eSJernej Skrabec 	int i;
8235e023e7eSJernej Skrabec 
8245e023e7eSJernej Skrabec 	if (mode->pixclock_khz <= 27000)
8255e023e7eSJernej Skrabec 		avi_info_frame[5] = 0x40; /* SD-modes, ITU601 colorspace */
8265e023e7eSJernej Skrabec 	else
8275e023e7eSJernej Skrabec 		avi_info_frame[5] = 0x80; /* HD-modes, ITU709 colorspace */
8285e023e7eSJernej Skrabec 
8295e023e7eSJernej Skrabec 	if (mode->xres * 100 / mode->yres < 156)
8305e023e7eSJernej Skrabec 		avi_info_frame[5] |= 0x18; /* 4 : 3 */
8315e023e7eSJernej Skrabec 	else
8325e023e7eSJernej Skrabec 		avi_info_frame[5] |= 0x28; /* 16 : 9 */
8335e023e7eSJernej Skrabec 
8345e023e7eSJernej Skrabec 	for (i = 0; i < ARRAY_SIZE(avi_info_frame); i++)
8355e023e7eSJernej Skrabec 		checksum += avi_info_frame[i];
8365e023e7eSJernej Skrabec 
8375e023e7eSJernej Skrabec 	avi_info_frame[3] = 0x100 - checksum;
8385e023e7eSJernej Skrabec 
8395e023e7eSJernej Skrabec 	for (i = 0; i < ARRAY_SIZE(avi_info_frame); i++)
8405e023e7eSJernej Skrabec 		writeb(avi_info_frame[i], &hdmi->avi_info_frame[i]);
8415e023e7eSJernej Skrabec 
8425e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_QCP_PACKET0, &hdmi->qcp_packet0);
8435e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_QCP_PACKET1, &hdmi->qcp_packet1);
8445e023e7eSJernej Skrabec 
8455e023e7eSJernej Skrabec 	for (i = 0; i < ARRAY_SIZE(vendor_info_frame); i++)
8465e023e7eSJernej Skrabec 		writeb(vendor_info_frame[i], &hdmi->vendor_info_frame[i]);
8475e023e7eSJernej Skrabec 
8485e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PKT_CTRL0, &hdmi->pkt_ctrl0);
8495e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PKT_CTRL1, &hdmi->pkt_ctrl1);
8505e023e7eSJernej Skrabec 
8515e023e7eSJernej Skrabec 	setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_HDMI);
8525e023e7eSJernej Skrabec }
8535e023e7eSJernej Skrabec 
8545e023e7eSJernej Skrabec static void sunxi_hdmi_mode_set(const struct ctfb_res_modes *mode,
8555e023e7eSJernej Skrabec 				int clk_div, int clk_double)
8565e023e7eSJernej Skrabec {
8575e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
8585e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
8595e023e7eSJernej Skrabec 	int x, y;
8605e023e7eSJernej Skrabec 
8615e023e7eSJernej Skrabec 	/* Write clear interrupt status bits */
8625e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_IRQ_STATUS_BITS, &hdmi->irq);
8635e023e7eSJernej Skrabec 
8645e023e7eSJernej Skrabec 	if (sunxi_display.monitor == sunxi_monitor_hdmi)
8655e023e7eSJernej Skrabec 		sunxi_hdmi_setup_info_frames(mode);
8665e023e7eSJernej Skrabec 
8675e023e7eSJernej Skrabec 	/* Set input sync enable */
8685e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_UNKNOWN_INPUT_SYNC, &hdmi->unknown);
8695e023e7eSJernej Skrabec 
8705e023e7eSJernej Skrabec 	/* Init various registers, select pll3 as clock source */
8715e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_VIDEO_POL_TX_CLK, &hdmi->video_polarity);
8725e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PAD_CTRL0_RUN, &hdmi->pad_ctrl0);
8735e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PAD_CTRL1, &hdmi->pad_ctrl1);
8745e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PLL_CTRL, &hdmi->pll_ctrl);
8755e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_PLL_DBG0_PLL3, &hdmi->pll_dbg0);
8765e023e7eSJernej Skrabec 
8775e023e7eSJernej Skrabec 	/* Setup clk div and doubler */
8785e023e7eSJernej Skrabec 	clrsetbits_le32(&hdmi->pll_ctrl, SUNXI_HDMI_PLL_CTRL_DIV_MASK,
8795e023e7eSJernej Skrabec 			SUNXI_HDMI_PLL_CTRL_DIV(clk_div));
8805e023e7eSJernej Skrabec 	if (!clk_double)
8815e023e7eSJernej Skrabec 		setbits_le32(&hdmi->pad_ctrl1, SUNXI_HDMI_PAD_CTRL1_HALVE);
8825e023e7eSJernej Skrabec 
8835e023e7eSJernej Skrabec 	/* Setup timing registers */
8845e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_Y(mode->yres) | SUNXI_HDMI_X(mode->xres),
8855e023e7eSJernej Skrabec 	       &hdmi->video_size);
8865e023e7eSJernej Skrabec 
8875e023e7eSJernej Skrabec 	x = mode->hsync_len + mode->left_margin;
8885e023e7eSJernej Skrabec 	y = mode->vsync_len + mode->upper_margin;
8895e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_Y(y) | SUNXI_HDMI_X(x), &hdmi->video_bp);
8905e023e7eSJernej Skrabec 
8915e023e7eSJernej Skrabec 	x = mode->right_margin;
8925e023e7eSJernej Skrabec 	y = mode->lower_margin;
8935e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_Y(y) | SUNXI_HDMI_X(x), &hdmi->video_fp);
8945e023e7eSJernej Skrabec 
8955e023e7eSJernej Skrabec 	x = mode->hsync_len;
8965e023e7eSJernej Skrabec 	y = mode->vsync_len;
8975e023e7eSJernej Skrabec 	writel(SUNXI_HDMI_Y(y) | SUNXI_HDMI_X(x), &hdmi->video_spw);
8985e023e7eSJernej Skrabec 
8995e023e7eSJernej Skrabec 	if (mode->sync & FB_SYNC_HOR_HIGH_ACT)
9005e023e7eSJernej Skrabec 		setbits_le32(&hdmi->video_polarity, SUNXI_HDMI_VIDEO_POL_HOR);
9015e023e7eSJernej Skrabec 
9025e023e7eSJernej Skrabec 	if (mode->sync & FB_SYNC_VERT_HIGH_ACT)
9035e023e7eSJernej Skrabec 		setbits_le32(&hdmi->video_polarity, SUNXI_HDMI_VIDEO_POL_VER);
9045e023e7eSJernej Skrabec }
9055e023e7eSJernej Skrabec 
9065e023e7eSJernej Skrabec static void sunxi_hdmi_enable(void)
9075e023e7eSJernej Skrabec {
9085e023e7eSJernej Skrabec 	struct sunxi_hdmi_reg * const hdmi =
9095e023e7eSJernej Skrabec 		(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
9105e023e7eSJernej Skrabec 
9115e023e7eSJernej Skrabec 	udelay(100);
9125e023e7eSJernej Skrabec 	setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_ENABLE);
9135e023e7eSJernej Skrabec }
9145e023e7eSJernej Skrabec 
9155e023e7eSJernej Skrabec #endif /* CONFIG_VIDEO_HDMI */
9165e023e7eSJernej Skrabec 
9175e023e7eSJernej Skrabec #if defined CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_COMPOSITE
9185e023e7eSJernej Skrabec 
9195e023e7eSJernej Skrabec static void sunxi_tvencoder_mode_set(void)
9205e023e7eSJernej Skrabec {
9215e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
9225e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
9235e023e7eSJernej Skrabec 	struct sunxi_tve_reg * const tve =
9245e023e7eSJernej Skrabec 		(struct sunxi_tve_reg *)SUNXI_TVE0_BASE;
9255e023e7eSJernej Skrabec 
9265e023e7eSJernej Skrabec 	/* Reset off */
9275e023e7eSJernej Skrabec 	setbits_le32(&ccm->lcd0_ch0_clk_cfg, CCM_LCD_CH0_CTRL_TVE_RST);
9285e023e7eSJernej Skrabec 	/* Clock on */
9295e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_TVE0);
9305e023e7eSJernej Skrabec 
9315e023e7eSJernej Skrabec 	switch (sunxi_display.monitor) {
9325e023e7eSJernej Skrabec 	case sunxi_monitor_vga:
933*c1080626SJernej Skrabec 		tvencoder_mode_set(tve, tve_mode_vga);
9345e023e7eSJernej Skrabec 		break;
9355e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_nc:
936*c1080626SJernej Skrabec 		tvencoder_mode_set(tve, tve_mode_composite_pal_nc);
937*c1080626SJernej Skrabec 		break;
9385e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal:
939*c1080626SJernej Skrabec 		tvencoder_mode_set(tve, tve_mode_composite_pal);
9405e023e7eSJernej Skrabec 		break;
9415e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_m:
942*c1080626SJernej Skrabec 		tvencoder_mode_set(tve, tve_mode_composite_pal_m);
943*c1080626SJernej Skrabec 		break;
9445e023e7eSJernej Skrabec 	case sunxi_monitor_composite_ntsc:
945*c1080626SJernej Skrabec 		tvencoder_mode_set(tve, tve_mode_composite_ntsc);
9465e023e7eSJernej Skrabec 		break;
9475e023e7eSJernej Skrabec 	case sunxi_monitor_none:
9485e023e7eSJernej Skrabec 	case sunxi_monitor_dvi:
9495e023e7eSJernej Skrabec 	case sunxi_monitor_hdmi:
9505e023e7eSJernej Skrabec 	case sunxi_monitor_lcd:
9515e023e7eSJernej Skrabec 		break;
9525e023e7eSJernej Skrabec 	}
9535e023e7eSJernej Skrabec }
9545e023e7eSJernej Skrabec 
9555e023e7eSJernej Skrabec #endif /* CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_COMPOSITE */
9565e023e7eSJernej Skrabec 
9575e023e7eSJernej Skrabec static void sunxi_drc_init(void)
9585e023e7eSJernej Skrabec {
9595e023e7eSJernej Skrabec #ifdef CONFIG_SUNXI_GEN_SUN6I
9605e023e7eSJernej Skrabec 	struct sunxi_ccm_reg * const ccm =
9615e023e7eSJernej Skrabec 		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
9625e023e7eSJernej Skrabec 
9635e023e7eSJernej Skrabec 	/* On sun6i the drc must be clocked even when in pass-through mode */
9645e023e7eSJernej Skrabec #ifdef CONFIG_MACH_SUN8I_A33
9655e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_SAT);
9665e023e7eSJernej Skrabec #endif
9675e023e7eSJernej Skrabec 	setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DRC0);
9685e023e7eSJernej Skrabec 	clock_set_de_mod_clock(&ccm->iep_drc0_clk_cfg, 300000000);
9695e023e7eSJernej Skrabec #endif
9705e023e7eSJernej Skrabec }
9715e023e7eSJernej Skrabec 
9725e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_VGA_VIA_LCD
9735e023e7eSJernej Skrabec static void sunxi_vga_external_dac_enable(void)
9745e023e7eSJernej Skrabec {
9755e023e7eSJernej Skrabec 	int pin;
9765e023e7eSJernej Skrabec 
9775e023e7eSJernej Skrabec 	pin = sunxi_name_to_gpio(CONFIG_VIDEO_VGA_EXTERNAL_DAC_EN);
9785e023e7eSJernej Skrabec 	if (pin >= 0) {
9795e023e7eSJernej Skrabec 		gpio_request(pin, "vga_enable");
9805e023e7eSJernej Skrabec 		gpio_direction_output(pin, 1);
9815e023e7eSJernej Skrabec 	}
9825e023e7eSJernej Skrabec }
9835e023e7eSJernej Skrabec #endif /* CONFIG_VIDEO_VGA_VIA_LCD */
9845e023e7eSJernej Skrabec 
9855e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_SSD2828
9865e023e7eSJernej Skrabec static int sunxi_ssd2828_init(const struct ctfb_res_modes *mode)
9875e023e7eSJernej Skrabec {
9885e023e7eSJernej Skrabec 	struct ssd2828_config cfg = {
9895e023e7eSJernej Skrabec 		.csx_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_CS),
9905e023e7eSJernej Skrabec 		.sck_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_SCLK),
9915e023e7eSJernej Skrabec 		.sdi_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MOSI),
9925e023e7eSJernej Skrabec 		.sdo_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MISO),
9935e023e7eSJernej Skrabec 		.reset_pin = name_to_gpio(CONFIG_VIDEO_LCD_SSD2828_RESET),
9945e023e7eSJernej Skrabec 		.ssd2828_tx_clk_khz  = CONFIG_VIDEO_LCD_SSD2828_TX_CLK * 1000,
9955e023e7eSJernej Skrabec 		.ssd2828_color_depth = 24,
9965e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_PANEL_MIPI_4_LANE_513_MBPS_VIA_SSD2828
9975e023e7eSJernej Skrabec 		.mipi_dsi_number_of_data_lanes           = 4,
9985e023e7eSJernej Skrabec 		.mipi_dsi_bitrate_per_data_lane_mbps     = 513,
9995e023e7eSJernej Skrabec 		.mipi_dsi_delay_after_exit_sleep_mode_ms = 100,
10005e023e7eSJernej Skrabec 		.mipi_dsi_delay_after_set_display_on_ms  = 200
10015e023e7eSJernej Skrabec #else
10025e023e7eSJernej Skrabec #error MIPI LCD panel needs configuration parameters
10035e023e7eSJernej Skrabec #endif
10045e023e7eSJernej Skrabec 	};
10055e023e7eSJernej Skrabec 
10065e023e7eSJernej Skrabec 	if (cfg.csx_pin == -1 || cfg.sck_pin == -1 || cfg.sdi_pin == -1) {
10075e023e7eSJernej Skrabec 		printf("SSD2828: SPI pins are not properly configured\n");
10085e023e7eSJernej Skrabec 		return 1;
10095e023e7eSJernej Skrabec 	}
10105e023e7eSJernej Skrabec 	if (cfg.reset_pin == -1) {
10115e023e7eSJernej Skrabec 		printf("SSD2828: Reset pin is not properly configured\n");
10125e023e7eSJernej Skrabec 		return 1;
10135e023e7eSJernej Skrabec 	}
10145e023e7eSJernej Skrabec 
10155e023e7eSJernej Skrabec 	return ssd2828_init(&cfg, mode);
10165e023e7eSJernej Skrabec }
10175e023e7eSJernej Skrabec #endif /* CONFIG_VIDEO_LCD_SSD2828 */
10185e023e7eSJernej Skrabec 
10195e023e7eSJernej Skrabec static void sunxi_engines_init(void)
10205e023e7eSJernej Skrabec {
10215e023e7eSJernej Skrabec 	sunxi_composer_init();
10225e023e7eSJernej Skrabec 	sunxi_lcdc_init();
10235e023e7eSJernej Skrabec 	sunxi_drc_init();
10245e023e7eSJernej Skrabec }
10255e023e7eSJernej Skrabec 
10265e023e7eSJernej Skrabec static void sunxi_mode_set(const struct ctfb_res_modes *mode,
10275e023e7eSJernej Skrabec 			   unsigned int address)
10285e023e7eSJernej Skrabec {
10295e023e7eSJernej Skrabec 	int __maybe_unused clk_div, clk_double;
10305e023e7eSJernej Skrabec 	struct sunxi_lcdc_reg * const lcdc =
10315e023e7eSJernej Skrabec 		(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
1032*c1080626SJernej Skrabec 	struct sunxi_tve_reg * __maybe_unused const tve =
1033*c1080626SJernej Skrabec 		(struct sunxi_tve_reg *)SUNXI_TVE0_BASE;
10345e023e7eSJernej Skrabec 
10355e023e7eSJernej Skrabec 	switch (sunxi_display.monitor) {
10365e023e7eSJernej Skrabec 	case sunxi_monitor_none:
10375e023e7eSJernej Skrabec 		break;
10385e023e7eSJernej Skrabec 	case sunxi_monitor_dvi:
10395e023e7eSJernej Skrabec 	case sunxi_monitor_hdmi:
10405e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_HDMI
10415e023e7eSJernej Skrabec 		sunxi_composer_mode_set(mode, address);
10425e023e7eSJernej Skrabec 		sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 0);
10435e023e7eSJernej Skrabec 		sunxi_hdmi_mode_set(mode, clk_div, clk_double);
10445e023e7eSJernej Skrabec 		sunxi_composer_enable();
10455e023e7eSJernej Skrabec 		lcdc_enable(lcdc, sunxi_display.depth);
10465e023e7eSJernej Skrabec 		sunxi_hdmi_enable();
10475e023e7eSJernej Skrabec #endif
10485e023e7eSJernej Skrabec 		break;
10495e023e7eSJernej Skrabec 	case sunxi_monitor_lcd:
10505e023e7eSJernej Skrabec 		sunxi_lcdc_panel_enable();
10515e023e7eSJernej Skrabec 		if (IS_ENABLED(CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804)) {
10525e023e7eSJernej Skrabec 			/*
10535e023e7eSJernej Skrabec 			 * The anx9804 needs 1.8V from eldo3, we do this here
10545e023e7eSJernej Skrabec 			 * and not via CONFIG_AXP_ELDO3_VOLT from board_init()
10555e023e7eSJernej Skrabec 			 * to avoid turning this on when using hdmi output.
10565e023e7eSJernej Skrabec 			 */
10575e023e7eSJernej Skrabec 			axp_set_eldo(3, 1800);
10585e023e7eSJernej Skrabec 			anx9804_init(CONFIG_VIDEO_LCD_I2C_BUS, 4,
10595e023e7eSJernej Skrabec 				     ANX9804_DATA_RATE_1620M,
10605e023e7eSJernej Skrabec 				     sunxi_display.depth);
10615e023e7eSJernej Skrabec 		}
10625e023e7eSJernej Skrabec 		if (IS_ENABLED(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM)) {
10635e023e7eSJernej Skrabec 			mdelay(50); /* Wait for lcd controller power on */
10645e023e7eSJernej Skrabec 			hitachi_tx18d42vm_init();
10655e023e7eSJernej Skrabec 		}
10665e023e7eSJernej Skrabec 		if (IS_ENABLED(CONFIG_VIDEO_LCD_TL059WV5C0)) {
10675e023e7eSJernej Skrabec 			unsigned int orig_i2c_bus = i2c_get_bus_num();
10685e023e7eSJernej Skrabec 			i2c_set_bus_num(CONFIG_VIDEO_LCD_I2C_BUS);
10695e023e7eSJernej Skrabec 			i2c_reg_write(0x5c, 0x04, 0x42); /* Turn on the LCD */
10705e023e7eSJernej Skrabec 			i2c_set_bus_num(orig_i2c_bus);
10715e023e7eSJernej Skrabec 		}
10725e023e7eSJernej Skrabec 		sunxi_composer_mode_set(mode, address);
10735e023e7eSJernej Skrabec 		sunxi_lcdc_tcon0_mode_set(mode, false);
10745e023e7eSJernej Skrabec 		sunxi_composer_enable();
10755e023e7eSJernej Skrabec 		lcdc_enable(lcdc, sunxi_display.depth);
10765e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_LCD_SSD2828
10775e023e7eSJernej Skrabec 		sunxi_ssd2828_init(mode);
10785e023e7eSJernej Skrabec #endif
10795e023e7eSJernej Skrabec 		sunxi_lcdc_backlight_enable();
10805e023e7eSJernej Skrabec 		break;
10815e023e7eSJernej Skrabec 	case sunxi_monitor_vga:
10825e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_VGA
10835e023e7eSJernej Skrabec 		sunxi_composer_mode_set(mode, address);
10845e023e7eSJernej Skrabec 		sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 1);
10855e023e7eSJernej Skrabec 		sunxi_tvencoder_mode_set();
10865e023e7eSJernej Skrabec 		sunxi_composer_enable();
10875e023e7eSJernej Skrabec 		lcdc_enable(lcdc, sunxi_display.depth);
1088*c1080626SJernej Skrabec 		tvencoder_enable(tve);
10895e023e7eSJernej Skrabec #elif defined CONFIG_VIDEO_VGA_VIA_LCD
10905e023e7eSJernej Skrabec 		sunxi_composer_mode_set(mode, address);
10915e023e7eSJernej Skrabec 		sunxi_lcdc_tcon0_mode_set(mode, true);
10925e023e7eSJernej Skrabec 		sunxi_composer_enable();
10935e023e7eSJernej Skrabec 		lcdc_enable(lcdc, sunxi_display.depth);
10945e023e7eSJernej Skrabec 		sunxi_vga_external_dac_enable();
10955e023e7eSJernej Skrabec #endif
10965e023e7eSJernej Skrabec 		break;
10975e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal:
10985e023e7eSJernej Skrabec 	case sunxi_monitor_composite_ntsc:
10995e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_m:
11005e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_nc:
11015e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_COMPOSITE
11025e023e7eSJernej Skrabec 		sunxi_composer_mode_set(mode, address);
11035e023e7eSJernej Skrabec 		sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 0);
11045e023e7eSJernej Skrabec 		sunxi_tvencoder_mode_set();
11055e023e7eSJernej Skrabec 		sunxi_composer_enable();
11065e023e7eSJernej Skrabec 		lcdc_enable(lcdc, sunxi_display.depth);
1107*c1080626SJernej Skrabec 		tvencoder_enable(tve);
11085e023e7eSJernej Skrabec #endif
11095e023e7eSJernej Skrabec 		break;
11105e023e7eSJernej Skrabec 	}
11115e023e7eSJernej Skrabec }
11125e023e7eSJernej Skrabec 
11135e023e7eSJernej Skrabec static const char *sunxi_get_mon_desc(enum sunxi_monitor monitor)
11145e023e7eSJernej Skrabec {
11155e023e7eSJernej Skrabec 	switch (monitor) {
11165e023e7eSJernej Skrabec 	case sunxi_monitor_none:		return "none";
11175e023e7eSJernej Skrabec 	case sunxi_monitor_dvi:			return "dvi";
11185e023e7eSJernej Skrabec 	case sunxi_monitor_hdmi:		return "hdmi";
11195e023e7eSJernej Skrabec 	case sunxi_monitor_lcd:			return "lcd";
11205e023e7eSJernej Skrabec 	case sunxi_monitor_vga:			return "vga";
11215e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal:	return "composite-pal";
11225e023e7eSJernej Skrabec 	case sunxi_monitor_composite_ntsc:	return "composite-ntsc";
11235e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_m:	return "composite-pal-m";
11245e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_nc:	return "composite-pal-nc";
11255e023e7eSJernej Skrabec 	}
11265e023e7eSJernej Skrabec 	return NULL; /* never reached */
11275e023e7eSJernej Skrabec }
11285e023e7eSJernej Skrabec 
11295e023e7eSJernej Skrabec ulong board_get_usable_ram_top(ulong total_size)
11305e023e7eSJernej Skrabec {
11315e023e7eSJernej Skrabec 	return gd->ram_top - CONFIG_SUNXI_MAX_FB_SIZE;
11325e023e7eSJernej Skrabec }
11335e023e7eSJernej Skrabec 
11345e023e7eSJernej Skrabec static bool sunxi_has_hdmi(void)
11355e023e7eSJernej Skrabec {
11365e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_HDMI
11375e023e7eSJernej Skrabec 	return true;
11385e023e7eSJernej Skrabec #else
11395e023e7eSJernej Skrabec 	return false;
11405e023e7eSJernej Skrabec #endif
11415e023e7eSJernej Skrabec }
11425e023e7eSJernej Skrabec 
11435e023e7eSJernej Skrabec static bool sunxi_has_lcd(void)
11445e023e7eSJernej Skrabec {
11455e023e7eSJernej Skrabec 	char *lcd_mode = CONFIG_VIDEO_LCD_MODE;
11465e023e7eSJernej Skrabec 
11475e023e7eSJernej Skrabec 	return lcd_mode[0] != 0;
11485e023e7eSJernej Skrabec }
11495e023e7eSJernej Skrabec 
11505e023e7eSJernej Skrabec static bool sunxi_has_vga(void)
11515e023e7eSJernej Skrabec {
11525e023e7eSJernej Skrabec #if defined CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_VGA_VIA_LCD
11535e023e7eSJernej Skrabec 	return true;
11545e023e7eSJernej Skrabec #else
11555e023e7eSJernej Skrabec 	return false;
11565e023e7eSJernej Skrabec #endif
11575e023e7eSJernej Skrabec }
11585e023e7eSJernej Skrabec 
11595e023e7eSJernej Skrabec static bool sunxi_has_composite(void)
11605e023e7eSJernej Skrabec {
11615e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_COMPOSITE
11625e023e7eSJernej Skrabec 	return true;
11635e023e7eSJernej Skrabec #else
11645e023e7eSJernej Skrabec 	return false;
11655e023e7eSJernej Skrabec #endif
11665e023e7eSJernej Skrabec }
11675e023e7eSJernej Skrabec 
11685e023e7eSJernej Skrabec static enum sunxi_monitor sunxi_get_default_mon(bool allow_hdmi)
11695e023e7eSJernej Skrabec {
11705e023e7eSJernej Skrabec 	if (allow_hdmi && sunxi_has_hdmi())
11715e023e7eSJernej Skrabec 		return sunxi_monitor_dvi;
11725e023e7eSJernej Skrabec 	else if (sunxi_has_lcd())
11735e023e7eSJernej Skrabec 		return sunxi_monitor_lcd;
11745e023e7eSJernej Skrabec 	else if (sunxi_has_vga())
11755e023e7eSJernej Skrabec 		return sunxi_monitor_vga;
11765e023e7eSJernej Skrabec 	else if (sunxi_has_composite())
11775e023e7eSJernej Skrabec 		return sunxi_monitor_composite_pal;
11785e023e7eSJernej Skrabec 	else
11795e023e7eSJernej Skrabec 		return sunxi_monitor_none;
11805e023e7eSJernej Skrabec }
11815e023e7eSJernej Skrabec 
11825e023e7eSJernej Skrabec void *video_hw_init(void)
11835e023e7eSJernej Skrabec {
11845e023e7eSJernej Skrabec 	static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
11855e023e7eSJernej Skrabec 	const struct ctfb_res_modes *mode;
11865e023e7eSJernej Skrabec 	struct ctfb_res_modes custom;
11875e023e7eSJernej Skrabec 	const char *options;
11885e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_HDMI
11895e023e7eSJernej Skrabec 	int ret, hpd, hpd_delay, edid;
11905e023e7eSJernej Skrabec #endif
11915e023e7eSJernej Skrabec 	int i, overscan_offset, overscan_x, overscan_y;
11925e023e7eSJernej Skrabec 	unsigned int fb_dma_addr;
11935e023e7eSJernej Skrabec 	char mon[16];
11945e023e7eSJernej Skrabec 	char *lcd_mode = CONFIG_VIDEO_LCD_MODE;
11955e023e7eSJernej Skrabec 
11965e023e7eSJernej Skrabec 	memset(&sunxi_display, 0, sizeof(struct sunxi_display));
11975e023e7eSJernej Skrabec 
11985e023e7eSJernej Skrabec 	video_get_ctfb_res_modes(RES_MODE_1024x768, 24, &mode,
11995e023e7eSJernej Skrabec 				 &sunxi_display.depth, &options);
12005e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_HDMI
12015e023e7eSJernej Skrabec 	hpd = video_get_option_int(options, "hpd", 1);
12025e023e7eSJernej Skrabec 	hpd_delay = video_get_option_int(options, "hpd_delay", 500);
12035e023e7eSJernej Skrabec 	edid = video_get_option_int(options, "edid", 1);
12045e023e7eSJernej Skrabec #endif
12055e023e7eSJernej Skrabec 	overscan_x = video_get_option_int(options, "overscan_x", -1);
12065e023e7eSJernej Skrabec 	overscan_y = video_get_option_int(options, "overscan_y", -1);
12075e023e7eSJernej Skrabec 	sunxi_display.monitor = sunxi_get_default_mon(true);
12085e023e7eSJernej Skrabec 	video_get_option_string(options, "monitor", mon, sizeof(mon),
12095e023e7eSJernej Skrabec 				sunxi_get_mon_desc(sunxi_display.monitor));
12105e023e7eSJernej Skrabec 	for (i = 0; i <= SUNXI_MONITOR_LAST; i++) {
12115e023e7eSJernej Skrabec 		if (strcmp(mon, sunxi_get_mon_desc(i)) == 0) {
12125e023e7eSJernej Skrabec 			sunxi_display.monitor = i;
12135e023e7eSJernej Skrabec 			break;
12145e023e7eSJernej Skrabec 		}
12155e023e7eSJernej Skrabec 	}
12165e023e7eSJernej Skrabec 	if (i > SUNXI_MONITOR_LAST)
12175e023e7eSJernej Skrabec 		printf("Unknown monitor: '%s', falling back to '%s'\n",
12185e023e7eSJernej Skrabec 		       mon, sunxi_get_mon_desc(sunxi_display.monitor));
12195e023e7eSJernej Skrabec 
12205e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_HDMI
12215e023e7eSJernej Skrabec 	/* If HDMI/DVI is selected do HPD & EDID, and handle fallback */
12225e023e7eSJernej Skrabec 	if (sunxi_display.monitor == sunxi_monitor_dvi ||
12235e023e7eSJernej Skrabec 	    sunxi_display.monitor == sunxi_monitor_hdmi) {
12245e023e7eSJernej Skrabec 		/* Always call hdp_detect, as it also enables clocks, etc. */
12255e023e7eSJernej Skrabec 		ret = sunxi_hdmi_hpd_detect(hpd_delay);
12265e023e7eSJernej Skrabec 		if (ret) {
12275e023e7eSJernej Skrabec 			printf("HDMI connected: ");
12285e023e7eSJernej Skrabec 			if (edid && sunxi_hdmi_edid_get_mode(&custom) == 0)
12295e023e7eSJernej Skrabec 				mode = &custom;
12305e023e7eSJernej Skrabec 		} else if (hpd) {
12315e023e7eSJernej Skrabec 			sunxi_hdmi_shutdown();
12325e023e7eSJernej Skrabec 			sunxi_display.monitor = sunxi_get_default_mon(false);
12335e023e7eSJernej Skrabec 		} /* else continue with hdmi/dvi without a cable connected */
12345e023e7eSJernej Skrabec 	}
12355e023e7eSJernej Skrabec #endif
12365e023e7eSJernej Skrabec 
12375e023e7eSJernej Skrabec 	switch (sunxi_display.monitor) {
12385e023e7eSJernej Skrabec 	case sunxi_monitor_none:
12395e023e7eSJernej Skrabec 		return NULL;
12405e023e7eSJernej Skrabec 	case sunxi_monitor_dvi:
12415e023e7eSJernej Skrabec 	case sunxi_monitor_hdmi:
12425e023e7eSJernej Skrabec 		if (!sunxi_has_hdmi()) {
12435e023e7eSJernej Skrabec 			printf("HDMI/DVI not supported on this board\n");
12445e023e7eSJernej Skrabec 			sunxi_display.monitor = sunxi_monitor_none;
12455e023e7eSJernej Skrabec 			return NULL;
12465e023e7eSJernej Skrabec 		}
12475e023e7eSJernej Skrabec 		break;
12485e023e7eSJernej Skrabec 	case sunxi_monitor_lcd:
12495e023e7eSJernej Skrabec 		if (!sunxi_has_lcd()) {
12505e023e7eSJernej Skrabec 			printf("LCD not supported on this board\n");
12515e023e7eSJernej Skrabec 			sunxi_display.monitor = sunxi_monitor_none;
12525e023e7eSJernej Skrabec 			return NULL;
12535e023e7eSJernej Skrabec 		}
12545e023e7eSJernej Skrabec 		sunxi_display.depth = video_get_params(&custom, lcd_mode);
12555e023e7eSJernej Skrabec 		mode = &custom;
12565e023e7eSJernej Skrabec 		break;
12575e023e7eSJernej Skrabec 	case sunxi_monitor_vga:
12585e023e7eSJernej Skrabec 		if (!sunxi_has_vga()) {
12595e023e7eSJernej Skrabec 			printf("VGA not supported on this board\n");
12605e023e7eSJernej Skrabec 			sunxi_display.monitor = sunxi_monitor_none;
12615e023e7eSJernej Skrabec 			return NULL;
12625e023e7eSJernej Skrabec 		}
12635e023e7eSJernej Skrabec 		sunxi_display.depth = 18;
12645e023e7eSJernej Skrabec 		break;
12655e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal:
12665e023e7eSJernej Skrabec 	case sunxi_monitor_composite_ntsc:
12675e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_m:
12685e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_nc:
12695e023e7eSJernej Skrabec 		if (!sunxi_has_composite()) {
12705e023e7eSJernej Skrabec 			printf("Composite video not supported on this board\n");
12715e023e7eSJernej Skrabec 			sunxi_display.monitor = sunxi_monitor_none;
12725e023e7eSJernej Skrabec 			return NULL;
12735e023e7eSJernej Skrabec 		}
12745e023e7eSJernej Skrabec 		if (sunxi_display.monitor == sunxi_monitor_composite_pal ||
12755e023e7eSJernej Skrabec 		    sunxi_display.monitor == sunxi_monitor_composite_pal_nc)
12765e023e7eSJernej Skrabec 			mode = &composite_video_modes[0];
12775e023e7eSJernej Skrabec 		else
12785e023e7eSJernej Skrabec 			mode = &composite_video_modes[1];
12795e023e7eSJernej Skrabec 		sunxi_display.depth = 24;
12805e023e7eSJernej Skrabec 		break;
12815e023e7eSJernej Skrabec 	}
12825e023e7eSJernej Skrabec 
12835e023e7eSJernej Skrabec 	/* Yes these defaults are quite high, overscan on composite sucks... */
12845e023e7eSJernej Skrabec 	if (overscan_x == -1)
12855e023e7eSJernej Skrabec 		overscan_x = sunxi_is_composite() ? 32 : 0;
12865e023e7eSJernej Skrabec 	if (overscan_y == -1)
12875e023e7eSJernej Skrabec 		overscan_y = sunxi_is_composite() ? 20 : 0;
12885e023e7eSJernej Skrabec 
12895e023e7eSJernej Skrabec 	sunxi_display.fb_size =
12905e023e7eSJernej Skrabec 		(mode->xres * mode->yres * 4 + 0xfff) & ~0xfff;
12915e023e7eSJernej Skrabec 	overscan_offset = (overscan_y * mode->xres + overscan_x) * 4;
12925e023e7eSJernej Skrabec 	/* We want to keep the fb_base for simplefb page aligned, where as
12935e023e7eSJernej Skrabec 	 * the sunxi dma engines will happily accept an unaligned address. */
12945e023e7eSJernej Skrabec 	if (overscan_offset)
12955e023e7eSJernej Skrabec 		sunxi_display.fb_size += 0x1000;
12965e023e7eSJernej Skrabec 
12975e023e7eSJernej Skrabec 	if (sunxi_display.fb_size > CONFIG_SUNXI_MAX_FB_SIZE) {
12985e023e7eSJernej Skrabec 		printf("Error need %dkB for fb, but only %dkB is reserved\n",
12995e023e7eSJernej Skrabec 		       sunxi_display.fb_size >> 10,
13005e023e7eSJernej Skrabec 		       CONFIG_SUNXI_MAX_FB_SIZE >> 10);
13015e023e7eSJernej Skrabec 		return NULL;
13025e023e7eSJernej Skrabec 	}
13035e023e7eSJernej Skrabec 
13045e023e7eSJernej Skrabec 	printf("Setting up a %dx%d%s %s console (overscan %dx%d)\n",
13055e023e7eSJernej Skrabec 	       mode->xres, mode->yres,
13065e023e7eSJernej Skrabec 	       (mode->vmode == FB_VMODE_INTERLACED) ? "i" : "",
13075e023e7eSJernej Skrabec 	       sunxi_get_mon_desc(sunxi_display.monitor),
13085e023e7eSJernej Skrabec 	       overscan_x, overscan_y);
13095e023e7eSJernej Skrabec 
13105e023e7eSJernej Skrabec 	gd->fb_base = gd->bd->bi_dram[0].start +
13115e023e7eSJernej Skrabec 		      gd->bd->bi_dram[0].size - sunxi_display.fb_size;
13125e023e7eSJernej Skrabec 	sunxi_engines_init();
13135e023e7eSJernej Skrabec 
13145e023e7eSJernej Skrabec 	fb_dma_addr = gd->fb_base - CONFIG_SYS_SDRAM_BASE;
13155e023e7eSJernej Skrabec 	sunxi_display.fb_addr = gd->fb_base;
13165e023e7eSJernej Skrabec 	if (overscan_offset) {
13175e023e7eSJernej Skrabec 		fb_dma_addr += 0x1000 - (overscan_offset & 0xfff);
13185e023e7eSJernej Skrabec 		sunxi_display.fb_addr += (overscan_offset + 0xfff) & ~0xfff;
13195e023e7eSJernej Skrabec 		memset((void *)gd->fb_base, 0, sunxi_display.fb_size);
13205e023e7eSJernej Skrabec 		flush_cache(gd->fb_base, sunxi_display.fb_size);
13215e023e7eSJernej Skrabec 	}
13225e023e7eSJernej Skrabec 	sunxi_mode_set(mode, fb_dma_addr);
13235e023e7eSJernej Skrabec 
13245e023e7eSJernej Skrabec 	/*
13255e023e7eSJernej Skrabec 	 * These are the only members of this structure that are used. All the
13265e023e7eSJernej Skrabec 	 * others are driver specific. The pitch is stored in plnSizeX.
13275e023e7eSJernej Skrabec 	 */
13285e023e7eSJernej Skrabec 	graphic_device->frameAdrs = sunxi_display.fb_addr;
13295e023e7eSJernej Skrabec 	graphic_device->gdfIndex = GDF_32BIT_X888RGB;
13305e023e7eSJernej Skrabec 	graphic_device->gdfBytesPP = 4;
13315e023e7eSJernej Skrabec 	graphic_device->winSizeX = mode->xres - 2 * overscan_x;
13325e023e7eSJernej Skrabec 	graphic_device->winSizeY = mode->yres - 2 * overscan_y;
13335e023e7eSJernej Skrabec 	graphic_device->plnSizeX = mode->xres * graphic_device->gdfBytesPP;
13345e023e7eSJernej Skrabec 
13355e023e7eSJernej Skrabec 	return graphic_device;
13365e023e7eSJernej Skrabec }
13375e023e7eSJernej Skrabec 
13385e023e7eSJernej Skrabec /*
13395e023e7eSJernej Skrabec  * Simplefb support.
13405e023e7eSJernej Skrabec  */
13415e023e7eSJernej Skrabec #if defined(CONFIG_OF_BOARD_SETUP) && defined(CONFIG_VIDEO_DT_SIMPLEFB)
13425e023e7eSJernej Skrabec int sunxi_simplefb_setup(void *blob)
13435e023e7eSJernej Skrabec {
13445e023e7eSJernej Skrabec 	static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
13455e023e7eSJernej Skrabec 	int offset, ret;
13465e023e7eSJernej Skrabec 	u64 start, size;
13475e023e7eSJernej Skrabec 	const char *pipeline = NULL;
13485e023e7eSJernej Skrabec 
13495e023e7eSJernej Skrabec #ifdef CONFIG_MACH_SUN4I
13505e023e7eSJernej Skrabec #define PIPELINE_PREFIX "de_fe0-"
13515e023e7eSJernej Skrabec #else
13525e023e7eSJernej Skrabec #define PIPELINE_PREFIX
13535e023e7eSJernej Skrabec #endif
13545e023e7eSJernej Skrabec 
13555e023e7eSJernej Skrabec 	switch (sunxi_display.monitor) {
13565e023e7eSJernej Skrabec 	case sunxi_monitor_none:
13575e023e7eSJernej Skrabec 		return 0;
13585e023e7eSJernej Skrabec 	case sunxi_monitor_dvi:
13595e023e7eSJernej Skrabec 	case sunxi_monitor_hdmi:
13605e023e7eSJernej Skrabec 		pipeline = PIPELINE_PREFIX "de_be0-lcd0-hdmi";
13615e023e7eSJernej Skrabec 		break;
13625e023e7eSJernej Skrabec 	case sunxi_monitor_lcd:
13635e023e7eSJernej Skrabec 		pipeline = PIPELINE_PREFIX "de_be0-lcd0";
13645e023e7eSJernej Skrabec 		break;
13655e023e7eSJernej Skrabec 	case sunxi_monitor_vga:
13665e023e7eSJernej Skrabec #ifdef CONFIG_VIDEO_VGA
13675e023e7eSJernej Skrabec 		pipeline = PIPELINE_PREFIX "de_be0-lcd0-tve0";
13685e023e7eSJernej Skrabec #elif defined CONFIG_VIDEO_VGA_VIA_LCD
13695e023e7eSJernej Skrabec 		pipeline = PIPELINE_PREFIX "de_be0-lcd0";
13705e023e7eSJernej Skrabec #endif
13715e023e7eSJernej Skrabec 		break;
13725e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal:
13735e023e7eSJernej Skrabec 	case sunxi_monitor_composite_ntsc:
13745e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_m:
13755e023e7eSJernej Skrabec 	case sunxi_monitor_composite_pal_nc:
13765e023e7eSJernej Skrabec 		pipeline = PIPELINE_PREFIX "de_be0-lcd0-tve0";
13775e023e7eSJernej Skrabec 		break;
13785e023e7eSJernej Skrabec 	}
13795e023e7eSJernej Skrabec 
13805e023e7eSJernej Skrabec 	/* Find a prefilled simpefb node, matching out pipeline config */
13815e023e7eSJernej Skrabec 	offset = fdt_node_offset_by_compatible(blob, -1,
13825e023e7eSJernej Skrabec 					       "allwinner,simple-framebuffer");
13835e023e7eSJernej Skrabec 	while (offset >= 0) {
13845e023e7eSJernej Skrabec 		ret = fdt_stringlist_search(blob, offset, "allwinner,pipeline",
13855e023e7eSJernej Skrabec 					    pipeline);
13865e023e7eSJernej Skrabec 		if (ret == 0)
13875e023e7eSJernej Skrabec 			break;
13885e023e7eSJernej Skrabec 		offset = fdt_node_offset_by_compatible(blob, offset,
13895e023e7eSJernej Skrabec 					       "allwinner,simple-framebuffer");
13905e023e7eSJernej Skrabec 	}
13915e023e7eSJernej Skrabec 	if (offset < 0) {
13925e023e7eSJernej Skrabec 		eprintf("Cannot setup simplefb: node not found\n");
13935e023e7eSJernej Skrabec 		return 0; /* Keep older kernels working */
13945e023e7eSJernej Skrabec 	}
13955e023e7eSJernej Skrabec 
13965e023e7eSJernej Skrabec 	/*
13975e023e7eSJernej Skrabec 	 * Do not report the framebuffer as free RAM to the OS, note we cannot
13985e023e7eSJernej Skrabec 	 * use fdt_add_mem_rsv() here, because then it is still seen as RAM,
13995e023e7eSJernej Skrabec 	 * and e.g. Linux refuses to iomap RAM on ARM, see:
14005e023e7eSJernej Skrabec 	 * linux/arch/arm/mm/ioremap.c around line 301.
14015e023e7eSJernej Skrabec 	 */
14025e023e7eSJernej Skrabec 	start = gd->bd->bi_dram[0].start;
14035e023e7eSJernej Skrabec 	size = gd->bd->bi_dram[0].size - sunxi_display.fb_size;
14045e023e7eSJernej Skrabec 	ret = fdt_fixup_memory_banks(blob, &start, &size, 1);
14055e023e7eSJernej Skrabec 	if (ret) {
14065e023e7eSJernej Skrabec 		eprintf("Cannot setup simplefb: Error reserving memory\n");
14075e023e7eSJernej Skrabec 		return ret;
14085e023e7eSJernej Skrabec 	}
14095e023e7eSJernej Skrabec 
14105e023e7eSJernej Skrabec 	ret = fdt_setup_simplefb_node(blob, offset, sunxi_display.fb_addr,
14115e023e7eSJernej Skrabec 			graphic_device->winSizeX, graphic_device->winSizeY,
14125e023e7eSJernej Skrabec 			graphic_device->plnSizeX, "x8r8g8b8");
14135e023e7eSJernej Skrabec 	if (ret)
14145e023e7eSJernej Skrabec 		eprintf("Cannot setup simplefb: Error setting properties\n");
14155e023e7eSJernej Skrabec 
14165e023e7eSJernej Skrabec 	return ret;
14175e023e7eSJernej Skrabec }
14185e023e7eSJernej Skrabec #endif /* CONFIG_OF_BOARD_SETUP && CONFIG_VIDEO_DT_SIMPLEFB */
1419