Module Name:    src
Committed By:   snj
Date:           Wed Aug  9 05:49:50 UTC 2017

Modified Files:
        src/sys/arch/arm/sunxi [netbsd-8]: files.sunxi sun8i_h3_ccu.c
            sunxi_ccu.h sunxi_ccu_nkmp.c
        src/sys/arch/evbarm/conf [netbsd-8]: SUNXI
Added Files:
        src/sys/arch/arm/sunxi [netbsd-8]: sun6i_dma.c sun8i_h3_codec.c
            sunxi_codec.c sunxi_codec.h

Log Message:
Pull up following revision(s) (requested by jmcneill in ticket #200):
        sys/arch/arm/sunxi/files.sunxi: 1.15-1.16
        sys/arch/arm/sunxi/sun6i_dma.c: 1.1-1.2
        sys/arch/arm/sunxi/sun8i_h3_ccu.c: 1.9-1.10
        sys/arch/arm/sunxi/sun8i_h3_codec.c: 1.1-1.2
        sys/arch/arm/sunxi/sunxi_ccu.h: 1.8
        sys/arch/arm/sunxi/sunxi_ccu_nkmp.c: 1.5
        sys/arch/arm/sunxi/sunxi_codec.c: 1.1
        sys/arch/arm/sunxi/sunxi_codec.h: 1.1
        sys/arch/evbarm/conf/SUNXI: 1.20-1.21
Add DMA controller driver for sun6i and later family SoCs.
--
Enable sun6idma
--
add DMA gate
--
Fix burst field encoding, and add a helper function to dump registers
from ddb.
--
Add support for H3 audio PLL and digital audio part.
--
Add support for Allwinner H3 audio codec.
--
Enable H3 audio support
--
h3_codec_pr_write: clear write mode bit after setting it; fixes an issue with 
output being mutex when skipping tracks in mpg123


To generate a diff of this commit:
cvs rdiff -u -r1.12.4.4 -r1.12.4.5 src/sys/arch/arm/sunxi/files.sunxi
cvs rdiff -u -r0 -r1.2.2.2 src/sys/arch/arm/sunxi/sun6i_dma.c \
    src/sys/arch/arm/sunxi/sun8i_h3_codec.c
cvs rdiff -u -r1.8.4.3 -r1.8.4.4 src/sys/arch/arm/sunxi/sun8i_h3_ccu.c
cvs rdiff -u -r1.7.4.3 -r1.7.4.4 src/sys/arch/arm/sunxi/sunxi_ccu.h
cvs rdiff -u -r1.4.4.2 -r1.4.4.3 src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c
cvs rdiff -u -r0 -r1.1.2.2 src/sys/arch/arm/sunxi/sunxi_codec.c \
    src/sys/arch/arm/sunxi/sunxi_codec.h
cvs rdiff -u -r1.17.4.4 -r1.17.4.5 src/sys/arch/evbarm/conf/SUNXI

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/sunxi/files.sunxi
diff -u src/sys/arch/arm/sunxi/files.sunxi:1.12.4.4 src/sys/arch/arm/sunxi/files.sunxi:1.12.4.5
--- src/sys/arch/arm/sunxi/files.sunxi:1.12.4.4	Wed Jul 26 07:32:06 2017
+++ src/sys/arch/arm/sunxi/files.sunxi	Wed Aug  9 05:49:50 2017
@@ -1,4 +1,4 @@
-#	$NetBSD: files.sunxi,v 1.12.4.4 2017/07/26 07:32:06 martin Exp $
+#	$NetBSD: files.sunxi,v 1.12.4.5 2017/08/09 05:49:50 snj Exp $
 #
 # Configuration info for Allwinner sunxi family SoCs
 #
@@ -107,6 +107,21 @@ device	sunxiwdt: sysmon_wdog
 attach	sunxiwdt at fdt with sunxi_wdt
 file	arch/arm/sunxi/sunxi_wdt.c		sunxi_wdt
 
+# DMA controller
+device	sun6idma
+attach	sun6idma at fdt with sun6i_dma
+file	arch/arm/sunxi/sun6i_dma.c		sun6i_dma
+
+# Audio codec
+device	sunxicodec: audiobus, auconv, mulaw, aurateconv
+attach	sunxicodec at fdt with sunxi_codec
+file	arch/arm/sunxi/sunxi_codec.c		sunxi_codec
+
+# Audio codec (analog part)
+device	h3codec
+attach	h3codec at fdt with h3_codec
+file	arch/arm/sunxi/sun8i_h3_codec.c		h3_codec needs-flag
+
 # SOC parameters
 defflag	opt_soc.h			SOC_SUNXI
 defflag	opt_soc.h			SOC_SUN8I: SOC_SUNXI

Index: src/sys/arch/arm/sunxi/sun8i_h3_ccu.c
diff -u src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.8.4.3 src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.8.4.4
--- src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.8.4.3	Tue Jul 25 02:03:16 2017
+++ src/sys/arch/arm/sunxi/sun8i_h3_ccu.c	Wed Aug  9 05:49:50 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: sun8i_h3_ccu.c,v 1.8.4.3 2017/07/25 02:03:16 snj Exp $ */
+/* $NetBSD: sun8i_h3_ccu.c,v 1.8.4.4 2017/08/09 05:49:50 snj Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -29,7 +29,7 @@
 
 #include <sys/cdefs.h>
 
-__KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu.c,v 1.8.4.3 2017/07/25 02:03:16 snj Exp $");
+__KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu.c,v 1.8.4.4 2017/08/09 05:49:50 snj Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -41,6 +41,7 @@ __KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu
 #include <arm/sunxi/sunxi_ccu.h>
 #include <arm/sunxi/sun8i_h3_ccu.h>
 
+#define	PLL_AUDIO_CTRL_REG	0x008
 #define	PLL_PERIPH0_CTRL_REG	0x028
 #define	AHB1_APB1_CFG_REG	0x054
 #define	APB2_CFG_REG		0x058
@@ -55,6 +56,7 @@ __KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu
 #define	SDMMC2_CLK_REG		0x090
 #define	USBPHY_CFG_REG		0x0cc
 #define	MBUS_RST_REG		0x0fc
+#define	AC_DIG_CLK_REG		0x140
 #define	BUS_SOFT_RST_REG0	0x2c0
 #define	BUS_SOFT_RST_REG1	0x2c4
 #define	BUS_SOFT_RST_REG2	0x2c8
@@ -141,6 +143,11 @@ static const char *apb1_parents[] = { "a
 static const char *apb2_parents[] = { "losc", "hosc", "pll_periph0" };
 static const char *mod_parents[] = { "hosc", "pll_periph0", "pll_periph1" };
 
+static const struct sunxi_ccu_nkmp_tbl sunx8_h3_ac_dig_table[] = {
+	{ 24576000, 13, 0, 0, 13 },
+	{ 0 }
+};
+
 static struct sunxi_ccu_clk sun8i_h3_ccu_clks[] = {
 	SUNXI_CCU_NKMP(H3_CLK_PLL_PERIPH0, "pll_periph0", "hosc",
 	    PLL_PERIPH0_CTRL_REG,	/* reg */
@@ -151,6 +158,17 @@ static struct sunxi_ccu_clk sun8i_h3_ccu
 	    __BIT(31),			/* enable */
 	    SUNXI_CCU_NKMP_DIVIDE_BY_TWO),
 
