Add Allwinner A20 HDMI support.
The HDMI driver will attach a framebuffer device when a display is connected. If the EDID can be read and contains a preferred mode, it will be used. Otherwise the framebuffer will default to 800x600. In addition, if the EDID contains a CEA-861 extension block and the "basic audio" flag is set, audio playback at 48kHz 16-bit stereo is enabled on the controller. Reviewed by: andrew Approved by: gonzo (mentor) Differential Revision: https://reviews.freebsd.org/D5383
This commit is contained in:
parent
4de485fe5f
commit
dd03610a2e
@ -43,6 +43,18 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
#include "a10_clk.h"
|
||||
|
||||
#define TCON_PLL_WORST 1000000
|
||||
#define TCON_PLL_N_MIN 1
|
||||
#define TCON_PLL_N_MAX 15
|
||||
#define TCON_PLL_M_MIN 9
|
||||
#define TCON_PLL_M_MAX 127
|
||||
#define TCON_PLLREF_SINGLE 3000 /* kHz */
|
||||
#define TCON_PLLREF_DOUBLE 6000 /* kHz */
|
||||
#define TCON_RATE_KHZ(rate_hz) ((rate_hz) / 1000)
|
||||
#define TCON_RATE_HZ(rate_khz) ((rate_khz) * 1000)
|
||||
#define HDMI_DEFAULT_RATE 297000000
|
||||
#define DEBE_DEFAULT_RATE 300000000
|
||||
|
||||
struct a10_ccm_softc {
|
||||
struct resource *res;
|
||||
bus_space_tag_t bst;
|
||||
@ -307,6 +319,47 @@ a10_clk_pll2_set_rate(unsigned int freq)
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10_clk_pll3_set_rate(unsigned int freq)
|
||||
{
|
||||
struct a10_ccm_softc *sc;
|
||||
uint32_t reg_value;
|
||||
int m;
|
||||
|
||||
sc = a10_ccm_sc;
|
||||
if (sc == NULL)
|
||||
return (ENXIO);
|
||||
|
||||
if (freq == 0) {
|
||||
/* Disable PLL3 */
|
||||
ccm_write_4(sc, CCM_PLL3_CFG, 0);
|
||||
return (0);
|
||||
}
|
||||
|
||||
m = freq / TCON_RATE_HZ(TCON_PLLREF_SINGLE);
|
||||
|
||||
reg_value = CCM_PLL_CFG_ENABLE | CCM_PLL3_CFG_MODE_SEL_INT | m;
|
||||
ccm_write_4(sc, CCM_PLL3_CFG, reg_value);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
a10_clk_pll5x_get_rate(void)
|
||||
{
|
||||
struct a10_ccm_softc *sc;
|
||||
uint32_t k, n, p, reg_value;
|
||||
|
||||
sc = a10_ccm_sc;
|
||||
reg_value = ccm_read_4(sc, CCM_PLL5_CFG);
|
||||
n = ((reg_value & CCM_PLL_CFG_FACTOR_N) >> CCM_PLL_CFG_FACTOR_N_SHIFT);
|
||||
k = ((reg_value & CCM_PLL_CFG_FACTOR_K) >> CCM_PLL_CFG_FACTOR_K_SHIFT) +
|
||||
1;
|
||||
p = ((reg_value & CCM_PLL5_CFG_OUT_EXT_DIV_P) >> CCM_PLL5_CFG_OUT_EXT_DIV_P_SHIFT);
|
||||
|
||||
return ((CCM_CLK_REF_FREQ * n * k) >> p);
|
||||
}
|
||||
|
||||
int
|
||||
a10_clk_ahci_activate(void)
|
||||
{
|
||||
@ -465,3 +518,190 @@ a10_clk_codec_activate(unsigned int freq)
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
calc_tcon_pll(int f_ref, int f_out, int *pm, int *pn)
|
||||
{
|
||||
int best, m, n, f_cur, diff;
|
||||
|
||||
best = TCON_PLL_WORST;
|
||||
for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) {
|
||||
for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) {
|
||||
f_cur = (m * f_ref) / n;
|
||||
diff = f_out - f_cur;
|
||||
if (diff > 0 && diff < best) {
|
||||
best = diff;
|
||||
*pm = m;
|
||||
*pn = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
a10_clk_debe_activate(void)
|
||||
{
|
||||
struct a10_ccm_softc *sc;
|
||||
int pll_rate, clk_div;
|
||||
uint32_t reg_value;
|
||||
|
||||
sc = a10_ccm_sc;
|
||||
if (sc == NULL)
|
||||
return (ENXIO);
|
||||
|
||||
/* Leave reset */
|
||||
reg_value = ccm_read_4(sc, CCM_BE0_SCLK);
|
||||
reg_value |= CCM_BE_CLK_RESET;
|
||||
ccm_write_4(sc, CCM_BE0_SCLK, reg_value);
|
||||
|
||||
pll_rate = a10_clk_pll5x_get_rate();
|
||||
|
||||
clk_div = howmany(pll_rate, DEBE_DEFAULT_RATE);
|
||||
|
||||
/* Set BE0 source to PLL5 (DDR external peripheral clock) */
|
||||
reg_value = CCM_BE_CLK_RESET;
|
||||
reg_value |= (CCM_BE_CLK_SRC_SEL_PLL5 << CCM_BE_CLK_SRC_SEL_SHIFT);
|
||||
reg_value |= (clk_div - 1);
|
||||
ccm_write_4(sc, CCM_BE0_SCLK, reg_value);
|
||||
|
||||
/* Gating AHB clock for BE0 */
|
||||
reg_value = ccm_read_4(sc, CCM_AHB_GATING1);
|
||||
reg_value |= CCM_AHB_GATING_DE_BE0;
|
||||
ccm_write_4(sc, CCM_AHB_GATING1, reg_value);
|
||||
|
||||
/* Enable DRAM clock to BE0 */
|
||||
reg_value = ccm_read_4(sc, CCM_DRAM_CLK);
|
||||
reg_value |= CCM_DRAM_CLK_BE0_CLK_ENABLE;
|
||||
ccm_write_4(sc, CCM_DRAM_CLK, reg_value);
|
||||
|
||||
/* Enable BE0 clock */
|
||||
reg_value = ccm_read_4(sc, CCM_BE0_SCLK);
|
||||
reg_value |= CCM_BE_CLK_SCLK_GATING;
|
||||
ccm_write_4(sc, CCM_BE0_SCLK, reg_value);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
a10_clk_lcd_activate(void)
|
||||
{
|
||||
struct a10_ccm_softc *sc;
|
||||
uint32_t reg_value;
|
||||
|
||||
sc = a10_ccm_sc;
|
||||
if (sc == NULL)
|
||||
return (ENXIO);
|
||||
|
||||
/* Clear LCD0 reset */
|
||||
reg_value = ccm_read_4(sc, CCM_LCD0_CH0_CLK);
|
||||
reg_value |= CCM_LCD_CH0_RESET;
|
||||
ccm_write_4(sc, CCM_LCD0_CH0_CLK, reg_value);
|
||||
|
||||
/* Gating AHB clock for LCD0 */
|
||||
reg_value = ccm_read_4(sc, CCM_AHB_GATING1);
|
||||
reg_value |= CCM_AHB_GATING_LCD0;
|
||||
ccm_write_4(sc, CCM_AHB_GATING1, reg_value);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
a10_clk_tcon_activate(unsigned int freq)
|
||||
{
|
||||
struct a10_ccm_softc *sc;
|
||||
int m, n, m2, n2, f_single, f_double, dbl, src_sel;
|
||||
|
||||
sc = a10_ccm_sc;
|
||||
if (sc == NULL)
|
||||
return (ENXIO);
|
||||
|
||||
m = n = m2 = n2 = 0;
|
||||
dbl = 0;
|
||||
|
||||
calc_tcon_pll(TCON_PLLREF_SINGLE, TCON_RATE_KHZ(freq), &m, &n);
|
||||
calc_tcon_pll(TCON_PLLREF_DOUBLE, TCON_RATE_KHZ(freq), &m2, &n2);
|
||||
|
||||
f_single = n ? (m * TCON_PLLREF_SINGLE) / n : 0;
|
||||
f_double = n2 ? (m2 * TCON_PLLREF_DOUBLE) / n2 : 0;
|
||||
|
||||
if (f_double > f_single) {
|
||||
dbl = 1;
|
||||
m = m2;
|
||||
n = n2;
|
||||
}
|
||||
src_sel = dbl ? CCM_LCD_CH1_SRC_SEL_PLL3_2X : CCM_LCD_CH1_SRC_SEL_PLL3;
|
||||
|
||||
if (n == 0 || m == 0)
|
||||
return (EINVAL);
|
||||
|
||||
/* Set PLL3 to the closest possible rate */
|
||||
a10_clk_pll3_set_rate(TCON_RATE_HZ(m * TCON_PLLREF_SINGLE));
|
||||
|
||||
/* Enable LCD0 CH1 clock */
|
||||
ccm_write_4(sc, CCM_LCD0_CH1_CLK,
|
||||
CCM_LCD_CH1_SCLK2_GATING | CCM_LCD_CH1_SCLK1_GATING |
|
||||
(src_sel << CCM_LCD_CH1_SRC_SEL_SHIFT) | (n - 1));
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
a10_clk_tcon_get_config(int *pdiv, int *pdbl)
|
||||
{
|
||||
struct a10_ccm_softc *sc;
|
||||
uint32_t reg_value;
|
||||
int src;
|
||||
|
||||
sc = a10_ccm_sc;
|
||||
if (sc == NULL)
|
||||
return (ENXIO);
|
||||
|
||||
reg_value = ccm_read_4(sc, CCM_LCD0_CH1_CLK);
|
||||
|
||||
*pdiv = (reg_value & CCM_LCD_CH1_CLK_DIV_RATIO_M) + 1;
|
||||
|
||||
src = (reg_value & CCM_LCD_CH1_SRC_SEL) >> CCM_LCD_CH1_SRC_SEL_SHIFT;
|
||||
switch (src) {
|
||||
case CCM_LCD_CH1_SRC_SEL_PLL3:
|
||||
case CCM_LCD_CH1_SRC_SEL_PLL7:
|
||||
*pdbl = 0;
|
||||
break;
|
||||
case CCM_LCD_CH1_SRC_SEL_PLL3_2X:
|
||||
case CCM_LCD_CH1_SRC_SEL_PLL7_2X:
|
||||
*pdbl = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
a10_clk_hdmi_activate(void)
|
||||
{
|
||||
struct a10_ccm_softc *sc;
|
||||
uint32_t reg_value;
|
||||
int error;
|
||||
|
||||
sc = a10_ccm_sc;
|
||||
if (sc == NULL)
|
||||
return (ENXIO);
|
||||
|
||||
/* Set PLL3 to 297MHz */
|
||||
error = a10_clk_pll3_set_rate(HDMI_DEFAULT_RATE);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
/* Enable HDMI clock, source PLL3 */
|
||||
reg_value = ccm_read_4(sc, CCM_HDMI_CLK);
|
||||
reg_value |= CCM_HDMI_CLK_SCLK_GATING;
|
||||
reg_value &= ~CCM_HDMI_CLK_SRC_SEL;
|
||||
reg_value |= (CCM_HDMI_CLK_SRC_SEL_PLL3 << CCM_HDMI_CLK_SRC_SEL_SHIFT);
|
||||
ccm_write_4(sc, CCM_HDMI_CLK, reg_value);
|
||||
|
||||
/* Gating AHB clock for HDMI */
|
||||
reg_value = ccm_read_4(sc, CCM_AHB_GATING1);
|
||||
reg_value |= CCM_AHB_GATING_HDMI;
|
||||
ccm_write_4(sc, CCM_AHB_GATING1, reg_value);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
@ -120,9 +120,14 @@
|
||||
|
||||
/* AHB_GATING_REG1 */
|
||||
#define CCM_AHB_GATING_GMAC (1 << 17)
|
||||
#define CCM_AHB_GATING_DE_BE1 (1 << 13)
|
||||
#define CCM_AHB_GATING_DE_BE0 (1 << 12)
|
||||
#define CCM_AHB_GATING_HDMI (1 << 11)
|
||||
#define CCM_AHB_GATING_LCD1 (1 << 5)
|
||||
#define CCM_AHB_GATING_LCD0 (1 << 4)
|
||||
|
||||
/* APB1_GATING_REG */
|
||||
#define CCM_APB1_GATING_TWI (1 << 0)
|
||||
#define CCM_APB1_GATING_TWI (1 << 0)
|
||||
|
||||
#define CCM_USB_PHY (1 << 8)
|
||||
#define CCM_USB0_RESET (1 << 0)
|
||||
@ -144,6 +149,17 @@
|
||||
#define CCM_PLL2_CFG_PREDIV 0x1f
|
||||
#define CCM_PLL2_CFG_PREDIV_SHIFT 0
|
||||
|
||||
#define CCM_PLL3_CFG_MODE_SEL_SHIFT 15
|
||||
#define CCM_PLL3_CFG_MODE_SEL_FRACT (0 << CCM_PLL3_CFG_MODE_SEL_SHIFT)
|
||||
#define CCM_PLL3_CFG_MODE_SEL_INT (1 << CCM_PLL3_CFG_MODE_SEL_SHIFT)
|
||||
#define CCM_PLL3_CFG_FUNC_SET_SHIFT 14
|
||||
#define CCM_PLL3_CFG_FUNC_SET_270MHZ (0 << CCM_PLL3_CFG_FUNC_SET_SHIFT)
|
||||
#define CCM_PLL3_CFG_FUNC_SET_297MHZ (1 << CCM_PLL3_CFG_FUNC_SET_SHIFT)
|
||||
#define CCM_PLL3_CFG_FACTOR_M 0x7f
|
||||
|
||||
#define CCM_PLL5_CFG_OUT_EXT_DIV_P 0x30000
|
||||
#define CCM_PLL5_CFG_OUT_EXT_DIV_P_SHIFT 16
|
||||
|
||||
#define CCM_PLL6_CFG_SATA_CLKEN (1U << 14)
|
||||
|
||||
#define CCM_SD_CLK_SRC_SEL 0x3000000
|
||||
@ -160,6 +176,49 @@
|
||||
|
||||
#define CCM_AUDIO_CODEC_ENABLE (1U << 31)
|
||||
|
||||
#define CCM_LCD_CH0_SCLK_GATING (1U << 31)
|
||||
#define CCM_LCD_CH0_RESET (1U << 30)
|
||||
#define CCM_LCD_CH0_SRC_SEL 0x03000000
|
||||
#define CCM_LCD_CH0_SRC_SEL_SHIFT 24
|
||||
#define CCM_LCD_CH0_SRC_SEL_PLL3 0
|
||||
#define CCM_LCD_CH0_SRC_SEL_PLL7 1
|
||||
#define CCM_LCD_CH0_SRC_SEL_PLL3_2X 2
|
||||
#define CCM_LCD_CH0_SRC_SEL_PLL6_2X 3
|
||||
|
||||
#define CCM_LCD_CH1_SCLK2_GATING (1U << 31)
|
||||
#define CCM_LCD_CH1_SRC_SEL 0x03000000
|
||||
#define CCM_LCD_CH1_SRC_SEL_SHIFT 24
|
||||
#define CCM_LCD_CH1_SRC_SEL_PLL3 0
|
||||
#define CCM_LCD_CH1_SRC_SEL_PLL7 1
|
||||
#define CCM_LCD_CH1_SRC_SEL_PLL3_2X 2
|
||||
#define CCM_LCD_CH1_SRC_SEL_PLL7_2X 3
|
||||
#define CCM_LCD_CH1_SCLK1_GATING (1U << 15)
|
||||
#define CCM_LCD_CH1_SCLK1_SRC_SEL_SHIFT 11
|
||||
#define CCM_LCD_CH1_SCLK1_SRC_SEL_SCLK2 0
|
||||
#define CCM_LCD_CH1_SCLK1_SRC_SEL_SCLK2_DIV2 1
|
||||
#define CCM_LCD_CH1_CLK_DIV_RATIO_M 0xf
|
||||
|
||||
#define CCM_DRAM_CLK_BE1_CLK_ENABLE (1U << 27)
|
||||
#define CCM_DRAM_CLK_BE0_CLK_ENABLE (1U << 26)
|
||||
|
||||
#define CCM_BE_CLK_SCLK_GATING (1U << 31)
|
||||
#define CCM_BE_CLK_RESET (1U << 30)
|
||||
#define CCM_BE_CLK_SRC_SEL 0x03000000
|
||||
#define CCM_BE_CLK_SRC_SEL_SHIFT 24
|
||||
#define CCM_BE_CLK_SRC_SEL_PLL3 0
|
||||
#define CCM_BE_CLK_SRC_SEL_PLL7 1
|
||||
#define CCM_BE_CLK_SRC_SEL_PLL5 2
|
||||
#define CCM_BE_CLK_DIV_RATIO_M 0xf
|
||||
|
||||
#define CCM_HDMI_CLK_SCLK_GATING (1U << 31)
|
||||
#define CCM_HDMI_CLK_SRC_SEL 0x03000000
|
||||
#define CCM_HDMI_CLK_SRC_SEL_SHIFT 24
|
||||
#define CCM_HDMI_CLK_SRC_SEL_PLL3 0
|
||||
#define CCM_HDMI_CLK_SRC_SEL_PLL7 1
|
||||
#define CCM_HDMI_CLK_SRC_SEL_PLL3_2X 2
|
||||
#define CCM_HDMI_CLK_SRC_SEL_PLL7_2X 3
|
||||
#define CCM_HDMI_CLK_DIV_RATIO_M 0xf
|
||||
|
||||
#define CCM_CLK_REF_FREQ 24000000U
|
||||
|
||||
int a10_clk_usb_activate(void);
|
||||
@ -172,5 +231,10 @@ int a10_clk_mmc_cfg(int, int);
|
||||
int a10_clk_i2c_activate(int);
|
||||
int a10_clk_dmac_activate(void);
|
||||
int a10_clk_codec_activate(unsigned int);
|
||||
int a10_clk_debe_activate(void);
|
||||
int a10_clk_lcd_activate(void);
|
||||
int a10_clk_tcon_activate(unsigned int);
|
||||
int a10_clk_tcon_get_config(int *, int *);
|
||||
int a10_clk_hdmi_activate(void);
|
||||
|
||||
#endif /* _A10_CLK_H_ */
|
||||
|
545
sys/arm/allwinner/a10_fb.c
Normal file
545
sys/arm/allwinner/a10_fb.c
Normal file
@ -0,0 +1,545 @@
|
||||
/*-
|
||||
* Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
/*
|
||||
* Allwinner A10/A20 Framebuffer
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/rman.h>
|
||||
#include <sys/condvar.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/fbio.h>
|
||||
#include <vm/vm.h>
|
||||
#include <vm/vm_extern.h>
|
||||
#include <vm/vm_kern.h>
|
||||
#include <vm/pmap.h>
|
||||
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/ofw/ofw_bus.h>
|
||||
#include <dev/ofw/ofw_bus_subr.h>
|
||||
|
||||
#include <dev/videomode/videomode.h>
|
||||
#include <dev/videomode/edidvar.h>
|
||||
|
||||
#include <arm/allwinner/a10_clk.h>
|
||||
|
||||
#include "fb_if.h"
|
||||
#include "hdmi_if.h"
|
||||
|
||||
#define FB_DEFAULT_W 800
|
||||
#define FB_DEFAULT_H 600
|
||||
#define FB_DEFAULT_REF 60
|
||||
#define FB_BPP 32
|
||||
#define FB_ALIGN 0x1000
|
||||
|
||||
#define HDMI_ENABLE_DELAY 20000
|
||||
|
||||
#define DOT_CLOCK_TO_HZ(c) ((c) * 1000)
|
||||
|
||||
/* Display backend */
|
||||
#define DEBE_REG_START 0x800
|
||||
#define DEBE_REG_END 0x1000
|
||||
#define DEBE_REG_WIDTH 4
|
||||
#define DEBE_MODCTL 0x800
|
||||
#define MODCTL_ITLMOD_EN (1 << 28)
|
||||
#define MODCTL_OUT_SEL_MASK (0x7 << 20)
|
||||
#define MODCTL_OUT_SEL(sel) ((sel) << 20)
|
||||
#define OUT_SEL_LCD 0
|
||||
#define MODCTL_LAY0_EN (1 << 8)
|
||||
#define MODCTL_START_CTL (1 << 1)
|
||||
#define MODCTL_EN (1 << 0)
|
||||
#define DEBE_DISSIZE 0x808
|
||||
#define DIS_HEIGHT(h) (((h) - 1) << 16)
|
||||
#define DIS_WIDTH(w) (((w) - 1) << 0)
|
||||
#define DEBE_LAYSIZE0 0x810
|
||||
#define LAY_HEIGHT(h) (((h) - 1) << 16)
|
||||
#define LAY_WIDTH(w) (((w) - 1) << 0)
|
||||
#define DEBE_LAYCOOR0 0x820
|
||||
#define LAY_XCOOR(x) ((x) << 16)
|
||||
#define LAY_YCOOR(y) ((y) << 0)
|
||||
#define DEBE_LAYLINEWIDTH0 0x840
|
||||
#define DEBE_LAYFB_L32ADD0 0x850
|
||||
#define LAYFB_L32ADD(pa) ((pa) << 3)
|
||||
#define DEBE_LAYFB_H4ADD 0x860
|
||||
#define LAY0FB_H4ADD(pa) ((pa) >> 29)
|
||||
#define DEBE_REGBUFFCTL 0x870
|
||||
#define REGBUFFCTL_LOAD (1 << 0)
|
||||
#define DEBE_ATTCTL1 0x8a0
|
||||
#define ATTCTL1_FBFMT(fmt) ((fmt) << 8)
|
||||
#define FBFMT_XRGB8888 9
|
||||
#define ATTCTL1_FBPS(ps) ((ps) << 0)
|
||||
#define FBPS_32BPP_ARGB 0
|
||||
|
||||
/* Timing controller */
|
||||
#define TCON_GCTL 0x000
|
||||
#define GCTL_TCON_EN (1 << 31)
|
||||
#define GCTL_IO_MAP_SEL_TCON1 (1 << 0)
|
||||
#define TCON_GINT1 0x008
|
||||
#define GINT1_TCON1_LINENO(n) (((n) + 2) << 0)
|
||||
#define TCON0_DCLK 0x044
|
||||
#define DCLK_EN 0xf0000000
|
||||
#define TCON1_CTL 0x090
|
||||
#define TCON1_EN (1 << 31)
|
||||
#define INTERLACE_EN (1 << 20)
|
||||
#define TCON1_SRC_SEL(src) ((src) << 0)
|
||||
#define TCON1_SRC_CH1 0
|
||||
#define TCON1_SRC_CH2 1
|
||||
#define TCON1_SRC_BLUE 2
|
||||
#define TCON1_START_DELAY(sd) ((sd) << 4)
|
||||
#define TCON1_BASIC0 0x094
|
||||
#define TCON1_BASIC1 0x098
|
||||
#define TCON1_BASIC2 0x09c
|
||||
#define TCON1_BASIC3 0x0a0
|
||||
#define TCON1_BASIC4 0x0a4
|
||||
#define TCON1_BASIC5 0x0a8
|
||||
#define BASIC_X(x) (((x) - 1) << 16)
|
||||
#define BASIC_Y(y) (((y) - 1) << 0)
|
||||
#define BASIC3_HT(ht) (((ht) - 1) << 16)
|
||||
#define BASIC3_HBP(hbp) (((hbp) - 1) << 0)
|
||||
#define BASIC4_VT(vt) ((vt) << 16)
|
||||
#define BASIC4_VBP(vbp) (((vbp) - 1) << 0)
|
||||
#define BASIC5_HSPW(hspw) (((hspw) - 1) << 16)
|
||||
#define BASIC5_VSPW(vspw) (((vspw) - 1) << 0)
|
||||
#define TCON1_IO_POL 0x0f0
|
||||
#define IO_POL_IO2_INV (1 << 26)
|
||||
#define IO_POL_PHSYNC (1 << 25)
|
||||
#define IO_POL_PVSYNC (1 << 24)
|
||||
#define TCON1_IO_TRI 0x0f4
|
||||
#define IO0_OUTPUT_TRI_EN (1 << 24)
|
||||
#define IO1_OUTPUT_TRI_EN (1 << 25)
|
||||
#define IO_TRI_MASK 0xffffffff
|
||||
#define START_DELAY(vbl) (MIN(32, (vbl)) - 2)
|
||||
#define VBLANK_LEN(vt, vd, i) ((((vt) << (i)) >> 1) - (vd) - 2)
|
||||
#define VTOTAL(vt) ((vt) * 2)
|
||||
#define DIVIDE(x, y) (((x) + ((y) / 2)) / (y))
|
||||
|
||||
struct a10fb_softc {
|
||||
device_t dev;
|
||||
device_t fbdev;
|
||||
struct resource *res[2];
|
||||
|
||||
/* Framebuffer */
|
||||
struct fb_info info;
|
||||
size_t fbsize;
|
||||
bus_addr_t paddr;
|
||||
vm_offset_t vaddr;
|
||||
|
||||
/* HDMI */
|
||||
eventhandler_tag hdmi_evh;
|
||||
};
|
||||
|
||||
static struct resource_spec a10fb_spec[] = {
|
||||
{ SYS_RES_MEMORY, 0, RF_ACTIVE }, /* DEBE */
|
||||
{ SYS_RES_MEMORY, 1, RF_ACTIVE }, /* TCON */
|
||||
{ -1, 0 }
|
||||
};
|
||||
|
||||
#define DEBE_READ(sc, reg) bus_read_4((sc)->res[0], (reg))
|
||||
#define DEBE_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val))
|
||||
|
||||
#define TCON_READ(sc, reg) bus_read_4((sc)->res[1], (reg))
|
||||
#define TCON_WRITE(sc, reg, val) bus_write_4((sc)->res[1], (reg), (val))
|
||||
|
||||
static int
|
||||
a10fb_allocfb(struct a10fb_softc *sc)
|
||||
{
|
||||
sc->vaddr = kmem_alloc_contig(kernel_arena, sc->fbsize,
|
||||
M_NOWAIT | M_ZERO, 0, ~0, FB_ALIGN, 0, VM_MEMATTR_WRITE_COMBINING);
|
||||
if (sc->vaddr == 0) {
|
||||
device_printf(sc->dev, "failed to allocate FB memory\n");
|
||||
return (ENOMEM);
|
||||
}
|
||||
sc->paddr = pmap_kextract(sc->vaddr);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
a10fb_freefb(struct a10fb_softc *sc)
|
||||
{
|
||||
kmem_free(kernel_arena, sc->vaddr, sc->fbsize);
|
||||
}
|
||||
|
||||
static void
|
||||
a10fb_setup_debe(struct a10fb_softc *sc, const struct videomode *mode)
|
||||
{
|
||||
int width, height, interlace, reg;
|
||||
uint32_t val;
|
||||
|
||||
interlace = !!(mode->flags & VID_INTERLACE);
|
||||
width = mode->hdisplay;
|
||||
height = mode->vdisplay << interlace;
|
||||
|
||||
/* Enable DEBE clocks */
|
||||
a10_clk_debe_activate();
|
||||
|
||||
/* Initialize all registers to 0 */
|
||||
for (reg = DEBE_REG_START; reg < DEBE_REG_END; reg += DEBE_REG_WIDTH)
|
||||
DEBE_WRITE(sc, reg, 0);
|
||||
|
||||
/* Enable display backend */
|
||||
DEBE_WRITE(sc, DEBE_MODCTL, MODCTL_EN);
|
||||
|
||||
/* Set display size */
|
||||
DEBE_WRITE(sc, DEBE_DISSIZE, DIS_HEIGHT(height) | DIS_WIDTH(width));
|
||||
|
||||
/* Set layer 0 size, position, and stride */
|
||||
DEBE_WRITE(sc, DEBE_LAYSIZE0, LAY_HEIGHT(height) | LAY_WIDTH(width));
|
||||
DEBE_WRITE(sc, DEBE_LAYCOOR0, LAY_XCOOR(0) | LAY_YCOOR(0));
|
||||
DEBE_WRITE(sc, DEBE_LAYLINEWIDTH0, width * FB_BPP);
|
||||
|
||||
/* Point layer 0 to FB memory */
|
||||
DEBE_WRITE(sc, DEBE_LAYFB_L32ADD0, LAYFB_L32ADD(sc->paddr));
|
||||
DEBE_WRITE(sc, DEBE_LAYFB_H4ADD, LAY0FB_H4ADD(sc->paddr));
|
||||
|
||||
/* Set backend format and pixel sequence */
|
||||
DEBE_WRITE(sc, DEBE_ATTCTL1, ATTCTL1_FBFMT(FBFMT_XRGB8888) |
|
||||
ATTCTL1_FBPS(FBPS_32BPP_ARGB));
|
||||
|
||||
/* Enable layer 0, output to LCD, setup interlace */
|
||||
val = DEBE_READ(sc, DEBE_MODCTL);
|
||||
val |= MODCTL_LAY0_EN;
|
||||
val &= ~MODCTL_OUT_SEL_MASK;
|
||||
val |= MODCTL_OUT_SEL(OUT_SEL_LCD);
|
||||
if (interlace)
|
||||
val |= MODCTL_ITLMOD_EN;
|
||||
else
|
||||
val &= ~MODCTL_ITLMOD_EN;
|
||||
DEBE_WRITE(sc, DEBE_MODCTL, val);
|
||||
|
||||
/* Commit settings */
|
||||
DEBE_WRITE(sc, DEBE_REGBUFFCTL, REGBUFFCTL_LOAD);
|
||||
|
||||
/* Start DEBE */
|
||||
val = DEBE_READ(sc, DEBE_MODCTL);
|
||||
val |= MODCTL_START_CTL;
|
||||
DEBE_WRITE(sc, DEBE_MODCTL, val);
|
||||
}
|
||||
|
||||
static void
|
||||
a10fb_setup_tcon(struct a10fb_softc *sc, const struct videomode *mode)
|
||||
{
|
||||
u_int interlace, hspw, hbp, vspw, vbp, vbl, width, height, start_delay;
|
||||
u_int vtotal, framerate, clk;
|
||||
uint32_t val;
|
||||
|
||||
interlace = !!(mode->flags & VID_INTERLACE);
|
||||
width = mode->hdisplay;
|
||||
height = mode->vdisplay;
|
||||
hspw = mode->hsync_end - mode->hsync_start;
|
||||
hbp = mode->htotal - mode->hsync_start;
|
||||
vspw = mode->vsync_end - mode->vsync_start;
|
||||
vbp = mode->vtotal - mode->vsync_start;
|
||||
vbl = VBLANK_LEN(mode->vtotal, mode->vdisplay, interlace);
|
||||
start_delay = START_DELAY(vbl);
|
||||
|
||||
/* Enable LCD clocks */
|
||||
a10_clk_lcd_activate();
|
||||
|
||||
/* Disable TCON and TCON1 */
|
||||
TCON_WRITE(sc, TCON_GCTL, 0);
|
||||
TCON_WRITE(sc, TCON1_CTL, 0);
|
||||
|
||||
/* Enable clocks */
|
||||
TCON_WRITE(sc, TCON0_DCLK, DCLK_EN);
|
||||
|
||||
/* Disable IO and data output ports */
|
||||
TCON_WRITE(sc, TCON1_IO_TRI, IO_TRI_MASK);
|
||||
|
||||
/* Disable TCON and select TCON1 */
|
||||
TCON_WRITE(sc, TCON_GCTL, GCTL_IO_MAP_SEL_TCON1);
|
||||
|
||||
/* Source width and height */
|
||||
TCON_WRITE(sc, TCON1_BASIC0, BASIC_X(width) | BASIC_Y(height));
|
||||
/* Scaler width and height */
|
||||
TCON_WRITE(sc, TCON1_BASIC1, BASIC_X(width) | BASIC_Y(height));
|
||||
/* Output width and height */
|
||||
TCON_WRITE(sc, TCON1_BASIC2, BASIC_X(width) | BASIC_Y(height));
|
||||
/* Horizontal total and back porch */
|
||||
TCON_WRITE(sc, TCON1_BASIC3, BASIC3_HT(mode->htotal) | BASIC3_HBP(hbp));
|
||||
/* Vertical total and back porch */
|
||||
vtotal = VTOTAL(mode->vtotal);
|
||||
if (interlace) {
|
||||
framerate = DIVIDE(DIVIDE(DOT_CLOCK_TO_HZ(mode->dot_clock),
|
||||
mode->htotal), mode->vtotal);
|
||||
clk = mode->htotal * (VTOTAL(mode->vtotal) + 1) * framerate;
|
||||
if ((clk / 2) == DOT_CLOCK_TO_HZ(mode->dot_clock))
|
||||
vtotal += 1;
|
||||
}
|
||||
TCON_WRITE(sc, TCON1_BASIC4, BASIC4_VT(vtotal) | BASIC4_VBP(vbp));
|
||||
/* Horizontal and vertical sync */
|
||||
TCON_WRITE(sc, TCON1_BASIC5, BASIC5_HSPW(hspw) | BASIC5_VSPW(vspw));
|
||||
/* Polarity */
|
||||
val = IO_POL_IO2_INV;
|
||||
if (mode->flags & VID_PHSYNC)
|
||||
val |= IO_POL_PHSYNC;
|
||||
if (mode->flags & VID_PVSYNC)
|
||||
val |= IO_POL_PVSYNC;
|
||||
TCON_WRITE(sc, TCON1_IO_POL, val);
|
||||
|
||||
/* Set scan line for TCON1 line trigger */
|
||||
TCON_WRITE(sc, TCON_GINT1, GINT1_TCON1_LINENO(start_delay));
|
||||
|
||||
/* Enable TCON1 */
|
||||
val = TCON1_EN;
|
||||
if (interlace)
|
||||
val |= INTERLACE_EN;
|
||||
val |= TCON1_START_DELAY(start_delay);
|
||||
val |= TCON1_SRC_SEL(TCON1_SRC_CH1);
|
||||
TCON_WRITE(sc, TCON1_CTL, val);
|
||||
|
||||
/* Setup PLL */
|
||||
a10_clk_tcon_activate(DOT_CLOCK_TO_HZ(mode->dot_clock));
|
||||
}
|
||||
|
||||
static void
|
||||
a10fb_enable_tcon(struct a10fb_softc *sc, int onoff)
|
||||
{
|
||||
uint32_t val;
|
||||
|
||||
/* Enable TCON */
|
||||
val = TCON_READ(sc, TCON_GCTL);
|
||||
if (onoff)
|
||||
val |= GCTL_TCON_EN;
|
||||
else
|
||||
val &= ~GCTL_TCON_EN;
|
||||
TCON_WRITE(sc, TCON_GCTL, val);
|
||||
|
||||
/* Enable TCON1 IO0/IO1 outputs */
|
||||
val = TCON_READ(sc, TCON1_IO_TRI);
|
||||
if (onoff)
|
||||
val &= ~(IO0_OUTPUT_TRI_EN | IO1_OUTPUT_TRI_EN);
|
||||
else
|
||||
val |= (IO0_OUTPUT_TRI_EN | IO1_OUTPUT_TRI_EN);
|
||||
TCON_WRITE(sc, TCON1_IO_TRI, val);
|
||||
}
|
||||
|
||||
static int
|
||||
a10fb_configure(struct a10fb_softc *sc, const struct videomode *mode)
|
||||
{
|
||||
size_t fbsize;
|
||||
int error;
|
||||
|
||||
fbsize = round_page(mode->hdisplay * mode->vdisplay * (FB_BPP / NBBY));
|
||||
|
||||
/* Detach the old FB device */
|
||||
if (sc->fbdev != NULL) {
|
||||
device_delete_child(sc->dev, sc->fbdev);
|
||||
sc->fbdev = NULL;
|
||||
}
|
||||
|
||||
/* If the FB size has changed, free the old FB memory */
|
||||
if (sc->fbsize > 0 && sc->fbsize != fbsize) {
|
||||
a10fb_freefb(sc);
|
||||
sc->vaddr = 0;
|
||||
}
|
||||
|
||||
/* Allocate the FB if necessary */
|
||||
sc->fbsize = fbsize;
|
||||
if (sc->vaddr == 0) {
|
||||
error = a10fb_allocfb(sc);
|
||||
if (error != 0) {
|
||||
device_printf(sc->dev, "failed to allocate FB memory\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup display backend */
|
||||
a10fb_setup_debe(sc, mode);
|
||||
|
||||
/* Setup display timing controller */
|
||||
a10fb_setup_tcon(sc, mode);
|
||||
|
||||
/* Attach framebuffer device */
|
||||
sc->info.fb_name = device_get_nameunit(sc->dev);
|
||||
sc->info.fb_vbase = (intptr_t)sc->vaddr;
|
||||
sc->info.fb_pbase = sc->paddr;
|
||||
sc->info.fb_size = sc->fbsize;
|
||||
sc->info.fb_bpp = sc->info.fb_depth = FB_BPP;
|
||||
sc->info.fb_stride = mode->hdisplay * (FB_BPP / NBBY);
|
||||
sc->info.fb_width = mode->hdisplay;
|
||||
sc->info.fb_height = mode->vdisplay;
|
||||
|
||||
sc->fbdev = device_add_child(sc->dev, "fbd", device_get_unit(sc->dev));
|
||||
if (sc->fbdev == NULL) {
|
||||
device_printf(sc->dev, "failed to add fbd child\n");
|
||||
return (ENOENT);
|
||||
}
|
||||
|
||||
error = device_probe_and_attach(sc->fbdev);
|
||||
if (error != 0) {
|
||||
device_printf(sc->dev, "failed to attach fbd device\n");
|
||||
return (error);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
a10fb_hdmi_event(void *arg, device_t hdmi_dev)
|
||||
{
|
||||
const struct videomode *mode;
|
||||
struct videomode hdmi_mode;
|
||||
struct a10fb_softc *sc;
|
||||
struct edid_info ei;
|
||||
uint8_t *edid;
|
||||
uint32_t edid_len;
|
||||
int error;
|
||||
|
||||
sc = arg;
|
||||
edid = NULL;
|
||||
edid_len = 0;
|
||||
mode = NULL;
|
||||
|
||||
error = HDMI_GET_EDID(hdmi_dev, &edid, &edid_len);
|
||||
if (error != 0) {
|
||||
device_printf(sc->dev, "failed to get EDID: %d\n", error);
|
||||
} else {
|
||||
error = edid_parse(edid, &ei);
|
||||
if (error != 0) {
|
||||
device_printf(sc->dev, "failed to parse EDID: %d\n",
|
||||
error);
|
||||
} else {
|
||||
if (bootverbose)
|
||||
edid_print(&ei);
|
||||
mode = ei.edid_preferred_mode;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the preferred mode could not be determined, use the default */
|
||||
if (mode == NULL)
|
||||
mode = pick_mode_by_ref(FB_DEFAULT_W, FB_DEFAULT_H,
|
||||
FB_DEFAULT_REF);
|
||||
|
||||
if (mode == NULL) {
|
||||
device_printf(sc->dev, "failed to find usable video mode\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (bootverbose)
|
||||
device_printf(sc->dev, "using %dx%d\n",
|
||||
mode->hdisplay, mode->vdisplay);
|
||||
|
||||
/* Disable HDMI */
|
||||
HDMI_ENABLE(hdmi_dev, 0);
|
||||
|
||||
/* Disable timing controller */
|
||||
a10fb_enable_tcon(sc, 0);
|
||||
|
||||
/* Configure DEBE and TCON */
|
||||
error = a10fb_configure(sc, mode);
|
||||
if (error != 0) {
|
||||
device_printf(sc->dev, "failed to configure FB: %d\n", error);
|
||||
return;
|
||||
}
|
||||
|
||||
hdmi_mode = *mode;
|
||||
hdmi_mode.hskew = mode->hsync_end - mode->hsync_start;
|
||||
hdmi_mode.flags |= VID_HSKEW;
|
||||
HDMI_SET_VIDEOMODE(hdmi_dev, &hdmi_mode);
|
||||
|
||||
/* Enable timing controller */
|
||||
a10fb_enable_tcon(sc, 1);
|
||||
|
||||
DELAY(HDMI_ENABLE_DELAY);
|
||||
|
||||
/* Enable HDMI */
|
||||
HDMI_ENABLE(hdmi_dev, 1);
|
||||
}
|
||||
|
||||
static int
|
||||
a10fb_probe(device_t dev)
|
||||
{
|
||||
if (!ofw_bus_status_okay(dev))
|
||||
return (ENXIO);
|
||||
|
||||
if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-fb"))
|
||||
return (ENXIO);
|
||||
|
||||
device_set_desc(dev, "Allwinner Framebuffer");
|
||||
return (BUS_PROBE_DEFAULT);
|
||||
}
|
||||
|
||||
static int
|
||||
a10fb_attach(device_t dev)
|
||||
{
|
||||
struct a10fb_softc *sc;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
sc->dev = dev;
|
||||
|
||||
if (bus_alloc_resources(dev, a10fb_spec, sc->res)) {
|
||||
device_printf(dev, "cannot allocate resources for device\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
sc->hdmi_evh = EVENTHANDLER_REGISTER(hdmi_event,
|
||||
a10fb_hdmi_event, sc, 0);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static struct fb_info *
|
||||
a10fb_fb_getinfo(device_t dev)
|
||||
{
|
||||
struct a10fb_softc *sc;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
return (&sc->info);
|
||||
}
|
||||
|
||||
static device_method_t a10fb_methods[] = {
|
||||
/* Device interface */
|
||||
DEVMETHOD(device_probe, a10fb_probe),
|
||||
DEVMETHOD(device_attach, a10fb_attach),
|
||||
|
||||
/* FB interface */
|
||||
DEVMETHOD(fb_getinfo, a10fb_fb_getinfo),
|
||||
|
||||
DEVMETHOD_END
|
||||
};
|
||||
|
||||
static driver_t a10fb_driver = {
|
||||
"fb",
|
||||
a10fb_methods,
|
||||
sizeof(struct a10fb_softc),
|
||||
};
|
||||
|
||||
static devclass_t a10fb_devclass;
|
||||
|
||||
DRIVER_MODULE(fb, simplebus, a10fb_driver, a10fb_devclass, 0, 0);
|
601
sys/arm/allwinner/a10_hdmi.c
Normal file
601
sys/arm/allwinner/a10_hdmi.c
Normal file
@ -0,0 +1,601 @@
|
||||
/*-
|
||||
* Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
/*
|
||||
* Allwinner A10/A20 HDMI TX
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/rman.h>
|
||||
#include <sys/condvar.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/module.h>
|
||||
|
||||
#include <machine/bus.h>
|
||||
|
||||
#include <dev/ofw/ofw_bus.h>
|
||||
#include <dev/ofw/ofw_bus_subr.h>
|
||||
|
||||
#include <dev/videomode/videomode.h>
|
||||
#include <dev/videomode/edidvar.h>
|
||||
|
||||
#include <arm/allwinner/a10_clk.h>
|
||||
|
||||
#include "hdmi_if.h"
|
||||
|
||||
#define HDMI_CTRL 0x004
|
||||
#define CTRL_MODULE_EN (1 << 31)
|
||||
#define HDMI_INT_STATUS 0x008
|
||||
#define HDMI_HPD 0x00c
|
||||
#define HPD_DET (1 << 0)
|
||||
#define HDMI_VID_CTRL 0x010
|
||||
#define VID_CTRL_VIDEO_EN (1 << 31)
|
||||
#define VID_CTRL_HDMI_MODE (1 << 30)
|
||||
#define VID_CTRL_INTERLACE (1 << 4)
|
||||
#define VID_CTRL_REPEATER_2X (1 << 0)
|
||||
#define HDMI_VID_TIMING0 0x014
|
||||
#define VID_ACT_V(v) (((v) - 1) << 16)
|
||||
#define VID_ACT_H(h) (((h) - 1) << 0)
|
||||
#define HDMI_VID_TIMING1 0x018
|
||||
#define VID_VBP(vbp) (((vbp) - 1) << 16)
|
||||
#define VID_HBP(hbp) (((hbp) - 1) << 0)
|
||||
#define HDMI_VID_TIMING2 0x01c
|
||||
#define VID_VFP(vfp) (((vfp) - 1) << 16)
|
||||
#define VID_HFP(hfp) (((hfp) - 1) << 0)
|
||||
#define HDMI_VID_TIMING3 0x020
|
||||
#define VID_VSPW(vspw) (((vspw) - 1) << 16)
|
||||
#define VID_HSPW(hspw) (((hspw) - 1) << 0)
|
||||
#define HDMI_VID_TIMING4 0x024
|
||||
#define TX_CLOCK_NORMAL 0x03e00000
|
||||
#define VID_VSYNC_ACTSEL (1 << 1)
|
||||
#define VID_HSYNC_ACTSEL (1 << 0)
|
||||
#define HDMI_AUD_CTRL 0x040
|
||||
#define AUD_CTRL_EN (1 << 31)
|
||||
#define AUD_CTRL_RST (1 << 30)
|
||||
#define HDMI_ADMA_CTRL 0x044
|
||||
#define HDMI_ADMA_MODE (1 << 31)
|
||||
#define HDMI_ADMA_MODE_DDMA (0 << 31)
|
||||
#define HDMI_ADMA_MODE_NDMA (1 << 31)
|
||||
#define HDMI_AUD_FMT 0x048
|
||||
#define AUD_FMT_CH(n) ((n) - 1)
|
||||
#define HDMI_PCM_CTRL 0x04c
|
||||
#define HDMI_AUD_CTS 0x050
|
||||
#define HDMI_AUD_N 0x054
|
||||
#define HDMI_AUD_CH_STATUS0 0x058
|
||||
#define CH_STATUS0_FS_FREQ (0xf << 24)
|
||||
#define CH_STATUS0_FS_FREQ_48 (2 << 24)
|
||||
#define HDMI_AUD_CH_STATUS1 0x05c
|
||||
#define CH_STATUS1_WORD_LEN (0x7 << 1)
|
||||
#define CH_STATUS1_WORD_LEN_16 (1 << 1)
|
||||
#define HDMI_AUDIO_RESET_RETRY 1000
|
||||
#define HDMI_AUDIO_CHANNELS 2
|
||||
#define HDMI_AUDIO_CHANNELMAP 0x76543210
|
||||
#define HDMI_AUDIO_N 6144 /* 48 kHz */
|
||||
#define HDMI_AUDIO_CTS(r, n) ((((r) * 10) * ((n) / 128)) / 480)
|
||||
#define HDMI_PADCTRL0 0x200
|
||||
#define PADCTRL0_BIASEN (1 << 31)
|
||||
#define PADCTRL0_LDOCEN (1 << 30)
|
||||
#define PADCTRL0_LDODEN (1 << 29)
|
||||
#define PADCTRL0_PWENC (1 << 28)
|
||||
#define PADCTRL0_PWEND (1 << 27)
|
||||
#define PADCTRL0_PWENG (1 << 26)
|
||||
#define PADCTRL0_CKEN (1 << 25)
|
||||
#define PADCTRL0_SEN (1 << 24)
|
||||
#define PADCTRL0_TXEN (1 << 23)
|
||||
#define HDMI_PADCTRL1 0x204
|
||||
#define PADCTRL1_AMP_OPT (1 << 23)
|
||||
#define PADCTRL1_AMPCK_OPT (1 << 22)
|
||||
#define PADCTRL1_DMP_OPT (1 << 21)
|
||||
#define PADCTRL1_EMP_OPT (1 << 20)
|
||||
#define PADCTRL1_EMPCK_OPT (1 << 19)
|
||||
#define PADCTRL1_PWSCK (1 << 18)
|
||||
#define PADCTRL1_PWSDT (1 << 17)
|
||||
#define PADCTRL1_REG_CSMPS (1 << 16)
|
||||
#define PADCTRL1_REG_DEN (1 << 15)
|
||||
#define PADCTRL1_REG_DENCK (1 << 14)
|
||||
#define PADCTRL1_REG_PLRCK (1 << 13)
|
||||
#define PADCTRL1_REG_EMP (0x7 << 10)
|
||||
#define PADCTRL1_REG_EMP_EN (0x2 << 10)
|
||||
#define PADCTRL1_REG_CD (0x3 << 8)
|
||||
#define PADCTRL1_REG_CKSS (0x3 << 6)
|
||||
#define PADCTRL1_REG_CKSS_1X (0x1 << 6)
|
||||
#define PADCTRL1_REG_CKSS_2X (0x0 << 6)
|
||||
#define PADCTRL1_REG_AMP (0x7 << 3)
|
||||
#define PADCTRL1_REG_AMP_EN (0x6 << 3)
|
||||
#define PADCTRL1_REG_PLR (0x7 << 0)
|
||||
#define HDMI_PLLCTRL0 0x208
|
||||
#define PLLCTRL0_PLL_EN (1 << 31)
|
||||
#define PLLCTRL0_BWS (1 << 30)
|
||||
#define PLLCTRL0_HV_IS_33 (1 << 29)
|
||||
#define PLLCTRL0_LDO1_EN (1 << 28)
|
||||
#define PLLCTRL0_LDO2_EN (1 << 27)
|
||||
#define PLLCTRL0_SDIV2 (1 << 25)
|
||||
#define PLLCTRL0_VCO_GAIN (0x1 << 22)
|
||||
#define PLLCTRL0_S (0x7 << 17)
|
||||
#define PLLCTRL0_CP_S (0xf << 12)
|
||||
#define PLLCTRL0_CS (0x7 << 8)
|
||||
#define PLLCTRL0_PREDIV(x) ((x) << 4)
|
||||
#define PLLCTRL0_VCO_S (0x8 << 0)
|
||||
#define HDMI_PLLDBG0 0x20c
|
||||
#define PLLDBG0_CKIN_SEL (1 << 21)
|
||||
#define PLLDBG0_CKIN_SEL_PLL3 (0 << 21)
|
||||
#define PLLDBG0_CKIN_SEL_PLL7 (1 << 21)
|
||||
#define HDMI_PKTCTRL0 0x2f0
|
||||
#define HDMI_PKTCTRL1 0x2f4
|
||||
#define PKTCTRL_PACKET(n,t) ((t) << ((n) << 2))
|
||||
#define PKT_NULL 0
|
||||
#define PKT_GC 1
|
||||
#define PKT_AVI 2
|
||||
#define PKT_AI 3
|
||||
#define PKT_SPD 5
|
||||
#define PKT_END 15
|
||||
#define DDC_CTRL 0x500
|
||||
#define CTRL_DDC_EN (1 << 31)
|
||||
#define CTRL_DDC_ACMD_START (1 << 30)
|
||||
#define CTRL_DDC_FIFO_DIR (1 << 8)
|
||||
#define CTRL_DDC_FIFO_DIR_READ (0 << 8)
|
||||
#define CTRL_DDC_FIFO_DIR_WRITE (1 << 8)
|
||||
#define CTRL_DDC_SWRST (1 << 0)
|
||||
#define DDC_SLAVE_ADDR 0x504
|
||||
#define SLAVE_ADDR_SEG_SHIFT 24
|
||||
#define SLAVE_ADDR_EDDC_SHIFT 16
|
||||
#define SLAVE_ADDR_OFFSET_SHIFT 8
|
||||
#define SLAVE_ADDR_SHIFT 0
|
||||
#define DDC_INT_STATUS 0x50c
|
||||
#define INT_STATUS_XFER_DONE (1 << 0)
|
||||
#define DDC_FIFO_CTRL 0x510
|
||||
#define FIFO_CTRL_CLEAR (1 << 31)
|
||||
#define DDC_BYTE_COUNTER 0x51c
|
||||
#define DDC_COMMAND 0x520
|
||||
#define COMMAND_EOREAD (4 << 0)
|
||||
#define DDC_CLOCK 0x528
|
||||
#define DDC_CLOCK_M (1 << 3)
|
||||
#define DDC_CLOCK_N (5 << 0)
|
||||
#define DDC_FIFO 0x518
|
||||
#define SWRST_DELAY 1000
|
||||
#define DDC_DELAY 1000
|
||||
#define DDC_RETRY 1000
|
||||
#define DDC_BLKLEN 16
|
||||
#define DDC_ADDR 0x50
|
||||
#define EDDC_ADDR 0x60
|
||||
#define EDID_LENGTH 128
|
||||
#define HDMI_ENABLE_DELAY 50000
|
||||
#define DDC_READ_RETRY 4
|
||||
#define EXT_TAG 0x00
|
||||
#define CEA_TAG_ID 0x02
|
||||
#define CEA_DTD 0x03
|
||||
#define DTD_BASIC_AUDIO (1 << 6)
|
||||
|
||||
struct a10hdmi_softc {
|
||||
struct resource *res;
|
||||
|
||||
struct intr_config_hook mode_hook;
|
||||
|
||||
uint8_t edid[EDID_LENGTH];
|
||||
|
||||
int has_audio;
|
||||
};
|
||||
|
||||
static struct resource_spec a10hdmi_spec[] = {
|
||||
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
|
||||
{ -1, 0 }
|
||||
};
|
||||
|
||||
#define HDMI_READ(sc, reg) bus_read_4((sc)->res, (reg))
|
||||
#define HDMI_WRITE(sc, reg, val) bus_write_4((sc)->res, (reg), (val))
|
||||
|
||||
static void
|
||||
a10hdmi_init(struct a10hdmi_softc *sc)
|
||||
{
|
||||
/* Enable the HDMI module */
|
||||
HDMI_WRITE(sc, HDMI_CTRL, CTRL_MODULE_EN);
|
||||
|
||||
/* Configure PLL/DRV settings */
|
||||
HDMI_WRITE(sc, HDMI_PADCTRL0, PADCTRL0_BIASEN | PADCTRL0_LDOCEN |
|
||||
PADCTRL0_LDODEN | PADCTRL0_PWENC | PADCTRL0_PWEND |
|
||||
PADCTRL0_PWENG | PADCTRL0_CKEN | PADCTRL0_TXEN);
|
||||
HDMI_WRITE(sc, HDMI_PADCTRL1, PADCTRL1_AMP_OPT | PADCTRL1_AMPCK_OPT |
|
||||
PADCTRL1_EMP_OPT | PADCTRL1_EMPCK_OPT | PADCTRL1_REG_DEN |
|
||||
PADCTRL1_REG_DENCK | PADCTRL1_REG_EMP_EN | PADCTRL1_REG_AMP_EN);
|
||||
|
||||
/* Select PLL3 as input clock */
|
||||
HDMI_WRITE(sc, HDMI_PLLDBG0, PLLDBG0_CKIN_SEL_PLL3);
|
||||
|
||||
DELAY(HDMI_ENABLE_DELAY);
|
||||
}
|
||||
|
||||
static void
|
||||
a10hdmi_hpd(void *arg)
|
||||
{
|
||||
struct a10hdmi_softc *sc;
|
||||
device_t dev;
|
||||
uint32_t hpd;
|
||||
|
||||
dev = arg;
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
hpd = HDMI_READ(sc, HDMI_HPD);
|
||||
if ((hpd & HPD_DET) == HPD_DET)
|
||||
EVENTHANDLER_INVOKE(hdmi_event, dev, HDMI_EVENT_CONNECTED);
|
||||
|
||||
config_intrhook_disestablish(&sc->mode_hook);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_probe(device_t dev)
|
||||
{
|
||||
if (!ofw_bus_status_okay(dev))
|
||||
return (ENXIO);
|
||||
|
||||
if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmi"))
|
||||
return (ENXIO);
|
||||
|
||||
device_set_desc(dev, "Allwinner HDMI TX");
|
||||
return (BUS_PROBE_DEFAULT);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_attach(device_t dev)
|
||||
{
|
||||
struct a10hdmi_softc *sc;
|
||||
int error;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
if (bus_alloc_resources(dev, a10hdmi_spec, &sc->res)) {
|
||||
device_printf(dev, "cannot allocate resources for device\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
a10_clk_hdmi_activate();
|
||||
|
||||
a10hdmi_init(sc);
|
||||
|
||||
sc->mode_hook.ich_func = a10hdmi_hpd;
|
||||
sc->mode_hook.ich_arg = dev;
|
||||
|
||||
error = config_intrhook_establish(&sc->mode_hook);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_ddc_xfer(struct a10hdmi_softc *sc, uint16_t addr, uint8_t seg,
|
||||
uint8_t off, int len)
|
||||
{
|
||||
uint32_t val;
|
||||
int retry;
|
||||
|
||||
/* Set FIFO direction to read */
|
||||
val = HDMI_READ(sc, DDC_CTRL);
|
||||
val &= ~CTRL_DDC_FIFO_DIR;
|
||||
val |= CTRL_DDC_FIFO_DIR_READ;
|
||||
HDMI_WRITE(sc, DDC_CTRL, val);
|
||||
|
||||
/* Setup DDC slave address */
|
||||
val = (addr << SLAVE_ADDR_SHIFT) | (seg << SLAVE_ADDR_SEG_SHIFT) |
|
||||
(EDDC_ADDR << SLAVE_ADDR_EDDC_SHIFT) |
|
||||
(off << SLAVE_ADDR_OFFSET_SHIFT);
|
||||
HDMI_WRITE(sc, DDC_SLAVE_ADDR, val);
|
||||
|
||||
/* Clear FIFO */
|
||||
val = HDMI_READ(sc, DDC_FIFO_CTRL);
|
||||
val |= FIFO_CTRL_CLEAR;
|
||||
HDMI_WRITE(sc, DDC_FIFO_CTRL, val);
|
||||
|
||||
/* Set transfer length */
|
||||
HDMI_WRITE(sc, DDC_BYTE_COUNTER, len);
|
||||
|
||||
/* Set command to "Explicit Offset Address Read" */
|
||||
HDMI_WRITE(sc, DDC_COMMAND, COMMAND_EOREAD);
|
||||
|
||||
/* Start transfer */
|
||||
val = HDMI_READ(sc, DDC_CTRL);
|
||||
val |= CTRL_DDC_ACMD_START;
|
||||
HDMI_WRITE(sc, DDC_CTRL, val);
|
||||
|
||||
/* Wait for command to start */
|
||||
retry = DDC_RETRY;
|
||||
while (--retry > 0) {
|
||||
val = HDMI_READ(sc, DDC_CTRL);
|
||||
if ((val & CTRL_DDC_ACMD_START) == 0)
|
||||
break;
|
||||
DELAY(DDC_DELAY);
|
||||
}
|
||||
if (retry == 0)
|
||||
return (ETIMEDOUT);
|
||||
|
||||
/* Ensure that the transfer completed */
|
||||
val = HDMI_READ(sc, DDC_INT_STATUS);
|
||||
if ((val & INT_STATUS_XFER_DONE) == 0)
|
||||
return (EIO);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_ddc_read(struct a10hdmi_softc *sc, int block, uint8_t *edid)
|
||||
{
|
||||
int resid, off, len, error;
|
||||
uint8_t *pbuf;
|
||||
|
||||
pbuf = edid;
|
||||
resid = EDID_LENGTH;
|
||||
off = (block & 1) ? EDID_LENGTH : 0;
|
||||
|
||||
while (resid > 0) {
|
||||
len = min(resid, DDC_BLKLEN);
|
||||
error = a10hdmi_ddc_xfer(sc, DDC_ADDR, block >> 1, off, len);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
bus_read_multi_1(sc->res, DDC_FIFO, pbuf, len);
|
||||
|
||||
pbuf += len;
|
||||
off += len;
|
||||
resid -= len;
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_detect_audio(struct a10hdmi_softc *sc)
|
||||
{
|
||||
struct edid_info ei;
|
||||
uint8_t edid[EDID_LENGTH];
|
||||
int block;
|
||||
|
||||
if (edid_parse(sc->edid, &ei) != 0)
|
||||
return (0);
|
||||
|
||||
/* Scan through extension blocks, looking for a CEA-861 block. */
|
||||
for (block = 1; block <= ei.edid_ext_block_count; block++) {
|
||||
if (a10hdmi_ddc_read(sc, block, edid) != 0)
|
||||
break;
|
||||
|
||||
if (edid[EXT_TAG] == CEA_TAG_ID)
|
||||
return ((edid[CEA_DTD] & DTD_BASIC_AUDIO) != 0);
|
||||
}
|
||||
|
||||
/* No CEA-861 block found */
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_get_edid(device_t dev, uint8_t **edid, uint32_t *edid_len)
|
||||
{
|
||||
struct a10hdmi_softc *sc;
|
||||
int error, retry;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
retry = DDC_READ_RETRY;
|
||||
|
||||
while (--retry > 0) {
|
||||
/* I2C software reset */
|
||||
HDMI_WRITE(sc, DDC_FIFO_CTRL, 0);
|
||||
HDMI_WRITE(sc, DDC_CTRL, CTRL_DDC_EN | CTRL_DDC_SWRST);
|
||||
DELAY(SWRST_DELAY);
|
||||
if (HDMI_READ(sc, DDC_CTRL) & CTRL_DDC_SWRST) {
|
||||
device_printf(dev, "DDC software reset failed\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
/* Configure DDC clock */
|
||||
HDMI_WRITE(sc, DDC_CLOCK, DDC_CLOCK_M | DDC_CLOCK_N);
|
||||
|
||||
/* Read EDID block */
|
||||
error = a10hdmi_ddc_read(sc, 0, sc->edid);
|
||||
if (error == 0) {
|
||||
*edid = sc->edid;
|
||||
*edid_len = sizeof(sc->edid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (error == 0)
|
||||
sc->has_audio = a10hdmi_detect_audio(sc);
|
||||
else
|
||||
sc->has_audio = 0;
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
static void
|
||||
a10hdmi_set_audiomode(device_t dev, const struct videomode *mode)
|
||||
{
|
||||
struct a10hdmi_softc *sc;
|
||||
uint32_t val;
|
||||
int retry;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
/* Disable and reset audio module and wait for reset bit to clear */
|
||||
HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_RST);
|
||||
for (retry = HDMI_AUDIO_RESET_RETRY; retry > 0; retry--) {
|
||||
val = HDMI_READ(sc, HDMI_AUD_CTRL);
|
||||
if ((val & AUD_CTRL_RST) == 0)
|
||||
break;
|
||||
}
|
||||
if (retry == 0) {
|
||||
device_printf(dev, "timeout waiting for audio module\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sc->has_audio)
|
||||
return;
|
||||
|
||||
/* DMA and FIFO control */
|
||||
HDMI_WRITE(sc, HDMI_ADMA_CTRL, HDMI_ADMA_MODE_DDMA);
|
||||
|
||||
/* Audio format control (LPCM, S16LE, stereo) */
|
||||
HDMI_WRITE(sc, HDMI_AUD_FMT, AUD_FMT_CH(HDMI_AUDIO_CHANNELS));
|
||||
|
||||
/* Channel mappings */
|
||||
HDMI_WRITE(sc, HDMI_PCM_CTRL, HDMI_AUDIO_CHANNELMAP);
|
||||
|
||||
/* Clocks */
|
||||
HDMI_WRITE(sc, HDMI_AUD_CTS,
|
||||
HDMI_AUDIO_CTS(mode->dot_clock, HDMI_AUDIO_N));
|
||||
HDMI_WRITE(sc, HDMI_AUD_N, HDMI_AUDIO_N);
|
||||
|
||||
/* Set sampling frequency to 48 kHz, word length to 16-bit */
|
||||
HDMI_WRITE(sc, HDMI_AUD_CH_STATUS0, CH_STATUS0_FS_FREQ_48);
|
||||
HDMI_WRITE(sc, HDMI_AUD_CH_STATUS1, CH_STATUS1_WORD_LEN_16);
|
||||
|
||||
/* Enable */
|
||||
HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_EN);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_set_videomode(device_t dev, const struct videomode *mode)
|
||||
{
|
||||
struct a10hdmi_softc *sc;
|
||||
int error, clk_div, clk_dbl;
|
||||
int dblscan, hfp, hspw, hbp, vfp, vspw, vbp;
|
||||
uint32_t val;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
dblscan = !!(mode->flags & VID_DBLSCAN);
|
||||
hfp = mode->hsync_start - mode->hdisplay;
|
||||
hspw = mode->hsync_end - mode->hsync_start;
|
||||
hbp = mode->htotal - mode->hsync_start;
|
||||
vfp = mode->vsync_start - mode->vdisplay;
|
||||
vspw = mode->vsync_end - mode->vsync_start;
|
||||
vbp = mode->vtotal - mode->vsync_start;
|
||||
|
||||
error = a10_clk_tcon_get_config(&clk_div, &clk_dbl);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
/* Clear interrupt status */
|
||||
HDMI_WRITE(sc, HDMI_INT_STATUS, HDMI_READ(sc, HDMI_INT_STATUS));
|
||||
|
||||
/* Clock setup */
|
||||
val = HDMI_READ(sc, HDMI_PADCTRL1);
|
||||
val &= ~PADCTRL1_REG_CKSS;
|
||||
val |= (clk_dbl ? PADCTRL1_REG_CKSS_2X : PADCTRL1_REG_CKSS_1X);
|
||||
HDMI_WRITE(sc, HDMI_PADCTRL1, val);
|
||||
HDMI_WRITE(sc, HDMI_PLLCTRL0, PLLCTRL0_PLL_EN | PLLCTRL0_BWS |
|
||||
PLLCTRL0_HV_IS_33 | PLLCTRL0_LDO1_EN | PLLCTRL0_LDO2_EN |
|
||||
PLLCTRL0_SDIV2 | PLLCTRL0_VCO_GAIN | PLLCTRL0_S |
|
||||
PLLCTRL0_CP_S | PLLCTRL0_CS | PLLCTRL0_PREDIV(clk_div) |
|
||||
PLLCTRL0_VCO_S);
|
||||
|
||||
/* Setup display settings */
|
||||
val = VID_CTRL_HDMI_MODE;
|
||||
if (mode->flags & VID_INTERLACE)
|
||||
val |= VID_CTRL_INTERLACE;
|
||||
if (mode->flags & VID_DBLSCAN)
|
||||
val |= VID_CTRL_REPEATER_2X;
|
||||
HDMI_WRITE(sc, HDMI_VID_CTRL, val);
|
||||
|
||||
/* Setup display timings */
|
||||
HDMI_WRITE(sc, HDMI_VID_TIMING0,
|
||||
VID_ACT_V(mode->vdisplay) | VID_ACT_H(mode->hdisplay << dblscan));
|
||||
HDMI_WRITE(sc, HDMI_VID_TIMING1,
|
||||
VID_VBP(vbp) | VID_HBP(hbp << dblscan));
|
||||
HDMI_WRITE(sc, HDMI_VID_TIMING2,
|
||||
VID_VFP(vfp) | VID_HFP(hfp << dblscan));
|
||||
HDMI_WRITE(sc, HDMI_VID_TIMING3,
|
||||
VID_VSPW(vspw) | VID_HSPW(hspw << dblscan));
|
||||
val = TX_CLOCK_NORMAL;
|
||||
if (mode->flags & VID_PVSYNC)
|
||||
val |= VID_VSYNC_ACTSEL;
|
||||
if (mode->flags & VID_PHSYNC)
|
||||
val |= VID_HSYNC_ACTSEL;
|
||||
HDMI_WRITE(sc, HDMI_VID_TIMING4, val);
|
||||
|
||||
/* This is an ordered list of infoframe packets that the HDMI
|
||||
* transmitter will send. Transmit packets in the following order:
|
||||
* 1. General control packet
|
||||
* 2. AVI infoframe
|
||||
* 3. Audio infoframe
|
||||
* There are 2 registers with 4 slots each. The list is terminated
|
||||
* with the special PKT_END marker.
|
||||
*/
|
||||
HDMI_WRITE(sc, HDMI_PKTCTRL0,
|
||||
PKTCTRL_PACKET(0, PKT_GC) | PKTCTRL_PACKET(1, PKT_AVI) |
|
||||
PKTCTRL_PACKET(2, PKT_AI) | PKTCTRL_PACKET(3, PKT_END));
|
||||
HDMI_WRITE(sc, HDMI_PKTCTRL1, 0);
|
||||
|
||||
/* Setup audio */
|
||||
a10hdmi_set_audiomode(dev, mode);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmi_enable(device_t dev, int onoff)
|
||||
{
|
||||
struct a10hdmi_softc *sc;
|
||||
uint32_t val;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
/* Enable or disable video output */
|
||||
val = HDMI_READ(sc, HDMI_VID_CTRL);
|
||||
if (onoff)
|
||||
val |= VID_CTRL_VIDEO_EN;
|
||||
else
|
||||
val &= ~VID_CTRL_VIDEO_EN;
|
||||
HDMI_WRITE(sc, HDMI_VID_CTRL, val);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static device_method_t a10hdmi_methods[] = {
|
||||
/* Device interface */
|
||||
DEVMETHOD(device_probe, a10hdmi_probe),
|
||||
DEVMETHOD(device_attach, a10hdmi_attach),
|
||||
|
||||
/* HDMI interface */
|
||||
DEVMETHOD(hdmi_get_edid, a10hdmi_get_edid),
|
||||
DEVMETHOD(hdmi_set_videomode, a10hdmi_set_videomode),
|
||||
DEVMETHOD(hdmi_enable, a10hdmi_enable),
|
||||
|
||||
DEVMETHOD_END
|
||||
};
|
||||
|
||||
static driver_t a10hdmi_driver = {
|
||||
"a10hdmi",
|
||||
a10hdmi_methods,
|
||||
sizeof(struct a10hdmi_softc),
|
||||
};
|
||||
|
||||
static devclass_t a10hdmi_devclass;
|
||||
|
||||
DRIVER_MODULE(a10hdmi, simplebus, a10hdmi_driver, a10hdmi_devclass, 0, 0);
|
||||
MODULE_VERSION(a10hdmi, 1);
|
438
sys/arm/allwinner/a10_hdmiaudio.c
Normal file
438
sys/arm/allwinner/a10_hdmiaudio.c
Normal file
@ -0,0 +1,438 @@
|
||||
/*-
|
||||
* Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
/*
|
||||
* Allwinner A10/A20 HDMI Audio
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/rman.h>
|
||||
#include <sys/condvar.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/module.h>
|
||||
|
||||
#include <dev/sound/pcm/sound.h>
|
||||
#include <dev/sound/chip.h>
|
||||
|
||||
#include <dev/ofw/ofw_bus.h>
|
||||
#include <dev/ofw/ofw_bus_subr.h>
|
||||
|
||||
#include "sunxi_dma_if.h"
|
||||
#include "mixer_if.h"
|
||||
|
||||
#define DRQTYPE_HDMIAUDIO 24
|
||||
#define DRQTYPE_SDRAM 1
|
||||
|
||||
#define DMA_WIDTH 32
|
||||
#define DMA_BURST_LEN 8
|
||||
#define DDMA_BLKSIZE 32
|
||||
#define DDMA_WAIT_CYC 8
|
||||
|
||||
#define DMABUF_MIN 4096
|
||||
#define DMABUF_DEFAULT 65536
|
||||
#define DMABUF_MAX 131072
|
||||
|
||||
#define HDMI_SAMPLERATE 48000
|
||||
|
||||
#define TX_FIFO 0x01c16400
|
||||
|
||||
static uint32_t a10hdmiaudio_fmt[] = {
|
||||
SND_FORMAT(AFMT_S16_LE, 2, 0),
|
||||
0
|
||||
};
|
||||
|
||||
static struct pcmchan_caps a10hdmiaudio_pcaps = {
|
||||
HDMI_SAMPLERATE, HDMI_SAMPLERATE, a10hdmiaudio_fmt, 0
|
||||
};
|
||||
|
||||
struct a10hdmiaudio_info;
|
||||
|
||||
struct a10hdmiaudio_chinfo {
|
||||
struct snd_dbuf *buffer;
|
||||
struct pcm_channel *channel;
|
||||
struct a10hdmiaudio_info *parent;
|
||||
bus_dmamap_t dmamap;
|
||||
void *dmaaddr;
|
||||
bus_addr_t physaddr;
|
||||
device_t dmac;
|
||||
void *dmachan;
|
||||
|
||||
int run;
|
||||
uint32_t pos;
|
||||
uint32_t blocksize;
|
||||
};
|
||||
|
||||
struct a10hdmiaudio_info {
|
||||
device_t dev;
|
||||
struct mtx *lock;
|
||||
bus_dma_tag_t dmat;
|
||||
unsigned dmasize;
|
||||
|
||||
struct a10hdmiaudio_chinfo play;
|
||||
};
|
||||
|
||||
/*
|
||||
* Mixer interface
|
||||
*/
|
||||
|
||||
static int
|
||||
a10hdmiaudio_mixer_init(struct snd_mixer *m)
|
||||
{
|
||||
mix_setdevs(m, SOUND_MASK_PCM);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmiaudio_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left,
|
||||
unsigned right)
|
||||
{
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static kobj_method_t a10hdmiaudio_mixer_methods[] = {
|
||||
KOBJMETHOD(mixer_init, a10hdmiaudio_mixer_init),
|
||||
KOBJMETHOD(mixer_set, a10hdmiaudio_mixer_set),
|
||||
KOBJMETHOD_END
|
||||
};
|
||||
MIXER_DECLARE(a10hdmiaudio_mixer);
|
||||
|
||||
|
||||
/*
|
||||
* Channel interface
|
||||
*/
|
||||
|
||||
static void
|
||||
a10hdmiaudio_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
|
||||
{
|
||||
struct a10hdmiaudio_chinfo *ch = arg;
|
||||
|
||||
if (error != 0)
|
||||
return;
|
||||
|
||||
ch->physaddr = segs[0].ds_addr;
|
||||
}
|
||||
|
||||
static void
|
||||
a10hdmiaudio_transfer(struct a10hdmiaudio_chinfo *ch)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = SUNXI_DMA_TRANSFER(ch->dmac, ch->dmachan,
|
||||
ch->physaddr + ch->pos, TX_FIFO, ch->blocksize);
|
||||
if (error) {
|
||||
ch->run = 0;
|
||||
device_printf(ch->parent->dev, "DMA transfer failed: %d\n",
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
a10hdmiaudio_dmaconfig(struct a10hdmiaudio_chinfo *ch)
|
||||
{
|
||||
struct sunxi_dma_config conf;
|
||||
|
||||
memset(&conf, 0, sizeof(conf));
|
||||
conf.src_width = conf.dst_width = DMA_WIDTH;
|
||||
conf.src_burst_len = conf.dst_burst_len = DMA_BURST_LEN;
|
||||
conf.src_blksize = conf.dst_blksize = DDMA_BLKSIZE;
|
||||
conf.src_wait_cyc = conf.dst_wait_cyc = DDMA_WAIT_CYC;
|
||||
conf.src_drqtype = DRQTYPE_SDRAM;
|
||||
conf.dst_drqtype = DRQTYPE_HDMIAUDIO;
|
||||
conf.dst_noincr = true;
|
||||
|
||||
SUNXI_DMA_SET_CONFIG(ch->dmac, ch->dmachan, &conf);
|
||||
}
|
||||
|
||||
static void
|
||||
a10hdmiaudio_dmaintr(void *priv)
|
||||
{
|
||||
struct a10hdmiaudio_chinfo *ch = priv;
|
||||
unsigned bufsize;
|
||||
|
||||
bufsize = sndbuf_getsize(ch->buffer);
|
||||
|
||||
ch->pos += ch->blocksize;
|
||||
if (ch->pos >= bufsize)
|
||||
ch->pos -= bufsize;
|
||||
|
||||
if (ch->run) {
|
||||
chn_intr(ch->channel);
|
||||
a10hdmiaudio_transfer(ch);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
a10hdmiaudio_start(struct a10hdmiaudio_chinfo *ch)
|
||||
{
|
||||
ch->pos = 0;
|
||||
|
||||
/* Configure DMA channel */
|
||||
a10hdmiaudio_dmaconfig(ch);
|
||||
|
||||
/* Start DMA transfer */
|
||||
a10hdmiaudio_transfer(ch);
|
||||
}
|
||||
|
||||
static void
|
||||
a10hdmiaudio_stop(struct a10hdmiaudio_chinfo *ch)
|
||||
{
|
||||
/* Disable DMA channel */
|
||||
SUNXI_DMA_HALT(ch->dmac, ch->dmachan);
|
||||
}
|
||||
|
||||
static void *
|
||||
a10hdmiaudio_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
|
||||
struct pcm_channel *c, int dir)
|
||||
{
|
||||
struct a10hdmiaudio_info *sc = devinfo;
|
||||
struct a10hdmiaudio_chinfo *ch = &sc->play;
|
||||
int error;
|
||||
|
||||
ch->parent = sc;
|
||||
ch->channel = c;
|
||||
ch->buffer = b;
|
||||
|
||||
ch->dmac = devclass_get_device(devclass_find("a10dmac"), 0);
|
||||
if (ch->dmac == NULL) {
|
||||
device_printf(sc->dev, "cannot find DMA controller\n");
|
||||
return (NULL);
|
||||
}
|
||||
ch->dmachan = SUNXI_DMA_ALLOC(ch->dmac, true, a10hdmiaudio_dmaintr, ch);
|
||||
if (ch->dmachan == NULL) {
|
||||
device_printf(sc->dev, "cannot allocate DMA channel\n");
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
error = bus_dmamem_alloc(sc->dmat, &ch->dmaaddr,
|
||||
BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &ch->dmamap);
|
||||
if (error != 0) {
|
||||
device_printf(sc->dev, "cannot allocate channel buffer\n");
|
||||
return (NULL);
|
||||
}
|
||||
error = bus_dmamap_load(sc->dmat, ch->dmamap, ch->dmaaddr,
|
||||
sc->dmasize, a10hdmiaudio_dmamap_cb, ch, BUS_DMA_NOWAIT);
|
||||
if (error != 0) {
|
||||
device_printf(sc->dev, "cannot load DMA map\n");
|
||||
return (NULL);
|
||||
}
|
||||
memset(ch->dmaaddr, 0, sc->dmasize);
|
||||
|
||||
if (sndbuf_setup(ch->buffer, ch->dmaaddr, sc->dmasize) != 0) {
|
||||
device_printf(sc->dev, "cannot setup sndbuf\n");
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
return (ch);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmiaudio_chan_free(kobj_t obj, void *data)
|
||||
{
|
||||
struct a10hdmiaudio_chinfo *ch = data;
|
||||
struct a10hdmiaudio_info *sc = ch->parent;
|
||||
|
||||
SUNXI_DMA_FREE(ch->dmac, ch->dmachan);
|
||||
bus_dmamap_unload(sc->dmat, ch->dmamap);
|
||||
bus_dmamem_free(sc->dmat, ch->dmaaddr, ch->dmamap);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmiaudio_chan_setformat(kobj_t obj, void *data, uint32_t format)
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
a10hdmiaudio_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
|
||||
{
|
||||
return (HDMI_SAMPLERATE);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
a10hdmiaudio_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
|
||||
{
|
||||
struct a10hdmiaudio_chinfo *ch = data;
|
||||
|
||||
ch->blocksize = blocksize & ~3;
|
||||
|
||||
return (ch->blocksize);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmiaudio_chan_trigger(kobj_t obj, void *data, int go)
|
||||
{
|
||||
struct a10hdmiaudio_chinfo *ch = data;
|
||||
struct a10hdmiaudio_info *sc = ch->parent;
|
||||
|
||||
if (!PCMTRIG_COMMON(go))
|
||||
return (0);
|
||||
|
||||
snd_mtxlock(sc->lock);
|
||||
switch (go) {
|
||||
case PCMTRIG_START:
|
||||
ch->run = 1;
|
||||
a10hdmiaudio_start(ch);
|
||||
break;
|
||||
case PCMTRIG_STOP:
|
||||
case PCMTRIG_ABORT:
|
||||
ch->run = 0;
|
||||
a10hdmiaudio_stop(ch);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
snd_mtxunlock(sc->lock);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
a10hdmiaudio_chan_getptr(kobj_t obj, void *data)
|
||||
{
|
||||
struct a10hdmiaudio_chinfo *ch = data;
|
||||
|
||||
return (ch->pos);
|
||||
}
|
||||
|
||||
static struct pcmchan_caps *
|
||||
a10hdmiaudio_chan_getcaps(kobj_t obj, void *data)
|
||||
{
|
||||
return (&a10hdmiaudio_pcaps);
|
||||
}
|
||||
|
||||
static kobj_method_t a10hdmiaudio_chan_methods[] = {
|
||||
KOBJMETHOD(channel_init, a10hdmiaudio_chan_init),
|
||||
KOBJMETHOD(channel_free, a10hdmiaudio_chan_free),
|
||||
KOBJMETHOD(channel_setformat, a10hdmiaudio_chan_setformat),
|
||||
KOBJMETHOD(channel_setspeed, a10hdmiaudio_chan_setspeed),
|
||||
KOBJMETHOD(channel_setblocksize, a10hdmiaudio_chan_setblocksize),
|
||||
KOBJMETHOD(channel_trigger, a10hdmiaudio_chan_trigger),
|
||||
KOBJMETHOD(channel_getptr, a10hdmiaudio_chan_getptr),
|
||||
KOBJMETHOD(channel_getcaps, a10hdmiaudio_chan_getcaps),
|
||||
KOBJMETHOD_END
|
||||
};
|
||||
CHANNEL_DECLARE(a10hdmiaudio_chan);
|
||||
|
||||
|
||||
/*
|
||||
* Device interface
|
||||
*/
|
||||
|
||||
static int
|
||||
a10hdmiaudio_probe(device_t dev)
|
||||
{
|
||||
if (!ofw_bus_status_okay(dev))
|
||||
return (ENXIO);
|
||||
|
||||
if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmiaudio"))
|
||||
return (ENXIO);
|
||||
|
||||
device_set_desc(dev, "Allwinner HDMI Audio");
|
||||
return (BUS_PROBE_DEFAULT);
|
||||
}
|
||||
|
||||
static int
|
||||
a10hdmiaudio_attach(device_t dev)
|
||||
{
|
||||
struct a10hdmiaudio_info *sc;
|
||||
char status[SND_STATUSLEN];
|
||||
int error;
|
||||
|
||||
sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
|
||||
sc->dev = dev;
|
||||
sc->lock = snd_mtxcreate(device_get_nameunit(dev), "a10hdmiaudio softc");
|
||||
|
||||
sc->dmasize = pcm_getbuffersize(dev, DMABUF_MIN,
|
||||
DMABUF_DEFAULT, DMABUF_MAX);
|
||||
error = bus_dma_tag_create(
|
||||
bus_get_dma_tag(dev),
|
||||
4, sc->dmasize, /* alignment, boundary */
|
||||
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
||||
BUS_SPACE_MAXADDR, /* highaddr */
|
||||
NULL, NULL, /* filter, filterarg */
|
||||
sc->dmasize, 1, /* maxsize, nsegs */
|
||||
sc->dmasize, 0, /* maxsegsize, flags */
|
||||
NULL, NULL, /* lockfunc, lockarg */
|
||||
&sc->dmat);
|
||||
if (error != 0) {
|
||||
device_printf(dev, "cannot create DMA tag\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (mixer_init(dev, &a10hdmiaudio_mixer_class, sc)) {
|
||||
device_printf(dev, "mixer_init failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
|
||||
pcm_setflags(dev, pcm_getflags(dev) | SD_F_SOFTPCMVOL);
|
||||
|
||||
if (pcm_register(dev, sc, 1, 0)) {
|
||||
device_printf(dev, "pcm_register failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pcm_addchan(dev, PCMDIR_PLAY, &a10hdmiaudio_chan_class, sc);
|
||||
|
||||
snprintf(status, SND_STATUSLEN, "at %s", ofw_bus_get_name(dev));
|
||||
pcm_setstatus(dev, status);
|
||||
|
||||
return (0);
|
||||
|
||||
fail:
|
||||
snd_mtxfree(sc->lock);
|
||||
free(sc, M_DEVBUF);
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
static device_method_t a10hdmiaudio_pcm_methods[] = {
|
||||
/* Device interface */
|
||||
DEVMETHOD(device_probe, a10hdmiaudio_probe),
|
||||
DEVMETHOD(device_attach, a10hdmiaudio_attach),
|
||||
|
||||
DEVMETHOD_END
|
||||
};
|
||||
|
||||
static driver_t a10hdmiaudio_pcm_driver = {
|
||||
"pcm",
|
||||
a10hdmiaudio_pcm_methods,
|
||||
PCM_SOFTC_SIZE,
|
||||
};
|
||||
|
||||
DRIVER_MODULE(a10hdmiaudio, simplebus, a10hdmiaudio_pcm_driver, pcm_devclass, 0, 0);
|
||||
MODULE_DEPEND(a10hdmiaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
|
||||
MODULE_VERSION(a10hdmiaudio, 1);
|
@ -18,3 +18,8 @@ arm/allwinner/if_emac.c optional emac
|
||||
arm/allwinner/sunxi_dma_if.m standard
|
||||
dev/iicbus/twsi/a10_twsi.c optional twsi
|
||||
#arm/allwinner/console.c standard
|
||||
|
||||
arm/allwinner/a10_fb.c optional vt
|
||||
arm/allwinner/a10_hdmi.c optional hdmi
|
||||
arm/allwinner/a10_hdmiaudio.c optional hdmi sound
|
||||
arm/arm/hdmi_if.m optional hdmi
|
||||
|
@ -57,3 +57,11 @@ METHOD int set_videomode {
|
||||
device_t dev;
|
||||
const struct videomode *videomode;
|
||||
};
|
||||
|
||||
#
|
||||
# Enable/disable output
|
||||
#
|
||||
METHOD int enable {
|
||||
device_t dev;
|
||||
int onoff;
|
||||
};
|
||||
|
@ -120,6 +120,14 @@ device sound
|
||||
# Pinmux
|
||||
device fdt_pinctrl
|
||||
|
||||
# Framebuffer support
|
||||
device vt
|
||||
device kbdmux
|
||||
device ums
|
||||
device ukbd
|
||||
device videomode
|
||||
device hdmi
|
||||
|
||||
# Flattened Device Tree
|
||||
options FDT # Configure using FDT/DTB data
|
||||
makeoptions MODULES_EXTRA=dtb/allwinner
|
||||
|
@ -28,6 +28,7 @@
|
||||
*/
|
||||
|
||||
#include "sun7i-a20-cubieboard2.dts"
|
||||
#include "sun7i-a20-hdmi.dtsi"
|
||||
|
||||
/ {
|
||||
soc@01c00000 {
|
||||
@ -37,5 +38,13 @@
|
||||
#size-cells = <1>;
|
||||
reg = < 0x01c20000 0x400 >;
|
||||
};
|
||||
|
||||
hdmi@01c16000 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
hdmiaudio {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
48
sys/boot/fdt/dts/arm/sun7i-a20-hdmi.dtsi
Normal file
48
sys/boot/fdt/dts/arm/sun7i-a20-hdmi.dtsi
Normal file
@ -0,0 +1,48 @@
|
||||
/*-
|
||||
* Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
/ {
|
||||
soc@01c00000 {
|
||||
hdmi: hdmi@01c16000 {
|
||||
compatible = "allwinner,sun7i-a20-hdmi";
|
||||
reg = <0x01c16000 0x1000>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
hdmiaudio {
|
||||
compatible = "allwinner,sun7i-a20-hdmiaudio";
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
fb: fb@01e60000 {
|
||||
compatible = "allwinner,sun7i-a20-fb";
|
||||
reg = <0x01e60000 0x10000>, /* DEBE0 */
|
||||
<0x01c0c000 0x1000>; /* LCD0 */
|
||||
};
|
||||
};
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user