[U-Boot] [PATCH 3/6] sunxi: video: Add cfb console driver for sunxi

Hans de Goede
2014-11-14T16:54:45Z

From: Luc Verhaegen <libv@skynet.be>

This adds a fixed mode hdmi driver for the sunxi platform. The fixed
mode is a relatively safe 1024x768, more complete EDID handling is
currently not provided. Only HDMI is supported today.

This code is enabled when HPD detects an attached monitor.

Current config is such that 8MB is shaved off at the top of the RAM.
This avoids several memory handling issues, most significant is the fact
that on linux on ARM you are not allowed to remap known RAM as IO. A
clued in display driver will be able to recycle this reserved RAM in
future though.

cfbconsole was chosen as it provides the most important functionality: a
working u-boot console, allowing for the debugging of certain issues
without the need for a UART.

Signed-off-by: Luc Verhaegen <libv@skynet.be>
[hdegoede at redhat.com: Major cleanups and some small bugfixes]
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
arch/arm/include/asm/arch-sunxi/display.h | 198 +++++++++++++++
board/sunxi/Kconfig | 7 +
configs/Ippo_q8h_v5_defconfig | 1 +
drivers/video/Makefile | 1 +
drivers/video/sunxi_display.c | 387 ++++++++++++++++++++++++++++++
include/configs/sunxi-common.h | 34 +++
6 files changed, 628 insertions(+)
create mode 100644 arch/arm/include/asm/arch-sunxi/display.h
create mode 100644 drivers/video/sunxi_display.c

