mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-26 04:53:42 +00:00
2421497cb7
Two displays supported by the sunxi display driver (each one used by a single board) require initialization over I2C. Both previously used i2c_soft; replace this with the i2c-gpio instance that already exists in those boards' device trees (sun5i-a13-utoo-p66 and sun6i-a31-colombus). Since the i2c-gpio nodes are not referenced by any other node in the device trees (the device trees have no panel node), the I2C bus is selected by its node name. This panel initialization code was the only i2c_soft user, so the i2c_soft GPIO setup code can be removed now as well. Reviewed-by: Heiko Schocher <hs@denx.de> Signed-off-by: Samuel Holland <samuel@sholland.org> Signed-off-by: Andre Przywara <andre.przywara@arm.com>
1356 lines
38 KiB
C
1356 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Display driver for Allwinner SoCs.
|
|
*
|
|
* (C) Copyright 2013-2014 Luc Verhaegen <libv@skynet.be>
|
|
* (C) Copyright 2014-2015 Hans de Goede <hdegoede@redhat.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <display.h>
|
|
#include <dm.h>
|
|
#include <cpu_func.h>
|
|
#include <efi_loader.h>
|
|
#include <init.h>
|
|
#include <time.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <asm/arch/clock.h>
|
|
#include <asm/arch/display.h>
|
|
#include <asm/arch/lcdc.h>
|
|
#include <asm/arch/pwm.h>
|
|
#include <asm/arch/tve.h>
|
|
#include <asm/global_data.h>
|
|
#include <asm/gpio.h>
|
|
#include <asm/io.h>
|
|
#include <axp_pmic.h>
|
|
#include <errno.h>
|
|
#include <fdtdec.h>
|
|
#include <fdt_support.h>
|
|
#include <i2c.h>
|
|
#include <malloc.h>
|
|
#include <video.h>
|
|
#include <video_fb.h>
|
|
#include <dm/uclass-internal.h>
|
|
#include "../videomodes.h"
|
|
#include "../anx9804.h"
|
|
#include "../hitachi_tx18d42vm_lcd.h"
|
|
#include "../ssd2828.h"
|
|
#include "simplefb_common.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;
|
|
|
|
/* Maximum LCD size we support */
|
|
#define LCD_MAX_WIDTH 3840
|
|
#define LCD_MAX_HEIGHT 2160
|
|
#define LCD_MAX_LOG2_BPP VIDEO_BPP32
|
|
|
|
enum sunxi_monitor {
|
|
sunxi_monitor_none,
|
|
sunxi_monitor_dvi,
|
|
sunxi_monitor_hdmi,
|
|
sunxi_monitor_lcd,
|
|
sunxi_monitor_vga,
|
|
sunxi_monitor_composite_pal,
|
|
sunxi_monitor_composite_ntsc,
|
|
sunxi_monitor_composite_pal_m,
|
|
sunxi_monitor_composite_pal_nc,
|
|
};
|
|
#define SUNXI_MONITOR_LAST sunxi_monitor_composite_pal_nc
|
|
|
|
struct sunxi_display_priv {
|
|
enum sunxi_monitor monitor;
|
|
unsigned int depth;
|
|
unsigned int fb_addr;
|
|
unsigned int fb_size;
|
|
};
|
|
|
|
const struct ctfb_res_modes composite_video_modes[2] = {
|
|
/* x y hz pixclk ps/kHz le ri up lo hs vs s vmode */
|
|
{ 720, 576, 50, 37037, 27000, 137, 5, 20, 27, 2, 2, 0, FB_VMODE_INTERLACED },
|
|
{ 720, 480, 60, 37037, 27000, 116, 20, 16, 27, 2, 2, 0, FB_VMODE_INTERLACED },
|
|
};
|
|
|
|
#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);
|
|
|
|
/* Enable PLLs for eventual DDC */
|
|
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);
|
|
|
|
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 sunxi_display_priv *sunxi_display,
|
|
struct ctfb_res_modes *mode,
|
|
bool verbose_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;
|
|
|
|
/* 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) {
|
|
if (verbose_mode)
|
|
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
|
|
|
|
static bool sunxi_is_composite(enum sunxi_monitor monitor)
|
|
{
|
|
switch (monitor) {
|
|
case sunxi_monitor_none:
|
|
case sunxi_monitor_dvi:
|
|
case sunxi_monitor_hdmi:
|
|
case sunxi_monitor_lcd:
|
|
case sunxi_monitor_vga:
|
|
return false;
|
|
case sunxi_monitor_composite_pal:
|
|
case sunxi_monitor_composite_ntsc:
|
|
case sunxi_monitor_composite_pal_m:
|
|
case sunxi_monitor_composite_pal_nc:
|
|
return true;
|
|
}
|
|
|
|
return false; /* Never reached */
|
|
}
|
|
|
|
/*
|
|
* 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 const u32 sunxi_rgb2yuv_coef[12] = {
|
|
0x00000107, 0x00000204, 0x00000064, 0x00000108,
|
|
0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808,
|
|
0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808
|
|
};
|
|
|
|
static void sunxi_composer_mode_set(const struct ctfb_res_modes *mode,
|
|
unsigned int address,
|
|
enum sunxi_monitor monitor)
|
|
{
|
|
struct sunxi_de_be_reg * const de_be =
|
|
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
|
|
int i;
|
|
|
|
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);
|
|
if (mode->vmode == FB_VMODE_INTERLACED)
|
|
setbits_le32(&de_be->mode,
|
|
#ifndef CONFIG_MACH_SUN5I
|
|
SUNXI_DE_BE_MODE_DEFLICKER_ENABLE |
|
|
#endif
|
|
SUNXI_DE_BE_MODE_INTERLACE_ENABLE);
|
|
|
|
if (sunxi_is_composite(monitor)) {
|
|
writel(SUNXI_DE_BE_OUTPUT_COLOR_CTRL_ENABLE,
|
|
&de_be->output_color_ctrl);
|
|
for (i = 0; i < 12; i++)
|
|
writel(sunxi_rgb2yuv_coef[i],
|
|
&de_be->output_color_coef[i]);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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
|
|
|
|
lcdc_init(lcdc);
|
|
}
|
|
|
|
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);
|
|
#ifdef SUNXI_PWM_PIN0
|
|
if (pin == SUNXI_PWM_PIN0) {
|
|
writel(SUNXI_PWM_CTRL_POLARITY0(PWM_ON) |
|
|
SUNXI_PWM_CTRL_ENABLE0 |
|
|
SUNXI_PWM_CTRL_PRESCALE0(0xf), SUNXI_PWM_CTRL_REG);
|
|
writel(SUNXI_PWM_PERIOD_80PCT, SUNXI_PWM_CH0_PERIOD);
|
|
sunxi_gpio_set_cfgpin(pin, SUNXI_PWM_MUX);
|
|
return;
|
|
}
|
|
#endif
|
|
if (pin >= 0)
|
|
gpio_direction_output(pin, PWM_ON);
|
|
}
|
|
|
|
static void sunxi_lcdc_tcon0_mode_set(struct sunxi_display_priv *sunxi_display,
|
|
const struct ctfb_res_modes *mode,
|
|
bool for_ext_vga_dac)
|
|
{
|
|
struct sunxi_lcdc_reg * const lcdc =
|
|
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
|
|
struct sunxi_ccm_reg * const ccm =
|
|
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
|
|
int clk_div, clk_double, pin;
|
|
struct display_timing timing;
|
|
|
|
#if defined CONFIG_MACH_SUN8I && defined CONFIG_VIDEO_LCD_IF_LVDS
|
|
for (pin = SUNXI_GPD(18); pin <= SUNXI_GPD(27); pin++) {
|
|
#else
|
|
for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(27); pin++) {
|
|
#endif
|
|
#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
|
|
sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LCD0);
|
|
#endif
|
|
#ifdef CONFIG_VIDEO_LCD_IF_LVDS
|
|
sunxi_gpio_set_cfgpin(pin, SUNXI_GPD_LVDS0);
|
|
#endif
|
|
#ifdef CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804
|
|
sunxi_gpio_set_drv(pin, 3);
|
|
#endif
|
|
}
|
|
|
|
lcdc_pll_set(ccm, 0, mode->pixclock_khz, &clk_div, &clk_double,
|
|
sunxi_is_composite(sunxi_display->monitor));
|
|
|
|
video_ctfb_mode_to_display_timing(mode, &timing);
|
|
lcdc_tcon0_mode_set(lcdc, &timing, clk_div, for_ext_vga_dac,
|
|
sunxi_display->depth, CONFIG_VIDEO_LCD_DCLK_PHASE);
|
|
}
|
|
|
|
#if defined CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_COMPOSITE
|
|
static void sunxi_lcdc_tcon1_mode_set(const struct ctfb_res_modes *mode,
|
|
int *clk_div, int *clk_double,
|
|
bool use_portd_hvsync,
|
|
enum sunxi_monitor monitor)
|
|
{
|
|
struct sunxi_lcdc_reg * const lcdc =
|
|
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
|
|
struct sunxi_ccm_reg * const ccm =
|
|
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
|
|
struct display_timing timing;
|
|
|
|
video_ctfb_mode_to_display_timing(mode, &timing);
|
|
lcdc_tcon1_mode_set(lcdc, &timing, use_portd_hvsync,
|
|
sunxi_is_composite(monitor));
|
|
|
|
if (use_portd_hvsync) {
|
|
sunxi_gpio_set_cfgpin(SUNXI_GPD(26), SUNXI_GPD_LCD0);
|
|
sunxi_gpio_set_cfgpin(SUNXI_GPD(27), SUNXI_GPD_LCD0);
|
|
}
|
|
|
|
lcdc_pll_set(ccm, 1, mode->pixclock_khz, clk_div, clk_double,
|
|
sunxi_is_composite(monitor));
|
|
}
|
|
#endif /* CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA || CONFIG_VIDEO_COMPOSITE */
|
|
|
|
#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,
|
|
enum sunxi_monitor monitor)
|
|
{
|
|
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 (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 */
|
|
|
|
#if defined CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_COMPOSITE
|
|
|
|
static void sunxi_tvencoder_mode_set(enum sunxi_monitor monitor)
|
|
{
|
|
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;
|
|
|
|
/* Reset off */
|
|
setbits_le32(&ccm->lcd0_ch0_clk_cfg, CCM_LCD_CH0_CTRL_TVE_RST);
|
|
/* Clock on */
|
|
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_TVE0);
|
|
|
|
switch (monitor) {
|
|
case sunxi_monitor_vga:
|
|
tvencoder_mode_set(tve, tve_mode_vga);
|
|
break;
|
|
case sunxi_monitor_composite_pal_nc:
|
|
tvencoder_mode_set(tve, tve_mode_composite_pal_nc);
|
|
break;
|
|
case sunxi_monitor_composite_pal:
|
|
tvencoder_mode_set(tve, tve_mode_composite_pal);
|
|
break;
|
|
case sunxi_monitor_composite_pal_m:
|
|
tvencoder_mode_set(tve, tve_mode_composite_pal_m);
|
|
break;
|
|
case sunxi_monitor_composite_ntsc:
|
|
tvencoder_mode_set(tve, tve_mode_composite_ntsc);
|
|
break;
|
|
case sunxi_monitor_none:
|
|
case sunxi_monitor_dvi:
|
|
case sunxi_monitor_hdmi:
|
|
case sunxi_monitor_lcd:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_COMPOSITE */
|
|
|
|
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 = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_SPI_CS),
|
|
.sck_pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_SPI_SCLK),
|
|
.sdi_pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_SPI_MOSI),
|
|
.sdo_pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_SPI_MISO),
|
|
.reset_pin = sunxi_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 */
|
|
|
|
#ifdef CONFIG_VIDEO_LCD_PANEL_I2C
|
|
static void sunxi_panel_i2c_init(struct sunxi_display_priv *sunxi_display)
|
|
{
|
|
const char *name = CONFIG_VIDEO_LCD_PANEL_I2C_NAME;
|
|
struct udevice *i2c_bus;
|
|
int ret;
|
|
|
|
ret = uclass_get_device_by_name(UCLASS_I2C, name, &i2c_bus);
|
|
if (ret)
|
|
return;
|
|
|
|
if (IS_ENABLED(CONFIG_VIDEO_LCD_PANEL_EDP_4_LANE_1620M_VIA_ANX9804)) {
|
|
/*
|
|
* The anx9804 needs 1.8V from eldo3, we do this here
|
|
* and not via CONFIG_AXP_ELDO3_VOLT from board_init()
|
|
* to avoid turning this on when using hdmi output.
|
|
*/
|
|
axp_set_eldo(3, 1800);
|
|
anx9804_init(i2c_bus, 4,
|
|
ANX9804_DATA_RATE_1620M,
|
|
sunxi_display->depth);
|
|
}
|
|
if (IS_ENABLED(CONFIG_VIDEO_LCD_TL059WV5C0)) {
|
|
struct udevice *chip;
|
|
|
|
ret = i2c_get_chip(i2c_bus, 0x5c, 1, &chip);
|
|
if (ret)
|
|
return;
|
|
|
|
dm_i2c_reg_write(chip, 0x04, 0x42); /* Turn on the LCD */
|
|
}
|
|
}
|
|
#else
|
|
static void sunxi_panel_i2c_init(struct sunxi_display_priv *sunxi_display) {}
|
|
#endif
|
|
|
|
static void sunxi_engines_init(void)
|
|
{
|
|
sunxi_composer_init();
|
|
sunxi_lcdc_init();
|
|
sunxi_drc_init();
|
|
}
|
|
|
|
static void sunxi_mode_set(struct sunxi_display_priv *sunxi_display,
|
|
const struct ctfb_res_modes *mode,
|
|
unsigned int address)
|
|
{
|
|
enum sunxi_monitor monitor = sunxi_display->monitor;
|
|
int __maybe_unused clk_div, clk_double;
|
|
struct sunxi_lcdc_reg * const lcdc =
|
|
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
|
|
struct sunxi_tve_reg * __maybe_unused const tve =
|
|
(struct sunxi_tve_reg *)SUNXI_TVE0_BASE;
|
|
|
|
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, monitor);
|
|
sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 0, monitor);
|
|
sunxi_hdmi_mode_set(mode, clk_div, clk_double, monitor);
|
|
sunxi_composer_enable();
|
|
lcdc_enable(lcdc, sunxi_display->depth);
|
|
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_PANEL_I2C))
|
|
sunxi_panel_i2c_init(sunxi_display);
|
|
sunxi_composer_mode_set(mode, address, monitor);
|
|
sunxi_lcdc_tcon0_mode_set(sunxi_display, mode, false);
|
|
sunxi_composer_enable();
|
|
lcdc_enable(lcdc, sunxi_display->depth);
|
|
#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, monitor);
|
|
sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 1, monitor);
|
|
sunxi_tvencoder_mode_set(monitor);
|
|
sunxi_composer_enable();
|
|
lcdc_enable(lcdc, sunxi_display->depth);
|
|
tvencoder_enable(tve);
|
|
#elif defined CONFIG_VIDEO_VGA_VIA_LCD
|
|
sunxi_composer_mode_set(mode, address, monitor);
|
|
sunxi_lcdc_tcon0_mode_set(sunxi_display, mode, true);
|
|
sunxi_composer_enable();
|
|
lcdc_enable(lcdc, sunxi_display->depth);
|
|
sunxi_vga_external_dac_enable();
|
|
#endif
|
|
break;
|
|
case sunxi_monitor_composite_pal:
|
|
case sunxi_monitor_composite_ntsc:
|
|
case sunxi_monitor_composite_pal_m:
|
|
case sunxi_monitor_composite_pal_nc:
|
|
#ifdef CONFIG_VIDEO_COMPOSITE
|
|
sunxi_composer_mode_set(mode, address, monitor);
|
|
sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 0, monitor);
|
|
sunxi_tvencoder_mode_set(monitor);
|
|
sunxi_composer_enable();
|
|
lcdc_enable(lcdc, sunxi_display->depth);
|
|
tvencoder_enable(tve);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const char *sunxi_get_mon_desc(enum sunxi_monitor monitor)
|
|
{
|
|
switch (monitor) {
|
|
case sunxi_monitor_dvi: return "dvi";
|
|
case sunxi_monitor_hdmi: return "hdmi";
|
|
case sunxi_monitor_lcd: return "lcd";
|
|
case sunxi_monitor_vga: return "vga";
|
|
case sunxi_monitor_composite_pal: return "composite-pal";
|
|
case sunxi_monitor_composite_ntsc: return "composite-ntsc";
|
|
case sunxi_monitor_composite_pal_m: return "composite-pal-m";
|
|
case sunxi_monitor_composite_pal_nc: return "composite-pal-nc";
|
|
case sunxi_monitor_none: /* fall through */
|
|
default: return "none";
|
|
}
|
|
}
|
|
|
|
static bool sunxi_has_hdmi(void)
|
|
{
|
|
#ifdef CONFIG_VIDEO_HDMI
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool sunxi_has_lcd(void)
|
|
{
|
|
char *lcd_mode = CONFIG_VIDEO_LCD_MODE;
|
|
|
|
return lcd_mode[0] != 0;
|
|
}
|
|
|
|
static bool sunxi_has_vga(void)
|
|
{
|
|
#if defined CONFIG_VIDEO_VGA || defined CONFIG_VIDEO_VGA_VIA_LCD
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static bool sunxi_has_composite(void)
|
|
{
|
|
#ifdef CONFIG_VIDEO_COMPOSITE
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static enum sunxi_monitor sunxi_get_default_mon(bool allow_hdmi)
|
|
{
|
|
if (allow_hdmi && sunxi_has_hdmi())
|
|
return sunxi_monitor_dvi;
|
|
else if (sunxi_has_lcd())
|
|
return sunxi_monitor_lcd;
|
|
else if (sunxi_has_vga())
|
|
return sunxi_monitor_vga;
|
|
else if (sunxi_has_composite())
|
|
return sunxi_monitor_composite_pal;
|
|
else
|
|
return sunxi_monitor_none;
|
|
}
|
|
|
|
static int sunxi_de_probe(struct udevice *dev)
|
|
{
|
|
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
|
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
|
|
struct sunxi_display_priv *sunxi_display = dev_get_priv(dev);
|
|
const struct ctfb_res_modes *mode;
|
|
struct ctfb_res_modes custom;
|
|
const char *options;
|
|
#ifdef CONFIG_VIDEO_HDMI
|
|
int hpd, hpd_delay, edid;
|
|
bool hdmi_present;
|
|
#endif
|
|
int i, overscan_offset, overscan_x, overscan_y;
|
|
unsigned int fb_dma_addr;
|
|
char mon[16];
|
|
char *lcd_mode = CONFIG_VIDEO_LCD_MODE;
|
|
|
|
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);
|
|
#endif
|
|
overscan_x = video_get_option_int(options, "overscan_x", -1);
|
|
overscan_y = video_get_option_int(options, "overscan_y", -1);
|
|
sunxi_display->monitor = sunxi_get_default_mon(true);
|
|
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. */
|
|
hdmi_present = (sunxi_hdmi_hpd_detect(hpd_delay) == 1);
|
|
if (hdmi_present && edid) {
|
|
printf("HDMI connected: ");
|
|
if (sunxi_hdmi_edid_get_mode(sunxi_display, &custom, true) == 0)
|
|
mode = &custom;
|
|
else
|
|
hdmi_present = false;
|
|
}
|
|
/* Fall back to EDID in case HPD failed */
|
|
if (edid && !hdmi_present) {
|
|
if (sunxi_hdmi_edid_get_mode(sunxi_display, &custom, false) == 0) {
|
|
mode = &custom;
|
|
hdmi_present = true;
|
|
}
|
|
}
|
|
/* Shut down when display was not found */
|
|
if ((hpd || edid) && !hdmi_present) {
|
|
sunxi_hdmi_shutdown();
|
|
sunxi_display->monitor = sunxi_get_default_mon(false);
|
|
} /* else continue with hdmi/dvi without a cable connected */
|
|
}
|
|
#endif
|
|
|
|
switch (sunxi_display->monitor) {
|
|
case sunxi_monitor_none:
|
|
printf("Unknown monitor\n");
|
|
return -EINVAL;
|
|
case sunxi_monitor_dvi:
|
|
case sunxi_monitor_hdmi:
|
|
if (!sunxi_has_hdmi()) {
|
|
printf("HDMI/DVI not supported on this board\n");
|
|
sunxi_display->monitor = sunxi_monitor_none;
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
case sunxi_monitor_lcd:
|
|
if (!sunxi_has_lcd()) {
|
|
printf("LCD not supported on this board\n");
|
|
sunxi_display->monitor = sunxi_monitor_none;
|
|
return -EINVAL;
|
|
}
|
|
sunxi_display->depth = video_get_params(&custom, lcd_mode);
|
|
mode = &custom;
|
|
break;
|
|
case sunxi_monitor_vga:
|
|
if (!sunxi_has_vga()) {
|
|
printf("VGA not supported on this board\n");
|
|
sunxi_display->monitor = sunxi_monitor_none;
|
|
return -EINVAL;
|
|
}
|
|
sunxi_display->depth = 18;
|
|
break;
|
|
case sunxi_monitor_composite_pal:
|
|
case sunxi_monitor_composite_ntsc:
|
|
case sunxi_monitor_composite_pal_m:
|
|
case sunxi_monitor_composite_pal_nc:
|
|
if (!sunxi_has_composite()) {
|
|
printf("Composite video not supported on this board\n");
|
|
sunxi_display->monitor = sunxi_monitor_none;
|
|
return -EINVAL;
|
|
}
|
|
if (sunxi_display->monitor == sunxi_monitor_composite_pal ||
|
|
sunxi_display->monitor == sunxi_monitor_composite_pal_nc)
|
|
mode = &composite_video_modes[0];
|
|
else
|
|
mode = &composite_video_modes[1];
|
|
sunxi_display->depth = 24;
|
|
break;
|
|
}
|
|
|
|
/* Yes these defaults are quite high, overscan on composite sucks... */
|
|
if (overscan_x == -1)
|
|
overscan_x = sunxi_is_composite(sunxi_display->monitor) ? 32 : 0;
|
|
if (overscan_y == -1)
|
|
overscan_y = sunxi_is_composite(sunxi_display->monitor) ? 20 : 0;
|
|
|
|
sunxi_display->fb_size = plat->size;
|
|
overscan_offset = (overscan_y * mode->xres + overscan_x) * 4;
|
|
/* We want to keep the fb_base for simplefb page aligned, where as
|
|
* the sunxi dma engines will happily accept an unaligned address. */
|
|
if (overscan_offset)
|
|
sunxi_display->fb_size += 0x1000;
|
|
|
|
printf("Setting up a %dx%d%s %s console (overscan %dx%d)\n",
|
|
mode->xres, mode->yres,
|
|
(mode->vmode == FB_VMODE_INTERLACED) ? "i" : "",
|
|
sunxi_get_mon_desc(sunxi_display->monitor),
|
|
overscan_x, overscan_y);
|
|
|
|
sunxi_display->fb_addr = plat->base;
|
|
sunxi_engines_init();
|
|
|
|
#ifdef CONFIG_EFI_LOADER
|
|
efi_add_memory_map(sunxi_display->fb_addr, sunxi_display->fb_size,
|
|
EFI_RESERVED_MEMORY_TYPE);
|
|
#endif
|
|
|
|
fb_dma_addr = sunxi_display->fb_addr - CONFIG_SYS_SDRAM_BASE;
|
|
if (overscan_offset) {
|
|
fb_dma_addr += 0x1000 - (overscan_offset & 0xfff);
|
|
sunxi_display->fb_addr += ALIGN(overscan_offset, 0x1000);
|
|
memset((void *)sunxi_display->fb_addr, 0, sunxi_display->fb_size);
|
|
flush_cache(sunxi_display->fb_addr, sunxi_display->fb_size);
|
|
}
|
|
sunxi_mode_set(sunxi_display, mode, fb_dma_addr);
|
|
|
|
/* The members of struct video_priv to be set by the driver. */
|
|
uc_priv->bpix = VIDEO_BPP32;
|
|
uc_priv->xsize = mode->xres;
|
|
uc_priv->ysize = mode->yres;
|
|
|
|
video_set_flush_dcache(dev, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_de_bind(struct udevice *dev)
|
|
{
|
|
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
|
|
|
|
plat->size = LCD_MAX_WIDTH * LCD_MAX_HEIGHT * VNBYTES(LCD_MAX_LOG2_BPP);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct video_ops sunxi_de_ops = {
|
|
};
|
|
|
|
U_BOOT_DRIVER(sunxi_de) = {
|
|
.name = "sunxi_de",
|
|
.id = UCLASS_VIDEO,
|
|
.ops = &sunxi_de_ops,
|
|
.bind = sunxi_de_bind,
|
|
.probe = sunxi_de_probe,
|
|
.priv_auto = sizeof(struct sunxi_display_priv),
|
|
.flags = DM_FLAG_PRE_RELOC,
|
|
};
|
|
|
|
U_BOOT_DRVINFO(sunxi_de) = {
|
|
.name = "sunxi_de"
|
|
};
|
|
|
|
/*
|
|
* Simplefb support.
|
|
*/
|
|
#if defined(CONFIG_OF_BOARD_SETUP) && defined(CONFIG_VIDEO_DT_SIMPLEFB)
|
|
int sunxi_simplefb_setup(void *blob)
|
|
{
|
|
struct sunxi_display_priv *sunxi_display;
|
|
struct video_priv *uc_priv;
|
|
struct udevice *de;
|
|
int offset, ret;
|
|
u64 start, size;
|
|
const char *pipeline = NULL;
|
|
|
|
#ifdef CONFIG_MACH_SUN4I
|
|
#define PIPELINE_PREFIX "de_fe0-"
|
|
#else
|
|
#define PIPELINE_PREFIX
|
|
#endif
|
|
|
|
ret = uclass_find_device_by_name(UCLASS_VIDEO, "sunxi_de", &de);
|
|
if (ret) {
|
|
printf("DE not present\n");
|
|
return 0;
|
|
} else if (!device_active(de)) {
|
|
printf("DE is present but not probed\n");
|
|
return 0;
|
|
}
|
|
|
|
uc_priv = dev_get_uclass_priv(de);
|
|
sunxi_display = dev_get_priv(de);
|
|
|
|
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;
|
|
case sunxi_monitor_composite_pal:
|
|
case sunxi_monitor_composite_ntsc:
|
|
case sunxi_monitor_composite_pal_m:
|
|
case sunxi_monitor_composite_pal_nc:
|
|
pipeline = PIPELINE_PREFIX "de_be0-lcd0-tve0";
|
|
break;
|
|
}
|
|
|
|
offset = sunxi_simplefb_fdt_match(blob, pipeline);
|
|
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 = sunxi_display->fb_addr - start;
|
|
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, sunxi_display->fb_addr,
|
|
uc_priv->xsize, uc_priv->ysize,
|
|
VNBYTES(uc_priv->bpix) * uc_priv->xsize,
|
|
"x8r8g8b8");
|
|
if (ret)
|
|
eprintf("Cannot setup simplefb: Error setting properties\n");
|
|
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_OF_BOARD_SETUP && CONFIG_VIDEO_DT_SIMPLEFB */
|