u-boot-brain/drivers/video/sunxi_display.c
Hans de Goede 83edb2ace6 sunxi: video: Fix lvds panel support for sun6i+
We've never tested the lvds panel support on sun6i+ SoCs until now, and
unsurprisingly the lvds code needed some fixes to work on my ga10h A33
tablet with lvds panel. This makes the panel on that tablet actually work.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Ian Campbell <ijc@hellion.org.uk>
2015-05-19 18:46:44 +02:00

1313 lines
38 KiB
C

/*
* 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/arch/gpio.h>
#include <asm/global_data.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <errno.h>
#include <fdtdec.h>
#include <fdt_support.h>
#include <i2c.h>
#include <video_fb.h>
#include "videomodes.h"
#include "hitachi_tx18d42vm_lcd.h"
#include "ssd2828.h"
#ifdef CONFIG_VIDEO_LCD_BL_PWM_ACTIVE_LOW
#define PWM_ON 0
#define PWM_OFF 1
#else
#define PWM_ON 1
#define PWM_OFF 0
#endif
DECLARE_GLOBAL_DATA_PTR;
enum sunxi_monitor {
sunxi_monitor_none,
sunxi_monitor_dvi,
sunxi_monitor_hdmi,
sunxi_monitor_lcd,
sunxi_monitor_vga,
};
#define SUNXI_MONITOR_LAST sunxi_monitor_vga
struct sunxi_display {
GraphicDevice graphic_device;
enum sunxi_monitor monitor;
unsigned int depth;
unsigned int fb_size;
} sunxi_display;
#ifdef CONFIG_VIDEO_HDMI
/*
* Wait up to 200ms for value to be set in given part of reg.
*/
static int await_completion(u32 *reg, u32 mask, u32 val)
{
unsigned long tmo = timer_get_us() + 200000;
while ((readl(reg) & mask) != val) {
if (timer_get_us() > tmo) {
printf("DDC: timeout reading EDID\n");
return -ETIME;
}
}
return 0;
}
static int sunxi_hdmi_hpd_detect(int hpd_delay)
{
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;
unsigned long tmo = timer_get_us() + hpd_delay * 1000;
/* 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 */
#ifdef CONFIG_SUNXI_GEN_SUN6I
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI);
#endif
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);
while (timer_get_us() < tmo) {
if (readl(&hdmi->hpd) & SUNXI_HDMI_HPD_DETECT)
return 1;
}
return 0;
}
static void sunxi_hdmi_shutdown(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;
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);
#ifdef CONFIG_SUNXI_GEN_SUN6I
clrbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI);
#endif
clock_set_pll3(0);
}
static int sunxi_hdmi_ddc_do_command(u32 cmnd, int offset, int n)
{
struct sunxi_hdmi_reg * const hdmi =
(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
setbits_le32(&hdmi->ddc_fifo_ctrl, SUNXI_HDMI_DDC_FIFO_CTRL_CLEAR);
writel(SUNXI_HMDI_DDC_ADDR_EDDC_SEGMENT(offset >> 8) |
SUNXI_HMDI_DDC_ADDR_EDDC_ADDR |
SUNXI_HMDI_DDC_ADDR_OFFSET(offset) |
SUNXI_HMDI_DDC_ADDR_SLAVE_ADDR, &hdmi->ddc_addr);
#ifndef CONFIG_MACH_SUN6I
writel(n, &hdmi->ddc_byte_count);
writel(cmnd, &hdmi->ddc_cmnd);
#else
writel(n << 16 | cmnd, &hdmi->ddc_cmnd);
#endif
setbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START);
return await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START, 0);
}
static int sunxi_hdmi_ddc_read(int offset, u8 *buf, int count)
{
struct sunxi_hdmi_reg * const hdmi =
(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
int i, n;
while (count > 0) {
if (count > 16)
n = 16;
else
n = count;
if (sunxi_hdmi_ddc_do_command(
SUNXI_HDMI_DDC_CMND_EXPLICIT_EDDC_READ,
offset, n))
return -ETIME;
for (i = 0; i < n; i++)
*buf++ = readb(&hdmi->ddc_fifo_data);
offset += n;
count -= n;
}
return 0;
}
static int sunxi_hdmi_edid_get_block(int block, u8 *buf)
{
int r, retries = 2;
do {
r = sunxi_hdmi_ddc_read(block * 128, buf, 128);
if (r)
continue;
r = edid_check_checksum(buf);
if (r) {
printf("EDID block %d: checksum error%s\n",
block, retries ? ", retrying" : "");
}
} while (r && retries--);
return r;
}
static int sunxi_hdmi_edid_get_mode(struct ctfb_res_modes *mode)
{
struct edid1_info edid1;
struct edid_cea861_info cea681[4];
struct edid_detailed_timing *t =
(struct edid_detailed_timing *)edid1.monitor_details.timing;
struct sunxi_hdmi_reg * const hdmi =
(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
struct sunxi_ccm_reg * const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
int i, r, ext_blocks = 0;
/* SUNXI_HDMI_CTRL_ENABLE & PAD_CTRL0 are already set by hpd_detect */
writel(SUNXI_HDMI_PAD_CTRL1 | SUNXI_HDMI_PAD_CTRL1_HALVE,
&hdmi->pad_ctrl1);
writel(SUNXI_HDMI_PLL_CTRL | SUNXI_HDMI_PLL_CTRL_DIV(15),
&hdmi->pll_ctrl);
writel(SUNXI_HDMI_PLL_DBG0_PLL3, &hdmi->pll_dbg0);
/* Reset i2c controller */
setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
writel(SUNXI_HMDI_DDC_CTRL_ENABLE |
SUNXI_HMDI_DDC_CTRL_SDA_ENABLE |
SUNXI_HMDI_DDC_CTRL_SCL_ENABLE |
SUNXI_HMDI_DDC_CTRL_RESET, &hdmi->ddc_ctrl);
if (await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_RESET, 0))
return -EIO;
writel(SUNXI_HDMI_DDC_CLOCK, &hdmi->ddc_clock);
#ifndef CONFIG_MACH_SUN6I
writel(SUNXI_HMDI_DDC_LINE_CTRL_SDA_ENABLE |
SUNXI_HMDI_DDC_LINE_CTRL_SCL_ENABLE, &hdmi->ddc_line_ctrl);
#endif
r = sunxi_hdmi_edid_get_block(0, (u8 *)&edid1);
if (r == 0) {
r = edid_check_info(&edid1);
if (r) {
printf("EDID: invalid EDID data\n");
r = -EINVAL;
}
}
if (r == 0) {
ext_blocks = edid1.extension_flag;
if (ext_blocks > 4)
ext_blocks = 4;
for (i = 0; i < ext_blocks; i++) {
if (sunxi_hdmi_edid_get_block(1 + i,
(u8 *)&cea681[i]) != 0) {
ext_blocks = i;
break;
}
}
}
/* Disable DDC engine, no longer needed */
clrbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_ENABLE);
clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
if (r)
return r;
/* We want version 1.3 or 1.2 with detailed timing info */
if (edid1.version != 1 || (edid1.revision < 3 &&
!EDID1_INFO_FEATURE_PREFERRED_TIMING_MODE(edid1))) {
printf("EDID: unsupported version %d.%d\n",
edid1.version, edid1.revision);
return -EINVAL;
}
/* Take the first usable detailed timing */
for (i = 0; i < 4; i++, t++) {
r = video_edid_dtd_to_ctfb_res_modes(t, mode);
if (r == 0)
break;
}
if (i == 4) {
printf("EDID: no usable detailed timing found\n");
return -ENOENT;
}
/* Check for basic audio support, if found enable hdmi output */
sunxi_display.monitor = sunxi_monitor_dvi;
for (i = 0; i < ext_blocks; i++) {
if (cea681[i].extension_tag != EDID_CEA861_EXTENSION_TAG ||
cea681[i].revision < 2)
continue;
if (EDID_CEA861_SUPPORTS_BASIC_AUDIO(cea681[i]))
sunxi_display.monitor = sunxi_monitor_hdmi;
}
return 0;
}
#endif /* CONFIG_VIDEO_HDMI */
#ifdef CONFIG_MACH_SUN4I
/*
* Testing has shown that on sun4i the display backend engine does not have
* deep enough fifo-s causing flickering / tearing in full-hd mode due to
* fifo underruns. So on sun4i we use the display frontend engine to do the
* dma from memory, as the frontend does have deep enough fifo-s.
*/
static const u32 sun4i_vert_coef[32] = {
0x00004000, 0x000140ff, 0x00033ffe, 0x00043ffd,
0x00063efc, 0xff083dfc, 0x000a3bfb, 0xff0d39fb,
0xff0f37fb, 0xff1136fa, 0xfe1433fb, 0xfe1631fb,
0xfd192ffb, 0xfd1c2cfb, 0xfd1f29fb, 0xfc2127fc,
0xfc2424fc, 0xfc2721fc, 0xfb291ffd, 0xfb2c1cfd,
0xfb2f19fd, 0xfb3116fe, 0xfb3314fe, 0xfa3611ff,
0xfb370fff, 0xfb390dff, 0xfb3b0a00, 0xfc3d08ff,
0xfc3e0600, 0xfd3f0400, 0xfe3f0300, 0xff400100,
};
static const u32 sun4i_horz_coef[64] = {
0x40000000, 0x00000000, 0x40fe0000, 0x0000ff03,
0x3ffd0000, 0x0000ff05, 0x3ffc0000, 0x0000ff06,
0x3efb0000, 0x0000ff08, 0x3dfb0000, 0x0000ff09,
0x3bfa0000, 0x0000fe0d, 0x39fa0000, 0x0000fe0f,
0x38fa0000, 0x0000fe10, 0x36fa0000, 0x0000fe12,
0x33fa0000, 0x0000fd16, 0x31fa0000, 0x0000fd18,
0x2ffa0000, 0x0000fd1a, 0x2cfa0000, 0x0000fc1e,
0x29fa0000, 0x0000fc21, 0x27fb0000, 0x0000fb23,
0x24fb0000, 0x0000fb26, 0x21fb0000, 0x0000fb29,
0x1ffc0000, 0x0000fa2b, 0x1cfc0000, 0x0000fa2e,
0x19fd0000, 0x0000fa30, 0x16fd0000, 0x0000fa33,
0x14fd0000, 0x0000fa35, 0x11fe0000, 0x0000fa37,
0x0ffe0000, 0x0000fa39, 0x0dfe0000, 0x0000fa3b,
0x0afe0000, 0x0000fa3e, 0x08ff0000, 0x0000fb3e,
0x06ff0000, 0x0000fb40, 0x05ff0000, 0x0000fc40,
0x03ff0000, 0x0000fd41, 0x01ff0000, 0x0000fe42,
};
static void sunxi_frontend_init(void)
{
struct sunxi_ccm_reg * const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
struct sunxi_de_fe_reg * const de_fe =
(struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
int i;
/* Clocks on */
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_FE0);
setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_FE0);
clock_set_de_mod_clock(&ccm->fe0_clk_cfg, 300000000);
setbits_le32(&de_fe->enable, SUNXI_DE_FE_ENABLE_EN);
for (i = 0; i < 32; i++) {
writel(sun4i_horz_coef[2 * i], &de_fe->ch0_horzcoef0[i]);
writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch0_horzcoef1[i]);
writel(sun4i_vert_coef[i], &de_fe->ch0_vertcoef[i]);
writel(sun4i_horz_coef[2 * i], &de_fe->ch1_horzcoef0[i]);
writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch1_horzcoef1[i]);
writel(sun4i_vert_coef[i], &de_fe->ch1_vertcoef[i]);
}
setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_COEF_RDY);
}
static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
unsigned int address)
{
struct sunxi_de_fe_reg * const de_fe =
(struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
setbits_le32(&de_fe->bypass, SUNXI_DE_FE_BYPASS_CSC_BYPASS);
writel(CONFIG_SYS_SDRAM_BASE + address, &de_fe->ch0_addr);
writel(mode->xres * 4, &de_fe->ch0_stride);
writel(SUNXI_DE_FE_INPUT_FMT_ARGB8888, &de_fe->input_fmt);
writel(SUNXI_DE_FE_OUTPUT_FMT_ARGB8888, &de_fe->output_fmt);
writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
&de_fe->ch0_insize);
writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
&de_fe->ch0_outsize);
writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_horzfact);
writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_vertfact);
writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
&de_fe->ch1_insize);
writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
&de_fe->ch1_outsize);
writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_horzfact);
writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_vertfact);
setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_REG_RDY);
}
static void sunxi_frontend_enable(void)
{
struct sunxi_de_fe_reg * const de_fe =
(struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_FRM_START);
}
#else
static void sunxi_frontend_init(void) {}
static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
unsigned int address) {}
static void sunxi_frontend_enable(void) {}
#endif
/*
* 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;
sunxi_frontend_init();
#ifdef CONFIG_SUNXI_GEN_SUN6I
/* Reset off */
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DE_BE0);
#endif
/* Clocks on */
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_BE0);
#ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_BE0);
#endif
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(const struct ctfb_res_modes *mode,
unsigned int address)
{
struct sunxi_de_be_reg * const de_be =
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
sunxi_frontend_mode_set(mode, address);
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);
#ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
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);
#else
writel(SUNXI_DE_BE_LAYER_ATTR0_SRC_FE0, &de_be->layer0_attr0_ctrl);
#endif
writel(SUNXI_DE_BE_LAYER_ATTR1_FMT_XRGB8888, &de_be->layer0_attr1_ctrl);
setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_LAYER0_ENABLE);
}
static void sunxi_composer_enable(void)
{
struct sunxi_de_be_reg * const de_be =
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
sunxi_frontend_enable();
setbits_le32(&de_be->reg_ctrl, SUNXI_DE_BE_REG_CTRL_LOAD_REGS);
setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
}
/*
* LCDC, what allwinner calls a CRTC, so timing controller and serializer.
*/
static void sunxi_lcdc_pll_set(int tcon, 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, min_m, max_m, diff;
int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF;
int best_double = 0;
if (tcon == 0) {
#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
min_m = 6;
max_m = 127;
#endif
#ifdef CONFIG_VIDEO_LCD_IF_LVDS
min_m = max_m = 7;
#endif
} else {
min_m = 1;
max_m = 15;
}
/*
* 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 = min_m; m <= max_m; 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);
if (tcon == 0) {
writel(CCM_LCD_CH0_CTRL_GATE | CCM_LCD_CH0_CTRL_RST |
(best_double ? CCM_LCD_CH0_CTRL_PLL3_2X :
CCM_LCD_CH0_CTRL_PLL3),
&ccm->lcd0_ch0_clk_cfg);
} else {
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 */
#ifdef CONFIG_SUNXI_GEN_SUN6I
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0);
#else
setbits_le32(&ccm->lcd0_ch0_clk_cfg, CCM_LCD_CH0_CTRL_RST);
#endif
/* Clock on */
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
#ifdef CONFIG_VIDEO_LCD_IF_LVDS
#ifdef CONFIG_SUNXI_GEN_SUN6I
setbits_le32(&ccm->ahb_reset2_cfg, 1 << AHB_RESET_OFFSET_LVDS);
#else
setbits_le32(&ccm->lvds_clk_cfg, CCM_LVDS_CTRL_RST);
#endif
#endif
/* 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_enable(void)
{
struct sunxi_lcdc_reg * const lcdc =
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
setbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE);
#ifdef CONFIG_VIDEO_LCD_IF_LVDS
setbits_le32(&lcdc->tcon0_lvds_intf, SUNXI_LCDC_TCON0_LVDS_INTF_ENABLE);
setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0);
#ifdef CONFIG_SUNXI_GEN_SUN6I
udelay(2); /* delay at least 1200 ns */
setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_EN_MB);
udelay(2); /* delay at least 1200 ns */
setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_DRVC);
if (sunxi_display.depth == 18)
setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_DRVD(0x7));
else
setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_DRVD(0xf));
#else
setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_UPDATE);
udelay(2); /* delay at least 1200 ns */
setbits_le32(&lcdc->lvds_ana1, SUNXI_LCDC_LVDS_ANA1_INIT1);
udelay(1); /* delay at least 120 ns */
setbits_le32(&lcdc->lvds_ana1, SUNXI_LCDC_LVDS_ANA1_INIT2);
setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_UPDATE);
#endif
#endif
}
static void sunxi_lcdc_panel_enable(void)
{
int pin, reset_pin;
/*
* Start with backlight disabled to avoid the screen flashing to
* white while the lcd inits.
*/
pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN);
if (pin >= 0) {
gpio_request(pin, "lcd_backlight_enable");
gpio_direction_output(pin, 0);
}
pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
if (pin >= 0) {
gpio_request(pin, "lcd_backlight_pwm");
gpio_direction_output(pin, PWM_OFF);
}
reset_pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_RESET);
if (reset_pin >= 0) {
gpio_request(reset_pin, "lcd_reset");
gpio_direction_output(reset_pin, 0); /* Assert reset */
}
/* Give the backlight some time to turn off and power up the panel. */
mdelay(40);
pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_POWER);
if (pin >= 0) {
gpio_request(pin, "lcd_power");
gpio_direction_output(pin, 1);
}
if (reset_pin >= 0)
gpio_direction_output(reset_pin, 1); /* De-assert reset */
}
static void sunxi_lcdc_backlight_enable(void)
{
int pin;
/*
* We want to have scanned out at least one frame before enabling the
* backlight to avoid the screen flashing to white when we enable it.
*/
mdelay(40);
pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN);
if (pin >= 0)
gpio_direction_output(pin, 1);
pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
if (pin >= 0)
gpio_direction_output(pin, PWM_ON);
}
static int sunxi_lcdc_get_clk_delay(const struct ctfb_res_modes *mode)
{
int delay;
delay = mode->lower_margin + mode->vsync_len + mode->upper_margin - 2;
return (delay > 30) ? 30 : delay;
}
static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode,
bool for_ext_vga_dac)
{
struct sunxi_lcdc_reg * const lcdc =
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
int bp, clk_delay, clk_div, clk_double, pin, total, val;
for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(27); pin++)
#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0);
#endif
#ifdef CONFIG_VIDEO_LCD_IF_LVDS
sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LVDS0);
#endif
sunxi_lcdc_pll_set(0, mode->pixclock_khz, &clk_div, &clk_double);
/* Use tcon0 */
clrsetbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_IO_MAP_MASK,
SUNXI_LCDC_CTRL_IO_MAP_TCON0);
clk_delay = sunxi_lcdc_get_clk_delay(mode);
writel(SUNXI_LCDC_TCON0_CTRL_ENABLE |
SUNXI_LCDC_TCON0_CTRL_CLK_DELAY(clk_delay), &lcdc->tcon0_ctrl);
writel(SUNXI_LCDC_TCON0_DCLK_ENABLE |
SUNXI_LCDC_TCON0_DCLK_DIV(clk_div), &lcdc->tcon0_dclk);
writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(mode->yres),
&lcdc->tcon0_timing_active);
bp = mode->hsync_len + mode->left_margin;
total = mode->xres + mode->right_margin + bp;
writel(SUNXI_LCDC_TCON0_TIMING_H_TOTAL(total) |
SUNXI_LCDC_TCON0_TIMING_H_BP(bp), &lcdc->tcon0_timing_h);
bp = mode->vsync_len + mode->upper_margin;
total = mode->yres + mode->lower_margin + bp;
writel(SUNXI_LCDC_TCON0_TIMING_V_TOTAL(total) |
SUNXI_LCDC_TCON0_TIMING_V_BP(bp), &lcdc->tcon0_timing_v);
#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
writel(SUNXI_LCDC_X(mode->hsync_len) | SUNXI_LCDC_Y(mode->vsync_len),
&lcdc->tcon0_timing_sync);
writel(0, &lcdc->tcon0_hv_intf);
writel(0, &lcdc->tcon0_cpu_intf);
#endif
#ifdef CONFIG_VIDEO_LCD_IF_LVDS
val = (sunxi_display.depth == 18) ? 1 : 0;
writel(SUNXI_LCDC_TCON0_LVDS_INTF_BITWIDTH(val) |
SUNXI_LCDC_TCON0_LVDS_CLK_SEL_TCON0, &lcdc->tcon0_lvds_intf);
#endif
if (sunxi_display.depth == 18 || sunxi_display.depth == 16) {
writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[0]);
writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[1]);
writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[2]);
writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[3]);
writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[4]);
writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[5]);
writel(SUNXI_LCDC_TCON0_FRM_TAB0, &lcdc->tcon0_frm_table[0]);
writel(SUNXI_LCDC_TCON0_FRM_TAB1, &lcdc->tcon0_frm_table[1]);
writel(SUNXI_LCDC_TCON0_FRM_TAB2, &lcdc->tcon0_frm_table[2]);
writel(SUNXI_LCDC_TCON0_FRM_TAB3, &lcdc->tcon0_frm_table[3]);
writel(((sunxi_display.depth == 18) ?
SUNXI_LCDC_TCON0_FRM_CTRL_RGB666 :
SUNXI_LCDC_TCON0_FRM_CTRL_RGB565),
&lcdc->tcon0_frm_ctrl);
}
val = SUNXI_LCDC_TCON0_IO_POL_DCLK_PHASE(CONFIG_VIDEO_LCD_DCLK_PHASE);
if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
val |= SUNXI_LCDC_TCON_HSYNC_MASK;
if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
val |= SUNXI_LCDC_TCON_VSYNC_MASK;
#ifdef CONFIG_VIDEO_VGA_VIA_LCD_FORCE_SYNC_ACTIVE_HIGH
if (for_ext_vga_dac)
val = 0;
#endif
writel(val, &lcdc->tcon0_io_polarity);
writel(0, &lcdc->tcon0_io_tristate);
}
#if defined CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA
static void sunxi_lcdc_tcon1_mode_set(const struct ctfb_res_modes *mode,
int *clk_div, int *clk_double,
bool use_portd_hvsync)
{
struct sunxi_lcdc_reg * const lcdc =
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
int bp, clk_delay, total, val;
/* Use tcon1 */
clrsetbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_IO_MAP_MASK,
SUNXI_LCDC_CTRL_IO_MAP_TCON1);
clk_delay = sunxi_lcdc_get_clk_delay(mode);
writel(SUNXI_LCDC_TCON1_CTRL_ENABLE |
SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(clk_delay), &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);
if (use_portd_hvsync) {
sunxi_gpio_set_cfgpin(SUNXI_GPD(26), SUNXI_GPD_LCD0);
sunxi_gpio_set_cfgpin(SUNXI_GPD(27), SUNXI_GPD_LCD0);
val = 0;
if (mode->sync & FB_SYNC_HOR_HIGH_ACT)
val |= SUNXI_LCDC_TCON_HSYNC_MASK;
if (mode->sync & FB_SYNC_VERT_HIGH_ACT)
val |= SUNXI_LCDC_TCON_VSYNC_MASK;
writel(val, &lcdc->tcon1_io_polarity);
clrbits_le32(&lcdc->tcon1_io_tristate,
SUNXI_LCDC_TCON_VSYNC_MASK |
SUNXI_LCDC_TCON_HSYNC_MASK);
}
sunxi_lcdc_pll_set(1, mode->pixclock_khz, clk_div, clk_double);
}
#endif /* CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA */
#ifdef CONFIG_VIDEO_HDMI
static void sunxi_hdmi_setup_info_frames(const struct ctfb_res_modes *mode)
{
struct sunxi_hdmi_reg * const hdmi =
(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
u8 checksum = 0;
u8 avi_info_frame[17] = {
0x82, 0x02, 0x0d, 0x00, 0x12, 0x00, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00
};
u8 vendor_info_frame[19] = {
0x81, 0x01, 0x06, 0x29, 0x03, 0x0c, 0x00, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00
};
int i;
if (mode->pixclock_khz <= 27000)
avi_info_frame[5] = 0x40; /* SD-modes, ITU601 colorspace */
else
avi_info_frame[5] = 0x80; /* HD-modes, ITU709 colorspace */
if (mode->xres * 100 / mode->yres < 156)
avi_info_frame[5] |= 0x18; /* 4 : 3 */
else
avi_info_frame[5] |= 0x28; /* 16 : 9 */
for (i = 0; i < ARRAY_SIZE(avi_info_frame); i++)
checksum += avi_info_frame[i];
avi_info_frame[3] = 0x100 - checksum;
for (i = 0; i < ARRAY_SIZE(avi_info_frame); i++)
writeb(avi_info_frame[i], &hdmi->avi_info_frame[i]);
writel(SUNXI_HDMI_QCP_PACKET0, &hdmi->qcp_packet0);
writel(SUNXI_HDMI_QCP_PACKET1, &hdmi->qcp_packet1);
for (i = 0; i < ARRAY_SIZE(vendor_info_frame); i++)
writeb(vendor_info_frame[i], &hdmi->vendor_info_frame[i]);
writel(SUNXI_HDMI_PKT_CTRL0, &hdmi->pkt_ctrl0);
writel(SUNXI_HDMI_PKT_CTRL1, &hdmi->pkt_ctrl1);
setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_HDMI);
}
static void sunxi_hdmi_mode_set(const struct ctfb_res_modes *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);
if (sunxi_display.monitor == sunxi_monitor_hdmi)
sunxi_hdmi_setup_info_frames(mode);
/* Set input sync enable */
writel(SUNXI_HDMI_UNKNOWN_INPUT_SYNC, &hdmi->unknown);
/* 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_hdmi_enable(void)
{
struct sunxi_hdmi_reg * const hdmi =
(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
udelay(100);
setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_ENABLE);
}
#endif /* CONFIG_VIDEO_HDMI */
#ifdef CONFIG_VIDEO_VGA
static void sunxi_vga_mode_set(void)
{
struct sunxi_ccm_reg * const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
struct sunxi_tve_reg * const tve =
(struct sunxi_tve_reg *)SUNXI_TVE0_BASE;
/* Clock on */
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_TVE0);
/* Set TVE in VGA mode */
writel(SUNXI_TVE_GCTRL_DAC_INPUT(0, 1) |
SUNXI_TVE_GCTRL_DAC_INPUT(1, 2) |
SUNXI_TVE_GCTRL_DAC_INPUT(2, 3), &tve->gctrl);
writel(SUNXI_TVE_GCTRL_CFG0_VGA, &tve->cfg0);
writel(SUNXI_TVE_GCTRL_DAC_CFG0_VGA, &tve->dac_cfg0);
writel(SUNXI_TVE_GCTRL_UNKNOWN1_VGA, &tve->unknown1);
}
static void sunxi_vga_enable(void)
{
struct sunxi_tve_reg * const tve =
(struct sunxi_tve_reg *)SUNXI_TVE0_BASE;
setbits_le32(&tve->gctrl, SUNXI_TVE_GCTRL_ENABLE);
}
#endif /* CONFIG_VIDEO_VGA */
static void sunxi_drc_init(void)
{
#ifdef CONFIG_SUNXI_GEN_SUN6I
struct sunxi_ccm_reg * const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
/* On sun6i the drc must be clocked even when in pass-through mode */
#ifdef CONFIG_MACH_SUN8I_A33
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_SAT);
#endif
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DRC0);
clock_set_de_mod_clock(&ccm->iep_drc0_clk_cfg, 300000000);
#endif
}
#ifdef CONFIG_VIDEO_VGA_VIA_LCD
static void sunxi_vga_external_dac_enable(void)
{
int pin;
pin = sunxi_name_to_gpio(CONFIG_VIDEO_VGA_EXTERNAL_DAC_EN);
if (pin >= 0) {
gpio_request(pin, "vga_enable");
gpio_direction_output(pin, 1);
}
}
#endif /* CONFIG_VIDEO_VGA_VIA_LCD */
#ifdef CONFIG_VIDEO_LCD_SSD2828
static int sunxi_ssd2828_init(const struct ctfb_res_modes *mode)
{
struct ssd2828_config cfg = {
.csx_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_CS),
.sck_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_SCLK),
.sdi_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MOSI),
.sdo_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MISO),
.reset_pin = name_to_gpio(CONFIG_VIDEO_LCD_SSD2828_RESET),
.ssd2828_tx_clk_khz = CONFIG_VIDEO_LCD_SSD2828_TX_CLK * 1000,
.ssd2828_color_depth = 24,
#ifdef CONFIG_VIDEO_LCD_PANEL_MIPI_4_LANE_513_MBPS_VIA_SSD2828
.mipi_dsi_number_of_data_lanes = 4,
.mipi_dsi_bitrate_per_data_lane_mbps = 513,
.mipi_dsi_delay_after_exit_sleep_mode_ms = 100,
.mipi_dsi_delay_after_set_display_on_ms = 200
#else
#error MIPI LCD panel needs configuration parameters
#endif
};
if (cfg.csx_pin == -1 || cfg.sck_pin == -1 || cfg.sdi_pin == -1) {
printf("SSD2828: SPI pins are not properly configured\n");
return 1;
}
if (cfg.reset_pin == -1) {
printf("SSD2828: Reset pin is not properly configured\n");
return 1;
}
return ssd2828_init(&cfg, mode);
}
#endif /* CONFIG_VIDEO_LCD_SSD2828 */
static void sunxi_engines_init(void)
{
sunxi_composer_init();
sunxi_lcdc_init();
sunxi_drc_init();
}
static void sunxi_mode_set(const struct ctfb_res_modes *mode,
unsigned int address)
{
int __maybe_unused clk_div, clk_double;
switch (sunxi_display.monitor) {
case sunxi_monitor_none:
break;
case sunxi_monitor_dvi:
case sunxi_monitor_hdmi:
#ifdef CONFIG_VIDEO_HDMI
sunxi_composer_mode_set(mode, address);
sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 0);
sunxi_hdmi_mode_set(mode, clk_div, clk_double);
sunxi_composer_enable();
sunxi_lcdc_enable();
sunxi_hdmi_enable();
#endif
break;
case sunxi_monitor_lcd:
sunxi_lcdc_panel_enable();
if (IS_ENABLED(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM)) {
mdelay(50); /* Wait for lcd controller power on */
hitachi_tx18d42vm_init();
}
if (IS_ENABLED(CONFIG_VIDEO_LCD_TL059WV5C0)) {
unsigned int orig_i2c_bus = i2c_get_bus_num();
i2c_set_bus_num(CONFIG_VIDEO_LCD_I2C_BUS);
i2c_reg_write(0x5c, 0x04, 0x42); /* Turn on the LCD */
i2c_set_bus_num(orig_i2c_bus);
}
sunxi_composer_mode_set(mode, address);
sunxi_lcdc_tcon0_mode_set(mode, false);
sunxi_composer_enable();
sunxi_lcdc_enable();
#ifdef CONFIG_VIDEO_LCD_SSD2828
sunxi_ssd2828_init(mode);
#endif
sunxi_lcdc_backlight_enable();
break;
case sunxi_monitor_vga:
#ifdef CONFIG_VIDEO_VGA
sunxi_composer_mode_set(mode, address);
sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 1);
sunxi_vga_mode_set();
sunxi_composer_enable();
sunxi_lcdc_enable();
sunxi_vga_enable();
#elif defined CONFIG_VIDEO_VGA_VIA_LCD
sunxi_composer_mode_set(mode, address);
sunxi_lcdc_tcon0_mode_set(mode, true);
sunxi_composer_enable();
sunxi_lcdc_enable();
sunxi_vga_external_dac_enable();
#endif
break;
}
}
static const char *sunxi_get_mon_desc(enum sunxi_monitor monitor)
{
switch (monitor) {
case sunxi_monitor_none: return "none";
case sunxi_monitor_dvi: return "dvi";
case sunxi_monitor_hdmi: return "hdmi";
case sunxi_monitor_lcd: return "lcd";
case sunxi_monitor_vga: return "vga";
}
return NULL; /* never reached */
}
ulong board_get_usable_ram_top(ulong total_size)
{
return gd->ram_top - CONFIG_SUNXI_MAX_FB_SIZE;
}
void *video_hw_init(void)
{
static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
const struct ctfb_res_modes *mode;
struct ctfb_res_modes custom;
const char *options;
#ifdef CONFIG_VIDEO_HDMI
int ret, hpd, hpd_delay, edid;
#endif
char mon[16];
char *lcd_mode = CONFIG_VIDEO_LCD_MODE;
int i;
memset(&sunxi_display, 0, sizeof(struct sunxi_display));
video_get_ctfb_res_modes(RES_MODE_1024x768, 24, &mode,
&sunxi_display.depth, &options);
#ifdef CONFIG_VIDEO_HDMI
hpd = video_get_option_int(options, "hpd", 1);
hpd_delay = video_get_option_int(options, "hpd_delay", 500);
edid = video_get_option_int(options, "edid", 1);
sunxi_display.monitor = sunxi_monitor_dvi;
#elif defined CONFIG_VIDEO_VGA_VIA_LCD
sunxi_display.monitor = sunxi_monitor_vga;
#else
sunxi_display.monitor = sunxi_monitor_lcd;
#endif
video_get_option_string(options, "monitor", mon, sizeof(mon),
sunxi_get_mon_desc(sunxi_display.monitor));
for (i = 0; i <= SUNXI_MONITOR_LAST; i++) {
if (strcmp(mon, sunxi_get_mon_desc(i)) == 0) {
sunxi_display.monitor = i;
break;
}
}
if (i > SUNXI_MONITOR_LAST)
printf("Unknown monitor: '%s', falling back to '%s'\n",
mon, sunxi_get_mon_desc(sunxi_display.monitor));
#ifdef CONFIG_VIDEO_HDMI
/* If HDMI/DVI is selected do HPD & EDID, and handle fallback */
if (sunxi_display.monitor == sunxi_monitor_dvi ||
sunxi_display.monitor == sunxi_monitor_hdmi) {
/* Always call hdp_detect, as it also enables clocks, etc. */
ret = sunxi_hdmi_hpd_detect(hpd_delay);
if (ret) {
printf("HDMI connected: ");
if (edid && sunxi_hdmi_edid_get_mode(&custom) == 0)
mode = &custom;
} else if (hpd) {
sunxi_hdmi_shutdown();
/* Fallback to lcd / vga / none */
if (lcd_mode[0]) {
sunxi_display.monitor = sunxi_monitor_lcd;
} else {
#if defined CONFIG_VIDEO_VGA_VIA_LCD || defined CONFIG_VIDEO_VGA
sunxi_display.monitor = sunxi_monitor_vga;
#else
sunxi_display.monitor = sunxi_monitor_none;
#endif
}
} /* else continue with hdmi/dvi without a cable connected */
}
#endif
switch (sunxi_display.monitor) {
case sunxi_monitor_none:
return NULL;
case sunxi_monitor_dvi:
case sunxi_monitor_hdmi:
#ifdef CONFIG_VIDEO_HDMI
break;
#else
printf("HDMI/DVI not supported on this board\n");
sunxi_display.monitor = sunxi_monitor_none;
return NULL;
#endif
case sunxi_monitor_lcd:
if (lcd_mode[0]) {
sunxi_display.depth = video_get_params(&custom, lcd_mode);
mode = &custom;
break;
}
printf("LCD not supported on this board\n");
sunxi_display.monitor = sunxi_monitor_none;
return NULL;
case sunxi_monitor_vga:
#if defined CONFIG_VIDEO_VGA_VIA_LCD || defined CONFIG_VIDEO_VGA
sunxi_display.depth = 18;
break;
#else
printf("VGA not supported on this board\n");
sunxi_display.monitor = sunxi_monitor_none;
return NULL;
#endif
}
if (mode->vmode != FB_VMODE_NONINTERLACED) {
printf("Only non-interlaced modes supported, falling back to 1024x768\n");
mode = &res_mode_init[RES_MODE_1024x768];
} else {
printf("Setting up a %dx%d %s console\n", mode->xres,
mode->yres, sunxi_get_mon_desc(sunxi_display.monitor));
}
sunxi_display.fb_size =
(mode->xres * mode->yres * 4 + 0xfff) & ~0xfff;
if (sunxi_display.fb_size > CONFIG_SUNXI_MAX_FB_SIZE) {
printf("Error need %dkB for fb, but only %dkB is reserved\n",
sunxi_display.fb_size >> 10,
CONFIG_SUNXI_MAX_FB_SIZE >> 10);
return NULL;
}
gd->fb_base = gd->bd->bi_dram[0].start +
gd->bd->bi_dram[0].size - sunxi_display.fb_size;
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;
}
/*
* Simplefb support.
*/
#if defined(CONFIG_OF_BOARD_SETUP) && defined(CONFIG_VIDEO_DT_SIMPLEFB)
int sunxi_simplefb_setup(void *blob)
{
static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
int offset, ret;
u64 start, size;
const char *pipeline = NULL;
#ifdef CONFIG_MACH_SUN4I
#define PIPELINE_PREFIX "de_fe0-"
#else
#define PIPELINE_PREFIX
#endif
switch (sunxi_display.monitor) {
case sunxi_monitor_none:
return 0;
case sunxi_monitor_dvi:
case sunxi_monitor_hdmi:
pipeline = PIPELINE_PREFIX "de_be0-lcd0-hdmi";
break;
case sunxi_monitor_lcd:
pipeline = PIPELINE_PREFIX "de_be0-lcd0";
break;
case sunxi_monitor_vga:
#ifdef CONFIG_VIDEO_VGA
pipeline = PIPELINE_PREFIX "de_be0-lcd0-tve0";
#elif defined CONFIG_VIDEO_VGA_VIA_LCD
pipeline = PIPELINE_PREFIX "de_be0-lcd0";
#endif
break;
}
/* Find a prefilled simpefb node, matching out pipeline config */
offset = fdt_node_offset_by_compatible(blob, -1,
"allwinner,simple-framebuffer");
while (offset >= 0) {
ret = fdt_find_string(blob, offset, "allwinner,pipeline",
pipeline);
if (ret == 0)
break;
offset = fdt_node_offset_by_compatible(blob, offset,
"allwinner,simple-framebuffer");
}
if (offset < 0) {
eprintf("Cannot setup simplefb: node not found\n");
return 0; /* Keep older kernels working */
}
/*
* Do not report the framebuffer as free RAM to the OS, note we cannot
* use fdt_add_mem_rsv() here, because then it is still seen as RAM,
* and e.g. Linux refuses to iomap RAM on ARM, see:
* linux/arch/arm/mm/ioremap.c around line 301.
*/
start = gd->bd->bi_dram[0].start;
size = gd->bd->bi_dram[0].size - sunxi_display.fb_size;
ret = fdt_fixup_memory_banks(blob, &start, &size, 1);
if (ret) {
eprintf("Cannot setup simplefb: Error reserving memory\n");
return ret;
}
ret = fdt_setup_simplefb_node(blob, offset, gd->fb_base,
graphic_device->winSizeX, graphic_device->winSizeY,
graphic_device->winSizeX * graphic_device->gdfBytesPP,
"x8r8g8b8");
if (ret)
eprintf("Cannot setup simplefb: Error setting properties\n");
return ret;
}
#endif /* CONFIG_OF_BOARD_SETUP && CONFIG_VIDEO_DT_SIMPLEFB */