Module Name:    src
Committed By:   jmcneill
Date:           Sun Aug  6 17:15:45 UTC 2017

Modified Files:
        src/sys/arch/arm/sunxi: files.sunxi
Added Files:
        src/sys/arch/arm/sunxi: sun8i_h3_codec.c sunxi_codec.c sunxi_codec.h

Log Message:
Add support for Allwinner H3 audio codec.


To generate a diff of this commit:
cvs rdiff -u -r1.15 -r1.16 src/sys/arch/arm/sunxi/files.sunxi
cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sun8i_h3_codec.c \
    src/sys/arch/arm/sunxi/sunxi_codec.c src/sys/arch/arm/sunxi/sunxi_codec.h

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.15 src/sys/arch/arm/sunxi/files.sunxi:1.16
--- src/sys/arch/arm/sunxi/files.sunxi:1.15	Sat Aug  5 17:51:49 2017
+++ src/sys/arch/arm/sunxi/files.sunxi	Sun Aug  6 17:15:45 2017
@@ -1,4 +1,4 @@
-#	$NetBSD: files.sunxi,v 1.15 2017/08/05 17:51:49 jmcneill Exp $
+#	$NetBSD: files.sunxi,v 1.16 2017/08/06 17:15:45 jmcneill Exp $
 #
 # Configuration info for Allwinner sunxi family SoCs
 #
@@ -113,6 +113,16 @@ 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

Added files:

Index: src/sys/arch/arm/sunxi/sun8i_h3_codec.c
diff -u /dev/null src/sys/arch/arm/sunxi/sun8i_h3_codec.c:1.1
--- /dev/null	Sun Aug  6 17:15:45 2017
+++ src/sys/arch/arm/sunxi/sun8i_h3_codec.c	Sun Aug  6 17:15:45 2017
@@ -0,0 +1,471 @@
+/* $NetBSD: sun8i_h3_codec.c,v 1.1 2017/08/06 17:15:45 jmcneill 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.1 2017/08/06 17:15:45 jmcneill 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);
+}
+
+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
--- /dev/null	Sun Aug  6 17:15:45 2017
+++ src/sys/arch/arm/sunxi/sunxi_codec.c	Sun Aug  6 17:15:45 2017
@@ -0,0 +1,751 @@
+/* $NetBSD: sunxi_codec.c,v 1.1 2017/08/06 17:15:45 jmcneill 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 2017/08/06 17:15:45 jmcneill 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
--- /dev/null	Sun Aug  6 17:15:45 2017
+++ src/sys/arch/arm/sunxi/sunxi_codec.h	Sun Aug  6 17:15:45 2017
@@ -0,0 +1,129 @@
+/* $NetBSD: sunxi_codec.h,v 1.1 2017/08/06 17:15:45 jmcneill 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