+	SUNXI_CCU_NKMP_TABLE(H3_CLK_PLL_AUDIO_BASE, "pll_audio", "hosc",
+	    PLL_AUDIO_CTRL_REG,		/* reg */
+	    __BITS(14,8),		/* n */
+	    0,				/* k */
+	    __BITS(4,0),		/* m */
+	    __BITS(19,16),		/* p */
+	    __BIT(31),			/* enable */
+	    __BIT(28),			/* lock */
+	    sunx8_h3_ac_dig_table,	/* table */
+	    0),
+
 	SUNXI_CCU_PREDIV(H3_CLK_AHB1, "ahb1", ahb1_parents,
 	    AHB1_APB1_CFG_REG,	/* reg */
 	    __BITS(7,6),	/* prediv */
@@ -203,6 +221,11 @@ static struct sunxi_ccu_clk sun8i_h3_ccu
 	SUNXI_CCU_PHASE(H3_CLK_MMC2_OUTPUT, "mmc2_output", "mmc2",
 	    SDMMC2_CLK_REG, __BITS(10,8)),
 
+	SUNXI_CCU_GATE(H3_CLK_AC_DIG, "ac_dig", "pll_audio",
+	    AC_DIG_CLK_REG, 31),
+
+	SUNXI_CCU_GATE(H3_CLK_BUS_DMA, "bus-dma", "ahb1",
+	    BUS_CLK_GATING_REG0, 6),
 	SUNXI_CCU_GATE(H3_CLK_BUS_MMC0, "bus-mmc0", "ahb1",
 	    BUS_CLK_GATING_REG0, 8),
 	SUNXI_CCU_GATE(H3_CLK_BUS_MMC1, "bus-mmc1", "ahb1",
@@ -230,6 +253,8 @@ static struct sunxi_ccu_clk sun8i_h3_ccu
 	SUNXI_CCU_GATE(H3_CLK_BUS_OHCI3, "bus-ohci3", "ahb2",
 	    BUS_CLK_GATING_REG0, 31),
 
+	SUNXI_CCU_GATE(H3_CLK_BUS_CODEC, "bus-codec", "apb1",
+	    BUS_CLK_GATING_REG2, 0),
 	SUNXI_CCU_GATE(H3_CLK_BUS_PIO, "bus-pio", "apb1",
 	    BUS_CLK_GATING_REG2, 5),
 

Index: src/sys/arch/arm/sunxi/sunxi_ccu.h
diff -u src/sys/arch/arm/sunxi/sunxi_ccu.h:1.7.4.3 src/sys/arch/arm/sunxi/sunxi_ccu.h:1.7.4.4
--- src/sys/arch/arm/sunxi/sunxi_ccu.h:1.7.4.3	Tue Jul 25 02:03:16 2017
+++ src/sys/arch/arm/sunxi/sunxi_ccu.h	Wed Aug  9 05:49:50 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_ccu.h,v 1.7.4.3 2017/07/25 02:03:16 snj Exp $ */
+/* $NetBSD: sunxi_ccu.h,v 1.7.4.4 2017/08/09 05:49:50 snj Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -79,6 +79,7 @@ const char *sunxi_ccu_gate_get_parent(st
 	[_id] = {						\
 		.type = SUNXI_CCU_GATE,				\
 		.base.name = (_name),				\
+		.base.flags = CLK_SET_RATE_PARENT,		\
 		.u.gate.parent = (_pname),			\
 		.u.gate.reg = (_reg),				\
 		.u.gate.mask = __BIT(_bit),			\
@@ -86,6 +87,14 @@ const char *sunxi_ccu_gate_get_parent(st
 		.get_parent = sunxi_ccu_gate_get_parent,	\
 	}
 
+struct sunxi_ccu_nkmp_tbl {
+	u_int		rate;
+	uint32_t	n;
+	uint32_t	k;
+	uint32_t	m;
+	uint32_t	p;
+};
+
 struct sunxi_ccu_nkmp {
 	bus_size_t	reg;
 	const char	*parent;
@@ -96,6 +105,7 @@ struct sunxi_ccu_nkmp {
 	uint32_t	lock;
 	uint32_t	enable;
 	uint32_t	flags;
+	const struct sunxi_ccu_nkmp_tbl *table;
 #define	SUNXI_CCU_NKMP_DIVIDE_BY_TWO	__BIT(0)
 #define	SUNXI_CCU_NKMP_FACTOR_N_EXACT	__BIT(1)
 };
@@ -109,8 +119,8 @@ int	sunxi_ccu_nkmp_set_rate(struct sunxi
 const char *sunxi_ccu_nkmp_get_parent(struct sunxi_ccu_softc *,
 				      struct sunxi_ccu_clk *);
 
-#define	SUNXI_CCU_NKMP(_id, _name, _parent, _reg, _n, _k, _m,	\
-		       _p, _enable, _flags)			\
+#define	SUNXI_CCU_NKMP_TABLE(_id, _name, _parent, _reg, _n, _k, _m, \
+		       _p, _enable, _lock, _tbl, _flags)	\
 	[_id] = {						\
 		.type = SUNXI_CCU_NKMP,				\
 		.base.name = (_name),				\
@@ -122,12 +132,20 @@ const char *sunxi_ccu_nkmp_get_parent(st
 		.u.nkmp.p = (_p),				\
 		.u.nkmp.enable = (_enable),			\
 		.u.nkmp.flags = (_flags),			\
+		.u.nkmp.lock = (_lock),				\
+		.u.nkmp.table = (_tbl),				\
 		.enable = sunxi_ccu_nkmp_enable,		\
 		.get_rate = sunxi_ccu_nkmp_get_rate,		\
 		.set_rate = sunxi_ccu_nkmp_set_rate,		\
 		.get_parent = sunxi_ccu_nkmp_get_parent,	\
 	}
 
+#define	SUNXI_CCU_NKMP(_id, _name, _parent, _reg, _n, _k, _m,	\
+		       _p, _enable, _flags)			\
+	SUNXI_CCU_NKMP_TABLE(_id, _name, _parent, _reg, _n, _k, _m, \
+			     _p, _enable, 0, NULL, _flags)
+
+
 struct sunxi_ccu_nm {
 	bus_size_t	reg;
 	const char	**parents;

Index: src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c
diff -u src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c:1.4.4.2 src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c:1.4.4.3
--- src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c:1.4.4.2	Tue Jul 18 19:13:08 2017
+++ src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c	Wed Aug  9 05:49:50 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.2 2017/07/18 19:13:08 snj Exp $ */
+/* $NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.3 2017/08/09 05:49:50 snj Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.2 2017/07/18 19:13:08 snj Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.3 2017/08/09 05:49:50 snj Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -42,6 +42,7 @@ sunxi_ccu_nkmp_enable(struct sunxi_ccu_s
 {
 	struct sunxi_ccu_nkmp *nkmp = &clk->u.nkmp;
 	uint32_t val;
+	int retry;
 
 	KASSERT(clk->type == SUNXI_CCU_NKMP);
 
@@ -55,6 +56,17 @@ sunxi_ccu_nkmp_enable(struct sunxi_ccu_s
 		val &= ~nkmp->enable;
 	CCU_WRITE(sc, nkmp->reg, val);
 
+	if (enable && nkmp->lock) {
+		for (retry = 1000; retry > 0; retry--) {
+			val = CCU_READ(sc, nkmp->reg);
+			if (val & nkmp->lock)
+				break;
+			delay(100);
+		}
+		if (retry == 0)
+			return ETIMEDOUT;
+	}
+
 	return 0;
 }
 
@@ -115,7 +127,41 @@ int
 sunxi_ccu_nkmp_set_rate(struct sunxi_ccu_softc *sc,
     struct sunxi_ccu_clk *clk, u_int rate)
 {
-	return EIO;
+	struct sunxi_ccu_nkmp *nkmp = &clk->u.nkmp;
+	const struct sunxi_ccu_nkmp_tbl *tab;
+	uint32_t val;
+
+	KASSERT(clk->type == SUNXI_CCU_NKMP);
+
+	if (nkmp->table == NULL || rate == 0)
+		return EIO;
+
+	for (tab = nkmp->table; tab->rate > 0; tab++)
+		if (tab->rate == rate)
+			break;
+	if (tab->rate == 0)
+		return EINVAL;
+
+	val = CCU_READ(sc, nkmp->reg);
+	if (nkmp->n) {
+		val &= ~nkmp->n;
+		val |= __SHIFTIN(tab->n, nkmp->n);
+	}
+	if (nkmp->k) {
+		val &= ~nkmp->k;
+		val |= __SHIFTIN(tab->k, nkmp->k);
+	}
+	if (nkmp->m) {
+		val &= ~nkmp->m;
+		val |= __SHIFTIN(tab->m, nkmp->m);
+	}
+	if (nkmp->p) {
+		val &= ~nkmp->p;
+		val |= __SHIFTIN(tab->p, nkmp->p);
+	}
+	CCU_WRITE(sc, nkmp->reg, val);
+
+	return 0;
 }
 
 const char *

Index: src/sys/arch/evbarm/conf/SUNXI
diff -u src/sys/arch/evbarm/conf/SUNXI:1.17.4.4 src/sys/arch/evbarm/conf/SUNXI:1.17.4.5
--- src/sys/arch/evbarm/conf/SUNXI:1.17.4.4	Wed Jul 26 07:32:06 2017
+++ src/sys/arch/evbarm/conf/SUNXI	Wed Aug  9 05:49:50 2017
@@ -1,5 +1,5 @@
 #
-#	$NetBSD: SUNXI,v 1.17.4.4 2017/07/26 07:32:06 martin Exp $
+#	$NetBSD: SUNXI,v 1.17.4.5 2017/08/09 05:49:50 snj Exp $
 #
 #	Allwinner sunxi family
 #
@@ -89,6 +89,7 @@ armgic0		at gic?
 # Memory controller
 
 # DMA controller
+sun6idma*	at fdt?			# DMA controller
 
 # Clock and Reset controller
 
@@ -114,6 +115,12 @@ iic*		at i2cbus?
 # RTC
 sunxirtc*	at fdt?			# RTC
 
+# Audio
+sunxicodec*	at fdt?			# Audio codec
+h3codec*	at fdt?			# H3 audio codec (analog part)
+audio*		at audiobus?
+spkr*		at audio?
+
 # SDMMC
 sunximmc*	at fdt?			# SDMMC
 sdmmc*		at sunximmc?

Added files:

Index: src/sys/arch/arm/sunxi/sun6i_dma.c
diff -u /dev/null src/sys/arch/arm/sunxi/sun6i_dma.c:1.2.2.2
--- /dev/null	Wed Aug  9 05:49:50 2017
+++ src/sys/arch/arm/sunxi/sun6i_dma.c	Wed Aug  9 05:49:50 2017
@@ -0,0 +1,453 @@
+/* $NetBSD: sun6i_dma.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $ */
+
+/*-
+ * Copyright (c) 2014-2017 Jared McNeill <jmcne...@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.
+ */
+
+#include "opt_ddb.h"
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sun6i_dma.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/mutex.h>
+#include <sys/bitops.h>
+#include <sys/kmem.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#define DMA_IRQ_EN_REG0_REG		0x0000
+#define DMA_IRQ_EN_REG1_REG		0x0004
+#define  DMA_IRQ_EN_REG0_QUEUE_IRQ_EN(n)	__BIT(n * 4 + 2)
+#define  DMA_IRQ_EN_REG0_PKG_IRQ_EN(n)		__BIT(n * 4 + 1)
+#define  DMA_IRQ_EN_REG0_HLAF_IRQ_EN(n)		__BIT(n * 4 + 0)
+#define  DMA_IRQ_EN_REG1_QUEUE_IRQ_EN(n)	__BIT((n - 8) * 4 + 2)
+#define  DMA_IRQ_EN_REG1_PKG_IRQ_EN(n)		__BIT((n - 8) * 4 + 1)
+#define  DMA_IRQ_EN_REG1_HLAF_IRQ_EN(n)		__BIT((n - 8) * 4 + 0)
+#define DMA_IRQ_PEND_REG0_REG		0x0010
+#define DMA_IRQ_PEND_REG1_REG		0x0014
+#define  DMA_IRQ_QUEUE_MASK			0x4444444444444444ULL
+#define  DMA_IRQ_PKG_MASK			0x2222222222222222ULL
+#define  DMA_IRQ_HF_MASK			0x1111111111111111ULL
+#define DMA_STA_REG			0x0030
+#define DMA_EN_REG(n)			(0x0100 + (n) * 0x40 + 0x00)
+#define  DMA_EN_EN				__BIT(0)
+#define DMA_PAU_REG(n)			(0x0100 + (n) * 0x40 + 0x04)
+#define  DMA_PAU_PAUSE				__BIT(0)
+#define DMA_START_ADDR_REG(n)		(0x0100 + (n) * 0x40 + 0x08)
+#define DMA_CFG_REG(n)			(0x0100 + (n) * 0x40 + 0x0C)
+#define  DMA_CFG_DEST_DATA_WIDTH		__BITS(26,25)
+#define   DMA_CFG_DATA_WIDTH(n)			((n) >> 4)
+#define  DMA_CFG_DEST_BST_LEN			__BITS(24,23)
+#define	  DMA_CFG_BST_LEN(n)			((n) == 1 ? 0 : (((n) >> 3) + 1))
+#define  DMA_CFG_DEST_ADDR_MODE			__BITS(22,21)
+#define   DMA_CFG_ADDR_MODE_LINEAR		0
+#define   DMA_CFG_ADDR_MODE_IO			1
+#define  DMA_CFG_DEST_DRQ_TYPE			__BITS(20,16)
+#define	  DMA_CFG_DRQ_TYPE_SDRAM		1
+#define  DMA_CFG_SRC_DATA_WIDTH			__BITS(10,9)
+#define  DMA_CFG_SRC_BST_LEN			__BITS(8,7)
+#define  DMA_CFG_SRC_ADDR_MODE			__BITS(6,5)
+#define  DMA_CFG_SRC_DRQ_TYPE			__BITS(4,0)
+#define DMA_CUR_SRC_REG(n)		(0x0100 + (n) * 0x40 + 0x10)
+#define DMA_CUR_DEST_REG(n)		(0x0100 + (n) * 0x40 + 0x14)
+#define DMA_BCNT_LEFT_REG(n)		(0x0100 + (n) * 0x40 + 0x18)
+#define DMA_PARA_REG(n)			(0x0100 + (n) * 0x40 + 0x1C)
+#define  DMA_PARA_DATA_BLK_SIZE			__BITS(15,8)
+#define  DMA_PARA_WAIT_CYC			__BITS(7,0)
+
+struct sun6idma_desc {
+	uint32_t	dma_config;
+	uint32_t	dma_srcaddr;
+	uint32_t	dma_dstaddr;
+	uint32_t	dma_bcnt;
+	uint32_t	dma_para;
+	uint32_t	dma_next;
+#define DMA_NULL	0xfffff800
+};
+
+static const struct of_compat_data compat_data[] = {
+	{ "allwinner,sun6i-a31-dma",		16 },
+	{ "allwinner,sun8i-a83t-dma",		8 },
+	{ "allwinner,sun8i-h3-dma",		12 },
+	{ NULL }
+};
+
+struct sun6idma_channel {
+	uint8_t			ch_index;
+	void			(*ch_callback)(void *);
+	void			*ch_callbackarg;
+	u_int			ch_portid;
+
+	bus_dma_segment_t	ch_dmasegs[1];
+	bus_dmamap_t		ch_dmamap;
+	void			*ch_dmadesc;
+	bus_size_t		ch_dmadesclen;
+};
+
+struct sun6idma_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+	bus_dma_tag_t		sc_dmat;
+	int			sc_phandle;
+	void			*sc_ih;
+
+	kmutex_t		sc_lock;
+
+	struct sun6idma_channel	*sc_chan;
+	u_int			sc_nchan;
+};
+
+#define DMA_READ(sc, reg)		\
+    bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define DMA_WRITE(sc, reg, val)		\
+    bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+
+static void *
+sun6idma_acquire(device_t dev, const void *data, size_t len,
+    void (*cb)(void *), void *cbarg)
+{
+	struct sun6idma_softc *sc = device_private(dev);
+	struct sun6idma_channel *ch = NULL;
+	uint32_t irqen;
+	uint8_t index;
+
+	if (len != 4)
+		return NULL;
+
+	const u_int portid = be32dec(data);
+	if (portid > __SHIFTOUT_MASK(DMA_CFG_SRC_DRQ_TYPE))
+		return NULL;
+
+	mutex_enter(&sc->sc_lock);
+
+	for (index = 0; index < sc->sc_nchan; index++) {
+		if (sc->sc_chan[index].ch_callback == NULL) {
+			ch = &sc->sc_chan[index];
+			ch->ch_callback = cb;
+			ch->ch_callbackarg = cbarg;
+			ch->ch_portid = portid;
+
+			irqen = DMA_READ(sc, index < 8 ?
+			    DMA_IRQ_EN_REG0_REG :
+			    DMA_IRQ_EN_REG1_REG);
+			irqen |= (index < 8 ?
+			    DMA_IRQ_EN_REG0_PKG_IRQ_EN(index) :
+			    DMA_IRQ_EN_REG1_PKG_IRQ_EN(index));
+			DMA_WRITE(sc, index < 8 ?
+			    DMA_IRQ_EN_REG0_REG :
+			    DMA_IRQ_EN_REG1_REG, irqen);
+
+			break;
+		}
+	}
+
+	mutex_exit(&sc->sc_lock);
+
+	return ch;
+}
+
+static void
+sun6idma_release(device_t dev, void *priv)
+{
+	struct sun6idma_softc *sc = device_private(dev);
+	struct sun6idma_channel *ch = priv;
+	uint32_t irqen;
+	uint8_t index = ch->ch_index;
+
+	mutex_enter(&sc->sc_lock);
+
+	irqen = DMA_READ(sc, index < 8 ?
+	    DMA_IRQ_EN_REG0_REG :
+	    DMA_IRQ_EN_REG1_REG);
+	irqen &= ~(index < 8 ?
+	    DMA_IRQ_EN_REG0_PKG_IRQ_EN(index) :
+	    DMA_IRQ_EN_REG1_PKG_IRQ_EN(index));
+	DMA_WRITE(sc, index < 8 ?
+	    DMA_IRQ_EN_REG0_REG :
+	    DMA_IRQ_EN_REG1_REG, irqen);
+
+	ch->ch_callback = NULL;
+	ch->ch_callbackarg = NULL;
+
+	mutex_exit(&sc->sc_lock);
+}
+
+static int
+sun6idma_transfer(device_t dev, void *priv, struct fdtbus_dma_req *req)
+{
+	struct sun6idma_softc *sc = device_private(dev);
+	struct sun6idma_channel *ch = priv;
+	struct sun6idma_desc *desc = ch->ch_dmadesc;
+	uint32_t src, dst, len, cfg, mem_cfg, dev_cfg;
+	uint32_t mem_width, dev_width, mem_burst, dev_burst;
+
+	if (req->dreq_nsegs != 1)
+		return EINVAL;
+
+	mem_width = DMA_CFG_DATA_WIDTH(req->dreq_mem_opt.opt_bus_width);
+	dev_width = DMA_CFG_DATA_WIDTH(req->dreq_dev_opt.opt_bus_width);
+	mem_burst = DMA_CFG_BST_LEN(req->dreq_mem_opt.opt_burst_len);
+	dev_burst = DMA_CFG_BST_LEN(req->dreq_dev_opt.opt_burst_len);
+
+	mem_cfg = __SHIFTIN(mem_width, DMA_CFG_SRC_DATA_WIDTH) |
+	    __SHIFTIN(mem_burst, DMA_CFG_SRC_BST_LEN) |
+	    __SHIFTIN(DMA_CFG_ADDR_MODE_LINEAR, DMA_CFG_SRC_ADDR_MODE) |
+	    __SHIFTIN(DMA_CFG_DRQ_TYPE_SDRAM, DMA_CFG_SRC_DRQ_TYPE);
+	dev_cfg = __SHIFTIN(dev_width, DMA_CFG_SRC_DATA_WIDTH) |
+	    __SHIFTIN(dev_burst, DMA_CFG_SRC_BST_LEN) |
+	    __SHIFTIN(DMA_CFG_ADDR_MODE_IO, DMA_CFG_SRC_ADDR_MODE) |
+	    __SHIFTIN(ch->ch_portid, DMA_CFG_SRC_DRQ_TYPE);
+
+	if (req->dreq_dir == FDT_DMA_READ) {
+		src = req->dreq_dev_phys;
+		dst = req->dreq_segs[0].ds_addr;
+		cfg = mem_cfg << 16 | dev_cfg;
+	} else {
+		src = req->dreq_segs[0].ds_addr;
+		dst = req->dreq_dev_phys;
+		cfg = dev_cfg << 16 | mem_cfg;
+	}
+	len = req->dreq_segs[0].ds_len;
+
+	desc->dma_config = htole32(cfg);
+	desc->dma_srcaddr = htole32(src);
+	desc->dma_dstaddr = htole32(dst);
+	desc->dma_bcnt = htole32(len);
+	desc->dma_para = htole32(0);
+	desc->dma_next = htole32(DMA_NULL);
+
+	bus_dmamap_sync(sc->sc_dmat, ch->ch_dmamap, 0, ch->ch_dmadesclen,
+	    BUS_DMASYNC_PREWRITE);
+
+	DMA_WRITE(sc, DMA_START_ADDR_REG(ch->ch_index),
+	    ch->ch_dmamap->dm_segs[0].ds_addr);
+	DMA_WRITE(sc, DMA_EN_REG(ch->ch_index), DMA_EN_EN);
+
+	if ((DMA_READ(sc, DMA_EN_REG(ch->ch_index)) & DMA_EN_EN) == 0) {
+		aprint_error_dev(sc->sc_dev,
+		    "DMA Channel %u failed to start\n", ch->ch_index);
+		return EIO;
+	}
+
+	return 0;
+}
+
+static void
+sun6idma_halt(device_t dev, void *priv)
+{
+	struct sun6idma_softc *sc = device_private(dev);
+	struct sun6idma_channel *ch = priv;
+
+	DMA_WRITE(sc, DMA_EN_REG(ch->ch_index), 0);
+}
+
+static const struct fdtbus_dma_controller_func sun6idma_funcs = {
+	.acquire = sun6idma_acquire,
+	.release = sun6idma_release,
+	.transfer = sun6idma_transfer,
+	.halt = sun6idma_halt
+};
+
+static int
+sun6idma_intr(void *priv)
+{
+	struct sun6idma_softc *sc = priv;
+	uint32_t pend0, pend1, bit;
+	uint64_t pend, mask;
+	uint8_t index;
+
+	pend0 = DMA_READ(sc, DMA_IRQ_PEND_REG0_REG);
+	pend1 = DMA_READ(sc, DMA_IRQ_PEND_REG1_REG);
+	if (!pend0 && !pend1)
+		return 0;
+
+	DMA_WRITE(sc, DMA_IRQ_PEND_REG0_REG, pend0);
+	DMA_WRITE(sc, DMA_IRQ_PEND_REG1_REG, pend1);
+
+	pend = pend0 | ((uint64_t)pend1 << 32);
+
+	while ((bit = ffs64(pend & DMA_IRQ_PKG_MASK)) != 0) {
+		mask = __BIT(bit - 1);
+		pend &= ~mask;
+		index = (bit - 1) / 4;
+
+		if (sc->sc_chan[index].ch_callback == NULL)
+			continue;
+		sc->sc_chan[index].ch_callback(
+		    sc->sc_chan[index].ch_callbackarg);
+	}
+
+	return 1;
+}
+
+static int
+sun6idma_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compat_data(faa->faa_phandle, compat_data);
+}
+
+static void
+sun6idma_attach(device_t parent, device_t self, void *aux)
+{
+	struct sun6idma_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	const size_t desclen = sizeof(struct sun6idma_desc);
+	struct fdtbus_reset *rst;
+	struct clk *clk;
+	char intrstr[128];
+	bus_addr_t addr;
+	bus_size_t size;
+	int error, nsegs;
+	u_int index;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	if ((clk = fdtbus_clock_get_index(phandle, 0)) == NULL ||
+	    clk_enable(clk) != 0) {
+		aprint_error(": couldn't enable clock\n");
+		return;
+	}
+	if ((rst = fdtbus_reset_get_index(phandle, 0)) == NULL ||
+	    fdtbus_reset_deassert(rst) != 0) {
+		aprint_error(": couldn't de-assert reset\n");
+		return;
+	}
+
+	sc->sc_dev = self;
+	sc->sc_phandle = phandle;
+	sc->sc_dmat = faa->faa_dmat;
+	sc->sc_bst = faa->faa_bst;
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SCHED);
+
+	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
+		aprint_error(": failed to decode interrupt\n");
+		return;
+	}
+
+	sc->sc_nchan = of_search_compatible(phandle, compat_data)->data;
+	sc->sc_chan = kmem_alloc(sizeof(*sc->sc_chan) * sc->sc_nchan, KM_SLEEP);
+
+	aprint_naive("\n");
+	aprint_normal(": DMA controller (%u channels)\n", sc->sc_nchan);
+
+	DMA_WRITE(sc, DMA_IRQ_EN_REG0_REG, 0);
+	DMA_WRITE(sc, DMA_IRQ_EN_REG1_REG, 0);
+	DMA_WRITE(sc, DMA_IRQ_PEND_REG0_REG, ~0);
+	DMA_WRITE(sc, DMA_IRQ_PEND_REG1_REG, ~0);
+
+	for (index = 0; index < sc->sc_nchan; index++) {
+		struct sun6idma_channel *ch = &sc->sc_chan[index];
+		ch->ch_index = index;
+		ch->ch_callback = NULL;
+		ch->ch_callbackarg = NULL;
+		ch->ch_dmadesclen = desclen;
+
+		error = bus_dmamem_alloc(sc->sc_dmat, desclen, 0, 0,
+		    ch->ch_dmasegs, 1, &nsegs, BUS_DMA_WAITOK);
+		if (error)
+			panic("bus_dmamem_alloc failed: %d", error);
+		error = bus_dmamem_map(sc->sc_dmat, ch->ch_dmasegs, nsegs,
+		    desclen, &ch->ch_dmadesc, BUS_DMA_WAITOK);
+		if (error)
+			panic("bus_dmamem_map failed: %d", error);
+		error = bus_dmamap_create(sc->sc_dmat, desclen, 1, desclen, 0,
+		    BUS_DMA_WAITOK, &ch->ch_dmamap);
+		if (error)
+			panic("bus_dmamap_create failed: %d", error);
+		error = bus_dmamap_load(sc->sc_dmat, ch->ch_dmamap,
+		    ch->ch_dmadesc, desclen, NULL, BUS_DMA_WAITOK);
+		if (error)
+			panic("bus_dmamap_load failed: %d", error);
+
+		DMA_WRITE(sc, DMA_EN_REG(index), 0);
+	}
+
+	sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_SCHED, FDT_INTR_MPSAFE,
+	    sun6idma_intr, sc);
+	if (sc->sc_ih == NULL) {
+		aprint_error_dev(sc->sc_dev,
+		    "couldn't establish interrupt on %s\n", intrstr);
+		return;
+	}
+	aprint_normal_dev(sc->sc_dev, "interrupting on %s\n", intrstr);
+
+	fdtbus_register_dma_controller(self, phandle, &sun6idma_funcs);
+}
+
+CFATTACH_DECL_NEW(sun6i_dma, sizeof(struct sun6idma_softc),
+        sun6idma_match, sun6idma_attach, NULL, NULL);
+
+#ifdef DDB
+void sun6idma_dump(void);
+
+void
+sun6idma_dump(void)
+{
+	struct sun6idma_softc *sc;
+	device_t dev;
+	u_int index;
+
+	dev = device_find_by_driver_unit("sun6idma", 0);
+	if (dev == NULL)
+		return;
+	sc = device_private(dev);
+
+	device_printf(dev, "DMA_IRQ_EN_REG0_REG:   %08x\n", DMA_READ(sc, DMA_IRQ_EN_REG0_REG));
+	device_printf(dev, "DMA_IRQ_EN_REG1_REG:   %08x\n", DMA_READ(sc, DMA_IRQ_EN_REG1_REG));
+	device_printf(dev, "DMA_IRQ_PEND_REG0_REG: %08x\n", DMA_READ(sc, DMA_IRQ_PEND_REG0_REG));
+	device_printf(dev, "DMA_IRQ_PEND_REG1_REG: %08x\n", DMA_READ(sc, DMA_IRQ_PEND_REG1_REG));
+	device_printf(dev, "DMA_STA_REG:           %08x\n", DMA_READ(sc, DMA_STA_REG));
+
+	for (index = 0; index < sc->sc_nchan; index++) {
+		struct sun6idma_channel *ch = &sc->sc_chan[index];
+		if (ch->ch_callback == NULL)
+			continue;
+		device_printf(dev, " %2d: DMA_EN_REG:         %08x\n", index, DMA_READ(sc, DMA_EN_REG(index)));
+		device_printf(dev, " %2d: DMA_PAU_REG:        %08x\n", index, DMA_READ(sc, DMA_PAU_REG(index)));
+		device_printf(dev, " %2d: DMA_START_ADDR_REG: %08x\n", index, DMA_READ(sc, DMA_START_ADDR_REG(index)));
+		device_printf(dev, " %2d: DMA_CFG_REG:        %08x\n", index, DMA_READ(sc, DMA_CFG_REG(index)));
+		device_printf(dev, " %2d: DMA_CUR_SRC_REG:    %08x\n", index, DMA_READ(sc, DMA_CUR_SRC_REG(index)));
+		device_printf(dev, " %2d: DMA_CUR_DEST_REG:   %08x\n", index, DMA_READ(sc, DMA_CUR_DEST_REG(index)));
+		device_printf(dev, " %2d: DMA_BCNT_LEFT_REG:  %08x\n", index, DMA_READ(sc, DMA_BCNT_LEFT_REG(index)));
+		device_printf(dev, " %2d: DMA_PARA_REG:       %08x\n", index, DMA_READ(sc, DMA_PARA_REG(index)));
+	}
+}
+#endif
Index: src/sys/arch/arm/sunxi/sun8i_h3_codec.c
diff -u /dev/null src/sys/arch/arm/sunxi/sun8i_h3_codec.c:1.2.2.2
--- /dev/null	Wed Aug  9 05:49:50 2017
+++ src/sys/arch/arm/sunxi/sun8i_h3_codec.c	Wed Aug  9 05:49:50 2017
@@ -0,0 +1,475 @@
+/* $NetBSD: sun8i_h3_codec.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $ */
+
+/*-
+ * Copyright (c) 2014-2017 Jared McNeill <jmcne...@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.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sun8i_h3_codec.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/cpu.h>
+#include <sys/device.h>
+#include <sys/kmem.h>
+#include <sys/bitops.h>
+
+#include <sys/audioio.h>
+#include <dev/audio_if.h>
+
+#include <arm/sunxi/sunxi_codec.h>
+
+#define	H3_PR_CFG		0x00
+#define	 H3_AC_PR_RW		__BIT(24)
+#define	 H3_AC_PR_RST		__BIT(18)
+#define	 H3_AC_PR_ADDR		__BITS(20,16)
+#define	 H3_ACDA_PR_WDAT	__BITS(15,8)
+#define	 H3_ACDA_PR_RDAT	__BITS(7,0)
+
+#define	H3_LOMIXSC		0x01
+#define	 H3_LOMIXSC_LDAC	__BIT(1)
+#define	H3_ROMIXSC		0x02
+#define	 H3_ROMIXSC_RDAC	__BIT(1)
+#define	H3_DAC_PA_SRC		0x03
+#define	 H3_DACAREN		__BIT(7)
+#define	 H3_DACALEN		__BIT(6)
+#define	 H3_RMIXEN		__BIT(5)
+#define	 H3_LMIXEN		__BIT(4)
+#define	H3_LINEIN_GCTR		0x05
+#define	 H3_LINEING		__BITS(6,4)
+#define	H3_MIC_GCTR		0x06
+#define	 H3_MIC1_GAIN		__BITS(6,4)
+#define	 H3_MIC2_GAIN		__BITS(2,0)
+#define	H3_PAEN_CTR		0x07
+#define	 H3_LINEOUTEN		__BIT(7)
+#define	H3_LINEOUT_VOLC		0x09
+#define	 H3_LINEOUTVOL		__BITS(7,3)
+#define	H3_MIC2G_LINEOUT_CTR	0x0a
+#define	 H3_LINEOUT_LSEL	__BIT(3)
+#define	 H3_LINEOUT_RSEL	__BIT(2)
+#define	H3_LADCMIXSC		0x0c
+#define	H3_RADCMIXSC		0x0d
+#define	 H3_ADCMIXSC_MIC1	__BIT(6)
+#define	 H3_ADCMIXSC_MIC2	__BIT(5)
+#define	 H3_ADCMIXSC_LINEIN	__BIT(2)
+#define	 H3_ADCMIXSC_OMIXER	__BITS(1,0)
+#define	H3_ADC_AP_EN		0x0f
+#define	 H3_ADCREN		__BIT(7)
+#define	 H3_ADCLEN		__BIT(6)
+#define	 H3_ADCG		__BITS(2,0)
+
+struct h3_codec_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+	int			sc_phandle;
+};
+
+enum h3_codec_mixer_ctrl {
+	H3_CODEC_OUTPUT_CLASS,
+	H3_CODEC_INPUT_CLASS,
+	H3_CODEC_RECORD_CLASS,
+
+	H3_CODEC_OUTPUT_MASTER_VOLUME,
+	H3_CODEC_INPUT_DAC_VOLUME,
+	H3_CODEC_INPUT_LINEIN_VOLUME,
+	H3_CODEC_INPUT_MIC1_VOLUME,
+	H3_CODEC_INPUT_MIC2_VOLUME,
+	H3_CODEC_RECORD_AGC_VOLUME,
+	H3_CODEC_RECORD_SOURCE,
+
+	H3_CODEC_MIXER_CTRL_LAST
+};
+
+static const struct h3_codec_mixer {
+	const char *			name;
+	enum h3_codec_mixer_ctrl	mixer_class;
+	u_int				reg;
+	u_int				mask;
+} h3_codec_mixers[H3_CODEC_MIXER_CTRL_LAST] = {
+	[H3_CODEC_OUTPUT_MASTER_VOLUME]	= { AudioNmaster,
+	    H3_CODEC_OUTPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL },
+	[H3_CODEC_INPUT_DAC_VOLUME]	= { AudioNdac,
+	    H3_CODEC_INPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL },
+	[H3_CODEC_INPUT_LINEIN_VOLUME]	= { AudioNline,
+	    H3_CODEC_INPUT_CLASS, H3_LINEIN_GCTR, H3_LINEING },
+	[H3_CODEC_INPUT_MIC1_VOLUME]	= { "mic1",
+	    H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC1_GAIN },
+	[H3_CODEC_INPUT_MIC2_VOLUME]	= { "mic2",
+	    H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC2_GAIN },
+	[H3_CODEC_RECORD_AGC_VOLUME]	= { AudioNagc,
+	    H3_CODEC_RECORD_CLASS, H3_ADC_AP_EN, H3_ADCG },
+};
+
+#define	RD4(sc, reg)			\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define	WR4(sc, reg, val)		\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+
+static struct h3_codec_softc *
+h3_codec_find(int phandle)
+{
+	struct h3_codec_softc *csc;
+	device_t dev;
+
+	dev = device_find_by_driver_unit("h3codec", 0);
+	if (dev == NULL)
+		return NULL;
+	csc = device_private(dev);
+	if (csc->sc_phandle != phandle)
+		return NULL;
+
+	return csc;
+}
+
+static u_int
+h3_codec_pr_read(struct h3_codec_softc *csc, u_int addr)
+{
+	uint32_t val;
+
+	/* Read current value */
+	val = RD4(csc, H3_PR_CFG);
+
+	/* De-assert reset */
+	val |= H3_AC_PR_RST;
+	WR4(csc, H3_PR_CFG, val);
+
+	/* Read mode */
+	val &= ~H3_AC_PR_RW;
+	WR4(csc, H3_PR_CFG, val);
+
+	/* Set address */
+	val &= ~H3_AC_PR_ADDR;
+	val |= __SHIFTIN(addr, H3_AC_PR_ADDR);
+	WR4(csc, H3_PR_CFG, val);
+
+	/* Read data */
+	return __SHIFTOUT(RD4(csc, H3_PR_CFG), H3_ACDA_PR_RDAT);
+}
+
+static void
+h3_codec_pr_write(struct h3_codec_softc *csc, u_int addr, u_int data)
+{
+	uint32_t val;
+
+	/* Read current value */
+	val = RD4(csc, H3_PR_CFG);
+
+	/* De-assert reset */
+	val |= H3_AC_PR_RST;
+	WR4(csc, H3_PR_CFG, val);
+
+	/* Set address */
+	val &= ~H3_AC_PR_ADDR;
+	val |= __SHIFTIN(addr, H3_AC_PR_ADDR);
+	WR4(csc, H3_PR_CFG, val);
+
+	/* Write data */
+	val &= ~H3_ACDA_PR_WDAT;
+	val |= __SHIFTIN(data, H3_ACDA_PR_WDAT);
+	WR4(csc, H3_PR_CFG, val);
+
+	/* Write mode */
+	val |= H3_AC_PR_RW;
+	WR4(csc, H3_PR_CFG, val);
+
+	/* Clear write mode */
+	val &= ~H3_AC_PR_RW;
+	WR4(csc, H3_PR_CFG, val);
+}
+
+static void
+h3_codec_pr_set_clear(struct h3_codec_softc *csc, u_int addr, u_int set, u_int clr)
+{
+	u_int old, new;
+
+	old = h3_codec_pr_read(csc, addr);
+	new = set | (old & ~clr);
+	h3_codec_pr_write(csc, addr, new);
+}
+
+static int
+h3_codec_init(struct sunxi_codec_softc *sc)
+{
+	struct h3_codec_softc *csc;
+	int phandle;
+
+	/* Lookup the codec analog controls phandle */
+	phandle = fdtbus_get_phandle(sc->sc_phandle,
+	    "allwinner,codec-analog-controls");
+	if (phandle < 0) {
+		aprint_error_dev(sc->sc_dev,
+		    "missing allwinner,codec-analog-controls property\n");
+		return ENXIO;
+	}
+
+	/* Find a matching h3codec instance */
+	sc->sc_codec_priv = h3_codec_find(phandle);
+	if (sc->sc_codec_priv == NULL) {
+		aprint_error_dev(sc->sc_dev, "couldn't find codec analog controls\n");
+		return ENOENT;
+	}
+	csc = sc->sc_codec_priv;
+
+	/* Right & Left LINEOUT enable */
+	h3_codec_pr_set_clear(csc, H3_PAEN_CTR, H3_LINEOUTEN, 0);
+	h3_codec_pr_set_clear(csc, H3_MIC2G_LINEOUT_CTR,
+	    H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0);
+
+	return 0;
+}
+
+static void
+h3_codec_mute(struct sunxi_codec_softc *sc, int mute, u_int mode)
+{
+	struct h3_codec_softc * const csc = sc->sc_codec_priv;
+
+	if (mode == AUMODE_PLAY) {
+		if (mute) {
+			/* Mute DAC l/r channels to output mixer */
+			h3_codec_pr_set_clear(csc, H3_LOMIXSC,
+			    0, H3_LOMIXSC_LDAC);
+			h3_codec_pr_set_clear(csc, H3_ROMIXSC,
+			    0, H3_ROMIXSC_RDAC);
+			/* Disable DAC analog l/r channels and output mixer */
+			h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC,
+			    0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN);
+		} else {
+			/* Enable DAC analog l/r channels and output mixer */
+			h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC,
+			    H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0);
+			/* Unmute DAC l/r channels to output mixer */
+			h3_codec_pr_set_clear(csc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0);
+			h3_codec_pr_set_clear(csc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0);
+		}
+	} else {
+		if (mute) {
+			/* Disable ADC analog l/r channels */
+			h3_codec_pr_set_clear(csc, H3_ADC_AP_EN,
+			    0, H3_ADCREN | H3_ADCLEN);
+		} else {
+			/* Enable ADC analog l/r channels */
+			h3_codec_pr_set_clear(csc, H3_ADC_AP_EN,
+			    H3_ADCREN | H3_ADCLEN, 0);
+		}
+	}
+}
+
+static int
+h3_codec_set_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc)
+{
+	struct h3_codec_softc * const csc = sc->sc_codec_priv;
+	const struct h3_codec_mixer *mix;
+	u_int val, shift;
+	int nvol;
+
+	switch (mc->dev) {
+	case H3_CODEC_OUTPUT_MASTER_VOLUME:
+	case H3_CODEC_INPUT_DAC_VOLUME:
+	case H3_CODEC_INPUT_LINEIN_VOLUME:
+	case H3_CODEC_INPUT_MIC1_VOLUME:
+	case H3_CODEC_INPUT_MIC2_VOLUME:
+	case H3_CODEC_RECORD_AGC_VOLUME:
+		mix = &h3_codec_mixers[mc->dev];
+		val = h3_codec_pr_read(csc, mix->reg);
+		shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask));
+		nvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] >> shift;
+		val &= ~mix->mask;
+		val |= __SHIFTIN(nvol, mix->mask);
+		h3_codec_pr_write(csc, mix->reg, val);
+		return 0;
+
+	case H3_CODEC_RECORD_SOURCE:
+		h3_codec_pr_write(csc, H3_LADCMIXSC, mc->un.mask);
+		h3_codec_pr_write(csc, H3_RADCMIXSC, mc->un.mask);
+		return 0;
+	}
+
+	return ENXIO;
+}
+
+static int
+h3_codec_get_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc)
+{
+	struct h3_codec_softc * const csc = sc->sc_codec_priv;
+	const struct h3_codec_mixer *mix;
+	u_int val, shift;
+	int nvol;
+
+	switch (mc->dev) {
+	case H3_CODEC_OUTPUT_MASTER_VOLUME:
+	case H3_CODEC_INPUT_DAC_VOLUME:
+	case H3_CODEC_INPUT_LINEIN_VOLUME:
+	case H3_CODEC_INPUT_MIC1_VOLUME:
+	case H3_CODEC_INPUT_MIC2_VOLUME:
+	case H3_CODEC_RECORD_AGC_VOLUME:
+		mix = &h3_codec_mixers[mc->dev];
+		val = h3_codec_pr_read(csc, mix->reg);
+		shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask));
+		nvol = __SHIFTOUT(val, mix->mask) << shift;
+		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = nvol;
+		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = nvol;
+		return 0;
+
+	case H3_CODEC_RECORD_SOURCE:
+		mc->un.mask =
+		    h3_codec_pr_read(csc, H3_LADCMIXSC) |
+		    h3_codec_pr_read(csc, H3_RADCMIXSC);
+		return 0;
+	}
+
+	return ENXIO;
+}
+
+static int
+h3_codec_query_devinfo(struct sunxi_codec_softc *sc, mixer_devinfo_t *di)
+{
+	const struct h3_codec_mixer *mix;
+
+	switch (di->index) {
+	case H3_CODEC_OUTPUT_CLASS:
+		di->mixer_class = di->index;
+		strcpy(di->label.name, AudioCoutputs);
+		di->type = AUDIO_MIXER_CLASS;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		return 0;
+
+	case H3_CODEC_INPUT_CLASS:
+		di->mixer_class = di->index;
+		strcpy(di->label.name, AudioCinputs);
+		di->type = AUDIO_MIXER_CLASS;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		return 0;
+
+	case H3_CODEC_RECORD_CLASS:
+		di->mixer_class = di->index;
+		strcpy(di->label.name, AudioCrecord);
+		di->type = AUDIO_MIXER_CLASS;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		return 0;
+
+	case H3_CODEC_OUTPUT_MASTER_VOLUME:
+	case H3_CODEC_INPUT_DAC_VOLUME:
+	case H3_CODEC_INPUT_LINEIN_VOLUME:
+	case H3_CODEC_INPUT_MIC1_VOLUME:
+	case H3_CODEC_INPUT_MIC2_VOLUME:
+	case H3_CODEC_RECORD_AGC_VOLUME:
+		mix = &h3_codec_mixers[di->index];
+		di->mixer_class = mix->mixer_class;
+		strcpy(di->label.name, mix->name);
+		di->un.v.delta =
+		    256 / (__SHIFTOUT_MASK(mix->mask) + 1);
+		di->type = AUDIO_MIXER_VALUE;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		di->un.v.num_channels = 2;
+		strcpy(di->un.v.units.name, AudioNvolume);
+		return 0;
+
+	case H3_CODEC_RECORD_SOURCE:
+		di->mixer_class = H3_CODEC_RECORD_CLASS;
+		strcpy(di->label.name, AudioNsource);
+		di->type = AUDIO_MIXER_SET;
+		di->next = di->prev = AUDIO_MIXER_LAST;
+		di->un.s.num_mem = 4;
+		strcpy(di->un.s.member[0].label.name, AudioNline);
+		di->un.s.member[0].mask = H3_ADCMIXSC_LINEIN;
+		strcpy(di->un.s.member[1].label.name, "mic1");
+		di->un.s.member[1].mask = H3_ADCMIXSC_MIC1;
+		strcpy(di->un.s.member[2].label.name, "mic2");
+		di->un.s.member[2].mask = H3_ADCMIXSC_MIC2;
+		strcpy(di->un.s.member[3].label.name, AudioNdac);
+		di->un.s.member[3].mask = H3_ADCMIXSC_OMIXER;
+		return 0;
+
+	}
+
+	return ENXIO;
+}
+
+const struct sunxi_codec_conf sun8i_h3_codecconf = {
+	.name = "H3 Audio Codec",
+
+	.init = h3_codec_init,
+	.mute = h3_codec_mute,
+	.set_port = h3_codec_set_port,
+	.get_port = h3_codec_get_port,
+	.query_devinfo = h3_codec_query_devinfo,
+
+	.DPC		= 0x00,
+	.DAC_FIFOC	= 0x04,
+	.DAC_FIFOS	= 0x08,
+	.DAC_TXDATA	= 0x20,
+	.ADC_FIFOC	= 0x10,
+	.ADC_FIFOS	= 0x14,
+	.ADC_RXDATA	= 0x18,
+	.DAC_CNT	= 0x40,
+	.ADC_CNT	= 0x44,
+};
+
+/*
+ * Device glue, only here to claim resources on behalf of the sunxi_codec driver.
+ */
+
+static const char * compatible[] = {
+	"allwinner,sun8i-h3-codec-analog",
+	NULL
+};
+
+static int
+h3_codec_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+h3_codec_attach(device_t parent, device_t self, void *aux)
+{
+	struct h3_codec_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	bus_addr_t addr;
+	bus_size_t size;
+
+	sc->sc_dev = self;
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+	sc->sc_bst = faa->faa_bst;
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+
+	sc->sc_phandle = phandle;
+
+	aprint_naive("\n");
+	aprint_normal(": H3 Audio Codec (analog part)\n");
+}
+
+CFATTACH_DECL_NEW(h3_codec, sizeof(struct h3_codec_softc),
+    h3_codec_match, h3_codec_attach, NULL, NULL);