diff --git a/arch/arm/include/asm/arch-sunxi/display.h b/arch/arm/include/asm/arch-sunxi/display.h
new file mode 100644
index 0000000..8ac7d1b
--- /dev/null
+++ b/arch/arm/include/asm/arch-sunxi/display.h
@@ -0,0 +1,198 @@
+/*
+ * Sunxi platform display cntroller register and constant defines
+ *
+ * (C) Copyright 2014 Hans de Goede <hdegoede@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef _SUNXI_DISPLAY_H
+#define _SUNXI_DISPLAY_H
+
+struct sunxi_de_be_reg {
+ u8 res0[0x800]; /* 0x000 */
+ u32 mode; /* 0x800 */
+ u32 backcolor; /* 0x804 */
+ u32 disp_size; /* 0x808 */
+ u8 res1[0x4]; /* 0x80c */
+ u32 layer0_size; /* 0x810 */
+ u32 layer1_size; /* 0x814 */
+ u32 layer2_size; /* 0x818 */
+ u32 layer3_size; /* 0x81c */
+ u32 layer0_pos; /* 0x820 */
+ u32 layer1_pos; /* 0x824 */
+ u32 layer2_pos; /* 0x828 */
+ u32 layer3_pos; /* 0x82c */
+ u8 res2[0x10]; /* 0x830 */
+ u32 layer0_stride; /* 0x840 */
+ u32 layer1_stride; /* 0x844 */
+ u32 layer2_stride; /* 0x848 */
+ u32 layer3_stride; /* 0x84c */
+ u32 layer0_addr_low32b; /* 0x850 */
+ u32 layer1_addr_low32b; /* 0x854 */
+ u32 layer2_addr_low32b; /* 0x858 */
+ u32 layer3_addr_low32b; /* 0x85c */
+ u32 layer0_addr_high4b; /* 0x860 */
+ u32 layer1_addr_high4b; /* 0x864 */
+ u32 layer2_addr_high4b; /* 0x868 */
+ u32 layer3_addr_high4b; /* 0x86c */
+ u32 reg_ctrl; /* 0x870 */
+ u8 res3[0xc]; /* 0x874 */
+ u32 color_key_max; /* 0x880 */
+ u32 color_key_min; /* 0x884 */
+ u32 color_key_config; /* 0x888 */
+ u8 res4[0x4]; /* 0x88c */
+ u32 layer0_attr0_ctrl; /* 0x890 */
+ u32 layer1_attr0_ctrl; /* 0x894 */
+ u32 layer2_attr0_ctrl; /* 0x898 */
+ u32 layer3_attr0_ctrl; /* 0x89c */
+ u32 layer0_attr1_ctrl; /* 0x8a0 */
+ u32 layer1_attr1_ctrl; /* 0x8a4 */
+ u32 layer2_attr1_ctrl; /* 0x8a8 */
+ u32 layer3_attr1_ctrl; /* 0x8ac */
+};
+
+struct sunxi_lcdc_reg {
+ u32 ctrl; /* 0x00 */
+ u32 int0; /* 0x04 */
+ u32 int1; /* 0x08 */
+ u8 res0[0x04]; /* 0x0c */
+ u32 frame_ctrl; /* 0x10 */
+ u8 res1[0x2c]; /* 0x14 */
+ u32 tcon0_ctrl; /* 0x40 */
+ u32 tcon0_dclk; /* 0x44 */
+ u32 tcon0_basic_timing0; /* 0x48 */
+ u32 tcon0_basic_timing1; /* 0x4c */
+ u32 tcon0_basic_timing2; /* 0x50 */
+ u32 tcon0_basic_timing3; /* 0x54 */
+ u32 tcon0_hv_intf; /* 0x58 */
+ u8 res2[0x04]; /* 0x5c */
+ u32 tcon0_cpu_intf; /* 0x60 */
+ u32 tcon0_cpu_wr_dat; /* 0x64 */
+ u32 tcon0_cpu_rd_dat0; /* 0x68 */
+ u32 tcon0_cpu_rd_dat1; /* 0x6c */
+ u32 tcon0_ttl_timing0; /* 0x70 */
+ u32 tcon0_ttl_timing1; /* 0x74 */
+ u32 tcon0_ttl_timing2; /* 0x78 */
+ u32 tcon0_ttl_timing3; /* 0x7c */
+ u32 tcon0_ttl_timing4; /* 0x80 */
+ u32 tcon0_lvds_intf; /* 0x84 */
+ u32 tcon0_io_polarity; /* 0x88 */
+ u32 tcon0_io_tristate; /* 0x8c */
+ u32 tcon1_ctrl; /* 0x90 */
+ u32 tcon1_timing_source; /* 0x94 */
+ u32 tcon1_timing_scale; /* 0x98 */
+ u32 tcon1_timing_out; /* 0x9c */
+ u32 tcon1_timing_h; /* 0xa0 */
+ u32 tcon1_timing_v; /* 0xa4 */
+ u32 tcon1_timing_sync; /* 0xa8 */
+ u8 res3[0x44]; /* 0xac */
+ u32 tcon1_io_polarity; /* 0xf0 */
+ u32 tcon1_io_tristate; /* 0xf4 */
+};
+
+#define SUNXI_HDMI_CTRL 0x004
+#define SUNXI_HDMI_INT_CTRL 0x008
+#define SUNXI_HDMI_HPD 0x00c
+#define SUNXI_HDMI_VIDEO_CTRL 0x010
+#define SUNXI_HDMI_VIDEO_SIZE 0x014
+#define SUNXI_HDMI_VIDEO_BP 0x018
+#define SUNXI_HDMI_VIDEO_FP 0x01c
+#define SUNXI_HDMI_VIDEO_SPW 0x020
+#define SUNXI_HDMI_VIDEO_POLARITY 0x024
+#define SUNXI_HDMI_TX_DRIVER0 0x200
+#define SUNXI_HDMI_TX_DRIVER1 0x204
+#define SUNXI_HDMI_TX_DRIVER2 0x208
+#define SUNXI_HDMI_TX_DRIVER3 0x20C
+struct sunxi_hdmi_reg {
+ u32 version_id; /* 0x000 */
+ u32 ctrl; /* 0x004 */
+ u32 irq; /* 0x008 */
+ u32 hpd; /* 0x00c */
+ u32 video_ctrl; /* 0x010 */
+ u32 video_size; /* 0x014 */
+ u32 video_bp; /* 0x018 */
+ u32 video_fp; /* 0x01c */
+ u32 video_spw; /* 0x020 */
+ u32 video_polarity; /* 0x024 */
+ u8 res0[0x1d8]; /* 0x028 */
+ u32 pad_ctrl0; /* 0x200 */
+ u32 pad_ctrl1; /* 0x204 */
+ u32 pll_ctrl; /* 0x208 */
+ u32 pll_dbg0; /* 0x20c */
+};
+
+/*
+ * DE-BE register constants.
+ */
+#define SUNXI_DE_BE_WIDTH(x) (((x) - 1) << 0)
+#define SUNXI_DE_BE_HEIGHT(y) (((y) - 1) << 16)
+#define SUNXI_DE_BE_MODE_ENABLE (1 << 0)
+#define SUNXI_DE_BE_MODE_START (1 << 1)
+#define SUNXI_DE_BE_MODE_LAYER0_ENABLE (1 << 8)
+#define SUNXI_DE_BE_LAYER_STRIDE(x) ((x) << 5)
+#define SUNXI_DE_BE_REG_CTRL_LOAD_REGS (1 << 0)
+#define SUNXI_DE_BE_LAYER_ATTR1_FMT_XRGB8888 (0x09 << 8)
+
+/*
+ * LCDC register constants.
+ */
+#define SUNXI_LCDC_X(x) (((x) - 1) << 16)
+#define SUNXI_LCDC_Y(y) (((y) - 1) << 0)
+#define SUNXI_LCDC_CTRL_IO_MAP_MASK (1 << 0)
+#define SUNXI_LCDC_CTRL_IO_MAP_TCON0 (0 << 0)
+#define SUNXI_LCDC_CTRL_IO_MAP_TCON1 (1 << 0)
+#define SUNXI_LCDC_CTRL_TCON_ENABLE (1 << 31)
+#define SUNXI_LCDC_TCON0_DCLK_ENABLE (0xf << 28)
+#define SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(n) (((n) & 0x1f) << 4)
+#define SUNXI_LCDC_TCON1_CTRL_ENABLE (1 << 31)
+#define SUNXI_LCDC_TCON1_TIMING_H_BP(n) (((n) - 1) << 0)
+#define SUNXI_LCDC_TCON1_TIMING_H_TOTAL(n) (((n) - 1) << 16)
+#define SUNXI_LCDC_TCON1_TIMING_V_BP(n) (((n) - 1) << 0)
+#define SUNXI_LCDC_TCON1_TIMING_V_TOTAL(n) (((n) * 2) << 16)
+
+/*
+ * HDMI register constants.
+ */
+#define SUNXI_HDMI_X(x) (((x) - 1) << 0)
+#define SUNXI_HDMI_Y(y) (((y) - 1) << 16)
+#define SUNXI_HDMI_CTRL_ENABLE (1 << 31)
+#define SUNXI_HDMI_IRQ_STATUS_FIFO_UF (1 << 0)
+#define SUNXI_HDMI_IRQ_STATUS_FIFO_OF (1 << 1)
+#define SUNXI_HDMI_IRQ_STATUS_BITS 0x73
+#define SUNXI_HDMI_HPD_DETECT (1 << 0)
+#define SUNXI_HDMI_VIDEO_CTRL_ENABLE (1 << 31)
+#define SUNXI_HDMI_VIDEO_POL_HOR (1 << 0)
+#define SUNXI_HDMI_VIDEO_POL_VER (1 << 1)
+#define SUNXI_HDMI_VIDEO_POL_TX_CLK (0x3e0 << 16)
+
+#ifdef CONFIG_MACH_SUN6I
+#define SUNXI_HDMI_PAD_CTRL0_HDP 0x7e80000f
+#define SUNXI_HDMI_PAD_CTRL0_RUN 0x7e8000ff
+#else
+#define SUNXI_HDMI_PAD_CTRL0_HDP 0xfe800000
+#define SUNXI_HDMI_PAD_CTRL0_RUN 0xfe800000
+#endif
+
+#ifdef CONFIG_MACH_SUN4I
+#define SUNXI_HDMI_PAD_CTRL1 0x00d8c820
+#elif defined CONFIG_MACH_SUN6I
+#define SUNXI_HDMI_PAD_CTRL1 0x01ded030
+#else
+#define SUNXI_HDMI_PAD_CTRL1 0x00d8c830
+#endif
+#define SUNXI_HDMI_PAD_CTRL1_HALVE (1 << 6)
+
+#ifdef CONFIG_MACH_SUN6I
+#define SUNXI_HDMI_PLL_CTRL 0xba48a308
+#define SUNXI_HDMI_PLL_CTRL_DIV(n) (((n) - 1) << 4)
+#else
+#define SUNXI_HDMI_PLL_CTRL 0xfa4ef708
+#define SUNXI_HDMI_PLL_CTRL_DIV(n) ((n) << 4)
+#endif
+#define SUNXI_HDMI_PLL_CTRL_DIV_MASK (0xf << 4)
+
+#define SUNXI_HDMI_PLL_DBG0_PLL3 (0 << 21)
+#define SUNXI_HDMI_PLL_DBG0_PLL7 (1 << 21)
+
+#endif /* _SUNXI_DISPLAY_H */
diff --git a/board/sunxi/Kconfig b/board/sunxi/Kconfig
index 5ea400e..422033a 100644
--- a/board/sunxi/Kconfig
+++ b/board/sunxi/Kconfig
@@ -216,4 +216,11 @@ config USB2_VBUS_PIN
---help---
See USB1_VBUS_PIN help text.

