Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F144519047
D5383.1775164135.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
55 KB
Referenced Files
None
Subscribers
None
D5383.1775164135.diff
View Options
Index: head/sys/arm/allwinner/a10_clk.h
===================================================================
--- head/sys/arm/allwinner/a10_clk.h
+++ head/sys/arm/allwinner/a10_clk.h
@@ -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_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_ */
Index: head/sys/arm/allwinner/a10_clk.c
===================================================================
--- head/sys/arm/allwinner/a10_clk.c
+++ head/sys/arm/allwinner/a10_clk.c
@@ -43,6 +43,18 @@
#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 @@
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 @@
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);
+}
Index: head/sys/arm/allwinner/a10_fb.c
===================================================================
--- head/sys/arm/allwinner/a10_fb.c
+++ head/sys/arm/allwinner/a10_fb.c
@@ -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);
Index: head/sys/arm/allwinner/a10_hdmi.c
===================================================================
--- head/sys/arm/allwinner/a10_hdmi.c
+++ head/sys/arm/allwinner/a10_hdmi.c
@@ -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);
Index: head/sys/arm/allwinner/a10_hdmiaudio.c
===================================================================
--- head/sys/arm/allwinner/a10_hdmiaudio.c
+++ head/sys/arm/allwinner/a10_hdmiaudio.c
@@ -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);
Index: head/sys/arm/allwinner/files.allwinner
===================================================================
--- head/sys/arm/allwinner/files.allwinner
+++ head/sys/arm/allwinner/files.allwinner
@@ -18,3 +18,8 @@
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
Index: head/sys/arm/arm/hdmi_if.m
===================================================================
--- head/sys/arm/arm/hdmi_if.m
+++ head/sys/arm/arm/hdmi_if.m
@@ -57,3 +57,11 @@
device_t dev;
const struct videomode *videomode;
};
+
+#
+# Enable/disable output
+#
+METHOD int enable {
+ device_t dev;
+ int onoff;
+};
Index: head/sys/arm/conf/A20
===================================================================
--- head/sys/arm/conf/A20
+++ head/sys/arm/conf/A20
@@ -120,6 +120,14 @@
# 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
Index: head/sys/boot/fdt/dts/arm/cubieboard2.dts
===================================================================
--- head/sys/boot/fdt/dts/arm/cubieboard2.dts
+++ head/sys/boot/fdt/dts/arm/cubieboard2.dts
@@ -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";
+ };
};
};
Index: head/sys/boot/fdt/dts/arm/sun7i-a20-hdmi.dtsi
===================================================================
--- head/sys/boot/fdt/dts/arm/sun7i-a20-hdmi.dtsi
+++ head/sys/boot/fdt/dts/arm/sun7i-a20-hdmi.dtsi
@@ -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 */
+ };
+ };
+};
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Apr 2, 9:08 PM (10 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28249055
Default Alt Text
D5383.1775164135.diff (55 KB)
Attached To
Mode
D5383: Allwinner A20 HDMI support
Attached
Detach File
Event Timeline
Log In to Comment