svn commit: r308269 - head/sys/arm/allwinner

Jared McNeill jmcneill at FreeBSD.org
Thu Nov 3 23:22:05 UTC 2016


Author: jmcneill
Date: Thu Nov  3 23:22:04 2016
New Revision: 308269
URL: https://svnweb.freebsd.org/changeset/base/308269

Log:
  Add support for Allwinner H3 audio codec.
  
  The audio controller in the H3 is more or less the same as A10/A20 except
  some registers are shuffled around. The mixer interface, however, is
  completely different between SoCs. Separate a10_mixer_class and
  h3_mixer_class implementations are now made available. This will also make
  adding support for other SoCs easier in the future.
  
  Reviewed by:		andrew, ganbold
  Relnotes:		yes
  Differential Revision:	https://reviews.freebsd.org/D8425

Modified:
  head/sys/arm/allwinner/a10_codec.c

Modified: head/sys/arm/allwinner/a10_codec.c
==============================================================================
--- head/sys/arm/allwinner/a10_codec.c	Thu Nov  3 23:11:33 2016	(r308268)
+++ head/sys/arm/allwinner/a10_codec.c	Thu Nov  3 23:22:04 2016	(r308269)
@@ -27,7 +27,7 @@
  */
 
 /*
- * Allwinner A10/A20 Audio Codec
+ * Allwinner A10/A20 and H3 Audio Codec
  */
 
 #include <sys/cdefs.h>
@@ -50,19 +50,46 @@ __FBSDID("$FreeBSD$");
 #include <dev/ofw/ofw_bus.h>
 #include <dev/ofw/ofw_bus_subr.h>
 
+#include <dev/gpio/gpiobusvar.h>
+
 #include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
 
 #include "sunxi_dma_if.h"
 #include "mixer_if.h"
-#include "gpio_if.h"
+
+struct a10codec_info;
+
+struct a10codec_config {
+	/* mixer class */
+	struct kobj_class *mixer_class;
+
+	/* toggle DAC/ADC mute */
+	void		(*mute)(struct a10codec_info *, int, int);
+
+	/* DRQ types */
+	u_int		drqtype_codec;
+	u_int		drqtype_sdram;
+
+	/* register map */
+	bus_size_t	DPC,
+			DAC_FIFOC,
+			DAC_FIFOS,
+			DAC_TXDATA,
+			ADC_FIFOC,
+			ADC_FIFOS,
+			ADC_RXDATA,
+			DAC_CNT,
+			ADC_CNT;
+};
 
 #define	TX_TRIG_LEVEL	0xf
 #define	RX_TRIG_LEVEL	0x7
 #define	DRQ_CLR_CNT	0x3
 
-#define	AC_DAC_DPC	0x00
+#define	AC_DAC_DPC(_sc)		((_sc)->cfg->DPC)	
 #define	 DAC_DPC_EN_DA			0x80000000
-#define	AC_DAC_FIFOC	0x04
+#define	AC_DAC_FIFOC(_sc)	((_sc)->cfg->DAC_FIFOC)
 #define	 DAC_FIFOC_FS_SHIFT		29
 #define	 DAC_FIFOC_FS_MASK		(7U << DAC_FIFOC_FS_SHIFT)
 #define	  DAC_FS_48KHZ			0
@@ -86,17 +113,9 @@ __FBSDID("$FreeBSD$");
 #define	 DAC_FIFOC_TX_BITS		(1U << 5)
 #define	 DAC_FIFOC_DRQ_EN		(1U << 4)
 #define	 DAC_FIFOC_FIFO_FLUSH		(1U << 0)
-#define	AC_DAC_FIFOS	0x08
-#define	AC_DAC_TXDATA	0x0c
-#define	AC_DAC_ACTL	0x10
-#define	 DAC_ACTL_DACAREN		(1U << 31)
-#define	 DAC_ACTL_DACALEN		(1U << 30)
-#define	 DAC_ACTL_MIXEN			(1U << 29)
-#define	 DAC_ACTL_DACPAS		(1U << 8)
-#define	 DAC_ACTL_PAMUTE		(1U << 6)
-#define	 DAC_ACTL_PAVOL_SHIFT		0
-#define	 DAC_ACTL_PAVOL_MASK		(0x3f << DAC_ACTL_PAVOL_SHIFT)
-#define	AC_ADC_FIFOC	0x1c
+#define	AC_DAC_FIFOS(_sc)	((_sc)->cfg->DAC_FIFOS)
+#define	AC_DAC_TXDATA(_sc)	((_sc)->cfg->DAC_TXDATA)
+#define	AC_ADC_FIFOC(_sc)	((_sc)->cfg->ADC_FIFOC)
 #define	 ADC_FIFOC_FS_SHIFT		29
 #define	 ADC_FIFOC_FS_MASK		(7U << ADC_FIFOC_FS_SHIFT)
 #define	  ADC_FS_48KHZ		0
@@ -108,33 +127,10 @@ __FBSDID("$FreeBSD$");
 #define	 ADC_FIFOC_RX_BITS		(1U << 6)
 #define	 ADC_FIFOC_DRQ_EN		(1U << 4)
 #define	 ADC_FIFOC_FIFO_FLUSH		(1U << 1)
