Commit 7f2c521f authored by Luc Verhaegen's avatar Luc Verhaegen Committed by Hans de Goede
Browse files

sunxi: video: Add cfb console driver for sunxi



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: default avatarLuc Verhaegen <libv@skynet.be>
[hdegoede@redhat.com: Major cleanups and some small bugfixes]
Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
Acked-by: default avatarAnatolij Gustschin <agust@denx.de>
Acked-by: default avatarIan Campbell <ijc@hellion.org.uk>
parent 0bd51251
/*
* Sunxi platform display controller 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 */
};
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 */
......@@ -215,4 +215,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
......@@ -2,6 +2,7 @@ CONFIG_SPL=y
CONFIG_SYS_EXTRA_OPTIONS="CONS_INDEX=2,USB_EHCI"
CONFIG_FDTFILE="sun5i-a13-olinuxino-micro.dtb"
CONFIG_USB1_VBUS_PIN="PG11"
CONFIG_VIDEO=n
+S:CONFIG_ARM=y
+S:CONFIG_ARCH_SUNXI=y
+S:CONFIG_MACH_SUN5I=y
......
......@@ -2,6 +2,7 @@ CONFIG_SPL=y
CONFIG_SYS_EXTRA_OPTIONS="CONS_INDEX=2,AXP209_POWER,USB_EHCI"
CONFIG_FDTFILE="sun5i-a13-olinuxino.dtb"
CONFIG_USB1_VBUS_PIN="PG11"
CONFIG_VIDEO=n
+S:CONFIG_ARM=y
+S:CONFIG_ARCH_SUNXI=y
+S:CONFIG_MACH_SUN5I=y
......
......@@ -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
......@@ -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
......
/*
* 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;
}
}
}
debug("dotclock: %dkHz = %dkHz: (%d * 3MHz * %d) / %d\n",
dotclock, (best_double + 1) * 3000 * best_n / best_m,
best_double + 1, best_n, best_m);
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;
printf("HDMI fifo under or overrun\n");
}
}
void *video_hw_init(void)
{
static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
/*
* Vesa standard 1024x768@60
* 65.0 1024 1048 1184 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;
}
......@@ -197,6 +197,28 @@
#define CONFIG_SPL_GPIO_SUPPORT
#define CONFIG_CMD_GPIO