+config VIDEO
+ boolean "Enable graphical uboot console on HDMI"
+ default y
+ ---help---
+ Say Y here to add support for using a cfb console on the HDMI output
+ found on most sunxi devices.
+
endif
diff --git a/configs/Ippo_q8h_v5_defconfig b/configs/Ippo_q8h_v5_defconfig
index fc67bd9..53df213 100644
--- a/configs/Ippo_q8h_v5_defconfig
+++ b/configs/Ippo_q8h_v5_defconfig
@@ -4,3 +4,4 @@ CONFIG_ARCH_SUNXI=y
CONFIG_MACH_SUN8I=y
CONFIG_TARGET_IPPO_Q8H_V5=y
CONFIG_DEFAULT_DEVICE_TREE="sun8i-a23-ippo-q8h-v5.dtb"
+CONFIG_VIDEO=n
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 14a6781..7301be3 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o
obj-$(CONFIG_VIDEO_SED13806) += sed13806.o
obj-$(CONFIG_VIDEO_SM501) += sm501.o
obj-$(CONFIG_VIDEO_SMI_LYNXEM) += smiLynxEM.o videomodes.o
+obj-$(CONFIG_VIDEO_SUNXI) += sunxi_display.o
obj-$(CONFIG_VIDEO_TEGRA) += tegra.o
obj-$(CONFIG_VIDEO_VCXK) += bus_vcxk.o
obj-$(CONFIG_FORMIKE) += formike.o
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c
new file mode 100644
index 0000000..74a92b5
--- /dev/null
+++ b/drivers/video/sunxi_display.c
@@ -0,0 +1,387 @@
+/*
+ * Display driver for Allwinner SoCs.
+ *
+ * (C) Copyright 2013-2014 Luc Verhaegen <libv@skynet.be>
+ * (C) Copyright 2014 Hans de Goede <hdegoede@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+
+#include <asm/arch/clock.h>
+#include <asm/arch/display.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <linux/fb.h>
+#include <video_fb.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_display {
+ GraphicDevice graphic_device;
+ bool enabled;
+} sunxi_display;
+
+static int
+sunxi_hdmi_hpd_detect(void)
+{
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+
+ /* Set pll3 to 300MHz */
+ clock_set_pll3(300000000);
+
+ /* Set hdmi parent to pll3 */
+ clrsetbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_PLL_MASK,
+ CCM_HDMI_CTRL_PLL3);
+
+ /* Set ahb gating to pass */
+ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI);
+
+ /* Clock on */
+ setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE);
+
+ writel(SUNXI_HDMI_CTRL_ENABLE, &hdmi->ctrl);
+ writel(SUNXI_HDMI_PAD_CTRL0_HDP, &hdmi->pad_ctrl0);
+
+ udelay(1000);
+
+ if (readl(&hdmi->hpd) & SUNXI_HDMI_HPD_DETECT)
+ return 1;
+
+ /* No need to keep these running */
+ clrbits_le32(&hdmi->ctrl, SUNXI_HDMI_CTRL_ENABLE);
+ clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE);
+ clrbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI);
+ clock_set_pll3(0);
+
+ return 0;
+}
+
+/*
+ * This is the entity that mixes and matches the different layers and inputs.
+ * Allwinner calls it the back-end, but i like composer better.
+ */
+static void
+sunxi_composer_init(void)
+{
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_de_be_reg * const de_be =
+ (struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
+ int i;
+
+ /* Clocks on */
+ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_BE0);
+ setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_BE0);
+ clock_set_de_mod_clock(&ccm->be0_clk_cfg, 300000000);
+
+ /* Engine bug, clear registers after reset */
+ for (i = 0x0800; i < 0x1000; i += 4)
+ writel(0, SUNXI_DE_BE0_BASE + i);
+
+ setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_ENABLE);
+}
+
+static void
+sunxi_composer_mode_set(struct fb_videomode *mode, unsigned int address)
+{
+ struct sunxi_de_be_reg * const de_be =
+ (struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
+
+ writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
+ &de_be->disp_size);
+ writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
+ &de_be->layer0_size);
+ writel(SUNXI_DE_BE_LAYER_STRIDE(mode->xres), &de_be->layer0_stride);
+ writel(address << 3, &de_be->layer0_addr_low32b);
+ writel(address >> 29, &de_be->layer0_addr_high4b);
+ writel(SUNXI_DE_BE_LAYER_ATTR1_FMT_XRGB8888, &de_be->layer0_attr1_ctrl);
+
+ setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_LAYER0_ENABLE);
+}
+
+/*
+ * LCDC, what allwinner calls a CRTC, so timing controller and serializer.
+ */
+static void
+sunxi_lcdc_pll_set(int dotclock, int *clk_div, int *clk_double)
+{
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ int value, n, m, diff;
+ int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF;
+ int best_double = 0;
+
+ /*
+ * Find the lowest divider resulting in a matching clock, if there
+ * is no match, pick the closest lower clock, as monitors tend to
+ * not sync to higher frequencies.
+ */
+ for (m = 15; m > 0; m--) {
+ n = (m * dotclock) / 3000;
+
+ if ((n >= 9) && (n <= 127)) {
+ value = (3000 * n) / m;
+ diff = dotclock - value;
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_m = m;
+ best_n = n;
+ best_double = 0;
+ }
+ }
+
+ /* These are just duplicates */
+ if (!(m & 1))
+ continue;
+
+ n = (m * dotclock) / 6000;
+ if ((n >= 9) && (n <= 127)) {
+ value = (6000 * n) / m;
+ diff = dotclock - value;
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_m = m;
+ best_n = n;
+ best_double = 1;
+ }
+ }
+ }
+
+#if 1
+ printf("dotclock: %dkHz = %dkHz: (%d * 3MHz * %d) / %d\n",
+ dotclock, (best_double + 1) * 3000 * best_n / best_m,
+ best_double + 1, best_n, best_m);
+#endif
+
+ clock_set_pll3(best_n * 3000000);
+
+ writel(CCM_LCD_CH1_CTRL_GATE |
+ (best_double ? CCM_LCD_CH1_CTRL_PLL3_2X : CCM_LCD_CH1_CTRL_PLL3) |
+ CCM_LCD_CH1_CTRL_M(best_m), &ccm->lcd0_ch1_clk_cfg);
+
+ *clk_div = best_m;
+ *clk_double = best_double;
+}
+
+static void
+sunxi_lcdc_init(void)
+{
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_lcdc_reg * const lcdc =
+ (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
+
+ /* Reset off */
+ setbits_le32(&ccm->lcd0_ch0_clk_cfg, CCM_LCD_CH0_CTRL_RST);
+
+ /* Clock on */
+ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
+
+ /* Init lcdc */
+ writel(0, &lcdc->ctrl); /* Disable tcon */
+ writel(0, &lcdc->int0); /* Disable all interrupts */
+
+ /* Disable tcon0 dot clock */
+ clrbits_le32(&lcdc->tcon0_dclk, SUNXI_LCDC_TCON0_DCLK_ENABLE);
+
+ /* Set all io lines to tristate */
+ writel(0xffffffff, &lcdc->tcon0_io_tristate);
+ writel(0xffffffff, &lcdc->tcon1_io_tristate);
+}
+
+static void
+sunxi_lcdc_mode_set(struct fb_videomode *mode, int *clk_div, int *clk_double)
+{
+ struct sunxi_lcdc_reg * const lcdc =
+ (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
+ int bp, total;
+
+ /* Use tcon1 */
+ clrsetbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_IO_MAP_MASK,
+ SUNXI_LCDC_CTRL_IO_MAP_TCON1);
+
+ /* Enabled, 0x1e start delay */
+ writel(SUNXI_LCDC_TCON1_CTRL_ENABLE |
+ SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(0x1e), &lcdc->tcon1_ctrl);
+
+ writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(mode->yres),
+ &lcdc->tcon1_timing_source);
+ writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(mode->yres),
+ &lcdc->tcon1_timing_scale);
+ writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(mode->yres),
+ &lcdc->tcon1_timing_out);
+
+ bp = mode->hsync_len + mode->left_margin;
+ total = mode->xres + mode->right_margin + bp;
+ writel(SUNXI_LCDC_TCON1_TIMING_H_TOTAL(total) |
+ SUNXI_LCDC_TCON1_TIMING_H_BP(bp), &lcdc->tcon1_timing_h);
+
+ bp = mode->vsync_len + mode->upper_margin;
+ total = mode->yres + mode->lower_margin + bp;
+ writel(SUNXI_LCDC_TCON1_TIMING_V_TOTAL(total) |
+ SUNXI_LCDC_TCON1_TIMING_V_BP(bp), &lcdc->tcon1_timing_v);
+
+ writel(SUNXI_LCDC_X(mode->hsync_len) | SUNXI_LCDC_Y(mode->vsync_len),
+ &lcdc->tcon1_timing_sync);
+
+ sunxi_lcdc_pll_set(mode->pixclock, clk_div, clk_double);
+}
+
+static void
+sunxi_hdmi_mode_set(struct fb_videomode *mode, int clk_div, int clk_double)
+{
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+ int x, y;
+
+ /* Write clear interrupt status bits */
+ writel(SUNXI_HDMI_IRQ_STATUS_BITS, &hdmi->irq);
+
+ /* Init various registers, select pll3 as clock source */
+ writel(SUNXI_HDMI_VIDEO_POL_TX_CLK, &hdmi->video_polarity);
+ writel(SUNXI_HDMI_PAD_CTRL0_RUN, &hdmi->pad_ctrl0);
+ writel(SUNXI_HDMI_PAD_CTRL1, &hdmi->pad_ctrl1);
+ writel(SUNXI_HDMI_PLL_CTRL, &hdmi->pll_ctrl);
+ writel(SUNXI_HDMI_PLL_DBG0_PLL3, &hdmi->pll_dbg0);
+
+ /* Setup clk div and doubler */
+ clrsetbits_le32(&hdmi->pll_ctrl, SUNXI_HDMI_PLL_CTRL_DIV_MASK,
+ SUNXI_HDMI_PLL_CTRL_DIV(clk_div));
+ if (!clk_double)
+ setbits_le32(&hdmi->pad_ctrl1, SUNXI_HDMI_PAD_CTRL1_HALVE);
+
+ /* Setup timing registers */
+ writel(SUNXI_HDMI_Y(mode->yres) | SUNXI_HDMI_X(mode->xres),
+ &hdmi->video_size);
+
+ x = mode->hsync_len + mode->left_margin;
+ y = mode->vsync_len + mode->upper_margin;
+ writel(SUNXI_HDMI_Y(y) | SUNXI_HDMI_X(x), &hdmi->video_bp);
+
+ x = mode->right_margin;
+ y = mode->lower_margin;
+ writel(SUNXI_HDMI_Y(y) | SUNXI_HDMI_X(x), &hdmi->video_fp);
+
+ x = mode->hsync_len;
+ y = mode->vsync_len;
+ writel(SUNXI_HDMI_Y(y) | SUNXI_HDMI_X(x), &hdmi->video_spw);
+
+ if (mode->sync & FB_SYNC_HOR_HIGH_ACT)
+ setbits_le32(&hdmi->video_polarity, SUNXI_HDMI_VIDEO_POL_HOR);
+
+ if (mode->sync & FB_SYNC_VERT_HIGH_ACT)
+ setbits_le32(&hdmi->video_polarity, SUNXI_HDMI_VIDEO_POL_VER);
+}
+
+static void
+sunxi_engines_init(void)
+{
+ sunxi_composer_init();
+ sunxi_lcdc_init();
+}
+
+static void
+sunxi_mode_set(struct fb_videomode *mode, unsigned int address)
+{
+ struct sunxi_de_be_reg * const de_be =
+ (struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
+ struct sunxi_lcdc_reg * const lcdc =
+ (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+ int clk_div, clk_double;
+ int retries = 3;
+
+retry:
+ clrbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_ENABLE);
+ clrbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE);
+ clrbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
+
+ sunxi_composer_mode_set(mode, address);
+ sunxi_lcdc_mode_set(mode, &clk_div, &clk_double);
+ sunxi_hdmi_mode_set(mode, clk_div, clk_double);
+
+ setbits_le32(&de_be->reg_ctrl, SUNXI_DE_BE_REG_CTRL_LOAD_REGS);
+ setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
+
+ udelay(1000000 / mode->refresh + 500);
+
+ setbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE);
+
+ udelay(1000000 / mode->refresh + 500);
+
+ setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_ENABLE);
+
+ udelay(1000000 / mode->refresh + 500);
+
+ /* Sometimes the display pipeline does not sync up properly, if
+ this happens the hdmi fifo underrun or overrun bits are set */
+ if (readl(&hdmi->irq) &
+ (SUNXI_HDMI_IRQ_STATUS_FIFO_UF | SUNXI_HDMI_IRQ_STATUS_FIFO_OF)) {
+ if (retries--)
+ goto retry;
+ eprintf("HDMI fifo under or overrun\n");
+ }
+}
+
+void *
+video_hw_init(void)
+{
+ static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
+ /*
+ * Vesa standard 1024x768 at 60
+ * 65.0 1024 1032 1176 1344 768 771 777 806 -hsync -vsync
+ */
+ struct fb_videomode mode = {
+ .name = "1024x768",
+ .refresh = 60,
+ .xres = 1024,
+ .yres = 768,
+ .pixclock = 65000,
+ .left_margin = 160,
+ .right_margin = 24,
+ .upper_margin = 29,
+ .lower_margin = 3,
+ .hsync_len = 136,
+ .vsync_len = 6,
+ .sync = 0,
+ .vmode = 0,
+ .flag = 0,
+ };
+ int ret;
+
+ memset(&sunxi_display, 0, sizeof(struct sunxi_display));
+
+ printf("Reserved %dkB of RAM for Framebuffer.\n",
+ CONFIG_SUNXI_FB_SIZE >> 10);
+ gd->fb_base = gd->ram_top;
+
+ ret = sunxi_hdmi_hpd_detect();
+ if (!ret)
+ return NULL;
+
+ printf("HDMI connected.\n");
+ sunxi_display.enabled = true;
+
+ printf("Setting up a %s console.\n", mode.name);
+ sunxi_engines_init();
+ sunxi_mode_set(&mode, gd->fb_base - CONFIG_SYS_SDRAM_BASE);
+
+ /*
+ * These are the only members of this structure that are used. All the
+ * others are driver specific. There is nothing to decribe pitch or
+ * stride, but we are lucky with our hw.
+ */
+ graphic_device->frameAdrs = gd->fb_base;
+ graphic_device->gdfIndex = GDF_32BIT_X888RGB;
+ graphic_device->gdfBytesPP = 4;
+ graphic_device->winSizeX = mode.xres;
+ graphic_device->winSizeY = mode.yres;
+
+ return graphic_device;
+}
diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h
index ce038ed..532fdb7 100644
--- a/include/configs/sunxi-common.h
+++ b/include/configs/sunxi-common.h
@@ -197,6 +197,30 @@
#define CONFIG_SPL_GPIO_SUPPORT
#define CONFIG_CMD_GPIO