Index: src/sys/arch/arm/sunxi/sunxi_codec.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_codec.c:1.1.2.2
--- /dev/null	Wed Aug  9 05:49:50 2017
+++ src/sys/arch/arm/sunxi/sunxi_codec.c	Wed Aug  9 05:49:50 2017
@@ -0,0 +1,751 @@
+/* $NetBSD: sunxi_codec.c,v 1.1.2.2 2017/08/09 05:49:50 snj Exp $ */
+
+/*-
+ * Copyright (c) 2014-2017 Jared McNeill <jmcne...@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.
+ */
+
+#include "opt_ddb.h"
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_codec.c,v 1.1.2.2 2017/08/09 05:49:50 snj Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/cpu.h>
+#include <sys/device.h>
+#include <sys/kmem.h>
+#include <sys/gpio.h>
+
+#include <sys/audioio.h>
+#include <dev/audio_if.h>
+#include <dev/auconv.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#include <arm/sunxi/sunxi_codec.h>
+
+#define	TX_TRIG_LEVEL	0xf
+#define	RX_TRIG_LEVEL	0x7
+#define	DRQ_CLR_CNT	0x3
+
+#define	AC_DAC_DPC(_sc)		((_sc)->sc_cfg->DPC)	
+#define	 DAC_DPC_EN_DA			0x80000000
+#define	AC_DAC_FIFOC(_sc)	((_sc)->sc_cfg->DAC_FIFOC)
+#define	 DAC_FIFOC_FS			__BITS(31,29)
+#define	  DAC_FS_48KHZ			0
+#define	  DAC_FS_32KHZ			1
+#define	  DAC_FS_24KHZ			2
+#define	  DAC_FS_16KHZ			3
+#define	  DAC_FS_12KHZ			4
+#define	  DAC_FS_8KHZ			5
+#define	  DAC_FS_192KHZ			6
+#define	  DAC_FS_96KHZ			7
+#define	 DAC_FIFOC_FIFO_MODE		__BITS(25,24)
+#define	  FIFO_MODE_24_31_8		0
+#define	  FIFO_MODE_16_31_16		0
+#define	  FIFO_MODE_16_15_0		1
+#define	 DAC_FIFOC_DRQ_CLR_CNT		__BITS(22,21)
+#define	 DAC_FIFOC_TX_TRIG_LEVEL	__BITS(14,8)
+#define	 DAC_FIFOC_MONO_EN		__BIT(6)
+#define	 DAC_FIFOC_TX_BITS		__BIT(5)
+#define	 DAC_FIFOC_DRQ_EN		__BIT(4)
+#define	 DAC_FIFOC_FIFO_FLUSH		__BIT(0)
+#define	AC_DAC_FIFOS(_sc)	((_sc)->sc_cfg->DAC_FIFOS)
+#define	AC_DAC_TXDATA(_sc)	((_sc)->sc_cfg->DAC_TXDATA)
+#define	AC_ADC_FIFOC(_sc)	((_sc)->sc_cfg->ADC_FIFOC)
+#define	 ADC_FIFOC_FS			__BITS(31,29)
+#define	  ADC_FS_48KHZ			0
+#define	 ADC_FIFOC_EN_AD		__BIT(28)
+#define	 ADC_FIFOC_RX_FIFO_MODE		__BIT(24)
+#define	 ADC_FIFOC_RX_TRIG_LEVEL	__BITS(12,8)
+#define	 ADC_FIFOC_MONO_EN		__BIT(7)
+#define	 ADC_FIFOC_RX_BITS		__BIT(6)
+#define	 ADC_FIFOC_DRQ_EN		__BIT(4)
+#define	 ADC_FIFOC_FIFO_FLUSH		__BIT(0)
+#define	AC_ADC_FIFOS(_sc)	((_sc)->sc_cfg->ADC_FIFOS)
+#define	AC_ADC_RXDATA(_sc)	((_sc)->sc_cfg->ADC_RXDATA)
+#define	AC_DAC_CNT(_sc)		((_sc)->sc_cfg->DAC_CNT)
+#define	AC_ADC_CNT(_sc)		((_sc)->sc_cfg->ADC_CNT)
+
+static const struct of_compat_data compat_data[] = {
+	H3_CODEC_COMPATDATA,
+	{ NULL }
+};
+
+#define	CODEC_READ(sc, reg)			\
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+#define	CODEC_WRITE(sc, reg, val)		\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+
+static int
+sunxi_codec_allocdma(struct sunxi_codec_softc *sc, size_t size,
+    size_t align, struct sunxi_codec_dma *dma)
+{
+	int error;
+
+	dma->dma_size = size;
+	error = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, align, 0,
+	    dma->dma_segs, 1, &dma->dma_nsegs, BUS_DMA_WAITOK);
+	if (error)
+		return error;
+
+	error = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs,
+	    dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | BUS_DMA_COHERENT);
+	if (error)
+		goto free;
+
+	error = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs,
+	    dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map);
+	if (error)
+		goto unmap;
+
+	error = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr,
+	    dma->dma_size, NULL, BUS_DMA_WAITOK);
+	if (error)
+		goto destroy;
+
+	return 0;
+
+destroy:
+	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
+unmap:
+	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
+free:
+	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
+
+	return error;
+}
+
+static void
+sunxi_codec_freedma(struct sunxi_codec_softc *sc, struct sunxi_codec_dma *dma)
+{
+	bus_dmamap_unload(sc->sc_dmat, dma->dma_map);
+	bus_dmamap_destroy(sc->sc_dmat, dma->dma_map);
+	bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size);
+	bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs);
+}
+
+static int
+sunxi_codec_transfer(struct sunxi_codec_chan *ch)
+{
+	bus_dma_segment_t seg;
+
+	seg.ds_addr = ch->ch_cur_phys;
+	seg.ds_len = ch->ch_blksize;
+	ch->ch_req.dreq_segs = &seg;
+	ch->ch_req.dreq_nsegs = 1;
+
+	return fdtbus_dma_transfer(ch->ch_dma, &ch->ch_req);
+}
+
+static int
+sunxi_codec_open(void *priv, int flags)
+{
+	return 0;
+}
+
+static void
+sunxi_codec_close(void *priv)
+{
+}
+
+static int
+sunxi_codec_drain(void *priv)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	uint32_t val;
+
+	val = CODEC_READ(sc, AC_DAC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_FIFO_FLUSH);
+
+	val = CODEC_READ(sc, AC_ADC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_FIFO_FLUSH);
+
+	return 0;
+}
+
+static int
+sunxi_codec_query_encoding(void *priv, struct audio_encoding *ae)
+{
+	struct sunxi_codec_softc * const sc = priv;
+
+	return auconv_query_encoding(sc->sc_encodings, ae);
+}
+
+static int
+sunxi_codec_set_params(void *priv, int setmode, int usemode,
+    audio_params_t *play, audio_params_t *rec,
+    stream_filter_list_t *pfil, stream_filter_list_t *rfil)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	int index;
+
+	if (play && (setmode & AUMODE_PLAY)) {
+		index = auconv_set_converter(&sc->sc_format, 1,
+		    AUMODE_PLAY, play, true, pfil);
+		if (index < 0)
+			return EINVAL;
+		sc->sc_pchan.ch_params = pfil->req_size > 0 ?
+		    pfil->filters[0].param : *play;
+	}
+	if (rec && (setmode & AUMODE_RECORD)) {
+		index = auconv_set_converter(&sc->sc_format, 1,
+		    AUMODE_RECORD, rec, true, rfil);
+		if (index < 0)
+			return EINVAL;
+		sc->sc_rchan.ch_params = rfil->req_size > 0 ?
+		    rfil->filters[0].param : *rec;
+	}
+
+	return 0;
+}
+
+static int
+sunxi_codec_set_port(void *priv, mixer_ctrl_t *mc)
+{
+	struct sunxi_codec_softc * const sc = priv;
+
+	return sc->sc_cfg->set_port(sc, mc);
+}
+
+static int
+sunxi_codec_get_port(void *priv, mixer_ctrl_t *mc)
+{
+	struct sunxi_codec_softc * const sc = priv;
+
+	return sc->sc_cfg->get_port(sc, mc);
+}
+
+static int
+sunxi_codec_query_devinfo(void *priv, mixer_devinfo_t *di)
+{
+	struct sunxi_codec_softc * const sc = priv;
+
+	return sc->sc_cfg->query_devinfo(sc, di);
+}
+
+static void *
+sunxi_codec_allocm(void *priv, int dir, size_t size)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	struct sunxi_codec_dma *dma;
+	int error;
+
+	dma = kmem_alloc(sizeof(*dma), KM_SLEEP);
+
+	error = sunxi_codec_allocdma(sc, size, 16, dma);
+	if (error) {
+		kmem_free(dma, sizeof(*dma));
+		device_printf(sc->sc_dev, "couldn't allocate DMA memory (%d)\n",
+		    error);
+		return NULL;
+	}
+
+	LIST_INSERT_HEAD(&sc->sc_dmalist, dma, dma_list);
+
+	return dma->dma_addr;
+}
+
+static void
+sunxi_codec_freem(void *priv, void *addr, size_t size)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	struct sunxi_codec_dma *dma;
+
+	LIST_FOREACH(dma, &sc->sc_dmalist, dma_list)
+		if (dma->dma_addr == addr) {
+			sunxi_codec_freedma(sc, dma);
+			LIST_REMOVE(dma, dma_list);
+			kmem_free(dma, sizeof(*dma));
+			break;
+		}
+}
+
+static paddr_t
+sunxi_codec_mappage(void *priv, void *addr, off_t off, int prot)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	struct sunxi_codec_dma *dma;
+
+	if (off < 0)
+		return -1;
+
+	LIST_FOREACH(dma, &sc->sc_dmalist, dma_list)
+		if (dma->dma_addr == addr) {
+			return bus_dmamem_mmap(sc->sc_dmat, dma->dma_segs,
+			    dma->dma_nsegs, off, prot, BUS_DMA_WAITOK);
+		}
+
+	return -1;
+}
+
+static int
+sunxi_codec_getdev(void *priv, struct audio_device *adev)
+{
+	struct sunxi_codec_softc * const sc = priv;
+
+	snprintf(adev->name, sizeof(adev->name), "Allwinner");
+	snprintf(adev->version, sizeof(adev->version), "%s",
+	    sc->sc_cfg->name);
+	snprintf(adev->config, sizeof(adev->config), "sunxicodec");
+
+	return 0;
+}
+
+static int
+sunxi_codec_get_props(void *priv)
+{
+	return AUDIO_PROP_PLAYBACK|AUDIO_PROP_CAPTURE|
+	    AUDIO_PROP_INDEPENDENT|AUDIO_PROP_MMAP|
+	    AUDIO_PROP_FULLDUPLEX;
+}
+
+static int
+sunxi_codec_round_blocksize(void *priv, int bs, int mode,
+    const audio_params_t *params)
+{
+	bs &= ~3;
+	if (bs == 0)
+		bs = 4;
+	return bs;
+}
+
+static size_t
+sunxi_codec_round_buffersize(void *priv, int dir, size_t bufsize)
+{
+	return bufsize;
+}
+
+static int
+sunxi_codec_trigger_output(void *priv, void *start, void *end, int blksize,
+    void (*intr)(void *), void *intrarg, const audio_params_t *params)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	struct sunxi_codec_chan *ch = &sc->sc_pchan;
+	struct sunxi_codec_dma *dma;
+	bus_addr_t pstart;
+	bus_size_t psize;
+	uint32_t val;
+	int error;
+
+	pstart = 0;
+	psize = (uintptr_t)end - (uintptr_t)start;
+
+	LIST_FOREACH(dma, &sc->sc_dmalist, dma_list)
+		if (dma->dma_addr == start) {
+			pstart = dma->dma_map->dm_segs[0].ds_addr;
+			break;
+		}
+	if (pstart == 0) {
+		device_printf(sc->sc_dev, "bad addr %p\n", start);
+		return EINVAL;
+	}
+
+	ch->ch_intr = intr;
+	ch->ch_intrarg = intrarg;
+	ch->ch_start_phys = ch->ch_cur_phys = pstart;
+	ch->ch_end_phys = pstart + psize;
+	ch->ch_blksize = blksize;
+
+	/* Flush DAC FIFO */
+	val = CODEC_READ(sc, AC_DAC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_FIFO_FLUSH);
+
+	/* Clear DAC FIFO status */
+	val = CODEC_READ(sc, AC_DAC_FIFOS(sc));
+	CODEC_WRITE(sc, AC_DAC_FIFOS(sc), val);
+
+	/* Unmute output */
+	if (sc->sc_cfg->mute)
+		sc->sc_cfg->mute(sc, 0, ch->ch_mode);
+
+	/* Configure DAC FIFO */
+	CODEC_WRITE(sc, AC_DAC_FIFOC(sc),
+	    __SHIFTIN(DAC_FS_48KHZ, DAC_FIFOC_FS) |
+	    __SHIFTIN(FIFO_MODE_16_15_0, DAC_FIFOC_FIFO_MODE) |
+	    __SHIFTIN(DRQ_CLR_CNT, DAC_FIFOC_DRQ_CLR_CNT) |
+	    __SHIFTIN(TX_TRIG_LEVEL, DAC_FIFOC_TX_TRIG_LEVEL));
+
+	/* Enable DAC DRQ */
+	val = CODEC_READ(sc, AC_DAC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_DRQ_EN);
+
+	/* Start DMA transfer */
+	error = sunxi_codec_transfer(ch);
+	if (error != 0) {
+		aprint_error_dev(sc->sc_dev,
+		    "failed to start DMA transfer: %d\n", error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int
+sunxi_codec_trigger_input(void *priv, void *start, void *end, int blksize,
+    void (*intr)(void *), void *intrarg, const audio_params_t *params)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	struct sunxi_codec_chan *ch = &sc->sc_rchan;
+	struct sunxi_codec_dma *dma;
+	bus_addr_t pstart;
+	bus_size_t psize;
+	uint32_t val;
+	int error;
+
+	pstart = 0;
+	psize = (uintptr_t)end - (uintptr_t)start;
+
+	LIST_FOREACH(dma, &sc->sc_dmalist, dma_list)
+		if (dma->dma_addr == start) {
+			pstart = dma->dma_map->dm_segs[0].ds_addr;
+			break;
+		}
+	if (pstart == 0) {
+		device_printf(sc->sc_dev, "bad addr %p\n", start);
+		return EINVAL;
+	}
+
+	ch->ch_intr = intr;
+	ch->ch_intrarg = intrarg;
+	ch->ch_start_phys = ch->ch_cur_phys = pstart;
+	ch->ch_end_phys = pstart + psize;
+	ch->ch_blksize = blksize;
+
+	/* Flush ADC FIFO */
+	val = CODEC_READ(sc, AC_ADC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_FIFO_FLUSH);
+
+	/* Clear ADC FIFO status */
+	val = CODEC_READ(sc, AC_ADC_FIFOS(sc));
+	CODEC_WRITE(sc, AC_ADC_FIFOS(sc), val);
+
+	/* Unmute input */
+	if (sc->sc_cfg->mute)
+		sc->sc_cfg->mute(sc, 0, ch->ch_mode);
+
+	/* Configure ADC FIFO */
+	CODEC_WRITE(sc, AC_ADC_FIFOC(sc),
+	    __SHIFTIN(ADC_FS_48KHZ, ADC_FIFOC_FS) |
+	    __SHIFTIN(RX_TRIG_LEVEL, ADC_FIFOC_RX_TRIG_LEVEL) |
+	    ADC_FIFOC_EN_AD | ADC_FIFOC_RX_FIFO_MODE);
+
+	/* Enable ADC DRQ */
+	val = CODEC_READ(sc, AC_ADC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_DRQ_EN);
+
+	/* Start DMA transfer */
+	error = sunxi_codec_transfer(ch);
+	if (error != 0) {
+		aprint_error_dev(sc->sc_dev,
+		    "failed to start DMA transfer: %d\n", error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int
+sunxi_codec_halt_output(void *priv)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	struct sunxi_codec_chan *ch = &sc->sc_pchan;
+	uint32_t val;
+
+	/* Disable DMA channel */
+	fdtbus_dma_halt(ch->ch_dma);
+
+	/* Mute output */
+	if (sc->sc_cfg->mute)
+		sc->sc_cfg->mute(sc, 1, ch->ch_mode);
+
+	/* Disable DAC DRQ */
+	val = CODEC_READ(sc, AC_DAC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val & ~DAC_FIFOC_DRQ_EN);
+
+	ch->ch_intr = NULL;
+	ch->ch_intrarg = NULL;
+
+	return 0;
+}
+
+static int
+sunxi_codec_halt_input(void *priv)
+{
+	struct sunxi_codec_softc * const sc = priv;
+	struct sunxi_codec_chan *ch = &sc->sc_rchan;
+	uint32_t val;
+
+	/* Disable DMA channel */
+	fdtbus_dma_halt(ch->ch_dma);
+
+	/* Mute output */
+	if (sc->sc_cfg->mute)
+		sc->sc_cfg->mute(sc, 1, ch->ch_mode);
+
+	/* Disable ADC DRQ */
+	val = CODEC_READ(sc, AC_ADC_FIFOC(sc));
+	CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val & ~ADC_FIFOC_DRQ_EN);
+
+	return 0;
+}
+
+static void
+sunxi_codec_get_locks(void *priv, kmutex_t **intr, kmutex_t **thread)
+{
+	struct sunxi_codec_softc * const sc = priv;
+
+	*intr = &sc->sc_intr_lock;
+	*thread = &sc->sc_lock;
+}
+
+static const struct audio_hw_if sunxi_codec_hw_if = {
+	.open = sunxi_codec_open,
+	.close = sunxi_codec_close,
+	.drain = sunxi_codec_drain,
+	.query_encoding = sunxi_codec_query_encoding,
+	.set_params = sunxi_codec_set_params,
+	.allocm = sunxi_codec_allocm,
+	.freem = sunxi_codec_freem,
+	.mappage = sunxi_codec_mappage,
+	.getdev = sunxi_codec_getdev,
+	.set_port = sunxi_codec_set_port,
+	.get_port = sunxi_codec_get_port,
+	.query_devinfo = sunxi_codec_query_devinfo,
+	.get_props = sunxi_codec_get_props,
+	.round_blocksize = sunxi_codec_round_blocksize,
+	.round_buffersize = sunxi_codec_round_buffersize,
+	.trigger_output = sunxi_codec_trigger_output,
+	.trigger_input = sunxi_codec_trigger_input,
+	.halt_output = sunxi_codec_halt_output,
+	.halt_input = sunxi_codec_halt_input,
+	.get_locks = sunxi_codec_get_locks,
+};
+
+static void
+sunxi_codec_dmaintr(void *priv)
+{
+	struct sunxi_codec_chan * const ch = priv;
+
+	ch->ch_cur_phys += ch->ch_blksize;
+	if (ch->ch_cur_phys >= ch->ch_end_phys)
+		ch->ch_cur_phys = ch->ch_start_phys;
+
+	if (ch->ch_intr) {
+		ch->ch_intr(ch->ch_intrarg);
+		sunxi_codec_transfer(ch);
+	}
+}
+
+static int
+sunxi_codec_chan_init(struct sunxi_codec_softc *sc,
+    struct sunxi_codec_chan *ch, u_int mode, const char *dmaname)
+{
+	ch->ch_sc = sc;
+	ch->ch_mode = mode;
+	ch->ch_dma = fdtbus_dma_get(sc->sc_phandle, dmaname, sunxi_codec_dmaintr, ch);
+	if (ch->ch_dma == NULL) {
+		aprint_error(": couldn't get dma channel \"%s\"\n", dmaname);
+		return ENXIO;
+	}
+
+	if (mode == AUMODE_PLAY) {
+		ch->ch_req.dreq_dir = FDT_DMA_WRITE;
+		ch->ch_req.dreq_dev_phys =
+		    sc->sc_baseaddr + AC_DAC_TXDATA(sc);
+	} else {
+		ch->ch_req.dreq_dir = FDT_DMA_READ;
+		ch->ch_req.dreq_dev_phys =
+		    sc->sc_baseaddr + AC_ADC_RXDATA(sc);
+	}
+	ch->ch_req.dreq_mem_opt.opt_bus_width = 16;
+	ch->ch_req.dreq_mem_opt.opt_burst_len = 4;
+	ch->ch_req.dreq_dev_opt.opt_bus_width = 16;
+	ch->ch_req.dreq_dev_opt.opt_burst_len = 4;
+
+	return 0;
+}
+
+static int
+sunxi_codec_clock_init(int phandle)
+{
+	struct fdtbus_reset *rst;
+	struct clk *clk;
+	int error;
+
+	/* Set codec clock to 24.576MHz, suitable for 48 kHz sampling rates */
+	clk = fdtbus_clock_get(phandle, "codec");
+	if (clk == NULL) {
+		aprint_error(": couldn't find codec clock\n");
+		return ENXIO;
+	}
+	error = clk_set_rate(clk, 24576000);
+	if (error != 0) {
+		aprint_error(": couldn't set codec clock rate: %d\n", error);
+		return error;
+	}
+	error = clk_enable(clk);
+	if (error != 0) {
+		aprint_error(": couldn't enable codec clock: %d\n", error);
+		return error;
+	}
+
+	/* Enable APB clock */
+	clk = fdtbus_clock_get(phandle, "apb");
+	if (clk == NULL) {
+		aprint_error(": couldn't find apb clock\n");
+		return ENXIO;
+	}
+	error = clk_enable(clk);
+	if (error != 0) {
+		aprint_error(": couldn't enable apb clock: %d\n", error);
+		return error;
+	}
+
+	/* De-assert reset */
+	rst = fdtbus_reset_get_index(phandle, 0);
+	if (rst == NULL) {
+		aprint_error(": couldn't find reset\n");
+		return ENXIO;
+	}
+	error = fdtbus_reset_deassert(rst);
+	if (error != 0) {
+		aprint_error(": couldn't de-assert reset: %d\n", error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int
+sunxi_codec_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compat_data(faa->faa_phandle, compat_data);
+}
+
+static void
+sunxi_codec_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_codec_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	bus_addr_t addr;
+	bus_size_t size;
+	uint32_t val;
+	int error;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	if (sunxi_codec_clock_init(phandle) != 0)
+		return;
+
+	sc->sc_dev = self;
+	sc->sc_phandle = phandle;
+	sc->sc_baseaddr = addr;
+	sc->sc_bst = faa->faa_bst;
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+	sc->sc_dmat = faa->faa_dmat;
+	LIST_INIT(&sc->sc_dmalist);
+	sc->sc_cfg = (void *)of_search_compatible(phandle, compat_data)->data;
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);
+
+	if (sunxi_codec_chan_init(sc, &sc->sc_pchan, AUMODE_PLAY, "tx") != 0 ||
+	    sunxi_codec_chan_init(sc, &sc->sc_rchan, AUMODE_RECORD, "rx") != 0) {
+		aprint_error(": couldn't setup channels\n");
+		return;
+	}
+
+	/* Optional PA mute GPIO */
+	sc->sc_pin_pa = fdtbus_gpio_acquire(phandle, "allwinner,pa-gpios", GPIO_PIN_OUTPUT);
+	if (sc->sc_pin_pa != NULL)
+		fdtbus_gpio_write(sc->sc_pin_pa, 1);
+
+	aprint_naive("\n");
+	aprint_normal(": %s\n", sc->sc_cfg->name);
+
+	/* Enable DAC */
+	val = CODEC_READ(sc, AC_DAC_DPC(sc));
+	val |= DAC_DPC_EN_DA;
+	CODEC_WRITE(sc, AC_DAC_DPC(sc), val);
+
+	/* Initialize codec */
+	if (sc->sc_cfg->init(sc) != 0) {
+		aprint_error_dev(self, "couldn't initialize codec\n");
+		return;
+	}
+
+	sc->sc_format.mode = AUMODE_PLAY|AUMODE_RECORD;
+	sc->sc_format.encoding = AUDIO_ENCODING_SLINEAR_LE;
+	sc->sc_format.validbits = 16;
+	sc->sc_format.precision = 16;
+	sc->sc_format.channels = 2;
+	sc->sc_format.channel_mask = AUFMT_STEREO;
+	sc->sc_format.frequency_type = 0;
+	sc->sc_format.frequency[0] = sc->sc_format.frequency[1] = 48000;
+
+	error = auconv_create_encodings(&sc->sc_format, 1, &sc->sc_encodings);
+	if (error) {
+		aprint_error_dev(self, "couldn't create encodings\n");
+		return;
+	}
+
+	audio_attach_mi(&sunxi_codec_hw_if, sc, self);
+}
+
+CFATTACH_DECL_NEW(sunxi_codec, sizeof(struct sunxi_codec_softc),
+    sunxi_codec_match, sunxi_codec_attach, NULL, NULL);
+
+#ifdef DDB
+void sunxicodec_dump(void);
+
+void
+sunxicodec_dump(void)
+{
+	struct sunxi_codec_softc *sc;
+	device_t dev;
+
+	dev = device_find_by_driver_unit("sunxicodec", 0);
+	if (dev == NULL)
+		return;
+	sc = device_private(dev);
+
+	device_printf(dev, "AC_DAC_DPC:   %08x\n", CODEC_READ(sc, AC_DAC_DPC(sc)));
+	device_printf(dev, "AC_DAC_FIFOC: %08x\n", CODEC_READ(sc, AC_DAC_FIFOC(sc)));
+	device_printf(dev, "AC_DAC_FIFOS: %08x\n", CODEC_READ(sc, AC_DAC_FIFOS(sc)));
+	device_printf(dev, "AC_ADC_FIFOC: %08x\n", CODEC_READ(sc, AC_ADC_FIFOC(sc)));
+	device_printf(dev, "AC_ADC_FIFOS: %08x\n", CODEC_READ(sc, AC_ADC_FIFOS(sc)));
+	device_printf(dev, "AC_DAC_CNT:   %08x\n", CODEC_READ(sc, AC_DAC_CNT(sc)));
+	device_printf(dev, "AC_ADC_CNT:   %08x\n", CODEC_READ(sc, AC_ADC_CNT(sc)));
+}
+#endif
Index: src/sys/arch/arm/sunxi/sunxi_codec.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_codec.h:1.1.2.2
--- /dev/null	Wed Aug  9 05:49:50 2017
+++ src/sys/arch/arm/sunxi/sunxi_codec.h	Wed Aug  9 05:49:50 2017
@@ -0,0 +1,129 @@
+/* $NetBSD: sunxi_codec.h,v 1.1.2.2 2017/08/09 05:49:50 snj Exp $ */
+
+/*-
+ * Copyright (c) 2014-2017 Jared McNeill <jmcne...@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.
+ */
+
+#ifndef _ARM_SUNXI_CODEC_H
+#define _ARM_SUNXI_CODEC_H
+
+#include <sys/audioio.h>
+#include <dev/audio_if.h>
+#include <dev/auconv.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#include "h3_codec.h"
+
+struct sunxi_codec_softc;
+
+struct sunxi_codec_conf {
+	const char	*name;
+
+	/* initialize codec */
+	int		(*init)(struct sunxi_codec_softc *);
+	/* toggle DAC/ADC mute */
+	void		(*mute)(struct sunxi_codec_softc *, int, u_int);
+	/* mixer controls */
+	int		(*set_port)(struct sunxi_codec_softc *,
+				    mixer_ctrl_t *);
+	int		(*get_port)(struct sunxi_codec_softc *,
+				    mixer_ctrl_t *);
+	int		(*query_devinfo)(struct sunxi_codec_softc *,
+					 mixer_devinfo_t *);
+
+	/* register map */
+	bus_size_t	DPC,
+			DAC_FIFOC,
+			DAC_FIFOS,
+			DAC_TXDATA,
+			ADC_FIFOC,
+			ADC_FIFOS,
+			ADC_RXDATA,
+			DAC_CNT,
+			ADC_CNT;
+};
+
+struct sunxi_codec_chan {
+	struct sunxi_codec_softc *ch_sc;
+	u_int			ch_mode;
+
+	struct fdtbus_dma	*ch_dma;
+	struct fdtbus_dma_req	ch_req;
+
+	audio_params_t		ch_params;
+
+	bus_addr_t		ch_start_phys;
+	bus_addr_t		ch_end_phys;
+	bus_addr_t		ch_cur_phys;
+	int			ch_blksize;
+
+	void			(*ch_intr)(void *);
+	void			*ch_intrarg;
+};
+
+struct sunxi_codec_dma {
+	LIST_ENTRY(sunxi_codec_dma) dma_list;
+	bus_dmamap_t		dma_map;
+	void			*dma_addr;
+	size_t			dma_size;
+	bus_dma_segment_t	dma_segs[1];
+	int			dma_nsegs;
+};
+
+struct sunxi_codec_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_bst;
+	bus_space_handle_t	sc_bsh;
+	bus_dma_tag_t		sc_dmat;
+	int			sc_phandle;
+	bus_addr_t		sc_baseaddr;
+
+	struct sunxi_codec_conf	*sc_cfg;
+	void			*sc_codec_priv;
+
+	struct fdtbus_gpio_pin	*sc_pin_pa;
+
+	LIST_HEAD(, sunxi_codec_dma) sc_dmalist;
+
+	kmutex_t		sc_lock;
+	kmutex_t		sc_intr_lock;
+
+	struct audio_format	sc_format;
+	struct audio_encoding_set *sc_encodings;
+
+	struct sunxi_codec_chan	sc_pchan;
+	struct sunxi_codec_chan	sc_rchan;
+};
+
+#if NH3_CODEC > 0
+extern const struct sunxi_codec_conf sun8i_h3_codecconf;
+#define	H3_CODEC_COMPATDATA	\
+	{ "allwinner,sun8i-h3-codec",	(uintptr_t)&sun8i_h3_codecconf }
+#else
+#define	H3_CODEC_COMPATDATA
+#endif
+
+#endif /* !_ARM_SUNXI_CODEC_H */

Reply via email to