diff --git a/arch/arm/include/asm/arch-sunxi/clock_sun6i.h b/arch/arm/include/asm/arch-sunxi/clock_sun6i.h index be9fcfda0e3..a414f69053d 100644 --- a/arch/arm/include/asm/arch-sunxi/clock_sun6i.h +++ b/arch/arm/include/asm/arch-sunxi/clock_sun6i.h @@ -67,12 +67,20 @@ struct sunxi_ccm_reg { u32 dram_pll_cfg; /* 0xf8 PLL_DDR cfg register, A33 only */ u32 mbus_reset; /* 0xfc MBUS reset control, A33 only */ u32 dram_clk_gate; /* 0x100 DRAM module gating */ +#ifdef CONFIG_MACH_SUN8I + u32 de_clk_cfg; /* 0x104 DE module clock */ +#else u32 be0_clk_cfg; /* 0x104 BE0 module clock */ +#endif u32 be1_clk_cfg; /* 0x108 BE1 module clock */ u32 fe0_clk_cfg; /* 0x10c FE0 module clock */ u32 fe1_clk_cfg; /* 0x110 FE1 module clock */ u32 mp_clk_cfg; /* 0x114 MP module clock */ +#ifdef CONFIG_MACH_SUN8I + u32 tcon0_clk_cfg; /* 0x118 TCON0 module clock */ +#else u32 lcd0_ch0_clk_cfg; /* 0x118 LCD0 CH0 module clock */ +#endif u32 lcd1_ch0_clk_cfg; /* 0x11c LCD1 CH0 module clock */ u32 reserved14[3]; u32 lcd0_ch1_clk_cfg; /* 0x12c LCD0 CH1 module clock */ @@ -85,7 +93,11 @@ struct sunxi_ccm_reg { u32 dmic_clk_cfg; /* 0x148 Digital Mic module clock*/ u32 reserved15; u32 hdmi_clk_cfg; /* 0x150 HDMI module clock */ +#ifdef CONFIG_MACH_SUN8I + u32 hdmi_slow_clk_cfg; /* 0x154 HDMI slow module clock */ +#else u32 ps_clk_cfg; /* 0x154 PS module clock */ +#endif u32 mtc_clk_cfg; /* 0x158 MTC module clock */ u32 mbus0_clk_cfg; /* 0x15c MBUS0 module clock */ u32 mbus1_clk_cfg; /* 0x160 MBUS1 module clock */ @@ -220,6 +232,15 @@ struct sunxi_ccm_reg { #define CCM_MIPI_PLL_CTRL_LDO_EN (0x3 << 22) #define CCM_MIPI_PLL_CTRL_EN (0x1 << 31) +#define CCM_PLL10_CTRL_M_SHIFT 0 +#define CCM_PLL10_CTRL_M_MASK (0xf << CCM_PLL10_CTRL_M_SHIFT) +#define CCM_PLL10_CTRL_M(n) ((((n) - 1) & 0xf) << 0) +#define CCM_PLL10_CTRL_N_SHIFT 8 +#define CCM_PLL10_CTRL_N_MASK (0x7f << CCM_PLL10_CTRL_N_SHIFT) +#define CCM_PLL10_CTRL_N(n) ((((n) - 1) & 0x7f) << 8) +#define CCM_PLL10_CTRL_INTEGER_MODE (0x1 << 24) +#define CCM_PLL10_CTRL_EN (0x1 << 31) + #define CCM_PLL11_CTRL_N(n) ((((n) - 1) & 0x3f) << 8) #define CCM_PLL11_CTRL_SIGMA_DELTA_EN (0x1 << 24) #define CCM_PLL11_CTRL_UPD (0x1 << 30) @@ -271,9 +292,12 @@ struct sunxi_ccm_reg { #define AHB_GATE_OFFSET_DRC0 25 #define AHB_GATE_OFFSET_DE_FE0 14 #define AHB_GATE_OFFSET_DE_BE0 12 +#define AHB_GATE_OFFSET_DE 12 #define AHB_GATE_OFFSET_HDMI 11 #define AHB_GATE_OFFSET_LCD1 5 #define AHB_GATE_OFFSET_LCD0 4 +#define AHB_GATE_OFFSET_TCON1 4 +#define AHB_GATE_OFFSET_TCON0 3 #define CCM_MMC_CTRL_M(x) ((x) - 1) #define CCM_MMC_CTRL_OCLK_DLY(x) ((x) << 8) @@ -346,6 +370,9 @@ struct sunxi_ccm_reg { #define CCM_LCD_CH0_CTRL_RST 0 #define CCM_LCD_CH0_CTRL_GATE (0x1 << 31) +#define CCM_TCON0_CTRL_GATE (0x1 << 31) +#define CCM_TCON0_CTRL_M(n) ((((n) - 1) & 0xf) << 0) + #define CCM_LCD_CH1_CTRL_M(n) ((((n) - 1) & 0xf) << 0) #define CCM_LCD_CH1_CTRL_HALF_SCLK1 0 /* no seperate sclk1 & 2 on sun6i */ #define CCM_LCD_CH1_CTRL_PLL3 (0 << 24) @@ -355,6 +382,7 @@ struct sunxi_ccm_reg { #define CCM_LCD_CH1_CTRL_GATE (0x1 << 31) #define CCM_HDMI_CTRL_M(n) ((((n) - 1) & 0xf) << 0) +#define CCM_HDMI_CTRL_M_MASK (0xf << 0) #define CCM_HDMI_CTRL_PLL_MASK (3 << 24) #define CCM_HDMI_CTRL_PLL3 (0 << 24) #define CCM_HDMI_CTRL_PLL7 (1 << 24) @@ -363,6 +391,8 @@ struct sunxi_ccm_reg { #define CCM_HDMI_CTRL_DDC_GATE (0x1 << 30) #define CCM_HDMI_CTRL_GATE (0x1 << 31) +#define CCM_HDMI_SLOW_CTRL_DDC_GATE (1 << 31) + #if defined(CONFIG_MACH_SUN50I) #define MBUS_CLK_DEFAULT 0x81000002 /* PLL6x2 / 3 */ #elif defined(CONFIG_MACH_SUN8I) @@ -390,9 +420,13 @@ struct sunxi_ccm_reg { #define AHB_RESET_OFFSET_DRC0 25 #define AHB_RESET_OFFSET_DE_FE0 14 #define AHB_RESET_OFFSET_DE_BE0 12 +#define AHB_RESET_OFFSET_DE 12 #define AHB_RESET_OFFSET_HDMI 11 +#define AHB_RESET_OFFSET_HDMI2 10 #define AHB_RESET_OFFSET_LCD1 5 #define AHB_RESET_OFFSET_LCD0 4 +#define AHB_RESET_OFFSET_TCON1 4 +#define AHB_RESET_OFFSET_TCON0 3 /* ahb_reset2 offsets */ #define AHB_RESET_OFFSET_EPHY 2 @@ -406,6 +440,7 @@ struct sunxi_ccm_reg { /* CCM bits common to all Display Engine (and IEP) clock ctrl regs */ #define CCM_DE_CTRL_M(n) ((((n) - 1) & 0xf) << 0) +#ifndef CONFIG_MACH_SUN8I #define CCM_DE_CTRL_PLL_MASK (0xf << 24) #define CCM_DE_CTRL_PLL3 (0 << 24) #define CCM_DE_CTRL_PLL7 (1 << 24) @@ -413,6 +448,11 @@ struct sunxi_ccm_reg { #define CCM_DE_CTRL_PLL8 (3 << 24) #define CCM_DE_CTRL_PLL9 (4 << 24) #define CCM_DE_CTRL_PLL10 (5 << 24) +#else +#define CCM_DE_CTRL_PLL_MASK (3 << 24) +#define CCM_DE_CTRL_PLL6_2X (0 << 24) +#define CCM_DE_CTRL_PLL10 (1 << 24) +#endif #define CCM_DE_CTRL_GATE (1 << 31) /* CCU security switch, H3 only */ @@ -423,7 +463,9 @@ struct sunxi_ccm_reg { #ifndef __ASSEMBLY__ void clock_set_pll1(unsigned int hz); void clock_set_pll3(unsigned int hz); +void clock_set_pll3_factors(int m, int n); void clock_set_pll5(unsigned int clk, bool sigma_delta_enable); +void clock_set_pll10(unsigned int hz); void clock_set_pll11(unsigned int clk, bool sigma_delta_enable); void clock_set_mipi_pll(unsigned int hz); unsigned int clock_get_pll3(void); diff --git a/arch/arm/include/asm/arch-sunxi/cpu_sun4i.h b/arch/arm/include/asm/arch-sunxi/cpu_sun4i.h index 5f938309150..9758a142042 100644 --- a/arch/arm/include/asm/arch-sunxi/cpu_sun4i.h +++ b/arch/arm/include/asm/arch-sunxi/cpu_sun4i.h @@ -46,7 +46,9 @@ #define SUNXI_USB1_BASE 0x01c14000 #endif #define SUNXI_SS_BASE 0x01c15000 +#ifndef CONFIG_MACH_SUN8I_H3 #define SUNXI_HDMI_BASE 0x01c16000 +#endif #define SUNXI_SPI2_BASE 0x01c17000 #define SUNXI_SATA_BASE 0x01c18000 #ifdef CONFIG_SUNXI_GEN_SUN4I @@ -163,6 +165,10 @@ defined(CONFIG_MACH_SUN50I) #define SUNXI_MP_BASE 0x01e80000 #define SUNXI_AVG_BASE 0x01ea0000 +#ifdef CONFIG_MACH_SUN8I_H3 +#define SUNXI_HDMI_BASE 0x01ee0000 +#endif + #define SUNXI_RTC_BASE 0x01f00000 #define SUNXI_PRCM_BASE 0x01f01400 diff --git a/arch/arm/include/asm/arch-sunxi/display2.h b/arch/arm/include/asm/arch-sunxi/display2.h new file mode 100644 index 00000000000..755adeb64f0 --- /dev/null +++ b/arch/arm/include/asm/arch-sunxi/display2.h @@ -0,0 +1,261 @@ +/* + * Sunxi platform display controller register and constant defines + * + * (C) Copyright 2016 Jernej Skrabec + * + * Based on work by: + * Copyright (C) 2016 Jean-Francois Moine + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _SUNXI_DISPLAY_H +#define _SUNXI_DISPLAY_H + +struct sun8i_lcdc_reg { + u32 gctl; + u32 gint0; + u32 gint1; + u32 dum0[13]; + u32 tcon0_ctl; /* 0x40 */ + u32 dum1[19]; + u32 tcon1_ctl; /* 0x90 */ + u32 basic0; /* XI/YI */ + u32 basic1; /* LS_XO/LS_YO */ + u32 basic2; /* XO/YO */ + u32 basic3; /* HT/HBP */ + u32 basic4; /* VT/VBP */ + u32 basic5; /* HSPW/VSPW */ + u32 dum2; + u32 ps_sync; /* 0xb0 */ + u32 dum3[15]; + u32 io_pol; /* 0xf0 */ + u32 io_tri; + u32 dum4[2]; + + u32 ceu_ctl; /* 0x100 */ + u32 dum5[3]; + u32 ceu_rr; + u32 ceu_rg; + u32 ceu_rb; + u32 ceu_rc; + u32 ceu_gr; + u32 ceu_gg; + u32 ceu_gb; + u32 ceu_gc; + u32 ceu_br; + u32 ceu_bg; + u32 ceu_bb; + u32 ceu_bc; + u32 ceu_rv; + u32 ceu_gv; + u32 ceu_bv; + u32 dum6[45]; + + u32 mux_ctl; /* 0x200 */ + u32 dum7[63]; + + u32 fill_ctl; /* 0x300 */ + u32 fill_start0; + u32 fill_end0; + u32 fill_data0; +}; + +/* global control */ +struct de_glb { + u32 ctl; +#define DE_MUX_GLB_CTL_rt_en BIT(0) +#define DE_MUX_GLB_CTL_finish_irq_en BIT(4) +#define DE_MUX_GLB_CTL_rtwb_port BIT(12) + u32 status; + u32 dbuff; + u32 size; +}; + +/* alpha blending */ +struct de_bld { + u32 fcolor_ctl; /* 00 */ + struct { + u32 fcolor; + u32 insize; + u32 offset; + u32 dum; + } attr[4]; + u32 dum0[15]; /* (end of clear offset) */ + u32 route; /* 80 */ + u32 premultiply; + u32 bkcolor; + u32 output_size; + u32 bld_mode[4]; + u32 dum1[4]; + u32 ck_ctl; /* b0 */ + u32 ck_cfg; + u32 dum2[2]; + u32 ck_max[4]; /* c0 */ + u32 dum3[4]; + u32 ck_min[4]; /* e0 */ + u32 dum4[3]; + u32 out_ctl; /* fc */ +}; + +/* VI channel */ +struct de_vi { + struct { + u32 attr; +#define VI_CFG_ATTR_en BIT(0) +#define VI_CFG_ATTR_fcolor_en BIT(4) +#define VI_CFG_ATTR_fmt_SHIFT 8 +#define VI_CFG_ATTR_fmt_MASK GENMASK(12, 8) +#define VI_CFG_ATTR_ui_sel BIT(15) +#define VI_CFG_ATTR_top_down BIT(23) + u32 size; + u32 coord; +#define VI_N_PLANES 3 + u32 pitch[VI_N_PLANES]; + u32 top_laddr[VI_N_PLANES]; + u32 bot_laddr[VI_N_PLANES]; + } cfg[4]; + u32 fcolor[4]; /* c0 */ + u32 top_haddr[VI_N_PLANES]; /* d0 */ + u32 bot_haddr[VI_N_PLANES]; /* dc */ + u32 ovl_size[2]; /* e8 */ + u32 hori[2]; /* f0 */ + u32 vert[2]; /* f8 */ +}; + +struct de_ui { + struct { + u32 attr; +#define UI_CFG_ATTR_en BIT(0) +#define UI_CFG_ATTR_alpmod_SHIFT 1 +#define UI_CFG_ATTR_alpmod_MASK GENMASK(2, 1) +#define UI_CFG_ATTR_fcolor_en BIT(4) +#define UI_CFG_ATTR_fmt_SHIFT 8 +#define UI_CFG_ATTR_fmt_MASK GENMASK(12, 8) +#define UI_CFG_ATTR_top_down BIT(23) +#define UI_CFG_ATTR_alpha_SHIFT 24 +#define UI_CFG_ATTR_alpha_MASK GENMASK(31, 24) + u32 size; + u32 coord; + u32 pitch; + u32 top_laddr; + u32 bot_laddr; + u32 fcolor; + u32 dum; + } cfg[4]; /* 00 */ + u32 top_haddr; /* 80 */ + u32 bot_haddr; + u32 ovl_size; /* 88 */ +}; + +#define HDMI_EDID_BLOCK_SIZE 128 + +/* + * HDMI register addresses + */ +#define SUN8I_HDMI_PHY_CTRL_REG (u32*)(SUNXI_HDMI_BASE + 0x10020) +#define SUN8I_HDMI_PHY_UNK1_REG (u32*)(SUNXI_HDMI_BASE + 0x10024) +#define SUN8I_HDMI_PHY_UNK2_REG (u32*)(SUNXI_HDMI_BASE + 0x10028) +#define SUN8I_HDMI_PHY_PLL_REG (u32*)(SUNXI_HDMI_BASE + 0x1002c) +#define SUN8I_HDMI_PHY_CLK_REG (u32*)(SUNXI_HDMI_BASE + 0x10030) +#define SUN8I_HDMI_PHY_UNK3_REG (u32*)(SUNXI_HDMI_BASE + 0x10034) +#define SUN8I_HDMI_PHY_STATUS_REG (u32*)(SUNXI_HDMI_BASE + 0x10038) + +#define SUN8I_HDMI_IH_I2CM_STAT0 (u32*)(SUNXI_HDMI_BASE + 0x0013) + +#define SUN8I_HDMI_I2CM_SLAVE (u32*)(SUNXI_HDMI_BASE + 0x0EE0) +#define SUN8I_HDMI_I2CM_ADDRESS (u32*)(SUNXI_HDMI_BASE + 0x0EE1) +#define SUN8I_HDMI_I2CM_DATAI (u32*)(SUNXI_HDMI_BASE + 0x8EE1) +#define SUN8I_HDMI_I2CM_OPERATION (u32*)(SUNXI_HDMI_BASE + 0x0EE2) +#define SUN8I_HDMI_I2CM_INT (u32*)(SUNXI_HDMI_BASE + 0x0EE3) +#define SUN8I_HDMI_I2CM_DIV (u32*)(SUNXI_HDMI_BASE + 0x8EE3) +#define SUN8I_HDMI_I2CM_SEGADDR (u32*)(SUNXI_HDMI_BASE + 0x4EE0) +#define SUN8I_HDMI_I2CM_SOFTRSTZ (u32*)(SUNXI_HDMI_BASE + 0x4EE1) +#define SUN8I_HDMI_I2CM_SEGPTR (u32*)(SUNXI_HDMI_BASE + 0xCEE0) +#define SUN8I_HDMI_I2CM_SS_SCL_HCNT_0_ADDR (u32*)(SUNXI_HDMI_BASE + 0x4EE2) +#define SUN8I_HDMI_I2CM_SS_SCL_LCNT_0_ADDR (u32*)(SUNXI_HDMI_BASE + 0xCEE2) + +/* + * DE register constants. + */ +/* TODO: move to appropriate place */ +#define SUN8I_DE_BASE 0x01000000 +#define SUN8I_DE_GATE_REG (u32*)(SUN8I_DE_BASE + 0x0004) +#define SUN8I_DE_MOD_REG (u32*)(SUN8I_DE_BASE + 0x0000) +#define SUN8I_DE_RESET_REG (u32*)(SUN8I_DE_BASE + 0x0008) +#define SUN8I_DE_DIV_REG (u32*)(SUN8I_DE_BASE + 0x000c) +#define SUN8I_DE_SEL_REG (u32*)(SUN8I_DE_BASE + 0x0010) + +#define DE_MUX0_BASE (u8*)(SUN8I_DE_BASE + 0x00100000) +/* MUX registers (addr / MUX base) */ +#define DE_MUX_GLB_REGS 0x00000 /* global control */ +#define DE_MUX_BLD_REGS 0x01000 /* alpha blending */ +#define DE_MUX_CHAN_REGS 0x02000 /* VI/UI overlay channels */ +#define DE_MUX_CHAN_SZ 0x1000 /* size of a channel */ +#define DE_MUX_VSU_REGS 0x20000 /* VSU */ +#define DE_MUX_GSU1_REGS 0x30000 /* GSUs */ +#define DE_MUX_GSU2_REGS 0x40000 +#define DE_MUX_GSU3_REGS 0x50000 +#define DE_MUX_FCE_REGS 0xa0000 /* FCE */ +#define DE_MUX_BWS_REGS 0xa2000 /* BWS */ +#define DE_MUX_LTI_REGS 0xa4000 /* LTI */ +#define DE_MUX_PEAK_REGS 0xa6000 /* PEAK */ +#define DE_MUX_ASE_REGS 0xa8000 /* ASE */ +#define DE_MUX_FCC_REGS 0xaa000 /* FCC */ +#define DE_MUX_DCSC_REGS 0xb0000 /* DCSC/SMBL */ + +#define DE2_FORMAT_ARGB_8888 0 +#define DE2_FORMAT_BGRA_8888 3 +#define DE2_FORMAT_XRGB_8888 4 +#define DE2_FORMAT_RGB_888 8 +#define DE2_FORMAT_BGR_888 9 + +/* coordinates and sizes */ +#define XY(x, y) (((y) << 16) | (x)) +#define WH(w, h) (((h - 1) << 16) | (w - 1)) + +/* + * LCDC register constants. + */ +#define SUN8I_TCON_GCTL_TCON_En (1 << 31) +#define SUN8I_TCON_GINT0_TCON1_Vb_Int_En (1 << 30) +#define SUN8I_TCON_GINT0_TCON1_Vb_Int_Flag (1 << 14) +#define SUN8I_TCON0_CTL_TCON_En (1 << 31) +#define SUN8I_TCON1_CTL_TCON_En (1 << 31) +#define SUN8I_TCON1_CTL_Interlace_En (1 << 20) +#define SUN8I_TCON1_CTL_Start_Delay_SHIFT 4 +/*#define SUN8I_TCON1_CTL_Start_Delay_MASK GENMASK(8, 4)*/ +#define SUN8I_TCON1_IO_POL_IO0_inv (1 << 24) +#define SUN8I_TCON1_IO_POL_IO1_inv (1 << 25) +#define SUN8I_TCON1_IO_POL_IO2_inv (1 << 26) +#define SUN8I_TCON_CEU_CTL_ceu_en (1 << 31) + +#define SUNXI_LCDC_X(x) (((x) - 1) << 16) +#define SUNXI_LCDC_Y(y) (((y) - 1) << 0) +#define SUNXI_LCDC_TCON_VSYNC_MASK (1 << 24) +#define SUNXI_LCDC_TCON_HSYNC_MASK (1 << 25) +#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_TCON1_CTRL_SRC_BLUE (1 << 1) +#define SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(n) (((n) & 0x1f) << 4) +#define SUNXI_LCDC_TCON1_CTRL_INTERLACE_ENABLE (1 << 20) +#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) << 16) + +/* + * HDMI register constants. + */ +#define SUNXI_HDMI_HPD_DETECT (1 << 19) + +#define SUN8I_HMDI_DDC_CTRL_RESET (1 << 0) +#define SUN8I_HMDI_DDC_ADDR_SLAVE_ADDR (0x50 << 0) +#define SUN8I_HMDI_DDC_ADDR_SEG_ADDR (0x30 << 0) + + +#endif /* _SUNXI_DISPLAY_H */ diff --git a/arch/arm/mach-sunxi/clock_sun6i.c b/arch/arm/mach-sunxi/clock_sun6i.c index ed8cd9bbb37..bcf9aa146a5 100644 --- a/arch/arm/mach-sunxi/clock_sun6i.c +++ b/arch/arm/mach-sunxi/clock_sun6i.c @@ -141,6 +141,17 @@ void clock_set_pll3(unsigned int clk) &ccm->pll3_cfg); } +void clock_set_pll3_factors(int m, int n) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + + /* PLL3 rate = 24000000 * n / m */ + writel(CCM_PLL3_CTRL_EN | CCM_PLL3_CTRL_INTEGER_MODE | + CCM_PLL3_CTRL_N(n) | CCM_PLL3_CTRL_M(m), + &ccm->pll3_cfg); +} + void clock_set_pll5(unsigned int clk, bool sigma_delta_enable) { struct sunxi_ccm_reg * const ccm = @@ -213,6 +224,23 @@ void clock_set_mipi_pll(unsigned int clk) } #endif +void clock_set_pll10(unsigned int clk) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + const int m = 2; /* 12 MHz steps */ + + if (clk == 0) { + clrbits_le32(&ccm->pll10_cfg, CCM_PLL10_CTRL_EN); + return; + } + + /* PLL10 rate = 24000000 * n / m */ + writel(CCM_PLL10_CTRL_EN | CCM_PLL10_CTRL_INTEGER_MODE | + CCM_PLL10_CTRL_N(clk / (24000000 / m)) | CCM_PLL10_CTRL_M(m), + &ccm->pll10_cfg); +} + #ifdef CONFIG_MACH_SUN8I_A33 void clock_set_pll11(unsigned int clk, bool sigma_delta_enable) { @@ -267,6 +295,7 @@ unsigned int clock_get_mipi_pll(void) return ((src / 1000) * n * k / m) * 1000; } +#ifndef CONFIG_MACH_SUN8I void clock_set_de_mod_clock(u32 *clk_cfg, unsigned int hz) { int pll = clock_get_pll6() * 2; @@ -278,3 +307,4 @@ void clock_set_de_mod_clock(u32 *clk_cfg, unsigned int hz) writel(CCM_DE_CTRL_GATE | CCM_DE_CTRL_PLL6_2X | CCM_DE_CTRL_M(div), clk_cfg); } +#endif diff --git a/board/sunxi/Kconfig b/board/sunxi/Kconfig index c0ffeb3333f..77fdc8f19fd 100644 --- a/board/sunxi/Kconfig +++ b/board/sunxi/Kconfig @@ -458,7 +458,7 @@ config AXP_GPIO config VIDEO bool "Enable graphical uboot console on HDMI, LCD or VGA" - depends on !MACH_SUN8I_A83T && !MACH_SUN8I_H3 && !MACH_SUN9I && !MACH_SUN50I + depends on !MACH_SUN8I_A83T && !MACH_SUN9I && !MACH_SUN50I default y ---help--- Say Y here to add support for using a cfb console on the HDMI, LCD @@ -467,7 +467,7 @@ config VIDEO config VIDEO_HDMI bool "HDMI output support" - depends on VIDEO && !MACH_SUN8I + depends on VIDEO default y ---help--- Say Y here to add support for outputting video over HDMI. diff --git a/drivers/video/Makefile b/drivers/video/Makefile index db34904a9a2..d05b74514f2 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_VIDEO_OMAP3) += omap3_dss.o obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o obj-$(CONFIG_VIDEO_SM501) += sm501.o obj-$(CONFIG_VIDEO_SUNXI) += sunxi_display.o videomodes.o +obj-$(CONFIG_VIDEO_SUNXI_H3) += sun8i_display.o videomodes.o obj-$(CONFIG_VIDEO_TEGRA20) += tegra.o obj-$(CONFIG_VIDEO_VCXK) += bus_vcxk.o obj-$(CONFIG_VIDEO_VESA) += vesa.o diff --git a/drivers/video/sun8i_display.c b/drivers/video/sun8i_display.c new file mode 100644 index 00000000000..e596e4c2c9e --- /dev/null +++ b/drivers/video/sun8i_display.c @@ -0,0 +1,928 @@ +/* + * Display driver for sunxi Allwinner SoCs with DE2. + * + * Copyright (C) 2016 Jernej Skrabec + * + * Based on sunxi_display.c: + * (C) Copyright 2013-2014 Luc Verhaegen + * (C) Copyright 2014-2015 Hans de Goede + * + * Based on Linux DRM driver: + * Copyright (C) 2016 Jean-Francois Moine + * Copyright (c) 2016 Allwinnertech Co., Ltd. + * + * Based on rk_hdmi.c: + * Copyright (c) 2015 Google, Inc + * Copyright 2014 Rockchip Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +#include +#include +#include +#include +#include +#include +#include "videomodes.h" + +DECLARE_GLOBAL_DATA_PTR; + +enum sunxi_monitor { + sunxi_monitor_none, + sunxi_monitor_dvi, + sunxi_monitor_hdmi, +}; +#define SUNXI_MONITOR_LAST sunxi_monitor_hdmi + +struct sunxi_display { + GraphicDevice graphic_device; + enum sunxi_monitor monitor; + unsigned int depth; + unsigned int fb_addr; + unsigned int fb_size; +} sunxi_display; + +#ifdef CONFIG_VIDEO_HDMI + +static void sun8i_hdmi_phy_init(void) +{ + unsigned long tmo; + u32 tmp; + + writel(0, SUN8I_HDMI_PHY_CTRL_REG); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(0)); + udelay(5); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(16)); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(1)); + udelay(10); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(2)); + udelay(5); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(3)); + udelay(40); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(19)); + udelay(100); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(18)); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, 7 << 4); + + /* Note that Allwinner code doesn't fail in case of timeout */ + tmo = timer_get_us() + 2000; + while ((readl(SUN8I_HDMI_PHY_STATUS_REG) & 0x80) == 0) { + if (timer_get_us() > tmo) { + printf("Warning: HDMI phy init timeout!\n"); + break; + } + } + + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, 0xf << 8); + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, BIT(7)); + + writel(0x39dc5040, SUN8I_HDMI_PHY_PLL_REG); + writel(0x80084343, SUN8I_HDMI_PHY_CLK_REG); + udelay(10000); + writel(1, SUN8I_HDMI_PHY_UNK3_REG); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(25)); + udelay(100000); + tmp = (readl(SUN8I_HDMI_PHY_STATUS_REG) & 0x1f800) >> 11; + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(31) | BIT(30)); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, tmp); + writel(0x01FF0F7F, SUN8I_HDMI_PHY_CTRL_REG); + writel(0x80639000, SUN8I_HDMI_PHY_UNK1_REG); + writel(0x0F81C405, SUN8I_HDMI_PHY_UNK2_REG); + + /* enable read access to HDMI controller*/ + writel(0x54524545, SUNXI_HDMI_BASE + 0x10010); + + writeb(0x00, SUNXI_HDMI_BASE + 0x8080); + + udelay(1); + + writeb(0x00, SUNXI_HDMI_BASE + 0xF01F); + writeb(0xff, SUNXI_HDMI_BASE + 0x8403); + writeb(0xff, SUNXI_HDMI_BASE + 0x904C); + writeb(0xff, SUNXI_HDMI_BASE + 0x904E); + writeb(0xff, SUNXI_HDMI_BASE + 0xD04C); + writeb(0xff, SUNXI_HDMI_BASE + 0x8250); + writeb(0xff, SUNXI_HDMI_BASE + 0x8A50); + writeb(0xff, SUNXI_HDMI_BASE + 0x8272); + writeb(0xff, SUNXI_HDMI_BASE + 0x40C0); + writeb(0xff, SUNXI_HDMI_BASE + 0x86F0); + writeb(0xff, SUNXI_HDMI_BASE + 0x0EE3); + writeb(0xff, SUNXI_HDMI_BASE + 0x8EE2); + writeb(0xf0, SUNXI_HDMI_BASE + 0xA049); + writeb(0x1e, SUNXI_HDMI_BASE + 0xB045); + writeb(0x00, SUNXI_HDMI_BASE + 0x00C1); + writeb(0x03, SUNXI_HDMI_BASE + 0x00C1); + writeb(0x00, SUNXI_HDMI_BASE + 0x00C0); + writeb(0x10, SUNXI_HDMI_BASE + 0x40C1); + writeb(0xfd, SUNXI_HDMI_BASE + 0x0081); + writeb(0x00, SUNXI_HDMI_BASE + 0x0081); + writeb(0xfd, SUNXI_HDMI_BASE + 0x0081); + writeb(0xff, SUNXI_HDMI_BASE + 0x0010); + writeb(0xff, SUNXI_HDMI_BASE + 0x0011); + writeb(0xff, SUNXI_HDMI_BASE + 0x8010); + writeb(0xff, SUNXI_HDMI_BASE + 0x8011); + writeb(0xff, SUNXI_HDMI_BASE + 0x0013); + writeb(0xff, SUNXI_HDMI_BASE + 0x8012); + writeb(0xff, SUNXI_HDMI_BASE + 0x8013); +} + +static int sun8i_hdmi_hpd_detect(int hpd_delay) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + unsigned long tmo = timer_get_us() + hpd_delay * 1000; + int status = 0; + + /* Set pll3 to 297 MHz */ + clock_set_pll3(297000000); + + /* 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_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI); + setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI2); + setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI); + setbits_le32(&ccm->hdmi_slow_clk_cfg, CCM_HDMI_SLOW_CTRL_DDC_GATE); + + /* Clock on */ + setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE); + + sun8i_hdmi_phy_init(); + + while (timer_get_us() < tmo) { + if (readl(SUN8I_HDMI_PHY_STATUS_REG) & SUNXI_HDMI_HPD_DETECT) { + status = 1; + break; + } + } + + return status; +} + +static void sunxi_hdmi_shutdown(void) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + + writel(0, SUN8I_HDMI_PHY_CTRL_REG); + clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE); + clrbits_le32(&ccm->hdmi_slow_clk_cfg, CCM_HDMI_SLOW_CTRL_DDC_GATE); + clrbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI); + clrbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI); + clrbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI2); + clock_set_pll3(0); +} + +static int sun8i_hdmi_ddc_wait_i2c_done(int msec) +{ + u32 val; + ulong start; + + start = get_timer(0); + do { + val = readb(SUN8I_HDMI_IH_I2CM_STAT0); + writeb(val, SUN8I_HDMI_IH_I2CM_STAT0); + + if (val & 0x2) + return 0; + if (val & 0x1) + return -EIO; + + udelay(100); + } while (get_timer(start) < msec); + + return 1; +} + +static int sunxi_hdmi_ddc_read(int block, u8 *buf) +{ + int shift = (block % 2) * 0x80; + int trytime = 5; + int edid_read_err = 0; + u32 op = (block == 0) ? 1 : 2; + int n; + + writeb(block >> 1, SUN8I_HDMI_I2CM_SEGPTR); + + while (trytime--) { + edid_read_err = 0; + + for (n = 0; n < HDMI_EDID_BLOCK_SIZE; n++) { + writeb(shift + n, SUN8I_HDMI_I2CM_ADDRESS); + writeb(op, SUN8I_HDMI_I2CM_OPERATION); + + if (sun8i_hdmi_ddc_wait_i2c_done(10)) { + edid_read_err = 1; + break; + } + + *buf++ = readb(SUN8I_HDMI_I2CM_DATAI); + } + + if (!edid_read_err) + break; + } + + return edid_read_err; +} + +static int sunxi_hdmi_edid_get_block(int block, u8 *buf) +{ + int r, retries = 2; + + do { + r = sunxi_hdmi_ddc_read(block, buf); + 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; + int i, r, ext_blocks = 0; + + /* Reset i2c controller */ + writeb(0, SUN8I_HDMI_I2CM_SOFTRSTZ); + + writeb(0x05, SUN8I_HDMI_I2CM_DIV); + writeb(0x08, SUN8I_HDMI_I2CM_INT); + writeb(0xd8, SUN8I_HDMI_I2CM_SS_SCL_HCNT_0_ADDR); + writeb(0xfe, SUN8I_HDMI_I2CM_SS_SCL_LCNT_0_ADDR); + writeb(SUN8I_HMDI_DDC_ADDR_SLAVE_ADDR, SUN8I_HDMI_I2CM_SLAVE); + writeb(SUN8I_HMDI_DDC_ADDR_SEG_ADDR, SUN8I_HDMI_I2CM_SEGADDR); + + 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; + } + } + } + + 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 */ + +/* + * This is the entity that mixes and matches the different layers and inputs. + * Allwinner calls it display engine, but here is called composer. + */ +static void sunxi_composer_init(void) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + + clock_set_pll10(432000000); + + /* Set DE parent to pll10 */ + clrsetbits_le32(&ccm->de_clk_cfg, CCM_DE_CTRL_PLL_MASK, + CCM_DE_CTRL_PLL10); + + /* Set ahb gating to pass */ + setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DE); + setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE); + + /* Clock on */ + setbits_le32(&ccm->de_clk_cfg, CCM_DE_CTRL_GATE); +} + +static void sunxi_composer_mode_set(const struct ctfb_res_modes *mode, + unsigned int address) +{ + struct de_glb * const de_glb_regs = + (struct de_glb *)(DE_MUX0_BASE + DE_MUX_GLB_REGS); + struct de_bld * const de_bld_regs = + (struct de_bld *)(DE_MUX0_BASE + DE_MUX_BLD_REGS); + struct de_ui * const de_ui_regs = + (struct de_ui *)(DE_MUX0_BASE + DE_MUX_CHAN_REGS + + DE_MUX_CHAN_SZ * 1); + u32 size = WH(mode->xres, mode->yres); + int channel, i; + u32 data; + + /* enable clock */ + setbits_le32(SUN8I_DE_RESET_REG, 1); + setbits_le32(SUN8I_DE_GATE_REG, 1); + setbits_le32(SUN8I_DE_MOD_REG, 1); + + clrbits_le32(SUN8I_DE_SEL_REG, 1); + + writel(DE_MUX_GLB_CTL_rt_en, &de_glb_regs->ctl); + writel(0, &de_glb_regs->status); + writel(1, &de_glb_regs->dbuff); + writel(size, &de_glb_regs->size); + + for (channel = 0; channel < 4; channel++) { + void *chan = DE_MUX0_BASE + DE_MUX_CHAN_REGS + + DE_MUX_CHAN_SZ * channel; + memset(chan, 0, channel == 0 ? + sizeof(struct de_vi) : sizeof(struct de_ui)); + } + + memset(de_bld_regs, 0, 0x44); + writel(0x00000101, &de_bld_regs->fcolor_ctl); + + writel(1, &de_bld_regs->route); + + writel(0, &de_bld_regs->premultiply); + writel(0xff000000, &de_bld_regs->bkcolor); + + writel(0x03010301, &de_bld_regs->bld_mode[0]); + writel(0x03010301, &de_bld_regs->bld_mode[1]); + + writel(size, &de_bld_regs->output_size); + writel(mode->vmode & FB_VMODE_INTERLACED ? 2 : 0, + &de_bld_regs->out_ctl); + writel(0, &de_bld_regs->ck_ctl); + + for (i = 0; i < 4; i++) { + writel(0xff000000, &de_bld_regs->attr[i].fcolor); + writel(size, &de_bld_regs->attr[i].insize); + } + + writel(0, DE_MUX0_BASE + DE_MUX_VSU_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_GSU1_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_GSU2_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_GSU3_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_FCE_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_BWS_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_LTI_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_PEAK_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_ASE_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_FCC_REGS); + writel(0, DE_MUX0_BASE + DE_MUX_DCSC_REGS); + + data = UI_CFG_ATTR_en | (DE2_FORMAT_XRGB_8888 << UI_CFG_ATTR_fmt_SHIFT) | + (1 << UI_CFG_ATTR_alpmod_SHIFT) | (0xff << UI_CFG_ATTR_alpha_SHIFT); + writel(data, &de_ui_regs->cfg[0].attr); + writel(size, &de_ui_regs->cfg[0].size); + writel(0, &de_ui_regs->cfg[0].coord); + writel(4 * mode->xres, &de_ui_regs->cfg[0].pitch); + writel(address, &de_ui_regs->cfg[0].top_laddr); + writel(size, &de_ui_regs->ovl_size); +} + +static void sunxi_composer_enable(void) +{ + struct de_glb * const de_glb_regs = + (struct de_glb *)(DE_MUX0_BASE + DE_MUX_GLB_REGS); + + writel(1, &de_glb_regs->dbuff); +} + +/* + * LCDC, what allwinner calls a CRTC, so timing controller and serializer. + */ +static void sunxi_lcdc_pll_set(int dotclock, int *clk_div) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + int value, n, m, x = 0, diff; + int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF; + + if (dotclock <= 27000) + x = 11; + else if (dotclock <= 74250) + x = 4; + else if (dotclock <= 148500) + x = 2; + else if (dotclock <= 297000) + x = 1; + + /* + * 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 = 1; m <= 16; m++) { + n = (m * x * dotclock) / 24000; + + if ((n >= 1) && (n <= 128)) { + value = (24000 * n) / m / x; + diff = dotclock - value; + if (diff < best_diff) { + best_diff = diff; + best_m = m; + best_n = n; + } + } + } + + clock_set_pll3_factors(best_m, best_n); + printf("dotclock: %dkHz = %dkHz: (24MHz * %d) / %d / %d\n", + dotclock, (clock_get_pll3() / 1000) / x, + best_n, best_m, x); + + writel(CCM_TCON0_CTRL_GATE | CCM_TCON0_CTRL_M(x), + &ccm->tcon0_clk_cfg); + + *clk_div = x; +} + +static void sunxi_lcdc_init(void) +{ + struct sunxi_ccm_reg * const ccm = + (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + struct sun8i_lcdc_reg * const lcdc = + (struct sun8i_lcdc_reg *)SUNXI_LCD0_BASE; + + /* Reset off */ + setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_TCON0); + + /* Clock on */ + setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_TCON0); + setbits_le32(&ccm->tcon0_clk_cfg, CCM_TCON0_CTRL_GATE); + + /* Init lcdc */ + clrbits_le32(&lcdc->tcon0_ctl, SUN8I_TCON0_CTL_TCON_En); /* Disable tcon0 */ + clrbits_le32(&lcdc->gctl, SUN8I_TCON_GCTL_TCON_En); /* Disable tcon globally */ + writel(0, &lcdc->gint0); /* Disable all interrupts */ + + /* Set all io lines to tristate */ + writel(0x0fffffff, &lcdc->io_tri); +} + +static void sunxi_lcdc_enable(void) +{ + struct sun8i_lcdc_reg * const lcdc = + (struct sun8i_lcdc_reg *)SUNXI_LCD0_BASE; + + setbits_le32(&lcdc->gctl, SUN8I_TCON_GCTL_TCON_En); +} + +static int sunxi_lcdc_get_clk_delay(const struct ctfb_res_modes *mode, int tcon) +{ + int delay; + + delay = mode->lower_margin + mode->vsync_len + mode->upper_margin; + if (mode->vmode == FB_VMODE_INTERLACED) + delay /= 2; + if (tcon == 1) + delay -= 2; + + return (delay > 31) ? 31 : delay; +} + +#if defined CONFIG_VIDEO_HDMI +static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode, + int *clk_div) +{ + struct sun8i_lcdc_reg * const lcdc = + (struct sun8i_lcdc_reg *)SUNXI_LCD0_BASE; + int bp, clk_delay, total, yres; + + setbits_le32(&lcdc->gctl, SUN8I_TCON_GCTL_TCON_En); + + clk_delay = sunxi_lcdc_get_clk_delay(mode, 1); + writel(SUNXI_LCDC_TCON1_CTRL_ENABLE | + ((mode->vmode == FB_VMODE_INTERLACED) ? + SUNXI_LCDC_TCON1_CTRL_INTERLACE_ENABLE : 0) | + SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(clk_delay), &lcdc->tcon1_ctl); + + yres = mode->yres; + if (mode->vmode == FB_VMODE_INTERLACED) + yres /= 2; + writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(yres), + &lcdc->basic0); + writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(yres), + &lcdc->basic1); + writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(yres), + &lcdc->basic2); + + 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->basic3); + + bp = mode->vsync_len + mode->upper_margin; + total = mode->yres + mode->lower_margin + bp; + if (mode->vmode == FB_VMODE_NONINTERLACED) + total *= 2; + writel(SUNXI_LCDC_TCON1_TIMING_V_TOTAL(total) | + SUNXI_LCDC_TCON1_TIMING_V_BP(bp), &lcdc->basic4); + + writel(SUNXI_LCDC_X(mode->hsync_len) | SUNXI_LCDC_Y(mode->vsync_len), + &lcdc->basic5); + + writel(0, &lcdc->ceu_ctl); + writel(0, &lcdc->fill_ctl); + + sunxi_lcdc_pll_set(mode->pixclock_khz, clk_div); +} +#endif /* CONFIG_VIDEO_HDMI */ + +#ifdef CONFIG_VIDEO_HDMI + +static void sunxi_hdmi_setup_info_frames(const struct ctfb_res_modes *mode) +{ + u8 tmp; + + if (mode->pixclock_khz <= 27000) + tmp = 0x40; /* SD-modes, ITU601 colorspace */ + else + tmp = 0x80; /* HD-modes, ITU709 colorspace */ + + if (mode->xres * 100 / mode->yres < 156) + tmp |= 0x18; /* 4 : 3 */ + else + tmp |= 0x28; /* 16 : 9 */ + + setbits_8(SUNXI_HDMI_BASE + 0x0040, 0x08); + writeb(0x60, SUNXI_HDMI_BASE + 0x4045); + writeb(tmp, SUNXI_HDMI_BASE + 0xC044); + writeb(0x88, SUNXI_HDMI_BASE + 0xC045); +} + +static int hdmi_phy_set(u32 divider) +{ + u32 tmp; + + switch(divider) + { + case 1: + writel(0x30dc5fc0, SUN8I_HDMI_PHY_PLL_REG); + writel(0x800863C0, SUN8I_HDMI_PHY_CLK_REG); + mdelay(10); + writel(0x00000001, SUN8I_HDMI_PHY_UNK3_REG); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(25)); + mdelay(200); + tmp = (readl(SUN8I_HDMI_PHY_STATUS_REG) & 0x1f800) >> 11; + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(31) | BIT(30)); + if (tmp < 0x3d) + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, tmp + 2); + else + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, 0x3f); + mdelay(100); + writel(0x01FFFF7F, SUN8I_HDMI_PHY_CTRL_REG); + writel(0x8063b000, SUN8I_HDMI_PHY_UNK1_REG); + writel(0x0F8246B5, SUN8I_HDMI_PHY_UNK2_REG); + break; + case 2: + writel(0x39dc5040, SUN8I_HDMI_PHY_PLL_REG); + writel(0x80084381, SUN8I_HDMI_PHY_CLK_REG); + mdelay(10); + writel(0x00000001, SUN8I_HDMI_PHY_UNK3_REG); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(25)); + mdelay(100); + tmp = (readl(SUN8I_HDMI_PHY_STATUS_REG) & 0x1f800) >> 11; + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(31) | BIT(30)); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, tmp); + writel(0x01FFFF7F, SUN8I_HDMI_PHY_CTRL_REG); + writel(0x8063a800, SUN8I_HDMI_PHY_UNK1_REG); + writel(0x0F81C485, SUN8I_HDMI_PHY_UNK2_REG); + break; + case 4: + writel(0x39dc5040, SUN8I_HDMI_PHY_PLL_REG); + writel(0x80084343, SUN8I_HDMI_PHY_CLK_REG); + mdelay(10); + writel(0x00000001, SUN8I_HDMI_PHY_UNK3_REG); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(25)); + mdelay(100); + tmp = (readl(SUN8I_HDMI_PHY_STATUS_REG) & 0x1f800) >> 11; + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(31) | BIT(30)); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, tmp); + writel(0x01FFFF7F, SUN8I_HDMI_PHY_CTRL_REG); + writel(0x8063b000, SUN8I_HDMI_PHY_UNK1_REG); + writel(0x0F81C405, SUN8I_HDMI_PHY_UNK2_REG); + break; + case 11: + writel(0x39dc5040, SUN8I_HDMI_PHY_PLL_REG); + writel(0x8008430a, SUN8I_HDMI_PHY_CLK_REG); + mdelay(10); + writel(0x00000001, SUN8I_HDMI_PHY_UNK3_REG); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(25)); + mdelay(100); + tmp = (readl(SUN8I_HDMI_PHY_STATUS_REG) & 0x1f800) >> 11; + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, BIT(31) | BIT(30)); + setbits_le32(SUN8I_HDMI_PHY_PLL_REG, tmp); + writel(0x01FFFF7F, SUN8I_HDMI_PHY_CTRL_REG); + writel(0x8063b000, SUN8I_HDMI_PHY_UNK1_REG); + writel(0x0F81C405, SUN8I_HDMI_PHY_UNK2_REG); + break; + default: + return -1; + } + + return 0; +} + +static void sunxi_hdmi_mode_set(const struct ctfb_res_modes *mode, + int clk_div) +{ + u8 invidconf, v_blanking; + u32 h_blanking; + + if(hdmi_phy_set(clk_div) != 0) { + printf("HDMI divider is invalid!\n"); + return; + } + + invidconf = 0; + if(mode->vmode & FB_VMODE_INTERLACED) + invidconf |= 0x01; + if(mode->sync & FB_SYNC_HOR_HIGH_ACT) + invidconf |= 0x20; + if(mode->sync & FB_SYNC_VERT_HIGH_ACT) + invidconf |= 0x40; + + h_blanking = mode->left_margin + mode->right_margin + mode->hsync_len; + v_blanking = mode->upper_margin + mode->lower_margin + mode->vsync_len; + + writeb(invidconf | 0x10, SUNXI_HDMI_BASE + 0x0040); + writeb(((invidconf < 96) ? 0x03 : 0x00), SUNXI_HDMI_BASE + 0x10001); + + writeb(mode->xres >> 8, SUNXI_HDMI_BASE + 0x8040); + writeb(mode->xres, SUNXI_HDMI_BASE + 0x0041); + writeb(mode->yres >> 8, SUNXI_HDMI_BASE + 0x8042); + writeb(mode->yres, SUNXI_HDMI_BASE + 0x0043); + writeb(mode->vsync_len, SUNXI_HDMI_BASE + 0x4043); + writeb(h_blanking >> 8, SUNXI_HDMI_BASE + 0x0042); + writeb(h_blanking, SUNXI_HDMI_BASE + 0x8041); + writeb(mode->lower_margin, SUNXI_HDMI_BASE + 0x4042); + writeb(mode->right_margin >> 8, SUNXI_HDMI_BASE + 0x4041); + writeb(mode->right_margin, SUNXI_HDMI_BASE + 0x4040); + writeb(mode->hsync_len >> 8, SUNXI_HDMI_BASE + 0xC041); + writeb(mode->hsync_len, SUNXI_HDMI_BASE + 0xC040); + writeb(v_blanking, SUNXI_HDMI_BASE + 0x8043); + + writeb(0x0c, SUNXI_HDMI_BASE + 0x0045); + writeb(0x20, SUNXI_HDMI_BASE + 0x8044); + writeb(0x01, SUNXI_HDMI_BASE + 0x8045); + writeb(0x0b, SUNXI_HDMI_BASE + 0x0046); + writeb(0x16, SUNXI_HDMI_BASE + 0x0047); + writeb(0x21, SUNXI_HDMI_BASE + 0x8046); + + writeb(0x40, SUNXI_HDMI_BASE + 0x0401); + writeb(0x07, SUNXI_HDMI_BASE + 0x8400); + + // default value, written 0 by rk_hdmi + writeb(0x00, SUNXI_HDMI_BASE + 0x8401); + + writeb(0x47, SUNXI_HDMI_BASE + 0x0402); + writeb(0x01, SUNXI_HDMI_BASE + 0x0800); + writeb(0x07, SUNXI_HDMI_BASE + 0x0801); + writeb(0x00, SUNXI_HDMI_BASE + 0x8800); + writeb(0x00, SUNXI_HDMI_BASE + 0x8801); + writeb(0x00, SUNXI_HDMI_BASE + 0x0802); + writeb(0x00, SUNXI_HDMI_BASE + 0x0803); + writeb(0x00, SUNXI_HDMI_BASE + 0x8802); + writeb(0x00, SUNXI_HDMI_BASE + 0x8803); + + if (sunxi_display.monitor == sunxi_monitor_hdmi) + sunxi_hdmi_setup_info_frames(mode); + + writeb(0x00, SUNXI_HDMI_BASE + 0x0082); + writeb(0x00, SUNXI_HDMI_BASE + 0x0081); +} + +static void sunxi_hdmi_enable(void) +{ + setbits_le32(SUN8I_HDMI_PHY_CTRL_REG, 0xf << 12); + printf("hdmi enabled\n"); +} + +#endif /* CONFIG_VIDEO_HDMI */ + +static void sunxi_engines_init(void) +{ + sunxi_composer_init(); + sunxi_lcdc_init(); +} + +static void sunxi_mode_set(const struct ctfb_res_modes *mode, + unsigned int address) +{ + int __maybe_unused clk_div; + + 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_tcon0_mode_set(mode, &clk_div); + sunxi_hdmi_mode_set(mode, clk_div); + sunxi_composer_enable(); + sunxi_lcdc_enable(); + sunxi_hdmi_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"; + } + return NULL; /* never reached */ +} + +ulong board_get_usable_ram_top(ulong total_size) +{ + return gd->ram_top - CONFIG_SUNXI_MAX_FB_SIZE; +} + +static bool sunxi_has_hdmi(void) +{ +#ifdef CONFIG_VIDEO_HDMI + 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 + return sunxi_monitor_none; +} + +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 + int i, overscan_offset, overscan_x, overscan_y; + unsigned int fb_dma_addr; + char mon[16]; + + 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); +#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. */ + ret = sun8i_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(); + 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: + return NULL; + 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 NULL; + } + break; + } + + if (overscan_x == -1) + overscan_x = 0; + if (overscan_y == -1) + overscan_y = 0; + + sunxi_display.fb_size = + (mode->xres * mode->yres * 4 + 0xfff) & ~0xfff; + 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; + + 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; + } + + 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); + + gd->fb_base = gd->bd->bi_dram[0].start + + gd->bd->bi_dram[0].size - sunxi_display.fb_size; + sunxi_engines_init(); + + fb_dma_addr = gd->fb_base; + sunxi_display.fb_addr = gd->fb_base; + if (overscan_offset) { + fb_dma_addr += 0x1000 - (overscan_offset & 0xfff); + sunxi_display.fb_addr += (overscan_offset + 0xfff) & ~0xfff; + memset((void *)gd->fb_base, 0, sunxi_display.fb_size); + flush_cache(gd->fb_base, sunxi_display.fb_size); + } + sunxi_mode_set(mode, fb_dma_addr); + + /* + * These are the only members of this structure that are used. All the + * others are driver specific. The pitch is stored in plnSizeX. + */ + graphic_device->frameAdrs = sunxi_display.fb_addr; + graphic_device->gdfIndex = GDF_32BIT_X888RGB; + graphic_device->gdfBytesPP = 4; + graphic_device->winSizeX = mode->xres - 2 * overscan_x; + graphic_device->winSizeY = mode->yres - 2 * overscan_y; + graphic_device->plnSizeX = mode->xres * graphic_device->gdfBytesPP; + + return graphic_device; +} diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h index e0464df0388..c63c1bc1ddc 100644 --- a/include/configs/sunxi-common.h +++ b/include/configs/sunxi-common.h @@ -284,9 +284,13 @@ extern int soft_i2c_gpio_scl; #define CONFIG_SUNXI_MAX_FB_SIZE (16 << 20) /* Do we want to initialize a simple FB? */ +#ifndef CONFIG_MACH_SUN8I_H3 #define CONFIG_VIDEO_DT_SIMPLEFB #define CONFIG_VIDEO_SUNXI +#else +#define CONFIG_VIDEO_SUNXI_H3 +#endif #define CONFIG_VIDEO_LOGO #define CONFIG_VIDEO_STD_TIMINGS diff --git a/scripts/config_whitelist.txt b/scripts/config_whitelist.txt index 11b5a221ee6..cef476f2cf6 100644 --- a/scripts/config_whitelist.txt +++ b/scripts/config_whitelist.txt @@ -8270,6 +8270,7 @@ CONFIG_VIDEO_SM501_8BPP CONFIG_VIDEO_SM501_PCI CONFIG_VIDEO_STD_TIMINGS CONFIG_VIDEO_SUNXI +CONFIG_VIDEO_SUNXI_H3 CONFIG_VIDEO_VCXK CONFIG_VID_FLS_ENV CONFIG_VM86