+#ifdef CONFIG_VIDEO
+/*
+ * The amount of RAM that is reserved for the FB. This will not show up as
+ * RAM to the kernel, but will be reclaimed by a KMS driver in future.
+ */
+#define CONFIG_SUNXI_FB_SIZE (8 << 20)
+
+#define CONFIG_VIDEO_SUNXI
+
+#define CONFIG_CFB_CONSOLE
+#define CONFIG_VIDEO_SW_CURSOR
+#define CONFIG_VIDEO_LOGO
+
+/* allow both serial and cfb console. */
+#define CONFIG_CONSOLE_MUX
+/* this is needed so the above will actually do something */
+#define CONFIG_SYS_CONSOLE_IS_IN_ENV
+/* stop x86 thinking in cfbconsole from trying to init a pc keyboard */
+#define CONFIG_VGA_AS_SINGLE_DEVICE
+
+#define CONFIG_SYS_MEM_TOP_HIDE ((CONFIG_SUNXI_FB_SIZE + 0xFFF) & ~0xFFF)
+
+#endif /* CONFIG_VIDEO */
+
/* Ethernet support */
#ifdef CONFIG_SUNXI_EMAC
#define CONFIG_MII /* MII PHY management */
@@ -266,7 +290,17 @@

#include <config_distro_bootcmd.h>

+#ifdef CONFIG_VIDEO
+#define CONSOLE_ENV_SETTINGS \
+ "stdin=serial\0" \
+ "stdout=serial,vga\0" \
+ "stderr=serial,vga\0"
+#else
+#define CONSOLE_ENV_SETTINGS
+#endif
+
#define CONFIG_EXTRA_ENV_SETTINGS \
+ CONSOLE_ENV_SETTINGS \
MEM_LAYOUT_ENV_SETTINGS \
"fdtfile=" CONFIG_FDTFILE "\0" \
"console=ttyS0,115200\0" \
--
2.1.0