-#define	AC_ADC_FIFOS	0x20
-#define	AC_ADC_RXDATA	0x24
-#define	AC_ADC_ACTL	0x28
-#define	 ADC_ACTL_ADCREN		(1U << 31)
-#define	 ADC_ACTL_ADCLEN		(1U << 30)
-#define	 ADC_ACTL_PREG1EN		(1U << 29)
-#define	 ADC_ACTL_PREG2EN		(1U << 28)
-#define	 ADC_ACTL_VMICEN		(1U << 27)
-#define	 ADC_ACTL_ADCG_SHIFT		20
-#define	 ADC_ACTL_ADCG_MASK		(7U << ADC_ACTL_ADCG_SHIFT)
-#define	 ADC_ACTL_ADCIS_SHIFT		17
-#define	 ADC_ACTL_ADCIS_MASK		(7U << ADC_ACTL_ADCIS_SHIFT)
-#define	  ADC_IS_LINEIN			0
-#define	  ADC_IS_FMIN			1
-#define	  ADC_IS_MIC1			2
-#define	  ADC_IS_MIC2			3
-#define	  ADC_IS_MIC1_L_MIC2_R		4
-#define	  ADC_IS_MIC1_LR_MIC2_LR	5
-#define	  ADC_IS_OMIX			6
-#define	  ADC_IS_LINEIN_L_MIC1_R	7
-#define	 ADC_ACTL_LNRDF			(1U << 16)
-#define	 ADC_ACTL_LNPREG_SHIFT		13
-#define	 ADC_ACTL_LNPREG_MASK		(7U << ADC_ACTL_LNPREG_SHIFT)
-#define	 ADC_ACTL_PA_EN			(1U << 4)
-#define	 ADC_ACTL_DDE			(1U << 3)
-#define	AC_DAC_CNT	0x30
-#define	AC_ADC_CNT	0x34
+#define	AC_ADC_FIFOS(_sc)	((_sc)->cfg->ADC_FIFOS)
+#define	AC_ADC_RXDATA(_sc)	((_sc)->cfg->ADC_RXDATA)
+#define	AC_DAC_CNT(_sc)		((_sc)->cfg->DAC_CNT)
+#define	AC_ADC_CNT(_sc)		((_sc)->cfg->ADC_CNT)
 
 static uint32_t a10codec_fmt[] = {
 	SND_FORMAT(AFMT_S16_LE, 1, 0),
@@ -168,14 +164,13 @@ struct a10codec_chinfo {
 
 struct a10codec_info {
 	device_t		dev;
-	struct resource		*res[2];
+	struct resource		*res[3];
 	struct mtx		*lock;
 	bus_dma_tag_t		dmat;
 	unsigned		dmasize;
 	void			*ih;
 
-	unsigned		drqtype_codec;
-	unsigned		drqtype_sdram;
+	struct a10codec_config	*cfg;
 
 	struct a10codec_chinfo	play;
 	struct a10codec_chinfo	rec;
@@ -183,6 +178,7 @@ struct a10codec_info {
 
 static struct resource_spec a10codec_spec[] = {
 	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
+	{ SYS_RES_MEMORY,	1,	RF_ACTIVE | RF_OPTIONAL },
 	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
 	{ -1, 0 }
 };
@@ -191,127 +187,417 @@ static struct resource_spec a10codec_spe
 #define	CODEC_WRITE(sc, reg, val)	bus_write_4((sc)->res[0], (reg), (val))
 
 /*
- * Mixer interface
+ * A10/A20 mixer interface
  */
 
+#define	A10_DAC_ACTL	0x10
+#define	 A10_DACAREN			(1U << 31)
+#define	 A10_DACALEN			(1U << 30)
+#define	 A10_MIXEN			(1U << 29)
+#define	 A10_DACPAS			(1U << 8)
+#define	 A10_PAMUTE			(1U << 6)
+#define	 A10_PAVOL_SHIFT		0
+#define	 A10_PAVOL_MASK			(0x3f << A10_PAVOL_SHIFT)
+#define	A10_ADC_ACTL	0x28
+#define	 A10_ADCREN			(1U << 31)
+#define	 A10_ADCLEN			(1U << 30)
+#define	 A10_PREG1EN			(1U << 29)
+#define	 A10_PREG2EN			(1U << 28)
+#define	 A10_VMICEN			(1U << 27)
+#define	 A10_ADCG_SHIFT			20
+#define	 A10_ADCG_MASK			(7U << A10_ADCG_SHIFT)
+#define	 A10_ADCIS_SHIFT		17
+#define	 A10_ADCIS_MASK			(7U << A10_ADCIS_SHIFT)
+#define	  A10_ADC_IS_LINEIN			0
+#define	  A10_ADC_IS_FMIN			1
+#define	  A10_ADC_IS_MIC1			2
+#define	  A10_ADC_IS_MIC2			3
+#define	  A10_ADC_IS_MIC1_L_MIC2_R		4
+#define	  A10_ADC_IS_MIC1_LR_MIC2_LR		5
+#define	  A10_ADC_IS_OMIX			6
+#define	  A10_ADC_IS_LINEIN_L_MIC1_R		7
+#define	 A10_LNRDF			(1U << 16)
+#define	 A10_LNPREG_SHIFT		13
+#define	 A10_LNPREG_MASK		(7U << A10_LNPREG_SHIFT)
+#define	 A10_PA_EN			(1U << 4)
+#define	 A10_DDE			(1U << 3)
+
 static int
-a10codec_mixer_init(struct snd_mixer *m)
+a10_mixer_init(struct snd_mixer *m)
 {
 	struct a10codec_info *sc = mix_getdevinfo(m);
-	pcell_t prop[4];
-	phandle_t node;
-	device_t gpio;
 	uint32_t val;
-	ssize_t len;
-	int pin;
 
 	mix_setdevs(m, SOUND_MASK_VOLUME | SOUND_MASK_LINE | SOUND_MASK_RECLEV);
 	mix_setrecdevs(m, SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_MIC);
 
 	/* Unmute input source to PA */
-	val = CODEC_READ(sc, AC_DAC_ACTL);
-	val |= DAC_ACTL_PAMUTE;
-	CODEC_WRITE(sc, AC_DAC_ACTL, val);
+	val = CODEC_READ(sc, A10_DAC_ACTL);
+	val |= A10_PAMUTE;
+	CODEC_WRITE(sc, A10_DAC_ACTL, val);
 
 	/* Enable PA */
-	val = CODEC_READ(sc, AC_ADC_ACTL);
-	val |= ADC_ACTL_PA_EN;
-	CODEC_WRITE(sc, AC_ADC_ACTL, val);
-
-	/* Unmute PA */
-	node = ofw_bus_get_node(sc->dev);
-	len = OF_getencprop(node, "allwinner,pa-gpios", prop, sizeof(prop));
-	if (len > 0 && (len / sizeof(prop[0])) == 4) {
-		gpio = OF_device_from_xref(prop[0]);
-		if (gpio != NULL) {
-			pin = prop[1] * 32 + prop[2];
-			GPIO_PIN_SETFLAGS(gpio, pin, GPIO_PIN_OUTPUT);
-			GPIO_PIN_SET(gpio, pin, GPIO_PIN_LOW);
-		}
-	}
+	val = CODEC_READ(sc, A10_ADC_ACTL);
+	val |= A10_PA_EN;
+	CODEC_WRITE(sc, A10_ADC_ACTL, val);
 
 	return (0);
 }
 
-static const struct a10codec_mixer {
+static const struct a10_mixer {
 	unsigned reg;
 	unsigned mask;
 	unsigned shift;
-} a10codec_mixers[SOUND_MIXER_NRDEVICES] = {
-	[SOUND_MIXER_VOLUME]	= { AC_DAC_ACTL, DAC_ACTL_PAVOL_MASK,
-				    DAC_ACTL_PAVOL_SHIFT },
-	[SOUND_MIXER_LINE]	= { AC_ADC_ACTL, ADC_ACTL_LNPREG_MASK,
-				    ADC_ACTL_LNPREG_SHIFT },
-	[SOUND_MIXER_RECLEV]	= { AC_ADC_ACTL, ADC_ACTL_ADCG_MASK,
-				    ADC_ACTL_ADCG_SHIFT },
+} a10_mixers[SOUND_MIXER_NRDEVICES] = {
+	[SOUND_MIXER_VOLUME]	= { A10_DAC_ACTL, A10_PAVOL_MASK,
+				    A10_PAVOL_SHIFT },
+	[SOUND_MIXER_LINE]	= { A10_ADC_ACTL, A10_LNPREG_MASK,
+				    A10_LNPREG_SHIFT },
+	[SOUND_MIXER_RECLEV]	= { A10_ADC_ACTL, A10_ADCG_MASK,
+				    A10_ADCG_SHIFT },
 }; 
 
 static int
-a10codec_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left,
+a10_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left,
     unsigned right)
 {
 	struct a10codec_info *sc = mix_getdevinfo(m);
 	uint32_t val;
 	unsigned nvol, max;
 
-	max = a10codec_mixers[dev].mask >> a10codec_mixers[dev].shift;
+	max = a10_mixers[dev].mask >> a10_mixers[dev].shift;
 	nvol = (left * max) / 100;
 
-	val = CODEC_READ(sc, a10codec_mixers[dev].reg);
-	val &= ~a10codec_mixers[dev].mask;
-	val |= (nvol << a10codec_mixers[dev].shift);
-	CODEC_WRITE(sc, a10codec_mixers[dev].reg, val);
+	val = CODEC_READ(sc, a10_mixers[dev].reg);
+	val &= ~a10_mixers[dev].mask;
+	val |= (nvol << a10_mixers[dev].shift);
+	CODEC_WRITE(sc, a10_mixers[dev].reg, val);
 
 	left = right = (left * 100) / max;
 	return (left | (right << 8));
 }
 
 static uint32_t
-a10codec_mixer_setrecsrc(struct snd_mixer *m, uint32_t src)
+a10_mixer_setrecsrc(struct snd_mixer *m, uint32_t src)
 {
 	struct a10codec_info *sc = mix_getdevinfo(m);
 	uint32_t val;
 
-	val = CODEC_READ(sc, AC_ADC_ACTL);
+	val = CODEC_READ(sc, A10_ADC_ACTL);
 
 	switch (src) {
 	case SOUND_MASK_LINE:	/* line-in */
-		val &= ~ADC_ACTL_ADCIS_MASK;
-		val |= (ADC_IS_LINEIN << ADC_ACTL_ADCIS_SHIFT);
+		val &= ~A10_ADCIS_MASK;
+		val |= (A10_ADC_IS_LINEIN << A10_ADCIS_SHIFT);
 		break;
 	case SOUND_MASK_MIC:	/* MIC1 */
-		val &= ~ADC_ACTL_ADCIS_MASK;
-		val |= (ADC_IS_MIC1 << ADC_ACTL_ADCIS_SHIFT);
+		val &= ~A10_ADCIS_MASK;
+		val |= (A10_ADC_IS_MIC1 << A10_ADCIS_SHIFT);
 		break;
 	case SOUND_MASK_LINE1:	/* MIC2 */
-		val &= ~ADC_ACTL_ADCIS_MASK;
-		val |= (ADC_IS_MIC2 << ADC_ACTL_ADCIS_SHIFT);
+		val &= ~A10_ADCIS_MASK;
+		val |= (A10_ADC_IS_MIC2 << A10_ADCIS_SHIFT);
 		break;
 	default:
 		break;
 	}
 
-	CODEC_WRITE(sc, AC_ADC_ACTL, val);
+	CODEC_WRITE(sc, A10_ADC_ACTL, val);
 
-	switch ((val & ADC_ACTL_ADCIS_MASK) >> ADC_ACTL_ADCIS_SHIFT) {
-	case ADC_IS_LINEIN:
+	switch ((val & A10_ADCIS_MASK) >> A10_ADCIS_SHIFT) {
+	case A10_ADC_IS_LINEIN:
 		return (SOUND_MASK_LINE);
-	case ADC_IS_MIC1:
+	case A10_ADC_IS_MIC1:
 		return (SOUND_MASK_MIC);
-	case ADC_IS_MIC2:
+	case A10_ADC_IS_MIC2:
 		return (SOUND_MASK_LINE1);
 	default:
 		return (0);
 	}
 }
 
-static kobj_method_t a10codec_mixer_methods[] = {
-	KOBJMETHOD(mixer_init,		a10codec_mixer_init),
-	KOBJMETHOD(mixer_set,		a10codec_mixer_set),
-	KOBJMETHOD(mixer_setrecsrc,	a10codec_mixer_setrecsrc),
+static void
+a10_mute(struct a10codec_info *sc, int mute, int dir)
+{
+	uint32_t val;
+
+	if (dir == PCMDIR_PLAY) {
+		val = CODEC_READ(sc, A10_DAC_ACTL);
+		if (mute) {
+			/* Disable DAC analog l/r channels and output mixer */
+			val &= ~A10_DACAREN;
+			val &= ~A10_DACALEN;
+			val &= ~A10_DACPAS;
+		} else {
+			/* Enable DAC analog l/r channels and output mixer */
+			val |= A10_DACAREN;
+			val |= A10_DACALEN;
+			val |= A10_DACPAS;
+		}
+		CODEC_WRITE(sc, A10_DAC_ACTL, val);
+	} else {
+		val = CODEC_READ(sc, A10_ADC_ACTL);
+		if (mute) {
+			/* Disable ADC analog l/r channels, MIC1 preamp,
+			 * and VMIC pin voltage
+			 */
+			val &= ~A10_ADCREN;
+			val &= ~A10_ADCLEN;
+			val &= ~A10_PREG1EN;
+			val &= ~A10_VMICEN;
+		} else {
+			/* Enable ADC analog l/r channels, MIC1 preamp,
+			 * and VMIC pin voltage
+			 */
+			val |= A10_ADCREN;
+			val |= A10_ADCLEN;
+			val |= A10_PREG1EN;
+			val |= A10_VMICEN;
+		}
+		CODEC_WRITE(sc, A10_ADC_ACTL, val);
+	}
+}
+
+static kobj_method_t a10_mixer_methods[] = {
+	KOBJMETHOD(mixer_init,		a10_mixer_init),
+	KOBJMETHOD(mixer_set,		a10_mixer_set),
+	KOBJMETHOD(mixer_setrecsrc,	a10_mixer_setrecsrc),
 	KOBJMETHOD_END
 };
-MIXER_DECLARE(a10codec_mixer);
+MIXER_DECLARE(a10_mixer);
+
+
+/*
+ * H3 mixer interface
+ */
+
+#define	H3_PR_CFG		0x00
+#define	 H3_AC_PR_RST		(1 << 18)
+#define	 H3_AC_PR_RW		(1 << 24)
+#define	 H3_AC_PR_ADDR_SHIFT	16
+#define	 H3_AC_PR_ADDR_MASK	(0x1f << H3_AC_PR_ADDR_SHIFT)
+#define	 H3_ACDA_PR_WDAT_SHIFT	8
+#define	 H3_ACDA_PR_WDAT_MASK	(0xff << H3_ACDA_PR_WDAT_SHIFT)
+#define	 H3_ACDA_PR_RDAT_SHIFT	0
+#define	 H3_ACDA_PR_RDAT_MASK	(0xff << H3_ACDA_PR_RDAT_SHIFT)
+
+#define	H3_LOMIXSC		0x01
+#define	 H3_LOMIXSC_LDAC	(1 << 1)
+#define	H3_ROMIXSC		0x02
+#define	 H3_ROMIXSC_RDAC	(1 << 1)
+#define	H3_DAC_PA_SRC		0x03
+#define	 H3_DACAREN		(1 << 7)
+#define	 H3_DACALEN		(1 << 6)
+#define	 H3_RMIXEN		(1 << 5)
+#define	 H3_LMIXEN		(1 << 4)
+#define	H3_LINEIN_GCTR		0x05
+#define	 H3_LINEING_SHIFT	4
+#define	 H3_LINEING_MASK	(0x7 << H3_LINEING_SHIFT)
+#define	H3_MIC_GCTR		0x06
+#define	 H3_MIC1_GAIN_SHIFT	4
+#define	 H3_MIC1_GAIN_MASK	(0x7 << H3_MIC1_GAIN_SHIFT)
+#define	 H3_MIC2_GAIN_SHIFT	0
+#define	 H3_MIC2_GAIN_MASK	(0x7 << H3_MIC2_GAIN_SHIFT)
+#define	H3_PAEN_CTR		0x07
+#define	 H3_LINEOUTEN		(1 << 7)
+#define	H3_LINEOUT_VOLC		0x09
+#define	 H3_LINEOUTVOL_SHIFT	3
+#define	 H3_LINEOUTVOL_MASK	(0x1f << H3_LINEOUTVOL_SHIFT)
+#define	H3_MIC2G_LINEOUT_CTR	0x0a
+#define	 H3_LINEOUT_LSEL	(1 << 3)
+#define	 H3_LINEOUT_RSEL	(1 << 2)
+#define	H3_LADCMIXSC		0x0c
+#define	H3_RADCMIXSC		0x0d
+#define	 H3_ADCMIXSC_MIC1	(1 << 6)
+#define	 H3_ADCMIXSC_MIC2	(1 << 5)
+#define	 H3_ADCMIXSC_LINEIN	(1 << 2)
+#define	 H3_ADCMIXSC_OMIXER	(3 << 0)
+#define	H3_ADC_AP_EN		0x0f
+#define	 H3_ADCREN		(1 << 7)
+#define	 H3_ADCLEN		(1 << 6)
+#define	 H3_ADCG_SHIFT		0
+#define	 H3_ADCG_MASK		(0x7 << H3_ADCG_SHIFT)
+
+static u_int 
+h3_pr_read(struct a10codec_info *sc, u_int addr)
+{
+	uint32_t val;
+
+	/* Read current value */
+	val = bus_read_4(sc->res[1], H3_PR_CFG);
+
+	/* De-assert reset */
+	val |= H3_AC_PR_RST;
+	bus_write_4(sc->res[1], H3_PR_CFG, val);
+
+	/* Read mode */
+	val &= ~H3_AC_PR_RW;
+	bus_write_4(sc->res[1], H3_PR_CFG, val);
+
+	/* Set address */
+	val &= ~H3_AC_PR_ADDR_MASK;
+	val |= (addr << H3_AC_PR_ADDR_SHIFT);
+	bus_write_4(sc->res[1], H3_PR_CFG, val);
+
+	/* Read data */
+	return (bus_read_4(sc->res[1], H3_PR_CFG) & H3_ACDA_PR_RDAT_MASK);
+}
+
+static void
+h3_pr_write(struct a10codec_info *sc, u_int addr, u_int data)
+{
+	uint32_t val;
+
+	/* Read current value */
+	val = bus_read_4(sc->res[1], H3_PR_CFG);
+
+	/* De-assert reset */
+	val |= H3_AC_PR_RST;
+	bus_write_4(sc->res[1], H3_PR_CFG, val);
+
+	/* Set address */
+	val &= ~H3_AC_PR_ADDR_MASK;
+	val |= (addr << H3_AC_PR_ADDR_SHIFT);
+	bus_write_4(sc->res[1], H3_PR_CFG, val);
+
+	/* Write data */
+	val &= ~H3_ACDA_PR_WDAT_MASK;
+	val |= (data << H3_ACDA_PR_WDAT_SHIFT);
+	bus_write_4(sc->res[1], H3_PR_CFG, val);
+
+	/* Write mode */
+	val |= H3_AC_PR_RW;
+	bus_write_4(sc->res[1], H3_PR_CFG, val);
+}
+
+static void
+h3_pr_set_clear(struct a10codec_info *sc, u_int addr, u_int set, u_int clr)
+{
+	u_int old, new;
+
+	old = h3_pr_read(sc, addr);
+	new = set | (old & ~clr);
+	h3_pr_write(sc, addr, new);
+}
+
+static int
+h3_mixer_init(struct snd_mixer *m)
+{
+	struct a10codec_info *sc = mix_getdevinfo(m);
+
+	mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV |
+	    SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_LINE1);
+	mix_setrecdevs(m, SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_LINE1 |
+	    SOUND_MASK_IMIX);
+
+	pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL);
+
+	/* Right & Left LINEOUT enable */
+	h3_pr_set_clear(sc, H3_PAEN_CTR, H3_LINEOUTEN, 0);
+	h3_pr_set_clear(sc, H3_MIC2G_LINEOUT_CTR,
+	    H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0);
+
+	return (0);
+}
+
+static const struct h3_mixer {
+	unsigned reg;
+	unsigned mask;
+	unsigned shift;
+} h3_mixers[SOUND_MIXER_NRDEVICES] = {
+	[SOUND_MIXER_VOLUME]	= { H3_LINEOUT_VOLC, H3_LINEOUTVOL_MASK,
+				    H3_LINEOUTVOL_SHIFT },
+	[SOUND_MIXER_RECLEV]	= { H3_ADC_AP_EN, H3_ADCG_MASK,
+				    H3_ADCG_SHIFT },
+	[SOUND_MIXER_LINE]	= { H3_LINEIN_GCTR, H3_LINEING_MASK,
+				    H3_LINEING_SHIFT },
+	[SOUND_MIXER_MIC]	= { H3_MIC_GCTR, H3_MIC1_GAIN_MASK,
+				    H3_MIC1_GAIN_SHIFT },
+	[SOUND_MIXER_LINE1]	= { H3_MIC_GCTR, H3_MIC2_GAIN_MASK,
+				    H3_MIC2_GAIN_SHIFT },
+};
+
+static int
+h3_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left,
+    unsigned right)
+{
+	struct a10codec_info *sc = mix_getdevinfo(m);
+	unsigned nvol, max;
+
+	max = h3_mixers[dev].mask >> h3_mixers[dev].shift;
+	nvol = (left * max) / 100;
+
+	h3_pr_set_clear(sc, h3_mixers[dev].reg,
+	    nvol << h3_mixers[dev].shift, h3_mixers[dev].mask);
+
+	left = right = (left * 100) / max;
+	return (left | (right << 8));
+}
+
+static uint32_t
+h3_mixer_setrecsrc(struct snd_mixer *m, uint32_t src)
+{
+	struct a10codec_info *sc = mix_getdevinfo(m);
+	uint32_t val;
+
+	val = 0;
+	src &= (SOUND_MASK_LINE | SOUND_MASK_MIC |
+	    SOUND_MASK_LINE1 | SOUND_MASK_IMIX);
+
+	if ((src & SOUND_MASK_LINE) != 0)	/* line-in */
+		val |= H3_ADCMIXSC_LINEIN;
+	if ((src & SOUND_MASK_MIC) != 0)	/* MIC1 */
+		val |= H3_ADCMIXSC_MIC1;
+	if ((src & SOUND_MASK_LINE1) != 0)	/* MIC2 */
+		val |= H3_ADCMIXSC_MIC2;
+	if ((src & SOUND_MASK_IMIX) != 0)	/* l/r output mixer */
+		val |= H3_ADCMIXSC_OMIXER;
+
+	h3_pr_write(sc, H3_LADCMIXSC, val);
+	h3_pr_write(sc, H3_RADCMIXSC, val);
+
+	return (src);
+}
+
+static void
+h3_mute(struct a10codec_info *sc, int mute, int dir)
+{
+	if (dir == PCMDIR_PLAY) {
+		if (mute) {
+			/* Mute DAC l/r channels to output mixer */
+			h3_pr_set_clear(sc, H3_LOMIXSC, 0, H3_LOMIXSC_LDAC);
+			h3_pr_set_clear(sc, H3_ROMIXSC, 0, H3_ROMIXSC_RDAC);
+			/* Disable DAC analog l/r channels and output mixer */
+			h3_pr_set_clear(sc, H3_DAC_PA_SRC,
+			    0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN);
+		} else {
+			/* Enable DAC analog l/r channels and output mixer */
+			h3_pr_set_clear(sc, H3_DAC_PA_SRC,
+			    H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0);
+			/* Unmute DAC l/r channels to output mixer */
+			h3_pr_set_clear(sc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0);
+			h3_pr_set_clear(sc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0);
+		}
+	} else {
+		if (mute) {
+			/* Disable ADC analog l/r channels */
+			h3_pr_set_clear(sc, H3_ADC_AP_EN,
+			    0, H3_ADCREN | H3_ADCLEN);
+		} else {
+			/* Enable ADC analog l/r channels */
+			h3_pr_set_clear(sc, H3_ADC_AP_EN,
+			    H3_ADCREN | H3_ADCLEN, 0);
+		}
+	}
+}
+
+static kobj_method_t h3_mixer_methods[] = {
+	KOBJMETHOD(mixer_init,		h3_mixer_init),
+	KOBJMETHOD(mixer_set,		h3_mixer_set),
+	KOBJMETHOD(mixer_setrecsrc,	h3_mixer_setrecsrc),
+	KOBJMETHOD_END
+};
+MIXER_DECLARE(h3_mixer);
 
 
 /*
@@ -364,12 +650,12 @@ a10codec_dmaconfig(struct a10codec_chinf
 
 	if (ch->dir == PCMDIR_PLAY) {
 		conf.dst_noincr = true;
-		conf.src_drqtype = sc->drqtype_sdram;
-		conf.dst_drqtype = sc->drqtype_codec;
+		conf.src_drqtype = sc->cfg->drqtype_sdram;
+		conf.dst_drqtype = sc->cfg->drqtype_codec;
 	} else {
 		conf.src_noincr = true;
-		conf.src_drqtype = sc->drqtype_codec;
-		conf.dst_drqtype = sc->drqtype_sdram;
+		conf.src_drqtype = sc->cfg->drqtype_codec;
+		conf.dst_drqtype = sc->cfg->drqtype_sdram;
 	}
 
 	SUNXI_DMA_SET_CONFIG(ch->dmac, ch->dmachan, &conf);
@@ -428,23 +714,20 @@ a10codec_start(struct a10codec_chinfo *c
 
 	if (ch->dir == PCMDIR_PLAY) {
 		/* Flush DAC FIFO */
-		CODEC_WRITE(sc, AC_DAC_FIFOC, DAC_FIFOC_FIFO_FLUSH);
+		CODEC_WRITE(sc, AC_DAC_FIFOC(sc), DAC_FIFOC_FIFO_FLUSH);
 
 		/* Clear DAC FIFO status */
-		CODEC_WRITE(sc, AC_DAC_FIFOS, CODEC_READ(sc, AC_DAC_FIFOS));
+		CODEC_WRITE(sc, AC_DAC_FIFOS(sc),
+		    CODEC_READ(sc, AC_DAC_FIFOS(sc)));
 
-		/* Enable DAC analog left/right channels and output mixer */
-		val = CODEC_READ(sc, AC_DAC_ACTL);
-		val |= DAC_ACTL_DACAREN;
-		val |= DAC_ACTL_DACALEN;
-		val |= DAC_ACTL_DACPAS;
-		CODEC_WRITE(sc, AC_DAC_ACTL, val);
+		/* Unmute output */
+		sc->cfg->mute(sc, 0, ch->dir);
 
 		/* Configure DAC DMA channel */
 		a10codec_dmaconfig(ch);
 
 		/* Configure DAC FIFO */
-		CODEC_WRITE(sc, AC_DAC_FIFOC,
+		CODEC_WRITE(sc, AC_DAC_FIFOC(sc),
 		    (AFMT_CHANNEL(ch->format) == 1 ? DAC_FIFOC_MONO_EN : 0) |
 		    (a10codec_fs(ch) << DAC_FIFOC_FS_SHIFT) |
 		    (FIFO_MODE_16_15_0 << DAC_FIFOC_FIFO_MODE_SHIFT) |
@@ -452,31 +735,25 @@ a10codec_start(struct a10codec_chinfo *c
 		    (TX_TRIG_LEVEL << DAC_FIFOC_TX_TRIG_LEVEL_SHIFT));
 
 		/* Enable DAC DRQ */
-		val = CODEC_READ(sc, AC_DAC_FIFOC);
+		val = CODEC_READ(sc, AC_DAC_FIFOC(sc));
 		val |= DAC_FIFOC_DRQ_EN;
-		CODEC_WRITE(sc, AC_DAC_FIFOC, val);
+		CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val);
 	} else {
 		/* Flush ADC FIFO */
-		CODEC_WRITE(sc, AC_ADC_FIFOC, ADC_FIFOC_FIFO_FLUSH);
+		CODEC_WRITE(sc, AC_ADC_FIFOC(sc), ADC_FIFOC_FIFO_FLUSH);
 
 		/* Clear ADC FIFO status */
-		CODEC_WRITE(sc, AC_ADC_FIFOS, CODEC_READ(sc, AC_ADC_FIFOS));
+		CODEC_WRITE(sc, AC_ADC_FIFOS(sc),
+		    CODEC_READ(sc, AC_ADC_FIFOS(sc)));
 
-		/* Enable ADC analog left/right channels, MIC1 preamp,
-		 * and VMIC pin voltage
-		 */
-		val = CODEC_READ(sc, AC_ADC_ACTL);
-		val |= ADC_ACTL_ADCREN;
-		val |= ADC_ACTL_ADCLEN;
-		val |= ADC_ACTL_PREG1EN;
-		val |= ADC_ACTL_VMICEN;
-		CODEC_WRITE(sc, AC_ADC_ACTL, val);
+		/* Unmute input */
+		sc->cfg->mute(sc, 0, ch->dir);
 
 		/* Configure ADC DMA channel */
 		a10codec_dmaconfig(ch);
 
 		/* Configure ADC FIFO */
-		CODEC_WRITE(sc, AC_ADC_FIFOC,
+		CODEC_WRITE(sc, AC_ADC_FIFOC(sc),
 		    ADC_FIFOC_EN_AD |
 		    ADC_FIFOC_RX_FIFO_MODE |
 		    (AFMT_CHANNEL(ch->format) == 1 ? ADC_FIFOC_MONO_EN : 0) |
@@ -484,9 +761,9 @@ a10codec_start(struct a10codec_chinfo *c
 		    (RX_TRIG_LEVEL << ADC_FIFOC_RX_TRIG_LEVEL_SHIFT));
 
 		/* Enable ADC DRQ */
-		val = CODEC_READ(sc, AC_ADC_FIFOC);
+		val = CODEC_READ(sc, AC_ADC_FIFOC(sc));
 		val |= ADC_FIFOC_DRQ_EN;
-		CODEC_WRITE(sc, AC_ADC_FIFOC, val);
+		CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val);
 	}
 
 	/* Start DMA transfer */
@@ -497,34 +774,18 @@ static void
 a10codec_stop(struct a10codec_chinfo *ch)
 {
 	struct a10codec_info *sc = ch->parent;
-	uint32_t val;
 
 	/* Disable DMA channel */
 	SUNXI_DMA_HALT(ch->dmac, ch->dmachan);
 
-	if (ch->dir == PCMDIR_PLAY) {
-		/* Disable DAC analog left/right channels and output mixer */
-		val = CODEC_READ(sc, AC_DAC_ACTL);
-		val &= ~DAC_ACTL_DACAREN;
-		val &= ~DAC_ACTL_DACALEN;
-		val &= ~DAC_ACTL_DACPAS;
-		CODEC_WRITE(sc, AC_DAC_ACTL, val);
+	sc->cfg->mute(sc, 1, ch->dir);
 
+	if (ch->dir == PCMDIR_PLAY) {
 		/* Disable DAC DRQ */
-		CODEC_WRITE(sc, AC_DAC_FIFOC, 0);
+		CODEC_WRITE(sc, AC_DAC_FIFOC(sc), 0);
 	} else {
-		/* Disable ADC analog left/right channels, MIC1 preamp,
-		 * and VMIC pin voltage
-		 */
-		val = CODEC_READ(sc, AC_ADC_ACTL);
-		val &= ~ADC_ACTL_ADCREN;
-		val &= ~ADC_ACTL_ADCLEN;
-		val &= ~ADC_ACTL_PREG1EN;
-		val &= ~ADC_ACTL_VMICEN;
-		CODEC_WRITE(sc, AC_ADC_ACTL, val);
-
 		/* Disable ADC DRQ */
-		CODEC_WRITE(sc, AC_ADC_FIFOC, 0);
+		CODEC_WRITE(sc, AC_ADC_FIFOC(sc), 0);
 	}
 }
 
@@ -534,18 +795,30 @@ a10codec_chan_init(kobj_t obj, void *dev
 {
 	struct a10codec_info *sc = devinfo;
 	struct a10codec_chinfo *ch = dir == PCMDIR_PLAY ? &sc->play : &sc->rec;
-	int error;
+	phandle_t xref;
+	pcell_t *cells;
+	int ncells, error;
+
+	error = ofw_bus_parse_xref_list_alloc(ofw_bus_get_node(sc->dev),
+	    "dmas", "#dma-cells", dir == PCMDIR_PLAY ? 1 : 0,
+	    &xref, &ncells, &cells);
+	if (error != 0) {
+		device_printf(sc->dev, "cannot parse 'dmas' property\n");
+		return (NULL);
+	}
+	OF_prop_free(cells);
 
 	ch->parent = sc;
 	ch->channel = c;
 	ch->buffer = b;
 	ch->dir = dir;
 	ch->fifo = rman_get_start(sc->res[0]) +
-	    (dir == PCMDIR_REC ? AC_ADC_RXDATA : AC_DAC_TXDATA);
+	    (dir == PCMDIR_REC ? AC_ADC_RXDATA(sc) : AC_DAC_TXDATA(sc));
 
-	ch->dmac = devclass_get_device(devclass_find("a10dmac"), 0);
+	ch->dmac = OF_device_from_xref(xref);
 	if (ch->dmac == NULL) {
 		device_printf(sc->dev, "cannot find DMA controller\n");
+		device_printf(sc->dev, "xref = 0x%x\n", (u_int)xref);
 		return (NULL);
 	}
 	ch->dmachan = SUNXI_DMA_ALLOC(ch->dmac, false, a10codec_dmaintr, ch);
@@ -720,10 +993,43 @@ CHANNEL_DECLARE(a10codec_chan);
  * Device interface
  */
 
+static const struct a10codec_config a10_config = {
+	.mixer_class	= &a10_mixer_class,
+	.mute		= a10_mute,
+	.drqtype_codec	= 19,
+	.drqtype_sdram	= 22,
+	.DPC		= 0x00,
+	.DAC_FIFOC	= 0x04,
+	.DAC_FIFOS	= 0x08,
+	.DAC_TXDATA	= 0x0c,
+	.ADC_FIFOC	= 0x1c,
+	.ADC_FIFOS	= 0x20,
+	.ADC_RXDATA	= 0x24,
+	.DAC_CNT	= 0x30,
+	.ADC_CNT	= 0x34,
+};
+
+static const struct a10codec_config h3_config = {
+	.mixer_class	= &h3_mixer_class,
+	.mute		= h3_mute,
+	.drqtype_codec	= 15,
+	.drqtype_sdram	= 1,
+	.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,
+};
+
 static struct ofw_compat_data compat_data[] = {
-	{"allwinner,sun4i-a10-codec", 1},
-	{"allwinner,sun7i-a20-codec", 1},
-	{NULL, 0},
+	{ "allwinner,sun4i-a10-codec",	(uintptr_t)&a10_config },
+	{ "allwinner,sun7i-a20-codec",	(uintptr_t)&a10_config },
+	{ "allwinner,sun8i-h3-codec",	(uintptr_t)&h3_config },
+	{ NULL, 0 }
 };
 
 static int
@@ -744,11 +1050,17 @@ a10codec_attach(device_t dev)
 {
 	struct a10codec_info *sc;
 	char status[SND_STATUSLEN];
-	clk_t clk_apb, clk_codec;
+	struct gpiobus_pin *pa_pin;
+	phandle_t node;
+	clk_t clk_bus, clk_codec;
+	hwreset_t rst;
 	uint32_t val;
 	int error;
 
+	node = ofw_bus_get_node(dev);
+
 	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
+	sc->cfg = (void *)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
 	sc->dev = dev;
 	sc->lock = snd_mtxcreate(device_get_nameunit(dev), "a10codec softc");
 
@@ -758,17 +1070,6 @@ a10codec_attach(device_t dev)
 		goto fail;
 	}
 
-	/* XXX DRQ types should come from FDT, but how? */
-	if (ofw_bus_is_compatible(dev, "allwinner,sun4i-a10-codec") ||
-	    ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-codec")) {
-		sc->drqtype_codec = 19;
-		sc->drqtype_sdram = 22;
-	} else {
-		device_printf(dev, "DRQ types not known for this SoC\n");
-		error = ENXIO;
-		goto fail;
-	}
-
 	sc->dmasize = 131072;
 	error = bus_dma_tag_create(
 	    bus_get_dma_tag(dev),
@@ -786,21 +1087,19 @@ a10codec_attach(device_t dev)
 	}
 
 	/* Get clocks */
-	error = clk_get_by_ofw_name(dev, 0, "apb", &clk_apb);
-	if (error != 0) {
-		device_printf(dev, "cannot find apb clock\n");
+	if (clk_get_by_ofw_name(dev, 0, "apb", &clk_bus) != 0 &&
+	    clk_get_by_ofw_name(dev, 0, "ahb", &clk_bus) != 0) {
+		device_printf(dev, "cannot find bus clock\n");
 		goto fail;
 	}
-	error = clk_get_by_ofw_name(dev, 0, "codec", &clk_codec);
-	if (error != 0) {
+	if (clk_get_by_ofw_name(dev, 0, "codec", &clk_codec) != 0) {
 		device_printf(dev, "cannot find codec clock\n");
 		goto fail;
 	}
 
-	/* Gating APB clock for codec */
-	error = clk_enable(clk_apb);
-	if (error != 0) {
-		device_printf(dev, "cannot enable apb clock\n");
+	/* Gating bus clock for codec */
+	if (clk_enable(clk_bus) != 0) {
+		device_printf(dev, "cannot enable bus clock\n");
 		goto fail;
 	}
 	/* Activate audio codec clock. According to the A10 and A20 user
@@ -824,10 +1123,20 @@ a10codec_attach(device_t dev)
 		goto fail;
 	}
 
+	/* De-assert hwreset */
+	if (hwreset_get_by_ofw_name(dev, 0, "apb", &rst) == 0 ||
+	    hwreset_get_by_ofw_name(dev, 0, "ahb", &rst) == 0) {
+		error = hwreset_deassert(rst);
+		if (error != 0) {
+			device_printf(dev, "cannot de-assert reset\n");
+			goto fail;
+		}
+	}
+
 	/* Enable DAC */
-	val = CODEC_READ(sc, AC_DAC_DPC);
+	val = CODEC_READ(sc, AC_DAC_DPC(sc));
 	val |= DAC_DPC_EN_DA;
-	CODEC_WRITE(sc, AC_DAC_DPC, val);
+	CODEC_WRITE(sc, AC_DAC_DPC(sc), val);
 
 #ifdef notdef
 	error = snd_setup_intr(dev, sc->irq, INTR_MPSAFE, a10codec_intr, sc,
@@ -838,11 +1147,19 @@ a10codec_attach(device_t dev)
 	}
 #endif
 
-	if (mixer_init(dev, &a10codec_mixer_class, sc)) {
+	if (mixer_init(dev, sc->cfg->mixer_class, sc)) {
 		device_printf(dev, "mixer_init failed\n");
 		goto fail;
 	}
 
+	/* Unmute PA */
+	if (gpio_pin_get_by_ofw_property(dev, node, "allwinner,pa-gpios",
+	    &pa_pin) == 0) {
+		error = gpio_pin_set_active(pa_pin, 1);
+		if (error != 0)
+			device_printf(dev, "failed to unmute PA\n");
+	}
+
 	pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
 
 	if (pcm_register(dev, sc, 1, 1)) {
@@ -863,7 +1180,7 @@ fail:
 	snd_mtxfree(sc->lock);
 	free(sc, M_DEVBUF);
 
-	return (error);
+	return (ENXIO);
 }
 
 static device_method_t a10codec_pcm_methods[] = {


More information about the svn-src-head mailing list