All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/4] ASoC: DAPM: Allow multiple mixer sources to be routed via the same switch
@ 2011-08-15 18:15 ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-15 18:15 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: alsa-devel, device-drivers-devel, linux-kernel, Mike Frysinger,
	Lars-Peter Clausen

Currently it is only possible to route one source per switch into a mixer.
This patch modifies the code, so that it is possible to route multiple sources
into a mixer via the same switch. One use-case for this is routing a stereo
channel pair into a mono-mixer via the same switch.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 sound/soc/soc-dapm.c |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index c265311..170c4ff 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -443,6 +443,11 @@ static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
 			if (path->name != (char *)w->kcontrol_news[i].name)
 				continue;
 
+			if (w->kcontrols[i]) {
+				path->kcontrol = w->kcontrols[i];
+				continue;
+			}
+
 			wlistsize = sizeof(struct snd_soc_dapm_widget_list) +
 				    sizeof(struct snd_soc_dapm_widget *),
 			wlist = kzalloc(wlistsize, GFP_KERNEL);
@@ -1556,7 +1561,6 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
 		/* found, now check type */
 		found = 1;
 		path->connect = connect;
-		break;
 	}
 
 	if (found)
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 1/4] ASoC: DAPM: Allow multiple mixer sources to be routed via the same switch
@ 2011-08-15 18:15 ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-15 18:15 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: Mike Frysinger, alsa-devel, Lars-Peter Clausen, linux-kernel,
	device-drivers-devel

Currently it is only possible to route one source per switch into a mixer.
This patch modifies the code, so that it is possible to route multiple sources
into a mixer via the same switch. One use-case for this is routing a stereo
channel pair into a mono-mixer via the same switch.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 sound/soc/soc-dapm.c |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index c265311..170c4ff 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -443,6 +443,11 @@ static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
 			if (path->name != (char *)w->kcontrol_news[i].name)
 				continue;
 
+			if (w->kcontrols[i]) {
+				path->kcontrol = w->kcontrols[i];
+				continue;
+			}
+
 			wlistsize = sizeof(struct snd_soc_dapm_widget_list) +
 				    sizeof(struct snd_soc_dapm_widget *),
 			wlist = kzalloc(wlistsize, GFP_KERNEL);
@@ -1556,7 +1561,6 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
 		/* found, now check type */
 		found = 1;
 		path->connect = connect;
-		break;
 	}
 
 	if (found)
-- 
1.7.2.5

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 2/4] ASoC: Add ADAU1373 codec support
  2011-08-15 18:15 ` Lars-Peter Clausen
  (?)
@ 2011-08-15 18:15 ` Lars-Peter Clausen
  2011-08-15 20:31   ` USB Audio questions Pierre-Louis Bossart
                     ` (3 more replies)
  -1 siblings, 4 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-15 18:15 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: alsa-devel, device-drivers-devel, linux-kernel, Mike Frysinger,
	Lars-Peter Clausen

This patch adds support for the Analog Devices ADAU1373 audio codec.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
Changes since v1:
	* Replace some magic values with defines
	* Use TLV volume controls instead of switches for boost controls
	* Made bias voltage platform data instead of exposing it as a user
	  adjustable control
	* Renamed "Mono Stero" controls to "LR Mux"
---
 include/sound/adau1373.h    |   34 +
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/adau1373.c | 1414 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/adau1373.h |   29 +
 5 files changed, 1483 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/adau1373.h
 create mode 100644 sound/soc/codecs/adau1373.c
 create mode 100644 sound/soc/codecs/adau1373.h

diff --git a/include/sound/adau1373.h b/include/sound/adau1373.h
new file mode 100644
index 0000000..1b19c76
--- /dev/null
+++ b/include/sound/adau1373.h
@@ -0,0 +1,34 @@
+/*
+ * Analog Devices ADAU1373 Audio Codec drive
+ *
+ * Copyright 2011 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __SOUND_ADAU1373_H__
+#define __SOUND_ADAU1373_H__
+
+enum adau1373_micbias_voltage {
+	ADAU1373_MICBIAS_2_9V = 0,
+	ADAU1373_MICBIAS_2_2V = 1,
+	ADAU1373_MICBIAS_2_6V = 2,
+	ADAU1373_MICBIAS_1_8V = 3,
+};
+
+#define ADAU1373_DRC_SIZE 13
+
+struct adau1373_platform_data {
+	bool input_differential[4];
+	bool lineout_differential;
+	bool lineout_ground_sense;
+
+	unsigned int num_drc;
+	uint8_t drc_setting[3][ADAU1373_DRC_SIZE];
+
+	enum adau1373_micbias_voltage micbias1;
+	enum adau1373_micbias_voltage micbias2;
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 665d924..71b46c8 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI
 	select SND_SOC_AD1980 if SND_SOC_AC97_BUS
 	select SND_SOC_AD73311
+	select SND_SOC_ADAU1373 if I2C
 	select SND_SOC_ADAV80X
 	select SND_SOC_ADS117X
 	select SND_SOC_AK4104 if SPI_MASTER
@@ -139,6 +140,9 @@ config SND_SOC_ADAU1701
 	select SIGMA
 	tristate
 
+config SND_SOC_ADAU1373
+	tristate
+
 config SND_SOC_ADAV80X
 	tristate
 
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5119a7e..70c1769 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -5,6 +5,7 @@ snd-soc-ad193x-objs := ad193x.o
 snd-soc-ad1980-objs := ad1980.o
 snd-soc-ad73311-objs := ad73311.o
 snd-soc-adau1701-objs := adau1701.o
+snd-soc-adau1373-objs := adau1373.o
 snd-soc-adav80x-objs := adav80x.o
 snd-soc-ads117x-objs := ads117x.o
 snd-soc-ak4104-objs := ak4104.o
@@ -100,6 +101,7 @@ obj-$(CONFIG_SND_SOC_AD1836)	+= snd-soc-ad1836.o
 obj-$(CONFIG_SND_SOC_AD193X)	+= snd-soc-ad193x.o
 obj-$(CONFIG_SND_SOC_AD1980)	+= snd-soc-ad1980.o
 obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_ADAU1373)	+= snd-soc-adau1373.o
 obj-$(CONFIG_SND_SOC_ADAU1701)  += snd-soc-adau1701.o
 obj-$(CONFIG_SND_SOC_ADAV80X)  += snd-soc-adav80x.o
 obj-$(CONFIG_SND_SOC_ADS117X)	+= snd-soc-ads117x.o
diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c
new file mode 100644
index 0000000..2aa40c3
--- /dev/null
+++ b/sound/soc/codecs/adau1373.c
@@ -0,0 +1,1414 @@
+/*
+ * Analog Devices ADAU1373 Audio Codec drive
+ *
+ * Copyright 2011 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/gcd.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/adau1373.h>
+
+#include "adau1373.h"
+
+struct adau1373_dai {
+	unsigned int clk_src;
+	unsigned int sysclk;
+	bool enable_src;
+	bool master;
+};
+
+struct adau1373 {
+	struct adau1373_dai dais[3];
+};
+
+#define ADAU1373_INPUT_MODE	0x00
+#define ADAU1373_AINL_CTRL(x)	(0x01 + (x) * 2)
+#define ADAU1373_AINR_CTRL(x)	(0x02 + (x) * 2)
+#define ADAU1373_LLINE_OUT(x)	(0x9 + (x) * 2)
+#define ADAU1373_RLINE_OUT(x)	(0xa + (x) * 2)
+#define ADAU1373_LSPK_OUT	0x0d
+#define ADAU1373_RSPK_OUT	0x0e
+#define ADAU1373_LHP_OUT	0x0f
+#define ADAU1373_RHP_OUT	0x10
+#define ADAU1373_ADC_GAIN	0x11
+#define ADAU1373_LADC_MIXER	0x12
+#define ADAU1373_RADC_MIXER	0x13
+#define ADAU1373_LLINE1_MIX	0x14
+#define ADAU1373_RLINE1_MIX	0x15
+#define ADAU1373_LLINE2_MIX	0x16
+#define ADAU1373_RLINE2_MIX	0x17
+#define ADAU1373_LSPK_MIX	0x18
+#define ADAU1373_RSPK_MIX	0x19
+#define ADAU1373_LHP_MIX	0x1a
+#define ADAU1373_RHP_MIX	0x1b
+#define ADAU1373_EP_MIX		0x1c
+#define ADAU1373_HP_CTRL	0x1d
+#define ADAU1373_HP_CTRL2	0x1e
+#define ADAU1373_LS_CTRL	0x1f
+#define ADAU1373_EP_CTRL	0x21
+#define ADAU1373_MICBIAS_CTRL1	0x22
+#define ADAU1373_MICBIAS_CTRL2	0x23
+#define ADAU1373_OUTPUT_CTRL	0x24
+#define ADAU1373_PWDN_CTRL1	0x25
+#define ADAU1373_PWDN_CTRL2	0x26
+#define ADAU1373_PWDN_CTRL3	0x27
+#define ADAU1373_DPLL_CTRL(x)	(0x28 + (x) * 7)
+#define ADAU1373_PLL_CTRL1(x)	(0x29 + (x) * 7)
+#define ADAU1373_PLL_CTRL2(x)	(0x2a + (x) * 7)
+#define ADAU1373_PLL_CTRL3(x)	(0x2b + (x) * 7)
+#define ADAU1373_PLL_CTRL4(x)	(0x2c + (x) * 7)
+#define ADAU1373_PLL_CTRL5(x)	(0x2d + (x) * 7)
+#define ADAU1373_PLL_CTRL6(x)	(0x2e + (x) * 7)
+#define ADAU1373_PLL_CTRL7(x)	(0x2f + (x) * 7)
+#define ADAU1373_HEADDECT	0x36
+#define ADAU1373_ADC_DAC_STATUS	0x37
+#define ADAU1373_ADC_CTRL	0x3c
+#define ADAU1373_DAI(x)		(0x44 + (x))
+#define ADAU1373_CLK_SRC_DIV(x)	(0x40 + (x) * 2)
+#define ADAU1373_BCLKDIV(x)	(0x47 + (x))
+#define ADAU1373_SRC_RATIOA(x)	(0x4a + (x) * 2)
+#define ADAU1373_SRC_RATIOB(x)	(0x4b + (x) * 2)
+#define ADAU1373_DEEMP_CTRL	0x50
+#define ADAU1373_SRC_DAI_CTRL(x) (0x51 + (x))
+#define ADAU1373_DIN_MIX_CTRL(x) (0x56 + (x))
+#define ADAU1373_DOUT_MIX_CTRL(x) (0x5b + (x))
+#define ADAU1373_DAI_PBL_VOL(x)	(0x62 + (x) * 2)
+#define ADAU1373_DAI_PBR_VOL(x)	(0x63 + (x) * 2)
+#define ADAU1373_DAI_RECL_VOL(x) (0x68 + (x) * 2)
+#define ADAU1373_DAI_RECR_VOL(x) (0x69 + (x) * 2)
+#define ADAU1373_DAC1_PBL_VOL	0x6e
+#define ADAU1373_DAC1_PBR_VOL	0x6f
+#define ADAU1373_DAC2_PBL_VOL	0x70
+#define ADAU1373_DAC2_PBR_VOL	0x71
+#define ADAU1373_ADC_RECL_VOL	0x72
+#define ADAU1373_ADC_RECR_VOL	0x73
+#define ADAU1373_DMIC_RECL_VOL	0x74
+#define ADAU1373_DMIC_RECR_VOL	0x75
+#define ADAU1373_VOL_GAIN1	0x76
+#define ADAU1373_VOL_GAIN2	0x77
+#define ADAU1373_VOL_GAIN3	0x78
+#define ADAU1373_HPF_CTRL	0x7d
+#define ADAU1373_BASS1		0x7e
+#define ADAU1373_BASS2		0x7f
+#define ADAU1373_DRC(x)		(0x80 + (x) * 0x10)
+#define ADAU1373_3D_CTRL1	0xc0
+#define ADAU1373_3D_CTRL2	0xc1
+#define ADAU1373_FDSP_SEL1	0xdc
+#define ADAU1373_FDSP_SEL2	0xdd
+#define ADAU1373_FDSP_SEL3	0xde
+#define ADAU1373_FDSP_SEL4	0xdf
+#define ADAU1373_DIGMICCTRL	0xe2
+#define ADAU1373_DIGEN		0xeb
+#define ADAU1373_SOFT_RESET	0xff
+
+
+#define ADAU1373_PLL_CTRL6_DPLL_BYPASS	BIT(1)
+#define ADAU1373_PLL_CTRL6_PLL_EN	BIT(0)
+
+#define ADAU1373_DAI_INVERT_BCLK	BIT(7)
+#define ADAU1373_DAI_MASTER		BIT(6)
+#define ADAU1373_DAI_INVERT_LRCLK	BIT(4)
+#define ADAU1373_DAI_WLEN_16		0x0
+#define ADAU1373_DAI_WLEN_20		0x4
+#define ADAU1373_DAI_WLEN_24		0x8
+#define ADAU1373_DAI_WLEN_32		0xc
+#define ADAU1373_DAI_WLEN_MASK		0xc
+#define ADAU1373_DAI_FORMAT_RIGHT_J	0x0
+#define ADAU1373_DAI_FORMAT_LEFT_J	0x1
+#define ADAU1373_DAI_FORMAT_I2S		0x2
+#define ADAU1373_DAI_FORMAT_DSP		0x3
+
+#define ADAU1373_BCLKDIV_SOURCE		BIT(5)
+#define ADAU1373_BCLKDIV_32		0x03
+#define ADAU1373_BCLKDIV_64		0x02
+#define ADAU1373_BCLKDIV_128		0x01
+#define ADAU1373_BCLKDIV_256		0x00
+
+#define ADAU1373_ADC_CTRL_PEAK_DETECT	BIT(0)
+#define ADAU1373_ADC_CTRL_RESET		BIT(1)
+#define ADAU1373_ADC_CTRL_RESET_FORCE	BIT(2)
+
+#define ADAU1373_OUTPUT_CTRL_LDIFF	BIT(3)
+#define ADAU1373_OUTPUT_CTRL_LNFBEN	BIT(2)
+
+#define ADAU1373_PWDN_CTRL3_PWR_EN BIT(0)
+
+#define ADAU1373_EP_CTRL_MICBIAS1_OFFSET 4
+#define ADAU1373_EP_CTRL_MICBIAS2_OFFSET 2
+
+static const uint8_t adau1373_default_regs[] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x00 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x10 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x20 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, /* 0x30 */
+	0x00, 0x00, 0x00, 0x80, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x00, /* 0x40 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x78, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, /* 0x80 */
+	0x00, 0xc0, 0x88, 0x7a, 0xdf, 0x20, 0x00, 0x00,
+	0x78, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, /* 0x90 */
+	0x00, 0xc0, 0x88, 0x7a, 0xdf, 0x20, 0x00, 0x00,
+	0x78, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, /* 0xa0 */
+	0x00, 0xc0, 0x88, 0x7a, 0xdf, 0x20, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0xb0 */
+	0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xd0 */
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, /* 0xe0 */
+	0x00, 0x1f, 0x0f, 0x00, 0x00,
+};
+
+static const unsigned int adau1373_out_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1),
+	8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0),
+	16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0),
+	24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0),
+};
+
+static const DECLARE_TLV_DB_MINMAX(adau1373_digital_tlv, -9563, 0);
+static const DECLARE_TLV_DB_SCALE(adau1373_in_pga_tlv, -1300, 100, 1);
+static const DECLARE_TLV_DB_SCALE(adau1373_ep_tlv, -600, 600, 1);
+
+static const DECLARE_TLV_DB_SCALE(adau1373_input_boost_tlv, 0, 2000, 0);
+static const DECLARE_TLV_DB_SCALE(adau1373_gain_boost_tlv, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adau1373_speaker_boost_tlv, 1200, 600, 0);
+
+static const char *adau1373_fdsp_sel_text[] = {
+	"None",
+	"Channel 1",
+	"Channel 2",
+	"Channel 3",
+	"Channel 4",
+	"Channel 5",
+};
+
+static const SOC_ENUM_SINGLE_DECL(adau1373_drc1_channel_enum,
+	ADAU1373_FDSP_SEL1, 4, adau1373_fdsp_sel_text);
+static const SOC_ENUM_SINGLE_DECL(adau1373_drc2_channel_enum,
+	ADAU1373_FDSP_SEL1, 0, adau1373_fdsp_sel_text);
+static const SOC_ENUM_SINGLE_DECL(adau1373_drc3_channel_enum,
+	ADAU1373_FDSP_SEL2, 0, adau1373_fdsp_sel_text);
+static const SOC_ENUM_SINGLE_DECL(adau1373_hpf_channel_enum,
+	ADAU1373_FDSP_SEL3, 0, adau1373_fdsp_sel_text);
+static const SOC_ENUM_SINGLE_DECL(adau1373_bass_channel_enum,
+	ADAU1373_FDSP_SEL4, 4, adau1373_fdsp_sel_text);
+
+static const char *adau1373_hpf_cutoff_text[] = {
+	"3.7Hz", "50Hz", "100Hz", "150Hz", "200Hz", "250Hz", "300Hz", "350Hz",
+	"400Hz", "450Hz", "500Hz", "550Hz", "600Hz", "650Hz", "700Hz", "750Hz",
+	"800Hz",
+};
+
+static const SOC_ENUM_SINGLE_DECL(adau1373_hpf_cutoff_enum,
+	ADAU1373_HPF_CTRL, 3, adau1373_hpf_cutoff_text);
+
+static const char *adau1373_bass_lpf_cutoff_text[] = {
+	"801Hz", "1001Hz",
+};
+
+static const char *adau1373_bass_clip_level_text[] = {
+	"0.125", "0.250", "0.370", "0.500", "0.625", "0.750", "0.875",
+};
+
+static const unsigned int adau1373_bass_clip_level_values[] = {
+	1, 2, 3, 4, 5, 6, 7,
+};
+
+static const char *adau1373_bass_hpf_cutoff_text[] = {
+	"158Hz", "232Hz", "347Hz", "520Hz",
+};
+
+static const unsigned int adau1373_bass_tlv[] = {
+	TLV_DB_RANGE_HEAD(4),
+	0, 2, TLV_DB_SCALE_ITEM(-600, 600, 1),
+	3, 4, TLV_DB_SCALE_ITEM(950, 250, 0),
+	5, 7, TLV_DB_SCALE_ITEM(1400, 150, 0),
+};
+
+static const SOC_ENUM_SINGLE_DECL(adau1373_bass_lpf_cutoff_enum,
+	ADAU1373_BASS1, 5, adau1373_bass_lpf_cutoff_text);
+
+static const SOC_VALUE_ENUM_SINGLE_DECL(adau1373_bass_clip_level_enum,
+	ADAU1373_BASS1, 2, 7, adau1373_bass_clip_level_text,
+	adau1373_bass_clip_level_values);
+
+static const SOC_ENUM_SINGLE_DECL(adau1373_bass_hpf_cutoff_enum,
+	ADAU1373_BASS1, 0, adau1373_bass_hpf_cutoff_text);
+
+static const char *adau1373_3d_level_text[] = {
+	"0%", "6.67%", "13.33%", "20%", "26.67%", "33.33%",
+	"40%", "46.67%", "53.33%", "60%", "66.67%", "73.33%",
+	"80%", "86.67", "99.33%", "100%"
+};
+
+static const char *adau1373_3d_cutoff_text[] = {
+	"No 3D", "0.03125 fs", "0.04583 fs", "0.075 fs", "0.11458 fs",
+	"0.16875 fs", "0.27083 fs"
+};
+
+static const SOC_ENUM_SINGLE_DECL(adau1373_3d_level_enum,
+	ADAU1373_3D_CTRL1, 4, adau1373_3d_level_text);
+static const SOC_ENUM_SINGLE_DECL(adau1373_3d_cutoff_enum,
+	ADAU1373_3D_CTRL1, 0, adau1373_3d_cutoff_text);
+
+static const unsigned int adau1373_3d_tlv[] = {
+	TLV_DB_RANGE_HEAD(2),
+	0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+	1, 7, TLV_DB_LINEAR_ITEM(-1800, -120),
+};
+
+static const char *adau1373_lr_mux_text[] = {
+	"Mute",
+	"Right Channel (L+R)",
+	"Left Channel (L+R)",
+	"Stereo",
+};
+
+static const SOC_ENUM_SINGLE_DECL(adau1373_lineout1_lr_mux_enum,
+	ADAU1373_OUTPUT_CTRL, 4, adau1373_lr_mux_text);
+static const SOC_ENUM_SINGLE_DECL(adau1373_lineout2_lr_mux_enum,
+	ADAU1373_OUTPUT_CTRL, 6, adau1373_lr_mux_text);
+static const SOC_ENUM_SINGLE_DECL(adau1373_speaker_lr_mux_enum,
+	ADAU1373_LS_CTRL, 4, adau1373_lr_mux_text);
+
+static const struct snd_kcontrol_new adau1373_controls[] = {
+	SOC_DOUBLE_R_TLV("AIF1 Capture Volume", ADAU1373_DAI_RECL_VOL(0),
+		ADAU1373_DAI_RECR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv),
+	SOC_DOUBLE_R_TLV("AIF2 Capture Volume", ADAU1373_DAI_RECL_VOL(1),
+		ADAU1373_DAI_RECR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv),
+	SOC_DOUBLE_R_TLV("AIF3 Capture Volume", ADAU1373_DAI_RECL_VOL(2),
+		ADAU1373_DAI_RECR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv),
+
+	SOC_DOUBLE_R_TLV("ADC Capture Volume", ADAU1373_ADC_RECL_VOL,
+		ADAU1373_ADC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
+	SOC_DOUBLE_R_TLV("DMIC Capture Volume", ADAU1373_DMIC_RECL_VOL,
+		ADAU1373_DMIC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
+
+	SOC_DOUBLE_R_TLV("AIF1 Playback Volume", ADAU1373_DAI_PBL_VOL(0),
+		ADAU1373_DAI_PBR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv),
+	SOC_DOUBLE_R_TLV("AIF2 Playback Volume", ADAU1373_DAI_PBL_VOL(1),
+		ADAU1373_DAI_PBR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv),
+	SOC_DOUBLE_R_TLV("AIF3 Playback Volume", ADAU1373_DAI_PBL_VOL(2),
+		ADAU1373_DAI_PBR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv),
+
+	SOC_DOUBLE_R_TLV("DAC1 Playback Volume", ADAU1373_DAC1_PBL_VOL,
+		ADAU1373_DAC1_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
+	SOC_DOUBLE_R_TLV("DAC2 Playback Volume", ADAU1373_DAC2_PBL_VOL,
+		ADAU1373_DAC2_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv),
+
+	SOC_DOUBLE_R_TLV("Lineout1 Playback Volume", ADAU1373_LLINE_OUT(0),
+		ADAU1373_RLINE_OUT(0), 0, 0x1f, 0, adau1373_out_tlv),
+	SOC_DOUBLE_R_TLV("Speaker Playback Volume", ADAU1373_LSPK_OUT,
+		ADAU1373_RSPK_OUT, 0, 0x1f, 0, adau1373_out_tlv),
+	SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1373_LHP_OUT,
+		ADAU1373_RHP_OUT, 0, 0x1f, 0, adau1373_out_tlv),
+
+	SOC_DOUBLE_R_TLV("Input 1 Capture Volume", ADAU1373_AINL_CTRL(0),
+		ADAU1373_AINR_CTRL(0), 0, 0x1f, 0, adau1373_in_pga_tlv),
+	SOC_DOUBLE_R_TLV("Input 2 Capture Volume", ADAU1373_AINL_CTRL(1),
+		ADAU1373_AINR_CTRL(1), 0, 0x1f, 0, adau1373_in_pga_tlv),
+	SOC_DOUBLE_R_TLV("Input 3 Capture Volume", ADAU1373_AINL_CTRL(2),
+		ADAU1373_AINR_CTRL(2), 0, 0x1f, 0, adau1373_in_pga_tlv),
+	SOC_DOUBLE_R_TLV("Input 4 Capture Volume", ADAU1373_AINL_CTRL(3),
+		ADAU1373_AINR_CTRL(3), 0, 0x1f, 0, adau1373_in_pga_tlv),
+
+	SOC_SINGLE_TLV("Earpiece Playback Volume", ADAU1373_EP_CTRL, 0, 3, 0,
+		adau1373_ep_tlv),
+
+	SOC_DOUBLE_TLV("AIF3 Boost Playback Volume", ADAU1373_VOL_GAIN1, 4, 5,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("AIF2 Boost Playback Volume", ADAU1373_VOL_GAIN1, 2, 3,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("AIF1 Boost Playback Volume", ADAU1373_VOL_GAIN1, 0, 1,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("AIF3 Boost Capture Volume", ADAU1373_VOL_GAIN2, 4, 5,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("AIF2 Boost Capture Volume", ADAU1373_VOL_GAIN2, 2, 3,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("AIF1 Boost Capture Volume", ADAU1373_VOL_GAIN2, 0, 1,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("DMIC Boost Capture Volume", ADAU1373_VOL_GAIN3, 6, 7,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("ADC Boost Capture Volume", ADAU1373_VOL_GAIN3, 4, 5,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("DAC2 Boost Playback Volume", ADAU1373_VOL_GAIN3, 2, 3,
+		1, 0, adau1373_gain_boost_tlv),
+	SOC_DOUBLE_TLV("DAC1 Boost Playback Volume", ADAU1373_VOL_GAIN3, 0, 1,
+		1, 0, adau1373_gain_boost_tlv),
+
+	SOC_DOUBLE_TLV("Input 1 Boost Capture Volume", ADAU1373_ADC_GAIN, 0, 4,
+		1, 0, adau1373_input_boost_tlv),
+	SOC_DOUBLE_TLV("Input 2 Boost Capture Volume", ADAU1373_ADC_GAIN, 1, 5,
+		1, 0, adau1373_input_boost_tlv),
+	SOC_DOUBLE_TLV("Input 3 Boost Capture Volume", ADAU1373_ADC_GAIN, 2, 6,
+		1, 0, adau1373_input_boost_tlv),
+	SOC_DOUBLE_TLV("Input 4 Boost Capture Volume", ADAU1373_ADC_GAIN, 3, 7,
+		1, 0, adau1373_input_boost_tlv),
+
+	SOC_DOUBLE_TLV("Speaker Boost Playback Volume", ADAU1373_LS_CTRL, 2, 3,
+		1, 0, adau1373_speaker_boost_tlv),
+
+	SOC_ENUM("Lineout1 LR Mux", adau1373_lineout1_lr_mux_enum),
+	SOC_ENUM("Speaker LR Mux", adau1373_speaker_lr_mux_enum),
+
+	SOC_ENUM("HPF Cutoff", adau1373_hpf_cutoff_enum),
+	SOC_DOUBLE("HPF Switch", ADAU1373_HPF_CTRL, 1, 0, 1, 0),
+	SOC_ENUM("HPF Channel", adau1373_hpf_channel_enum),
+
+	SOC_ENUM("Bass HPF Cutoff", adau1373_bass_hpf_cutoff_enum),
+	SOC_VALUE_ENUM("Bass Clip Level Threshold",
+	    adau1373_bass_clip_level_enum),
+	SOC_ENUM("Bass LPF Cutoff", adau1373_bass_lpf_cutoff_enum),
+	SOC_DOUBLE("Bass Playback Switch", ADAU1373_BASS2, 0, 1, 1, 0),
+	SOC_SINGLE_TLV("Bass Playback Volume", ADAU1373_BASS2, 2, 7, 0,
+	    adau1373_bass_tlv),
+	SOC_ENUM("Bass Channel", adau1373_bass_channel_enum),
+
+	SOC_ENUM("3D Freq", adau1373_3d_cutoff_enum),
+	SOC_ENUM("3D Level", adau1373_3d_level_enum),
+	SOC_SINGLE("3D Playback Switch", ADAU1373_3D_CTRL2, 0, 1, 0),
+	SOC_SINGLE_TLV("3D Playback Volume", ADAU1373_3D_CTRL2, 2, 7, 0,
+		adau1373_3d_tlv),
+	SOC_ENUM("3D Channel", adau1373_bass_channel_enum),
+
+	SOC_SINGLE("Zero Cross Switch", ADAU1373_PWDN_CTRL3, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new adau1373_lineout2_controls[] = {
+	SOC_DOUBLE_R_TLV("Lineout2 Playback Volume", ADAU1373_LLINE_OUT(1),
+		ADAU1373_RLINE_OUT(1), 0, 0x1f, 0, adau1373_out_tlv),
+	SOC_ENUM("Lineout2 LR Mux", adau1373_lineout2_lr_mux_enum),
+};
+
+static const struct snd_kcontrol_new adau1373_drc_controls[] = {
+	SOC_ENUM("DRC1 Channel", adau1373_drc1_channel_enum),
+	SOC_ENUM("DRC2 Channel", adau1373_drc2_channel_enum),
+	SOC_ENUM("DRC3 Channel", adau1373_drc3_channel_enum),
+};
+
+static int adau1373_pll_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	unsigned int pll_id = w->name[3] - '1';
+	unsigned int val;
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		val = ADAU1373_PLL_CTRL6_PLL_EN;
+	else
+		val = 0;
+
+	snd_soc_update_bits(codec, ADAU1373_PLL_CTRL6(pll_id),
+		ADAU1373_PLL_CTRL6_PLL_EN, val);
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		mdelay(5);
+
+	return 0;
+}
+
+static const char *adau1373_decimator_text[] = {
+	"ADC",
+	"DMIC1",
+};
+
+static const struct soc_enum adau1373_decimator_enum =
+	SOC_ENUM_SINGLE(0, 0, 2, adau1373_decimator_text);
+
+static const struct snd_kcontrol_new adau1373_decimator_mux =
+	SOC_DAPM_ENUM_VIRT("Decimator Mux", adau1373_decimator_enum);
+
+static const struct snd_kcontrol_new adau1373_left_adc_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_LADC_MIXER, 4, 1, 0),
+	SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_LADC_MIXER, 3, 1, 0),
+	SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_LADC_MIXER, 2, 1, 0),
+	SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_LADC_MIXER, 1, 1, 0),
+	SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_LADC_MIXER, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new adau1373_right_adc_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_RADC_MIXER, 4, 1, 0),
+	SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_RADC_MIXER, 3, 1, 0),
+	SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_RADC_MIXER, 2, 1, 0),
+	SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_RADC_MIXER, 1, 1, 0),
+	SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_RADC_MIXER, 0, 1, 0),
+};
+
+#define DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(_name, _reg) \
+const struct snd_kcontrol_new _name[] = { \
+	SOC_DAPM_SINGLE("Left DAC2 Switch", _reg, 7, 1, 0), \
+	SOC_DAPM_SINGLE("Right DAC2 Switch", _reg, 6, 1, 0), \
+	SOC_DAPM_SINGLE("Left DAC1 Switch", _reg, 5, 1, 0), \
+	SOC_DAPM_SINGLE("Right DAC1 Switch", _reg, 4, 1, 0), \
+	SOC_DAPM_SINGLE("Input 4 Bypass Switch", _reg, 3, 1, 0), \
+	SOC_DAPM_SINGLE("Input 3 Bypass Switch", _reg, 2, 1, 0), \
+	SOC_DAPM_SINGLE("Input 2 Bypass Switch", _reg, 1, 1, 0), \
+	SOC_DAPM_SINGLE("Input 1 Bypass Switch", _reg, 0, 1, 0), \
+}
+
+static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line1_mixer_controls,
+	ADAU1373_LLINE1_MIX);
+static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line1_mixer_controls,
+	ADAU1373_RLINE1_MIX);
+static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line2_mixer_controls,
+	ADAU1373_LLINE2_MIX);
+static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line2_mixer_controls,
+	ADAU1373_RLINE2_MIX);
+static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_spk_mixer_controls,
+	ADAU1373_LSPK_MIX);
+static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_spk_mixer_controls,
+	ADAU1373_RSPK_MIX);
+static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_ep_mixer_controls,
+	ADAU1373_EP_MIX);
+
+static const struct snd_kcontrol_new adau1373_left_hp_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Left DAC1 Switch", ADAU1373_LHP_MIX, 5, 1, 0),
+	SOC_DAPM_SINGLE("Left DAC2 Switch", ADAU1373_LHP_MIX, 4, 1, 0),
+	SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_LHP_MIX, 3, 1, 0),
+	SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_LHP_MIX, 2, 1, 0),
+	SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_LHP_MIX, 1, 1, 0),
+	SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_LHP_MIX, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new adau1373_right_hp_mixer_controls[] = {
+	SOC_DAPM_SINGLE("Right DAC1 Switch", ADAU1373_RHP_MIX, 5, 1, 0),
+	SOC_DAPM_SINGLE("Right DAC2 Switch", ADAU1373_RHP_MIX, 4, 1, 0),
+	SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_RHP_MIX, 3, 1, 0),
+	SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_RHP_MIX, 2, 1, 0),
+	SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_RHP_MIX, 1, 1, 0),
+	SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_RHP_MIX, 0, 1, 0),
+};
+
+#define DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(_name, _reg) \
+const struct snd_kcontrol_new _name[] = { \
+	SOC_DAPM_SINGLE("DMIC2 Swapped Switch", _reg, 6, 1, 0), \
+	SOC_DAPM_SINGLE("DMIC2 Switch", _reg, 5, 1, 0), \
+	SOC_DAPM_SINGLE("ADC/DMIC1 Swapped Switch", _reg, 4, 1, 0), \
+	SOC_DAPM_SINGLE("ADC/DMIC1 Switch", _reg, 3, 1, 0), \
+	SOC_DAPM_SINGLE("AIF3 Switch", _reg, 2, 1, 0), \
+	SOC_DAPM_SINGLE("AIF2 Switch", _reg, 1, 1, 0), \
+	SOC_DAPM_SINGLE("AIF1 Switch", _reg, 0, 1, 0), \
+}
+
+static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel1_mixer_controls,
+	ADAU1373_DIN_MIX_CTRL(0));
+static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel2_mixer_controls,
+	ADAU1373_DIN_MIX_CTRL(1));
+static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel3_mixer_controls,
+	ADAU1373_DIN_MIX_CTRL(2));
+static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel4_mixer_controls,
+	ADAU1373_DIN_MIX_CTRL(3));
+static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel5_mixer_controls,
+	ADAU1373_DIN_MIX_CTRL(4));
+
+#define DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(_name, _reg) \
+const struct snd_kcontrol_new _name[] = { \
+	SOC_DAPM_SINGLE("DSP Channel5 Switch", _reg, 4, 1, 0), \
+	SOC_DAPM_SINGLE("DSP Channel4 Switch", _reg, 3, 1, 0), \
+	SOC_DAPM_SINGLE("DSP Channel3 Switch", _reg, 2, 1, 0), \
+	SOC_DAPM_SINGLE("DSP Channel2 Switch", _reg, 1, 1, 0), \
+	SOC_DAPM_SINGLE("DSP Channel1 Switch", _reg, 0, 1, 0), \
+}
+
+static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif1_mixer_controls,
+	ADAU1373_DOUT_MIX_CTRL(0));
+static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif2_mixer_controls,
+	ADAU1373_DOUT_MIX_CTRL(1));
+static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif3_mixer_controls,
+	ADAU1373_DOUT_MIX_CTRL(2));
+static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac1_mixer_controls,
+	ADAU1373_DOUT_MIX_CTRL(3));
+static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac2_mixer_controls,
+	ADAU1373_DOUT_MIX_CTRL(4));
+
+static const struct snd_soc_dapm_widget adau1373_dapm_widgets[] = {
+	/* Datasheet claims Left ADC is bit 6 and Right ADC is bit 7, but that
+	 * doesn't seem to be the case. */
+	SND_SOC_DAPM_ADC("Left ADC", NULL, ADAU1373_PWDN_CTRL1, 7, 0),
+	SND_SOC_DAPM_ADC("Right ADC", NULL, ADAU1373_PWDN_CTRL1, 6, 0),
+
+	SND_SOC_DAPM_ADC("DMIC1", NULL, ADAU1373_DIGMICCTRL, 0, 0),
+	SND_SOC_DAPM_ADC("DMIC2", NULL, ADAU1373_DIGMICCTRL, 2, 0),
+
+	SND_SOC_DAPM_VIRT_MUX("Decimator Mux", SND_SOC_NOPM, 0, 0,
+		&adau1373_decimator_mux),
+
+	SND_SOC_DAPM_SUPPLY("MICBIAS2", ADAU1373_PWDN_CTRL1, 5, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("MICBIAS1", ADAU1373_PWDN_CTRL1, 4, 0, NULL, 0),
+
+	SND_SOC_DAPM_PGA("IN4PGA", ADAU1373_PWDN_CTRL1, 3, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("IN3PGA", ADAU1373_PWDN_CTRL1, 2, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("IN2PGA", ADAU1373_PWDN_CTRL1, 1, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("IN1PGA", ADAU1373_PWDN_CTRL1, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("Left DAC2", NULL, ADAU1373_PWDN_CTRL2, 7, 0),
+	SND_SOC_DAPM_DAC("Right DAC2", NULL, ADAU1373_PWDN_CTRL2, 6, 0),
+	SND_SOC_DAPM_DAC("Left DAC1", NULL, ADAU1373_PWDN_CTRL2, 5, 0),
+	SND_SOC_DAPM_DAC("Right DAC1", NULL, ADAU1373_PWDN_CTRL2, 4, 0),
+
+	SOC_MIXER_ARRAY("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_left_adc_mixer_controls),
+	SOC_MIXER_ARRAY("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_right_adc_mixer_controls),
+
+	SOC_MIXER_ARRAY("Left Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 3, 0,
+		adau1373_left_line2_mixer_controls),
+	SOC_MIXER_ARRAY("Right Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 2, 0,
+		adau1373_right_line2_mixer_controls),
+	SOC_MIXER_ARRAY("Left Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 1, 0,
+		adau1373_left_line1_mixer_controls),
+	SOC_MIXER_ARRAY("Right Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 0, 0,
+		adau1373_right_line1_mixer_controls),
+
+	SOC_MIXER_ARRAY("Earpiece Mixer", ADAU1373_PWDN_CTRL3, 4, 0,
+		adau1373_ep_mixer_controls),
+	SOC_MIXER_ARRAY("Left Speaker Mixer", ADAU1373_PWDN_CTRL3, 3, 0,
+		adau1373_left_spk_mixer_controls),
+	SOC_MIXER_ARRAY("Right Speaker Mixer", ADAU1373_PWDN_CTRL3, 2, 0,
+		adau1373_right_spk_mixer_controls),
+	SOC_MIXER_ARRAY("Left Headphone Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_left_hp_mixer_controls),
+	SOC_MIXER_ARRAY("Right Headphone Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_right_hp_mixer_controls),
+	SND_SOC_DAPM_SUPPLY("Headphone Enable", ADAU1373_PWDN_CTRL3, 1, 0,
+		NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY("AIF1 CLK", ADAU1373_SRC_DAI_CTRL(0), 0, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF2 CLK", ADAU1373_SRC_DAI_CTRL(1), 0, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF3 CLK", ADAU1373_SRC_DAI_CTRL(2), 0, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF1 IN SRC", ADAU1373_SRC_DAI_CTRL(0), 2, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF1 OUT SRC", ADAU1373_SRC_DAI_CTRL(0), 1, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF2 IN SRC", ADAU1373_SRC_DAI_CTRL(1), 2, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF2 OUT SRC", ADAU1373_SRC_DAI_CTRL(1), 1, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF3 IN SRC", ADAU1373_SRC_DAI_CTRL(2), 2, 0,
+	    NULL, 0),
+	SND_SOC_DAPM_SUPPLY("AIF3 OUT SRC", ADAU1373_SRC_DAI_CTRL(2), 1, 0,
+	    NULL, 0),
+
+	SND_SOC_DAPM_AIF_IN("AIF1 IN", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("AIF1 OUT", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("AIF2 IN", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("AIF2 OUT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("AIF3 IN", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("AIF3 OUT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+	SOC_MIXER_ARRAY("DSP Channel1 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_dsp_channel1_mixer_controls),
+	SOC_MIXER_ARRAY("DSP Channel2 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_dsp_channel2_mixer_controls),
+	SOC_MIXER_ARRAY("DSP Channel3 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_dsp_channel3_mixer_controls),
+	SOC_MIXER_ARRAY("DSP Channel4 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_dsp_channel4_mixer_controls),
+	SOC_MIXER_ARRAY("DSP Channel5 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_dsp_channel5_mixer_controls),
+
+	SOC_MIXER_ARRAY("AIF1 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_aif1_mixer_controls),
+	SOC_MIXER_ARRAY("AIF2 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_aif2_mixer_controls),
+	SOC_MIXER_ARRAY("AIF3 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_aif3_mixer_controls),
+	SOC_MIXER_ARRAY("DAC1 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_dac1_mixer_controls),
+	SOC_MIXER_ARRAY("DAC2 Mixer", SND_SOC_NOPM, 0, 0,
+		adau1373_dac2_mixer_controls),
+
+	SND_SOC_DAPM_SUPPLY("DSP", ADAU1373_DIGEN, 4, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("Recording Engine B", ADAU1373_DIGEN, 3, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("Recording Engine A", ADAU1373_DIGEN, 2, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("Playback Engine B", ADAU1373_DIGEN, 1, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("Playback Engine A", ADAU1373_DIGEN, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY("PLL1", SND_SOC_NOPM, 0, 0, adau1373_pll_event,
+		SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SUPPLY("PLL2", SND_SOC_NOPM, 0, 0, adau1373_pll_event,
+		SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SUPPLY("SYSCLK1", ADAU1373_CLK_SRC_DIV(0), 7, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("SYSCLK2", ADAU1373_CLK_SRC_DIV(1), 7, 0, NULL, 0),
+
+	SND_SOC_DAPM_INPUT("AIN1L"),
+	SND_SOC_DAPM_INPUT("AIN1R"),
+	SND_SOC_DAPM_INPUT("AIN2L"),
+	SND_SOC_DAPM_INPUT("AIN2R"),
+	SND_SOC_DAPM_INPUT("AIN3L"),
+	SND_SOC_DAPM_INPUT("AIN3R"),
+	SND_SOC_DAPM_INPUT("AIN4L"),
+	SND_SOC_DAPM_INPUT("AIN4R"),
+
+	SND_SOC_DAPM_INPUT("DMIC1DAT"),
+	SND_SOC_DAPM_INPUT("DMIC2DAT"),
+
+	SND_SOC_DAPM_OUTPUT("LOUT1L"),
+	SND_SOC_DAPM_OUTPUT("LOUT1R"),
+	SND_SOC_DAPM_OUTPUT("LOUT2L"),
+	SND_SOC_DAPM_OUTPUT("LOUT2R"),
+	SND_SOC_DAPM_OUTPUT("HPL"),
+	SND_SOC_DAPM_OUTPUT("HPR"),
+	SND_SOC_DAPM_OUTPUT("SPKL"),
+	SND_SOC_DAPM_OUTPUT("SPKR"),
+	SND_SOC_DAPM_OUTPUT("EP"),
+};
+
+static int adau1373_check_aif_clk(struct snd_soc_dapm_widget *source,
+	struct snd_soc_dapm_widget *sink)
+{
+	struct snd_soc_codec *codec = source->codec;
+	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec);
+	unsigned int dai;
+	const char *clk;
+
+	dai = sink->name[3] - '1';
+
+	if (!adau1373->dais[dai].master)
+		return 0;
+
+	if (adau1373->dais[dai].clk_src == ADAU1373_CLK_SRC_PLL1)
+		clk = "SYSCLK1";
+	else
+		clk = "SYSCLK2";
+
+	return strcmp(source->name, clk) == 0;
+}
+
+static int adau1373_check_src(struct snd_soc_dapm_widget *source,
+	struct snd_soc_dapm_widget *sink)
+{
+	struct snd_soc_codec *codec = source->codec;
+	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec);
+	unsigned int dai;
+
+	dai = sink->name[3] - '1';
+
+	return adau1373->dais[dai].enable_src;
+}
+
+#define DSP_CHANNEL_MIXER_ROUTES(_sink) \
+	{ _sink, "DMIC2 Swapped Switch", "DMIC2" }, \
+	{ _sink, "DMIC2 Switch", "DMIC2" }, \
+	{ _sink, "ADC/DMIC1 Swapped Switch", "Decimator Mux" }, \
+	{ _sink, "ADC/DMIC1 Switch", "Decimator Mux" }, \
+	{ _sink, "AIF1 Switch", "AIF1 IN" }, \
+	{ _sink, "AIF2 Switch", "AIF2 IN" }, \
+	{ _sink, "AIF3 Switch", "AIF3 IN" }
+
+#define DSP_OUTPUT_MIXER_ROUTES(_sink) \
+	{ _sink, "DSP Channel1 Switch", "DSP Channel1 Mixer" }, \
+	{ _sink, "DSP Channel2 Switch", "DSP Channel2 Mixer" }, \
+	{ _sink, "DSP Channel3 Switch", "DSP Channel3 Mixer" }, \
+	{ _sink, "DSP Channel4 Switch", "DSP Channel4 Mixer" }, \
+	{ _sink, "DSP Channel5 Switch", "DSP Channel5 Mixer" }
+
+#define LEFT_OUTPUT_MIXER_ROUTES(_sink) \
+	{ _sink, "Right DAC2 Switch", "Right DAC2" }, \
+	{ _sink, "Left DAC2 Switch", "Left DAC2" }, \
+	{ _sink, "Right DAC1 Switch", "Right DAC1" }, \
+	{ _sink, "Left DAC1 Switch", "Left DAC1" }, \
+	{ _sink, "Input 1 Bypass Switch", "IN1PGA" }, \
+	{ _sink, "Input 2 Bypass Switch", "IN2PGA" }, \
+	{ _sink, "Input 3 Bypass Switch", "IN3PGA" }, \
+	{ _sink, "Input 4 Bypass Switch", "IN4PGA" }
+
+#define RIGHT_OUTPUT_MIXER_ROUTES(_sink) \
+	{ _sink, "Right DAC2 Switch", "Right DAC2" }, \
+	{ _sink, "Left DAC2 Switch", "Left DAC2" }, \
+	{ _sink, "Right DAC1 Switch", "Right DAC1" }, \
+	{ _sink, "Left DAC1 Switch", "Left DAC1" }, \
+	{ _sink, "Input 1 Bypass Switch", "IN1PGA" }, \
+	{ _sink, "Input 2 Bypass Switch", "IN2PGA" }, \
+	{ _sink, "Input 3 Bypass Switch", "IN3PGA" }, \
+	{ _sink, "Input 4 Bypass Switch", "IN4PGA" }
+
+static const struct snd_soc_dapm_route adau1373_dapm_routes[] = {
+	{ "Left ADC Mixer", "DAC1 Switch", "Left DAC1" },
+	{ "Left ADC Mixer", "Input 1 Switch", "IN1PGA" },
+	{ "Left ADC Mixer", "Input 2 Switch", "IN2PGA" },
+	{ "Left ADC Mixer", "Input 3 Switch", "IN3PGA" },
+	{ "Left ADC Mixer", "Input 4 Switch", "IN4PGA" },
+
+	{ "Right ADC Mixer", "DAC1 Switch", "Right DAC1" },
+	{ "Right ADC Mixer", "Input 1 Switch", "IN1PGA" },
+	{ "Right ADC Mixer", "Input 2 Switch", "IN2PGA" },
+	{ "Right ADC Mixer", "Input 3 Switch", "IN3PGA" },
+	{ "Right ADC Mixer", "Input 4 Switch", "IN4PGA" },
+
+	{ "Left ADC", NULL, "Left ADC Mixer" },
+	{ "Right ADC", NULL, "Right ADC Mixer" },
+
+	{ "Decimator Mux", "ADC", "Left ADC" },
+	{ "Decimator Mux", "ADC", "Right ADC" },
+	{ "Decimator Mux", "DMIC1", "DMIC1" },
+
+	DSP_CHANNEL_MIXER_ROUTES("DSP Channel1 Mixer"),
+	DSP_CHANNEL_MIXER_ROUTES("DSP Channel2 Mixer"),
+	DSP_CHANNEL_MIXER_ROUTES("DSP Channel3 Mixer"),
+	DSP_CHANNEL_MIXER_ROUTES("DSP Channel4 Mixer"),
+	DSP_CHANNEL_MIXER_ROUTES("DSP Channel5 Mixer"),
+
+	DSP_OUTPUT_MIXER_ROUTES("AIF1 Mixer"),
+	DSP_OUTPUT_MIXER_ROUTES("AIF2 Mixer"),
+	DSP_OUTPUT_MIXER_ROUTES("AIF3 Mixer"),
+	DSP_OUTPUT_MIXER_ROUTES("DAC1 Mixer"),
+	DSP_OUTPUT_MIXER_ROUTES("DAC2 Mixer"),
+
+	{ "AIF1 OUT", NULL, "AIF1 Mixer" },
+	{ "AIF2 OUT", NULL, "AIF2 Mixer" },
+	{ "AIF3 OUT", NULL, "AIF3 Mixer" },
+	{ "Left DAC1", NULL, "DAC1 Mixer" },
+	{ "Right DAC1", NULL, "DAC1 Mixer" },
+	{ "Left DAC2", NULL, "DAC2 Mixer" },
+	{ "Right DAC2", NULL, "DAC2 Mixer" },
+
+	LEFT_OUTPUT_MIXER_ROUTES("Left Lineout1 Mixer"),
+	RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout1 Mixer"),
+	LEFT_OUTPUT_MIXER_ROUTES("Left Lineout2 Mixer"),
+	RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout2 Mixer"),
+	LEFT_OUTPUT_MIXER_ROUTES("Left Speaker Mixer"),
+	RIGHT_OUTPUT_MIXER_ROUTES("Right Speaker Mixer"),
+
+	{ "Left Headphone Mixer", "Left DAC2 Switch", "Left DAC2" },
+	{ "Left Headphone Mixer", "Left DAC1 Switch", "Left DAC1" },
+	{ "Left Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" },
+	{ "Left Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" },
+	{ "Left Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" },
+	{ "Left Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" },
+	{ "Right Headphone Mixer", "Right DAC2 Switch", "Right DAC2" },
+	{ "Right Headphone Mixer", "Right DAC1 Switch", "Right DAC1" },
+	{ "Right Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" },
+	{ "Right Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" },
+	{ "Right Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" },
+	{ "Right Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" },
+
+	{ "Left Headphone Mixer", NULL, "Headphone Enable" },
+	{ "Right Headphone Mixer", NULL, "Headphone Enable" },
+
+	{ "Earpiece Mixer", "Right DAC2 Switch", "Right DAC2" },
+	{ "Earpiece Mixer", "Left DAC2 Switch", "Left DAC2" },
+	{ "Earpiece Mixer", "Right DAC1 Switch", "Right DAC1" },
+	{ "Earpiece Mixer", "Left DAC1 Switch", "Left DAC1" },
+	{ "Earpiece Mixer", "Input 1 Bypass Switch", "IN1PGA" },
+	{ "Earpiece Mixer", "Input 2 Bypass Switch", "IN2PGA" },
+	{ "Earpiece Mixer", "Input 3 Bypass Switch", "IN3PGA" },
+	{ "Earpiece Mixer", "Input 4 Bypass Switch", "IN4PGA" },
+
+	{ "LOUT1L", NULL, "Left Lineout1 Mixer" },
+	{ "LOUT1R", NULL, "Right Lineout1 Mixer" },
+	{ "LOUT2L", NULL, "Left Lineout2 Mixer" },
+	{ "LOUT2R", NULL, "Right Lineout2 Mixer" },
+	{ "SPKL", NULL, "Left Speaker Mixer" },
+	{ "SPKR", NULL, "Right Speaker Mixer" },
+	{ "HPL", NULL, "Left Headphone Mixer" },
+	{ "HPR", NULL, "Right Headphone Mixer" },
+	{ "EP", NULL, "Earpiece Mixer" },
+
+	{ "IN1PGA", NULL, "AIN1L" },
+	{ "IN2PGA", NULL, "AIN2L" },
+	{ "IN3PGA", NULL, "AIN3L" },
+	{ "IN4PGA", NULL, "AIN4L" },
+	{ "IN1PGA", NULL, "AIN1R" },
+	{ "IN2PGA", NULL, "AIN2R" },
+	{ "IN3PGA", NULL, "AIN3R" },
+	{ "IN4PGA", NULL, "AIN4R" },
+
+	{ "SYSCLK1", NULL, "PLL1" },
+	{ "SYSCLK2", NULL, "PLL2" },
+
+	{ "Left DAC1", NULL, "SYSCLK1" },
+	{ "Right DAC1", NULL, "SYSCLK1" },
+	{ "Left DAC2", NULL, "SYSCLK1" },
+	{ "Right DAC2", NULL, "SYSCLK1" },
+	{ "Left ADC", NULL, "SYSCLK1" },
+	{ "Right ADC", NULL, "SYSCLK1" },
+
+	{ "DSP", NULL, "SYSCLK1" },
+
+	{ "AIF1 Mixer", NULL, "DSP" },
+	{ "AIF2 Mixer", NULL, "DSP" },
+	{ "AIF3 Mixer", NULL, "DSP" },
+	{ "DAC1 Mixer", NULL, "DSP" },
+	{ "DAC2 Mixer", NULL, "DSP" },
+	{ "DAC1 Mixer", NULL, "Playback Engine A" },
+	{ "DAC2 Mixer", NULL, "Playback Engine B" },
+	{ "Left ADC Mixer", NULL, "Recording Engine A" },
+	{ "Right ADC Mixer", NULL, "Recording Engine A" },
+
+	{ "AIF1 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk },
+	{ "AIF2 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk },
+	{ "AIF3 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk },
+	{ "AIF1 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk },
+	{ "AIF2 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk },
+	{ "AIF3 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk },
+
+	{ "AIF1 IN", NULL, "AIF1 CLK" },
+	{ "AIF1 OUT", NULL, "AIF1 CLK" },
+	{ "AIF2 IN", NULL, "AIF2 CLK" },
+	{ "AIF2 OUT", NULL, "AIF2 CLK" },
+	{ "AIF3 IN", NULL, "AIF3 CLK" },
+	{ "AIF3 OUT", NULL, "AIF3 CLK" },
+	{ "AIF1 IN", NULL, "AIF1 IN SRC", adau1373_check_src },
+	{ "AIF1 OUT", NULL, "AIF1 OUT SRC", adau1373_check_src },
+	{ "AIF2 IN", NULL, "AIF2 IN SRC", adau1373_check_src },
+	{ "AIF2 OUT", NULL, "AIF2 OUT SRC", adau1373_check_src },
+	{ "AIF3 IN", NULL, "AIF3 IN SRC", adau1373_check_src },
+	{ "AIF3 OUT", NULL, "AIF3 OUT SRC", adau1373_check_src },
+
+	{ "DMIC1", NULL, "DMIC1DAT" },
+	{ "DMIC1", NULL, "SYSCLK1" },
+	{ "DMIC1", NULL, "Recording Engine A" },
+	{ "DMIC2", NULL, "DMIC2DAT" },
+	{ "DMIC2", NULL, "SYSCLK1" },
+	{ "DMIC2", NULL, "Recording Engine B" },
+};
+
+static int adau1373_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec);
+	struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id];
+	unsigned int div;
+	unsigned int freq;
+	unsigned int ctrl;
+
+	freq = adau1373_dai->sysclk;
+
+	if (freq % params_rate(params) != 0)
+		return -EINVAL;
+
+	switch (freq / params_rate(params)) {
+	case 1024: /* sysclk / 256 */
+		div = 0;
+		break;
+	case 1536: /* 2/3 sysclk / 256 */
+		div = 1;
+		break;
+	case 2048: /* 1/2 sysclk / 256 */
+		div = 2;
+		break;
+	case 3072: /* 1/3 sysclk / 256 */
+		div = 3;
+		break;
+	case 4096: /* 1/4 sysclk / 256 */
+		div = 4;
+		break;
+	case 6144: /* 1/6 sysclk / 256 */
+		div = 5;
+		break;
+	case 5632: /* 2/11 sysclk / 256 */
+		div = 6;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	adau1373_dai->enable_src = (div != 0);
+
+	snd_soc_update_bits(codec, ADAU1373_BCLKDIV(dai->id),
+		~ADAU1373_BCLKDIV_SOURCE, (div << 2) | ADAU1373_BCLKDIV_64);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		ctrl = ADAU1373_DAI_WLEN_16;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		ctrl = ADAU1373_DAI_WLEN_20;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		ctrl = ADAU1373_DAI_WLEN_24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		ctrl = ADAU1373_DAI_WLEN_32;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return snd_soc_update_bits(codec, ADAU1373_DAI(dai->id),
+			ADAU1373_DAI_WLEN_MASK, ctrl);
+}
+
+static int adau1373_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec);
+	struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id];
+	unsigned int ctrl;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		ctrl = ADAU1373_DAI_MASTER;
+		adau1373_dai->master = true;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		ctrl = 0;
+		adau1373_dai->master = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		ctrl |= ADAU1373_DAI_FORMAT_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		ctrl |= ADAU1373_DAI_FORMAT_LEFT_J;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		ctrl |= ADAU1373_DAI_FORMAT_RIGHT_J;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		ctrl |= ADAU1373_DAI_FORMAT_DSP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		ctrl |= ADAU1373_DAI_INVERT_BCLK;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		ctrl |= ADAU1373_DAI_INVERT_LRCLK;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		ctrl |= ADAU1373_DAI_INVERT_LRCLK | ADAU1373_DAI_INVERT_BCLK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_update_bits(codec, ADAU1373_DAI(dai->id),
+		~ADAU1373_DAI_WLEN_MASK, ctrl);
+
+	return 0;
+}
+
+static int adau1373_set_dai_sysclk(struct snd_soc_dai *dai,
+	int clk_id, unsigned int freq, int dir)
+{
+	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(dai->codec);
+	struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id];
+
+	switch (clk_id) {
+	case ADAU1373_CLK_SRC_PLL1:
+	case ADAU1373_CLK_SRC_PLL2:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	adau1373_dai->sysclk = freq;
+	adau1373_dai->clk_src = clk_id;
+
+	snd_soc_update_bits(dai->codec, ADAU1373_BCLKDIV(dai->id),
+		ADAU1373_BCLKDIV_SOURCE, clk_id << 5);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops adau1373_dai_ops = {
+	.hw_params	= adau1373_hw_params,
+	.set_sysclk	= adau1373_set_dai_sysclk,
+	.set_fmt	= adau1373_set_dai_fmt,
+};
+
+#define ADAU1373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver adau1373_dai_driver[] = {
+	{
+		.id = 0,
+		.name = "adau1373-aif1",
+		.playback = {
+			.stream_name = "AIF1 Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = ADAU1373_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AIF1 Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = ADAU1373_FORMATS,
+		},
+		.ops = &adau1373_dai_ops,
+		.symmetric_rates = 1,
+	},
+	{
+		.id = 1,
+		.name = "adau1373-aif2",
+		.playback = {
+			.stream_name = "AIF2 Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = ADAU1373_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AIF2 Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = ADAU1373_FORMATS,
+		},
+		.ops = &adau1373_dai_ops,
+		.symmetric_rates = 1,
+	},
+	{
+		.id = 2,
+		.name = "adau1373-aif3",
+		.playback = {
+			.stream_name = "AIF3 Playback",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = ADAU1373_FORMATS,
+		},
+		.capture = {
+			.stream_name = "AIF3 Capture",
+			.channels_min = 2,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_48000,
+			.formats = ADAU1373_FORMATS,
+		},
+		.ops = &adau1373_dai_ops,
+		.symmetric_rates = 1,
+	},
+};
+
+static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id,
+	int source, unsigned int freq_in, unsigned int freq_out)
+{
+	unsigned int dpll_div = 0;
+	unsigned int x, r, n, m, i, j, mode;
+
+	switch (pll_id) {
+	case ADAU1373_PLL1:
+	case ADAU1373_PLL2:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (source) {
+	case ADAU1373_PLL_SRC_BCLK1:
+	case ADAU1373_PLL_SRC_BCLK2:
+	case ADAU1373_PLL_SRC_BCLK3:
+	case ADAU1373_PLL_SRC_LRCLK1:
+	case ADAU1373_PLL_SRC_LRCLK2:
+	case ADAU1373_PLL_SRC_LRCLK3:
+	case ADAU1373_PLL_SRC_MCLK1:
+	case ADAU1373_PLL_SRC_MCLK2:
+	case ADAU1373_PLL_SRC_GPIO1:
+	case ADAU1373_PLL_SRC_GPIO2:
+	case ADAU1373_PLL_SRC_GPIO3:
+	case ADAU1373_PLL_SRC_GPIO4:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (freq_in < 7813 || freq_in > 27000000)
+		return -EINVAL;
+
+	if (freq_out < 45158000 || freq_out > 49152000)
+		return -EINVAL;
+
+	/* APLL input needs to be >= 8Mhz, so in case freq_in is less we use the
+	 * DPLL to get it there. DPLL_out = (DPLL_in / div) * 1024 */
+	while (freq_in < 8000000) {
+		freq_in *= 2;
+		dpll_div++;
+	}
+
+	if (freq_out % freq_in != 0) {
+		/* fout = fin * (r + (n/m)) / x */
+		x = DIV_ROUND_UP(freq_in, 13500000);
+		freq_in /= x;
+		r = freq_out / freq_in;
+		i = freq_out % freq_in;
+		j = gcd(i, freq_in);
+		n = i / j;
+		m = freq_in / j;
+		x--;
+		mode = 1;
+	} else {
+		/* fout = fin / r */
+		r = freq_out / freq_in;
+		n = 0;
+		m = 0;
+		x = 0;
+		mode = 0;
+	}
+
+	if (r < 2 || r > 8 || x > 3 || m > 0xffff || n > 0xffff)
+		return -EINVAL;
+
+	if (dpll_div) {
+		dpll_div = 11 - dpll_div;
+		snd_soc_update_bits(codec, ADAU1373_PLL_CTRL6(pll_id),
+			ADAU1373_PLL_CTRL6_DPLL_BYPASS, 0);
+	} else {
+		snd_soc_update_bits(codec, ADAU1373_PLL_CTRL6(pll_id),
+			ADAU1373_PLL_CTRL6_DPLL_BYPASS,
+			ADAU1373_PLL_CTRL6_DPLL_BYPASS);
+	}
+
+	snd_soc_write(codec, ADAU1373_DPLL_CTRL(pll_id),
+		(source << 4) | dpll_div);
+	snd_soc_write(codec, ADAU1373_PLL_CTRL1(pll_id), (m >> 8) & 0xff);
+	snd_soc_write(codec, ADAU1373_PLL_CTRL2(pll_id), m & 0xff);
+	snd_soc_write(codec, ADAU1373_PLL_CTRL3(pll_id), (n >> 8) & 0xff);
+	snd_soc_write(codec, ADAU1373_PLL_CTRL4(pll_id), n & 0xff);
+	snd_soc_write(codec, ADAU1373_PLL_CTRL5(pll_id),
+		(r << 3) | (x << 1) | mode);
+
+	/* Set sysclk to pll_rate / 4 */
+	snd_soc_update_bits(codec, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09);
+
+	return 0;
+}
+
+static void adau1373_load_drc_settings(struct snd_soc_codec *codec,
+	unsigned int nr, uint8_t *drc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ADAU1373_DRC_SIZE; ++i)
+		snd_soc_write(codec, ADAU1373_DRC(nr) + i, drc[i]);
+}
+
+static bool adau1373_valid_micbias(enum adau1373_micbias_voltage micbias)
+{
+	switch (micbias) {
+	case ADAU1373_MICBIAS_2_9V:
+	case ADAU1373_MICBIAS_2_2V:
+	case ADAU1373_MICBIAS_2_6V:
+	case ADAU1373_MICBIAS_1_8V:
+		return true;
+	default:
+		break;
+	}
+	return false;
+}
+
+static int adau1373_probe(struct snd_soc_codec *codec)
+{
+	struct adau1373_platform_data *pdata = codec->dev->platform_data;
+	bool lineout_differential = false;
+	unsigned int val;
+	int ret;
+	int i;
+
+	ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+	if (ret) {
+		dev_err(codec->dev, "failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	codec->dapm.idle_bias_off = true;
+
+	if (pdata) {
+		if (pdata->num_drc > ARRAY_SIZE(pdata->drc_setting))
+			return -EINVAL;
+
+		if (!adau1373_valid_micbias(pdata->micbias1) ||
+			!adau1373_valid_micbias(pdata->micbias2))
+			return -EINVAL;
+
+		for (i = 0; i < pdata->num_drc; ++i) {
+			adau1373_load_drc_settings(codec, i,
+				pdata->drc_setting[i]);
+		}
+
+		snd_soc_add_controls(codec, adau1373_drc_controls,
+			pdata->num_drc);
+
+		val = 0;
+		for (i = 0; i < 4; ++i) {
+			if (pdata->input_differential[i])
+				val |= BIT(i);
+		}
+		snd_soc_write(codec, ADAU1373_INPUT_MODE, val);
+
+		val = 0;
+		if (pdata->lineout_differential)
+			val |= ADAU1373_OUTPUT_CTRL_LDIFF;
+		if (pdata->lineout_ground_sense)
+			val |= ADAU1373_OUTPUT_CTRL_LNFBEN;
+		snd_soc_write(codec, ADAU1373_OUTPUT_CTRL, val);
+
+		lineout_differential = pdata->lineout_differential;
+
+		snd_soc_write(codec, ADAU1373_EP_CTRL,
+			(pdata->micbias1 << ADAU1373_EP_CTRL_MICBIAS1_OFFSET) |
+			(pdata->micbias2 << ADAU1373_EP_CTRL_MICBIAS2_OFFSET));
+	}
+
+	if (!lineout_differential) {
+		snd_soc_add_controls(codec, adau1373_lineout2_controls,
+			ARRAY_SIZE(adau1373_lineout2_controls));
+	}
+
+	snd_soc_write(codec, ADAU1373_ADC_CTRL,
+	    ADAU1373_ADC_CTRL_RESET_FORCE | ADAU1373_ADC_CTRL_PEAK_DETECT);
+
+	return 0;
+}
+
+static int adau1373_set_bias_level(struct snd_soc_codec *codec,
+	enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		snd_soc_update_bits(codec, ADAU1373_PWDN_CTRL3,
+			ADAU1373_PWDN_CTRL3_PWR_EN, ADAU1373_PWDN_CTRL3_PWR_EN);
+		break;
+	case SND_SOC_BIAS_OFF:
+		snd_soc_update_bits(codec, ADAU1373_PWDN_CTRL3,
+			ADAU1373_PWDN_CTRL3_PWR_EN, 0);
+		break;
+	}
+	codec->dapm.bias_level = level;
+	return 0;
+}
+
+static int adau1373_remove(struct snd_soc_codec *codec)
+{
+	adau1373_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static int adau1373_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+	return adau1373_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int adau1373_resume(struct snd_soc_codec *codec)
+{
+	adau1373_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	snd_soc_cache_sync(codec);
+
+	return 0;
+}
+
+static struct snd_soc_codec_driver adau1373_codec_driver = {
+	.probe =	adau1373_probe,
+	.remove =	adau1373_remove,
+	.suspend =	adau1373_suspend,
+	.resume =	adau1373_resume,
+	.set_bias_level = adau1373_set_bias_level,
+	.reg_cache_size = ARRAY_SIZE(adau1373_default_regs),
+	.reg_cache_default = adau1373_default_regs,
+	.reg_word_size = sizeof(uint8_t),
+
+	.set_pll = adau1373_set_pll,
+
+	.controls = adau1373_controls,
+	.num_controls = ARRAY_SIZE(adau1373_controls),
+	.dapm_widgets = adau1373_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(adau1373_dapm_widgets),
+	.dapm_routes = adau1373_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(adau1373_dapm_routes),
+};
+
+static int __devinit adau1373_i2c_probe(struct i2c_client *client,
+	const struct i2c_device_id *id)
+{
+	struct adau1373 *adau1373;
+	int ret;
+
+	adau1373 = kzalloc(sizeof(*adau1373), GFP_KERNEL);
+	if (!adau1373)
+		return -ENOMEM;
+
+	dev_set_drvdata(&client->dev, adau1373);
+
+	ret = snd_soc_register_codec(&client->dev, &adau1373_codec_driver,
+			adau1373_dai_driver, ARRAY_SIZE(adau1373_dai_driver));
+	if (ret < 0)
+		kfree(adau1373);
+
+	return ret;
+}
+
+static int __devexit adau1373_i2c_remove(struct i2c_client *client)
+{
+	snd_soc_unregister_codec(&client->dev);
+	kfree(dev_get_drvdata(&client->dev));
+	return 0;
+}
+
+static const struct i2c_device_id adau1373_i2c_id[] = {
+	{ "adau1373", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, adau1373_i2c_id);
+
+static struct i2c_driver adau1373_i2c_driver = {
+	.driver = {
+		.name = "adau1373",
+		.owner = THIS_MODULE,
+	},
+	.probe = adau1373_i2c_probe,
+	.remove = __devexit_p(adau1373_i2c_remove),
+	.id_table = adau1373_i2c_id,
+};
+
+static int __init adau1373_init(void)
+{
+	return i2c_add_driver(&adau1373_i2c_driver);
+}
+module_init(adau1373_init);
+
+static void __exit adau1373_exit(void)
+{
+	i2c_del_driver(&adau1373_i2c_driver);
+}
+module_exit(adau1373_exit);
+
+MODULE_DESCRIPTION("ASoC ADAU1373 driver");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/adau1373.h b/sound/soc/codecs/adau1373.h
new file mode 100644
index 0000000..c6ab553
--- /dev/null
+++ b/sound/soc/codecs/adau1373.h
@@ -0,0 +1,29 @@
+#ifndef __ADAU1373_H__
+#define __ADAU1373_H__
+
+enum adau1373_pll_src {
+	ADAU1373_PLL_SRC_MCLK1 = 0,
+	ADAU1373_PLL_SRC_BCLK1 = 1,
+	ADAU1373_PLL_SRC_BCLK2 = 2,
+	ADAU1373_PLL_SRC_BCLK3 = 3,
+	ADAU1373_PLL_SRC_LRCLK1 = 4,
+	ADAU1373_PLL_SRC_LRCLK2 = 5,
+	ADAU1373_PLL_SRC_LRCLK3 = 6,
+	ADAU1373_PLL_SRC_GPIO1 = 7,
+	ADAU1373_PLL_SRC_GPIO2 = 8,
+	ADAU1373_PLL_SRC_GPIO3 = 9,
+	ADAU1373_PLL_SRC_GPIO4 = 10,
+	ADAU1373_PLL_SRC_MCLK2 = 11,
+};
+
+enum adau1373_pll {
+	ADAU1373_PLL1 = 0,
+	ADAU1373_PLL2 = 1,
+};
+
+enum adau1373_clk_src {
+	ADAU1373_CLK_SRC_PLL1 = 0,
+	ADAU1373_CLK_SRC_PLL2 = 1,
+};
+
+#endif
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 3/4] ASoC: Blackfin: ADAU1373 eval board support
  2011-08-15 18:15 ` Lars-Peter Clausen
@ 2011-08-15 18:15   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-15 18:15 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: alsa-devel, device-drivers-devel, linux-kernel, Mike Frysinger,
	Lars-Peter Clausen

Add a machine driver to support the EVAL-ADAU1373 board connected to a
Analog Devices BF5XX evaluation board.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
Changes since v1:
	* Make the drivers Kconfig symbol depend on I2C instead of selecting it
---
 sound/soc/blackfin/Kconfig              |   13 ++
 sound/soc/blackfin/Makefile             |    2 +
 sound/soc/blackfin/bfin-eval-adau1373.c |  202 +++++++++++++++++++++++++++++++
 3 files changed, 217 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/blackfin/bfin-eval-adau1373.c

diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig
index fe9d548..9f6bc55 100644
--- a/sound/soc/blackfin/Kconfig
+++ b/sound/soc/blackfin/Kconfig
@@ -27,6 +27,19 @@ config SND_SOC_BFIN_EVAL_ADAU1701
 	  board connected to one of the Blackfin evaluation boards like the
 	  BF5XX-STAMP or BF5XX-EZKIT.
 
+config SND_SOC_BFIN_EVAL_ADAU1373
+	tristate "Support for the EVAL-ADAU1373 board on Blackfin eval boards"
+	depends on SND_BF5XX_I2S && I2C
+	select SND_BF5XX_SOC_I2S
+	select SND_SOC_ADAU1373
+	help
+	  Say Y if you want to add support for the Analog Devices EVAL-ADAU1373
+	  board connected to one of the Blackfin evaluation boards like the
+	  BF5XX-STAMP or BF5XX-EZKIT.
+
+	  Note: This driver assumes that first ADAU1373 DAI is connected to the
+	  first SPORT port on the BF5XX board.
+
 config SND_SOC_BFIN_EVAL_ADAV80X
 	tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards"
 	depends on SND_BF5XX_I2S && (SPI_MASTER || I2C)
diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile
index 6018bf5..1bf86cc 100644
--- a/sound/soc/blackfin/Makefile
+++ b/sound/soc/blackfin/Makefile
@@ -21,6 +21,7 @@ snd-ad1980-objs := bf5xx-ad1980.o
 snd-ssm2602-objs := bf5xx-ssm2602.o
 snd-ad73311-objs := bf5xx-ad73311.o
 snd-ad193x-objs := bf5xx-ad193x.o
+snd-soc-bfin-eval-adau1373-objs := bfin-eval-adau1373.o
 snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o
 snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o
 
@@ -29,5 +30,6 @@ obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
 obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
 obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
 obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o
+obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) += snd-soc-bfin-eval-adau1373.o
 obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) += snd-soc-bfin-eval-adau1701.o
 obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAV80X) += snd-soc-bfin-eval-adav80x.o
diff --git a/sound/soc/blackfin/bfin-eval-adau1373.c b/sound/soc/blackfin/bfin-eval-adau1373.c
new file mode 100644
index 0000000..8df2a3b
--- /dev/null
+++ b/sound/soc/blackfin/bfin-eval-adau1373.c
@@ -0,0 +1,202 @@
+/*
+ * Machine driver for EVAL-ADAU1373 on Analog Devices bfin
+ * evaluation boards.
+ *
+ * Copyright 2011 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/adau1373.h"
+
+static const struct snd_soc_dapm_widget bfin_eval_adau1373_dapm_widgets[] = {
+	SND_SOC_DAPM_LINE("Line In1", NULL),
+	SND_SOC_DAPM_LINE("Line In2", NULL),
+	SND_SOC_DAPM_LINE("Line In3", NULL),
+	SND_SOC_DAPM_LINE("Line In4", NULL),
+
+	SND_SOC_DAPM_LINE("Line Out1", NULL),
+	SND_SOC_DAPM_LINE("Line Out2", NULL),
+	SND_SOC_DAPM_LINE("Stereo Out", NULL),
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_HP("Earpiece", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+static const struct snd_soc_dapm_route bfin_eval_adau1373_dapm_routes[] = {
+	{ "AIN1L", NULL, "Line In1" },
+	{ "AIN1R", NULL, "Line In1" },
+	{ "AIN2L", NULL, "Line In2" },
+	{ "AIN2R", NULL, "Line In2" },
+	{ "AIN3L", NULL, "Line In3" },
+	{ "AIN3R", NULL, "Line In3" },
+	{ "AIN4L", NULL, "Line In4" },
+	{ "AIN4R", NULL, "Line In4" },
+
+	/* MICBIAS can be connected via a jumper to the line-in jack, since w
+	   don't know which one is going to be used, just power both. */
+	{ "Line In1", NULL, "MICBIAS1" },
+	{ "Line In2", NULL, "MICBIAS1" },
+	{ "Line In3", NULL, "MICBIAS1" },
+	{ "Line In4", NULL, "MICBIAS1" },
+	{ "Line In1", NULL, "MICBIAS2" },
+	{ "Line In2", NULL, "MICBIAS2" },
+	{ "Line In3", NULL, "MICBIAS2" },
+	{ "Line In4", NULL, "MICBIAS2" },
+
+	{ "Line Out1", NULL, "LOUT1L" },
+	{ "Line Out1", NULL, "LOUT1R" },
+	{ "Line Out2", NULL, "LOUT2L" },
+	{ "Line Out2", NULL, "LOUT2R" },
+	{ "Headphone", NULL, "HPL" },
+	{ "Headphone", NULL, "HPR" },
+	{ "Earpiece", NULL, "EP" },
+	{ "Speaker", NULL, "SPKL" },
+	{ "Stereo Out", NULL, "SPKR" },
+};
+
+static int bfin_eval_adau1373_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret;
+	int pll_rate;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret)
+		return ret;
+
+	switch (params_rate(params)) {
+	case 48000:
+	case 8000:
+	case 12000:
+	case 16000:
+	case 24000:
+	case 32000:
+		pll_rate = 48000 * 1024;
+		break;
+	case 44100:
+	case 7350:
+	case 11025:
+	case 14700:
+	case 22050:
+	case 29400:
+		pll_rate = 44100 * 1024;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1,
+			ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate,
+			SND_SOC_CLOCK_IN);
+
+	return ret;
+}
+
+static int bfin_eval_adau1373_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int pll_rate = 48000 * 1024;
+	int ret;
+
+	ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1,
+			ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate,
+			SND_SOC_CLOCK_IN);
+
+	return ret;
+}
+static struct snd_soc_ops bfin_eval_adau1373_ops = {
+	.hw_params = bfin_eval_adau1373_hw_params,
+};
+
+static struct snd_soc_dai_link bfin_eval_adau1373_dai = {
+	.name = "adau1373",
+	.stream_name = "adau1373",
+	.cpu_dai_name = "bfin-i2s.0",
+	.codec_dai_name = "adau1373-aif1",
+	.platform_name = "bfin-i2s-pcm-audio",
+	.codec_name = "adau1373.0-001a",
+	.ops = &bfin_eval_adau1373_ops,
+	.init = bfin_eval_adau1373_codec_init,
+};
+
+static struct snd_soc_card bfin_eval_adau1373 = {
+	.name = "bfin-eval-adau1373",
+	.dai_link = &bfin_eval_adau1373_dai,
+	.num_links = 1,
+
+	.dapm_widgets		= bfin_eval_adau1373_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(bfin_eval_adau1373_dapm_widgets),
+	.dapm_routes		= bfin_eval_adau1373_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(bfin_eval_adau1373_dapm_routes),
+};
+
+static int bfin_eval_adau1373_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &bfin_eval_adau1373;
+
+	card->dev = &pdev->dev;
+
+	return snd_soc_register_card(&bfin_eval_adau1373);
+}
+
+static int __devexit bfin_eval_adau1373_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+
+	return 0;
+}
+
+static struct platform_driver bfin_eval_adau1373_driver = {
+	.driver = {
+		.name = "bfin-eval-adau1373",
+		.owner = THIS_MODULE,
+		.pm = &snd_soc_pm_ops,
+	},
+	.probe = bfin_eval_adau1373_probe,
+	.remove = __devexit_p(bfin_eval_adau1373_remove),
+};
+
+static int __init bfin_eval_adau1373_init(void)
+{
+	return platform_driver_register(&bfin_eval_adau1373_driver);
+}
+module_init(bfin_eval_adau1373_init);
+
+static void __exit bfin_eval_adau1373_exit(void)
+{
+	platform_driver_unregister(&bfin_eval_adau1373_driver);
+}
+module_exit(bfin_eval_adau1373_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC bfin adau1373 driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bfin-eval-adau1373");
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 3/4] ASoC: Blackfin: ADAU1373 eval board support
@ 2011-08-15 18:15   ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-15 18:15 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: Mike Frysinger, alsa-devel, Lars-Peter Clausen, linux-kernel,
	device-drivers-devel

Add a machine driver to support the EVAL-ADAU1373 board connected to a
Analog Devices BF5XX evaluation board.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
Changes since v1:
	* Make the drivers Kconfig symbol depend on I2C instead of selecting it
---
 sound/soc/blackfin/Kconfig              |   13 ++
 sound/soc/blackfin/Makefile             |    2 +
 sound/soc/blackfin/bfin-eval-adau1373.c |  202 +++++++++++++++++++++++++++++++
 3 files changed, 217 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/blackfin/bfin-eval-adau1373.c

diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig
index fe9d548..9f6bc55 100644
--- a/sound/soc/blackfin/Kconfig
+++ b/sound/soc/blackfin/Kconfig
@@ -27,6 +27,19 @@ config SND_SOC_BFIN_EVAL_ADAU1701
 	  board connected to one of the Blackfin evaluation boards like the
 	  BF5XX-STAMP or BF5XX-EZKIT.
 
+config SND_SOC_BFIN_EVAL_ADAU1373
+	tristate "Support for the EVAL-ADAU1373 board on Blackfin eval boards"
+	depends on SND_BF5XX_I2S && I2C
+	select SND_BF5XX_SOC_I2S
+	select SND_SOC_ADAU1373
+	help
+	  Say Y if you want to add support for the Analog Devices EVAL-ADAU1373
+	  board connected to one of the Blackfin evaluation boards like the
+	  BF5XX-STAMP or BF5XX-EZKIT.
+
+	  Note: This driver assumes that first ADAU1373 DAI is connected to the
+	  first SPORT port on the BF5XX board.
+
 config SND_SOC_BFIN_EVAL_ADAV80X
 	tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards"
 	depends on SND_BF5XX_I2S && (SPI_MASTER || I2C)
diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile
index 6018bf5..1bf86cc 100644
--- a/sound/soc/blackfin/Makefile
+++ b/sound/soc/blackfin/Makefile
@@ -21,6 +21,7 @@ snd-ad1980-objs := bf5xx-ad1980.o
 snd-ssm2602-objs := bf5xx-ssm2602.o
 snd-ad73311-objs := bf5xx-ad73311.o
 snd-ad193x-objs := bf5xx-ad193x.o
+snd-soc-bfin-eval-adau1373-objs := bfin-eval-adau1373.o
 snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o
 snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o
 
@@ -29,5 +30,6 @@ obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o
 obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o
 obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o
 obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o
+obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) += snd-soc-bfin-eval-adau1373.o
 obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) += snd-soc-bfin-eval-adau1701.o
 obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAV80X) += snd-soc-bfin-eval-adav80x.o
diff --git a/sound/soc/blackfin/bfin-eval-adau1373.c b/sound/soc/blackfin/bfin-eval-adau1373.c
new file mode 100644
index 0000000..8df2a3b
--- /dev/null
+++ b/sound/soc/blackfin/bfin-eval-adau1373.c
@@ -0,0 +1,202 @@
+/*
+ * Machine driver for EVAL-ADAU1373 on Analog Devices bfin
+ * evaluation boards.
+ *
+ * Copyright 2011 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/adau1373.h"
+
+static const struct snd_soc_dapm_widget bfin_eval_adau1373_dapm_widgets[] = {
+	SND_SOC_DAPM_LINE("Line In1", NULL),
+	SND_SOC_DAPM_LINE("Line In2", NULL),
+	SND_SOC_DAPM_LINE("Line In3", NULL),
+	SND_SOC_DAPM_LINE("Line In4", NULL),
+
+	SND_SOC_DAPM_LINE("Line Out1", NULL),
+	SND_SOC_DAPM_LINE("Line Out2", NULL),
+	SND_SOC_DAPM_LINE("Stereo Out", NULL),
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_HP("Earpiece", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+static const struct snd_soc_dapm_route bfin_eval_adau1373_dapm_routes[] = {
+	{ "AIN1L", NULL, "Line In1" },
+	{ "AIN1R", NULL, "Line In1" },
+	{ "AIN2L", NULL, "Line In2" },
+	{ "AIN2R", NULL, "Line In2" },
+	{ "AIN3L", NULL, "Line In3" },
+	{ "AIN3R", NULL, "Line In3" },
+	{ "AIN4L", NULL, "Line In4" },
+	{ "AIN4R", NULL, "Line In4" },
+
+	/* MICBIAS can be connected via a jumper to the line-in jack, since w
+	   don't know which one is going to be used, just power both. */
+	{ "Line In1", NULL, "MICBIAS1" },
+	{ "Line In2", NULL, "MICBIAS1" },
+	{ "Line In3", NULL, "MICBIAS1" },
+	{ "Line In4", NULL, "MICBIAS1" },
+	{ "Line In1", NULL, "MICBIAS2" },
+	{ "Line In2", NULL, "MICBIAS2" },
+	{ "Line In3", NULL, "MICBIAS2" },
+	{ "Line In4", NULL, "MICBIAS2" },
+
+	{ "Line Out1", NULL, "LOUT1L" },
+	{ "Line Out1", NULL, "LOUT1R" },
+	{ "Line Out2", NULL, "LOUT2L" },
+	{ "Line Out2", NULL, "LOUT2R" },
+	{ "Headphone", NULL, "HPL" },
+	{ "Headphone", NULL, "HPR" },
+	{ "Earpiece", NULL, "EP" },
+	{ "Speaker", NULL, "SPKL" },
+	{ "Stereo Out", NULL, "SPKR" },
+};
+
+static int bfin_eval_adau1373_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret;
+	int pll_rate;
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+	if (ret)
+		return ret;
+
+	switch (params_rate(params)) {
+	case 48000:
+	case 8000:
+	case 12000:
+	case 16000:
+	case 24000:
+	case 32000:
+		pll_rate = 48000 * 1024;
+		break;
+	case 44100:
+	case 7350:
+	case 11025:
+	case 14700:
+	case 22050:
+	case 29400:
+		pll_rate = 44100 * 1024;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1,
+			ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate,
+			SND_SOC_CLOCK_IN);
+
+	return ret;
+}
+
+static int bfin_eval_adau1373_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	unsigned int pll_rate = 48000 * 1024;
+	int ret;
+
+	ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1,
+			ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate,
+			SND_SOC_CLOCK_IN);
+
+	return ret;
+}
+static struct snd_soc_ops bfin_eval_adau1373_ops = {
+	.hw_params = bfin_eval_adau1373_hw_params,
+};
+
+static struct snd_soc_dai_link bfin_eval_adau1373_dai = {
+	.name = "adau1373",
+	.stream_name = "adau1373",
+	.cpu_dai_name = "bfin-i2s.0",
+	.codec_dai_name = "adau1373-aif1",
+	.platform_name = "bfin-i2s-pcm-audio",
+	.codec_name = "adau1373.0-001a",
+	.ops = &bfin_eval_adau1373_ops,
+	.init = bfin_eval_adau1373_codec_init,
+};
+
+static struct snd_soc_card bfin_eval_adau1373 = {
+	.name = "bfin-eval-adau1373",
+	.dai_link = &bfin_eval_adau1373_dai,
+	.num_links = 1,
+
+	.dapm_widgets		= bfin_eval_adau1373_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(bfin_eval_adau1373_dapm_widgets),
+	.dapm_routes		= bfin_eval_adau1373_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(bfin_eval_adau1373_dapm_routes),
+};
+
+static int bfin_eval_adau1373_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &bfin_eval_adau1373;
+
+	card->dev = &pdev->dev;
+
+	return snd_soc_register_card(&bfin_eval_adau1373);
+}
+
+static int __devexit bfin_eval_adau1373_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+
+	return 0;
+}
+
+static struct platform_driver bfin_eval_adau1373_driver = {
+	.driver = {
+		.name = "bfin-eval-adau1373",
+		.owner = THIS_MODULE,
+		.pm = &snd_soc_pm_ops,
+	},
+	.probe = bfin_eval_adau1373_probe,
+	.remove = __devexit_p(bfin_eval_adau1373_remove),
+};
+
+static int __init bfin_eval_adau1373_init(void)
+{
+	return platform_driver_register(&bfin_eval_adau1373_driver);
+}
+module_init(bfin_eval_adau1373_init);
+
+static void __exit bfin_eval_adau1373_exit(void)
+{
+	platform_driver_unregister(&bfin_eval_adau1373_driver);
+}
+module_exit(bfin_eval_adau1373_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC bfin adau1373 driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bfin-eval-adau1373");
-- 
1.7.2.5

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 4/4] Blackfin: bf537: Stamp: Register ASoC EVAL-ADAU1373 board driver
  2011-08-15 18:15 ` Lars-Peter Clausen
@ 2011-08-15 18:15   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-15 18:15 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: alsa-devel, device-drivers-devel, linux-kernel, Mike Frysinger,
	Lars-Peter Clausen, Mike Frysinger

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mike Frysinger <vapier@gentoo.org>
---
 arch/blackfin/mach-bf537/boards/stamp.c |   12 ++++++++++++
 1 files changed, 12 insertions(+), 0 deletions(-)

diff --git a/arch/blackfin/mach-bf537/boards/stamp.c b/arch/blackfin/mach-bf537/boards/stamp.c
index 4037043..6662c37 100644
--- a/arch/blackfin/mach-bf537/boards/stamp.c
+++ b/arch/blackfin/mach-bf537/boards/stamp.c
@@ -2674,6 +2674,13 @@ static struct platform_device iio_gpio_trigger = {
 };
 #endif
 
+#if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) || \
+	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373_MODULE)
+static struct platform_device bf5xx_adau1373_device = {
+	.name = "bfin-eval-adau1373",
+};
+#endif
+
 #if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) || \
 	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701_MODULE)
 static struct platform_device bf5xx_adau1701_device = {
@@ -2842,6 +2849,11 @@ static struct platform_device *stamp_devices[] __initdata = {
 	&iio_gpio_trigger,
 #endif
 
+#if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) || \
+	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373_MODULE)
+	&bf5xx_adau1373_device,
+#endif
+
 #if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) || \
 	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701_MODULE)
 	&bf5xx_adau1701_device,
-- 
1.7.2.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 4/4] Blackfin: bf537: Stamp: Register ASoC EVAL-ADAU1373 board driver
@ 2011-08-15 18:15   ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-15 18:15 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: alsa-devel, Lars-Peter Clausen, Mike Frysinger, Mike Frysinger,
	linux-kernel, device-drivers-devel

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mike Frysinger <vapier@gentoo.org>
---
 arch/blackfin/mach-bf537/boards/stamp.c |   12 ++++++++++++
 1 files changed, 12 insertions(+), 0 deletions(-)

diff --git a/arch/blackfin/mach-bf537/boards/stamp.c b/arch/blackfin/mach-bf537/boards/stamp.c
index 4037043..6662c37 100644
--- a/arch/blackfin/mach-bf537/boards/stamp.c
+++ b/arch/blackfin/mach-bf537/boards/stamp.c
@@ -2674,6 +2674,13 @@ static struct platform_device iio_gpio_trigger = {
 };
 #endif
 
+#if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) || \
+	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373_MODULE)
+static struct platform_device bf5xx_adau1373_device = {
+	.name = "bfin-eval-adau1373",
+};
+#endif
+
 #if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) || \
 	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701_MODULE)
 static struct platform_device bf5xx_adau1701_device = {
@@ -2842,6 +2849,11 @@ static struct platform_device *stamp_devices[] __initdata = {
 	&iio_gpio_trigger,
 #endif
 
+#if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) || \
+	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373_MODULE)
+	&bf5xx_adau1373_device,
+#endif
+
 #if defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) || \
 	defined(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701_MODULE)
 	&bf5xx_adau1701_device,
-- 
1.7.2.5

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* USB Audio questions
  2011-08-15 18:15 ` [PATCH 2/4] ASoC: Add ADAU1373 codec support Lars-Peter Clausen
@ 2011-08-15 20:31   ` Pierre-Louis Bossart
       [not found]   ` <4e498227.854fdf0a.0dd4.6281SMTPIN_ADDED@mx.google.com>
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 35+ messages in thread
From: Pierre-Louis Bossart @ 2011-08-15 20:31 UTC (permalink / raw)
  To: 'ALSA Development Mailing List'; +Cc: 'Sarah Sharp'

Hi USB Audio experts,
I am trying to prototype an application where the key requirement is
playback with minimal power on USB audio accessories while maintaining tight
synchronization with video. With the default ALSA USB code, I am getting
~130 interrupts/s due to audio, a fairly high CPU load and a very poor sync,
exactly the opposite of my goals...
After a careful analysis of the code I found a couple of issues where the
help of experts would be welcome:

- Is there any good reason why the max number of packets per urb defaults to
8? This end-ups being the root cause for the large number of interrupts. My
headset supports up to 192 bytes/packet, that means 1536 bytes/urb and 125
interrupts/s. Using more packets/urb results in a linear decrease of the
interrupt rate.

- Increasing the number of packets/urbs solves my power issue but not the
synchronization issue. If I reduce the number of urbs to reduce the
interrupt rate, then the accuracy of the hw_pointer is decreased big time
and it becomes difficult to synchronize with video. The .pointer routine
only keeps track of retired urbs, it seems there's no way to know how many
samples were played at a given time? It also makes pulseaudio behave
somewhat strangely since the driver only provides an approximate position.
In the CAIQ driver code, the .pointer routine seems to rely on a direct
register read, a more efficient way to do things. Isn't there a direct way
to query the usb core to know how many bytes have been transmitted and make
the hw_ptr/delay information independent of the urb buffer size?

- Is there any reason why the driver relies on vmalloc and memcpy? It seems
like using physically-contiguous memory would be more efficient, as done in
the CAIQ driver? I hacked something to use prealloc_pages/malloc_pages and
things seem to work fine.

- The period information is not updated when the urbs are allocated. In my
case the period specified was 25ms, but with all the approximations the
actual period (number of urbs*buffer_size_per_urb) was 24ms. In short
there's a discrepancy between what is known to the application, what is used
by the driver and the actual rate of period_elapsed events.

Thanks,
-Pierre

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]   ` <4e498227.854fdf0a.0dd4.6281SMTPIN_ADDED@mx.google.com>
@ 2011-08-15 20:55     ` Daniel Mack
  2011-08-15 21:48       ` Pierre-Louis Bossart
  0 siblings, 1 reply; 35+ messages in thread
From: Daniel Mack @ 2011-08-15 20:55 UTC (permalink / raw)
  To: Pierre-Louis Bossart; +Cc: Sarah Sharp, ALSA Development Mailing List

Hi,

On Mon, Aug 15, 2011 at 10:31 PM, Pierre-Louis Bossart
<pierre-louis.bossart@linux.intel.com> wrote:
> I am trying to prototype an application where the key requirement is
> playback with minimal power on USB audio accessories while maintaining tight
> synchronization with video. With the default ALSA USB code, I am getting
> ~130 interrupts/s due to audio, a fairly high CPU load and a very poor sync,
> exactly the opposite of my goals...
> After a careful analysis of the code I found a couple of issues where the
> help of experts would be welcome:
>
> - Is there any good reason why the max number of packets per urb defaults to
> 8? This end-ups being the root cause for the large number of interrupts. My
> headset supports up to 192 bytes/packet, that means 1536 bytes/urb and 125
> interrupts/s. Using more packets/urb results in a linear decrease of the
> interrupt rate.

You cannot use more than 8 subframes per ISO urb, that's what how USB
is designed. What exactly did you do in you test? Did you increase
MAX_URBS?

> - Increasing the number of packets/urbs solves my power issue but not the
> synchronization issue. If I reduce the number of urbs to reduce the
> interrupt rate, then the accuracy of the hw_pointer is decreased big time
> and it becomes difficult to synchronize with video. The .pointer routine
> only keeps track of retired urbs, it seems there's no way to know how many
> samples were played at a given time? It also makes pulseaudio behave
> somewhat strangely since the driver only provides an approximate position.
> In the CAIQ driver code, the .pointer routine seems to rely on a direct
> register read, a more efficient way to do things. Isn't there a direct way
> to query the usb core to know how many bytes have been transmitted and make
> the hw_ptr/delay information independent of the urb buffer size?

Just to be sure - you're talking about the driver in sound/usb/caiaq,
right? After a short comparison, I'd say both the caiaq driver and the
generic driver do more or less the same in regard to the .pointer
value, as for the output stream, they both increase their internal
counter when the packet is prepared, shortly before it is submitted to
the host controller. Increasing the number of URBs. Admittedly, this
approach is questionable, as a larger number of prepared output
buffers will actually cause the client believe that more data has
already been written already. You could try moving the hwptr_done
increase statement into the retire callback (which is the first place
we get to know about packets that have been sent to the device)?

> - Is there any reason why the driver relies on vmalloc and memcpy? It seems
> like using physically-contiguous memory would be more efficient, as done in
> the CAIQ driver? I hacked something to use prealloc_pages/malloc_pages and
> things seem to work fine.

Can you show a patch?


Daniel

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-15 20:55     ` Daniel Mack
@ 2011-08-15 21:48       ` Pierre-Louis Bossart
  0 siblings, 0 replies; 35+ messages in thread
From: Pierre-Louis Bossart @ 2011-08-15 21:48 UTC (permalink / raw)
  To: Daniel Mack; +Cc: Sarah Sharp, List, ALSA

[-- Attachment #1: Type: text/plain, Size: 2808 bytes --]

Hi Daniel,

> > - Is there any good reason why the max number of packets per urb defaults to
> > 8? This end-ups being the root cause for the large number of interrupts. My
> > headset supports up to 192 bytes/packet, that means 1536 bytes/urb and 125
> > interrupts/s. Using more packets/urb results in a linear decrease of the
> > interrupt rate.
> 
> You cannot use more than 8 subframes per ISO urb, that's what how USB
> is designed. What exactly did you do in you test? Did you increase
> MAX_URBS?

Can you point to a section of the spec where this restriction is
mentioned? Playback works fine with a larger buffer size and less urbs.
If indeed there was such a restriction, then why make does the module
take a parameter to change the max packet number of urb in card.c?

module_param(nrpacks, int, 0644);
MODULE_PARM_DESC(nrpacks, "Max. number of packets per URB.");

To make this work, I increased MAX_PACKS, MAX_QUEUE. See attached
(ugly!) patch. 

> Just to be sure - you're talking about the driver in sound/usb/caiaq,
> right? After a short comparison, I'd say both the caiaq driver and the
> generic driver do more or less the same in regard to the .pointer
> value, as for the output stream, they both increase their internal
> counter when the packet is prepared, shortly before it is submitted to
> the host controller. Increasing the number of URBs. Admittedly, this
> approach is questionable, as a larger number of prepared output
> buffers will actually cause the client believe that more data has
> already been written already. You could try moving the hwptr_done
> increase statement into the retire callback (which is the first place
> we get to know about packets that have been sent to the device)?

To really have a good video synchronization i'd need the ability to
query the number of audio bytes/samples transmitted whenever the video
renderer queries the pipeline clock. That makes A/V sync completely
independent of the sampling rate and the buffer sizes (just like for
HDAudio and most I2S outputs). This information on bytes transmitted is
probably available at a lower level (DMA, etc). If the audio time only
evolves in steps of 8ms, this is a deal-breaker for me. Having a delay
information in terms of ~1ms steps would be enough.

> > - Is there any reason why the driver relies on vmalloc and memcpy? It seems
> > like using physically-contiguous memory would be more efficient, as done in
> > the CAIQ driver? I hacked something to use prealloc_pages/malloc_pages and
> > things seem to work fine.
> 
> Can you show a patch?

Included in the same patch below. I only modified the allocation. It's
ugly as can be, since I don't really prealloc anything. Looks similar to
what is done in your CAIQ code, except that I am a much worse part-time
programmer...
-Pierre


[-- Attachment #2: usb-plb.patch --]
[-- Type: text/x-patch, Size: 5873 bytes --]

diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
index 150cb7e..09fb994 100644
--- a/sound/core/pcm_memory.c
+++ b/sound/core/pcm_memory.c
@@ -107,6 +107,7 @@ int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream)
 #endif
 	return 0;
 }
+EXPORT_SYMBOL(snd_pcm_lib_preallocate_free);
 
 /**
  * snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm
diff --git a/sound/usb/card.h b/sound/usb/card.h
index ae4251d..18dfa38 100644
--- a/sound/usb/card.h
+++ b/sound/usb/card.h
@@ -1,11 +1,13 @@
 #ifndef __USBAUDIO_CARD_H
 #define __USBAUDIO_CARD_H
 
-#define MAX_PACKS	20
+//#define MAX_PACKS	20
+#define MAX_PACKS	40
 #define MAX_PACKS_HS	(MAX_PACKS * 8)	/* in high speed mode */
 #define MAX_URBS	8
 #define SYNC_URBS	4	/* always four urbs for sync */
-#define MAX_QUEUE	24	/* try not to exceed this queue length, in ms */
+//#define MAX_QUEUE	24	/* try not to exceed this queue length, in ms */
+#define MAX_QUEUE	48	/* try not to exceed this queue length, in ms */
 
 struct audioformat {
 	struct list_head list;
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index b8dcbf4..5ca7999 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -328,11 +328,41 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
 	struct audioformat *fmt;
 	unsigned int channels, rate, format;
 	int ret, changed;
-
+#define PLB
+#ifndef PLB
 	ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
 					       params_buffer_bytes(hw_params));
-	if (ret < 0)
-		return ret;
+#else
+	{
+#define MAX_PREALLOC_SIZE	(32 * 1024 * 1024)
+#define USB_PREALLOC_SIZE	(128 * 1024)
+		int size;
+
+		size = USB_PREALLOC_SIZE;
+		if (size > MAX_PREALLOC_SIZE)
+			size = MAX_PREALLOC_SIZE;
+
+		ret = snd_pcm_lib_preallocate_pages(substream, 
+						SNDRV_DMA_TYPE_CONTINUOUS,
+						snd_dma_continuous_data(GFP_KERNEL),
+						size, MAX_PREALLOC_SIZE);
+		if (ret < 0) {
+			printk(KERN_ERR "plb: could not preallocate data");
+			return ret;
+		} else {
+			printk(KERN_ERR "plb: success preallocate data");
+		}
+		
+		ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+		
+		if (ret < 0) {
+			printk(KERN_ERR "plb: could not malloc data");
+			return ret;
+		} else {
+			printk(KERN_ERR "plb: success malloc data");
+		}
+	}
+#endif
 
 	format = params_format(hw_params);
 	rate = params_rate(hw_params);
@@ -391,7 +421,30 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream)
 	mutex_lock(&subs->stream->chip->shutdown_mutex);
 	snd_usb_release_substream_urbs(subs, 0);
 	mutex_unlock(&subs->stream->chip->shutdown_mutex);
+#ifndef PLB
 	return snd_pcm_lib_free_vmalloc_buffer(substream);
+#else
+	{ 
+		int ret;
+
+		ret = snd_pcm_lib_free_pages(substream);
+		
+		if (ret < 0) {
+			printk(KERN_ERR "plb: could not free data");
+			return ret;
+		} else {
+			printk(KERN_ERR "plb: success free data");
+		}
+
+		ret = snd_pcm_lib_preallocate_free(substream);
+		if (ret < 0) {
+			printk(KERN_ERR "plb: could not free preallocated data");
+			return ret;
+		} else {
+			printk(KERN_ERR "plb: success free prealloc data");
+		}
+	}
+#endif
 }
 
 /*
@@ -424,8 +477,11 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
 
 static struct snd_pcm_hardware snd_usb_hardware =
 {
-	.info =			SNDRV_PCM_INFO_MMAP |
-				SNDRV_PCM_INFO_MMAP_VALID |
+	.info =	
+#ifndef PLB		
+	SNDRV_PCM_INFO_MMAP |
+	SNDRV_PCM_INFO_MMAP_VALID |
+#endif
 				SNDRV_PCM_INFO_BATCH |
 				SNDRV_PCM_INFO_INTERLEAVED |
 				SNDRV_PCM_INFO_BLOCK_TRANSFER |
@@ -853,8 +909,10 @@ static struct snd_pcm_ops snd_usb_playback_ops = {
 	.prepare =	snd_usb_pcm_prepare,
 	.trigger =	snd_usb_substream_playback_trigger,
 	.pointer =	snd_usb_pcm_pointer,
+#ifndef PLB
 	.page =		snd_pcm_lib_get_vmalloc_page,
 	.mmap =		snd_pcm_lib_mmap_vmalloc,
+#endif
 };
 
 static struct snd_pcm_ops snd_usb_capture_ops = {
@@ -866,8 +924,10 @@ static struct snd_pcm_ops snd_usb_capture_ops = {
 	.prepare =	snd_usb_pcm_prepare,
 	.trigger =	snd_usb_substream_capture_trigger,
 	.pointer =	snd_usb_pcm_pointer,
+#ifndef PLB
 	.page =		snd_pcm_lib_get_vmalloc_page,
 	.mmap =		snd_pcm_lib_mmap_vmalloc,
+#endif
 };
 
 void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
diff --git a/sound/usb/urb.c b/sound/usb/urb.c
index e184349..a091f57 100644
--- a/sound/usb/urb.c
+++ b/sound/usb/urb.c
@@ -251,7 +251,8 @@ int snd_usb_init_substream_urbs(struct snd_usb_substream *subs,
 		packs_per_ms = 1;
 
 	if (is_playback) {
-		urb_packs = max(chip->nrpacks, 1);
+		//urb_packs = max(chip->nrpacks, 1);
+		urb_packs = 32; //max(chip->nrpacks, 1);
 		urb_packs = min(urb_packs, (unsigned int)MAX_PACKS);
 	} else
 		urb_packs = 1;
@@ -270,6 +271,9 @@ int snd_usb_init_substream_urbs(struct snd_usb_substream *subs,
 			minsize -= minsize >> 3;
 		minsize = max(minsize, 1u);
 		total_packs = (period_bytes + minsize - 1) / minsize;
+
+		printk(KERN_ERR "plb: urb_packs %d period_bytes %d minsize %d, total_packs %d \n", urb_packs, period_bytes, minsize, total_packs);
+
 		/* we need at least two URBs for queueing */
 		if (total_packs < 2) {
 			total_packs = 2;
@@ -284,6 +288,7 @@ int snd_usb_init_substream_urbs(struct snd_usb_substream *subs,
 		total_packs = MAX_URBS * urb_packs;
 	}
 	subs->nurbs = (total_packs + urb_packs - 1) / urb_packs;
+	printk(KERN_ERR "plb: total_packs %d nurbs %d \n", total_packs, subs->nurbs);
 	if (subs->nurbs > MAX_URBS) {
 		/* too much... */
 		subs->nurbs = MAX_URBS;
@@ -305,6 +310,9 @@ int snd_usb_init_substream_urbs(struct snd_usb_substream *subs,
 		u->buffer_size = maxsize * u->packets;
 		if (subs->fmt_type == UAC_FORMAT_TYPE_II)
 			u->packets++; /* for transfer delimiter */
+
+		printk(KERN_ERR "plb: urb %d packets %d buffer_size %d \n", i, u->packets, u->buffer_size);
+
 		u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
 		if (!u->urb)
 			goto out_of_memory;

[-- Attachment #3: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/4] ASoC: DAPM: Allow multiple mixer sources to be routed via the same switch
  2011-08-15 18:15 ` Lars-Peter Clausen
                   ` (3 preceding siblings ...)
  (?)
@ 2011-08-15 23:25 ` Mark Brown
  -1 siblings, 0 replies; 35+ messages in thread
From: Mark Brown @ 2011-08-15 23:25 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Liam Girdwood, alsa-devel, device-drivers-devel, linux-kernel,
	Mike Frysinger

On Mon, Aug 15, 2011 at 08:15:21PM +0200, Lars-Peter Clausen wrote:
> Currently it is only possible to route one source per switch into a mixer.
> This patch modifies the code, so that it is possible to route multiple sources
> into a mixer via the same switch. One use-case for this is routing a stereo
> channel pair into a mono-mixer via the same switch.

Applied, thanks.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]   ` <000001cc5b8a$4a6577c0$df306740$@bossart@linux.intel.com>
@ 2011-08-16  5:30     ` David Henningsson
  2011-08-16  6:47       ` Clemens Ladisch
  2011-08-16  6:37     ` Clemens Ladisch
  1 sibling, 1 reply; 35+ messages in thread
From: David Henningsson @ 2011-08-16  5:30 UTC (permalink / raw)
  To: Pierre-Louis Bossart; +Cc: 'ALSA Development Mailing List'

On 08/15/2011 10:31 PM, Pierre-Louis Bossart wrote:
> - Increasing the number of packets/urbs solves my power issue but not the
> synchronization issue. If I reduce the number of urbs to reduce the
> interrupt rate, then the accuracy of the hw_pointer is decreased big time
> and it becomes difficult to synchronize with video.

I think the same thing is a problem for quite a few other devices as 
well - I wonder if we need some kind of "pointer granularity" variable 
to be exported through the ALSA API? PulseAudio could use that to 
determine whether or not to enable timer-based scheduling. And in these 
cases, maybe a call to hw_pointer could return hw_pointer and time, and 
then PulseAudio etc could use that for extrapolation (or the 
extrapolation could be done in alsa-lib).

-- 
David Henningsson, Canonical Ltd.
http://launchpad.net/~diwic

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]   ` <000001cc5b8a$4a6577c0$df306740$@bossart@linux.intel.com>
  2011-08-16  5:30     ` David Henningsson
@ 2011-08-16  6:37     ` Clemens Ladisch
  2011-08-16  7:30       ` Daniel Mack
                         ` (3 more replies)
  1 sibling, 4 replies; 35+ messages in thread
From: Clemens Ladisch @ 2011-08-16  6:37 UTC (permalink / raw)
  To: Pierre-Louis Bossart
  Cc: 'Sarah Sharp', 'ALSA Development Mailing List'

Pierre-Louis Bossart wrote:
> - Is there any good reason why the max number of packets per urb defaults to
> 8?

Not really.  It is an attempt to compromise between interrupt frequency
and the latency resulting from packet queueing.

> - Increasing the number of packets/urbs solves my power issue but not the
> synchronization issue. If I reduce the number of urbs to reduce the
> interrupt rate, then the accuracy of the hw_pointer is decreased big time
> and it becomes difficult to synchronize with video. The .pointer routine
> only keeps track of retired urbs, it seems there's no way to know how many
> samples were played at a given time?

Only the URB completion callback tells us about finished packets.

For playback, we know how many bytes we had submitted, so we could
derive the current position from the USB frame counter, but for capture,
we don't know the precise byte count until the HCD has looked at the
descriptors.

> Isn't there a direct way to query the usb core to know how many bytes
> have been transmitted and make the hw_ptr/delay information independent
> of the urb buffer size?

No, this information isn't returned earlier than the completion callback.

It would be nice to be able to tell the HCD to collect information about
all currently completed USB frames, regardless of whether the entire URB
has finished or whether its interrupt flag is set, but this would require
changes in all host controller drivers.  (I'm contemplating something
like this for FireWire, but there we have only one controller driver.)

> - Is there any reason why the driver relies on vmalloc and memcpy? It seems
> like using physically-contiguous memory would be more efficient, as done in
> the CAIQ driver?

Why more efficient?  You cannot avoid double-buffering when a USB packet
wraps around the end of the ALSA ring buffer.

> - The period information is not updated when the urbs are allocated. In my
> case the period specified was 25ms, but with all the approximations the
> actual period (number of urbs*buffer_size_per_urb) was 24ms.

Please note that URBs are usually not completely filled.  For playback,
the driver makes URBs shorter so that they end as nearly as possible on
or after a period boundary; for capture, we cannot predict sample counts,
so the driver just uses one URB per ms.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-16  5:30     ` David Henningsson
@ 2011-08-16  6:47       ` Clemens Ladisch
  0 siblings, 0 replies; 35+ messages in thread
From: Clemens Ladisch @ 2011-08-16  6:47 UTC (permalink / raw)
  To: David Henningsson
  Cc: 'ALSA Development Mailing List', Pierre-Louis Bossart

David Henningsson wrote:
> On 08/15/2011 10:31 PM, Pierre-Louis Bossart wrote:
> > - Increasing the number of packets/urbs solves my power issue but not the
> > synchronization issue. If I reduce the number of urbs to reduce the
> > interrupt rate, then the accuracy of the hw_pointer is decreased big time
> > and it becomes difficult to synchronize with video.
> 
> I think the same thing is a problem for quite a few other devices as
> well

Yes; sometimes, the period interrupt is the only information from which
the driver can derive the position.

> I wonder if we need some kind of "pointer granularity" variable
> to be exported through the ALSA API?

This is certainly possible, and by putting it into the hw_params, it
would be possible to model any dependencies between granularity and
period size.  However, this isn't quite at the top of my steadily-growing
ToDo list.

> a call to hw_pointer could return hw_pointer and time,

After calling snd_pcm_status(), you have snd_pcm_status_get_avail() and
snd_pcm_status_get_(h)tstamp().

> and then PulseAudio etc could use that for extrapolation

IIRC PA does exactly this if it detects that the pointer values don't
increase smoothly.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-16  6:37     ` Clemens Ladisch
@ 2011-08-16  7:30       ` Daniel Mack
  2011-08-16 15:46         ` Clemens Ladisch
  2011-08-16 15:38       ` Pierre-Louis Bossart
                         ` (2 subsequent siblings)
  3 siblings, 1 reply; 35+ messages in thread
From: Daniel Mack @ 2011-08-16  7:30 UTC (permalink / raw)
  To: Clemens Ladisch
  Cc: Sarah Sharp, ALSA Development Mailing List, Pierre-Louis Bossart

On Tue, Aug 16, 2011 at 8:37 AM, Clemens Ladisch <clemens@ladisch.de> wrote:
> Pierre-Louis Bossart wrote:
>> - Is there any good reason why the max number of packets per urb defaults to
>> 8?
>
> Not really.  It is an attempt to compromise between interrupt frequency
> and the latency resulting from packet queueing.

Ah, I thought there is a limit of frames per urb that is the same than
subframes per packet on USB. That's not the case then?

>> - Increasing the number of packets/urbs solves my power issue but not the
>> synchronization issue. If I reduce the number of urbs to reduce the
>> interrupt rate, then the accuracy of the hw_pointer is decreased big time
>> and it becomes difficult to synchronize with video. The .pointer routine
>> only keeps track of retired urbs, it seems there's no way to know how many
>> samples were played at a given time?
>
> Only the URB completion callback tells us about finished packets.
>
> For playback, we know how many bytes we had submitted, so we could
> derive the current position from the USB frame counter, but for capture,
> we don't know the precise byte count until the HCD has looked at the
> descriptors.

Wouldn't it be possible to not count what we submitted but look at the
playback packets that return from the HCD and move the hwptr there?
That information is presumably closer to the actual hardware position
than the time when we queue.


Daniel

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-16  6:37     ` Clemens Ladisch
  2011-08-16  7:30       ` Daniel Mack
@ 2011-08-16 15:38       ` Pierre-Louis Bossart
       [not found]       ` <20110816145353.GB5233@xanatos>
       [not found]       ` <000601cc5c2a$87a26350$96e729f0$@bossart@linux.intel.com>
  3 siblings, 0 replies; 35+ messages in thread
From: Pierre-Louis Bossart @ 2011-08-16 15:38 UTC (permalink / raw)
  To: 'Clemens Ladisch'
  Cc: 'Sarah Sharp', 'ALSA Development Mailing List'

Hi Clemens,

> For playback, we know how many bytes we had submitted, so we could
> derive the current position from the USB frame counter, but for
> capture,
> we don't know the precise byte count until the HCD has looked at the
> descriptors.
>
> > Isn't there a direct way to query the usb core to know how many bytes
> > have been transmitted and make the hw_ptr/delay information
> independent
> > of the urb buffer size?
> 
> No, this information isn't returned earlier than the completion
> callback.

I am lost here, bear with my ignorance of USB... What is this USB frame
counter and what would to enable its use, at least for playback where there
is scope for lots of power optimizations.
 
> Why more efficient?  You cannot avoid double-buffering when a USB
> packet
> wraps around the end of the ALSA ring buffer.

What I had in mind is a case where the ring buffer is divided in separate
periods, which would each correspond to an integer number of URBs/packets.
This way there is _never_ a case of wrap-around and no need for memcpys.
It's probably less generic than the current driver but a whole lot
simpler/efficient.

> > - The period information is not updated when the urbs are allocated.
> In my
> > case the period specified was 25ms, but with all the approximations
> the
> > actual period (number of urbs*buffer_size_per_urb) was 24ms.
> 
> Please note that URBs are usually not completely filled.  For playback,
> the driver makes URBs shorter so that they end as nearly as possible on
> or after a period boundary; for capture, we cannot predict sample
> counts,
> so the driver just uses one URB per ms.

Well I am precisely preaching for filling the URBs as much as possible to
make things simpler for cases where latency doesn't matter. Of course for
capture/speech cases latency does matter and I understand the need to do
things differently there.
-Pierre

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]       ` <20110816145353.GB5233@xanatos>
@ 2011-08-16 15:44         ` Pierre-Louis Bossart
  0 siblings, 0 replies; 35+ messages in thread
From: Pierre-Louis Bossart @ 2011-08-16 15:44 UTC (permalink / raw)
  To: 'Sarah Sharp', 'Clemens Ladisch'
  Cc: 'ALSA Development Mailing List'

> For USB 3.0 host controllers, the HCD doesn't get the information about
> which frame exactly the transfer went out in.  This is because all the
> scheduling is done in hardware, not software.  The xHCI driver can only
> record the frame that was current when the interrupt for the URB
> happened.
> 
> Pierre, I'm not sure you can get the precise measurements you want out
> of a USB 3.0 host controller to do the audio/video sync.  You're
> welcome
> to suggest something to Steve McGowan to add to the xHCI spec, and you
> should probably double check with him that what I'm saying is correct.

Thanks Sarah for the heads-up. I was only looking at existing USB Audio
solutions for now, looks like there's some work to be done for USB3 as well.
We do need precise timing information for all audio drivers. Probably a good
topic for LPC discussions...
-Pierre

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-16  7:30       ` Daniel Mack
@ 2011-08-16 15:46         ` Clemens Ladisch
  0 siblings, 0 replies; 35+ messages in thread
From: Clemens Ladisch @ 2011-08-16 15:46 UTC (permalink / raw)
  To: Daniel Mack
  Cc: Sarah Sharp, ALSA Development Mailing List, Pierre-Louis Bossart

Daniel Mack wrote:
> On Tue, Aug 16, 2011 at 8:37 AM, Clemens Ladisch <clemens@ladisch.de> wrote:
>> Pierre-Louis Bossart wrote:
>>> - Is there any good reason why the max number of packets per urb defaults to
>>> 8?
>>
>> Not really.  It is an attempt to compromise between interrupt frequency
>> and the latency resulting from packet queueing.
> 
> Ah, I thought there is a limit of frames per urb that is the same than
> subframes per packet on USB. That's not the case then?

URBs are a Linux-defined data structure; they can be arbitrarily long
(as long as you don't reach the undocumented limit of how far in the
future the HCD can schedule packets).

> Wouldn't it be possible to not count what we submitted but look at the
> playback packets that return from the HCD and move the hwptr there?
> That information is presumably closer to the actual hardware position
> than the time when we queue.

In this case, the "hardware position" is the read pointer in ALSA's ring
buffer, which must be incremented whenever data is moved from there into
the URBs' buffers.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 2/4] ASoC: Add ADAU1373 codec support
  2011-08-15 18:15 ` [PATCH 2/4] ASoC: Add ADAU1373 codec support Lars-Peter Clausen
@ 2011-08-16 15:54     ` Mark Brown
       [not found]   ` <4e498227.854fdf0a.0dd4.6281SMTPIN_ADDED@mx.google.com>
                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 35+ messages in thread
From: Mark Brown @ 2011-08-16 15:54 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Liam Girdwood, alsa-devel, device-drivers-devel, linux-kernel,
	Mike Frysinger

On Mon, Aug 15, 2011 at 08:15:22PM +0200, Lars-Peter Clausen wrote:
> This patch adds support for the Analog Devices ADAU1373 audio codec.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 2/4] ASoC: Add ADAU1373 codec support
@ 2011-08-16 15:54     ` Mark Brown
  0 siblings, 0 replies; 35+ messages in thread
From: Mark Brown @ 2011-08-16 15:54 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Mike Frysinger, alsa-devel, Liam Girdwood, device-drivers-devel,
	linux-kernel

On Mon, Aug 15, 2011 at 08:15:22PM +0200, Lars-Peter Clausen wrote:
> This patch adds support for the Analog Devices ADAU1373 audio codec.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 3/4] ASoC: Blackfin: ADAU1373 eval board support
  2011-08-15 18:15   ` Lars-Peter Clausen
@ 2011-08-16 15:54     ` Mark Brown
  -1 siblings, 0 replies; 35+ messages in thread
From: Mark Brown @ 2011-08-16 15:54 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Liam Girdwood, alsa-devel, device-drivers-devel, linux-kernel,
	Mike Frysinger

On Mon, Aug 15, 2011 at 08:15:23PM +0200, Lars-Peter Clausen wrote:
> Add a machine driver to support the EVAL-ADAU1373 board connected to a
> Analog Devices BF5XX evaluation board.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 3/4] ASoC: Blackfin: ADAU1373 eval board support
@ 2011-08-16 15:54     ` Mark Brown
  0 siblings, 0 replies; 35+ messages in thread
From: Mark Brown @ 2011-08-16 15:54 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Mike Frysinger, alsa-devel, Liam Girdwood, device-drivers-devel,
	linux-kernel

On Mon, Aug 15, 2011 at 08:15:23PM +0200, Lars-Peter Clausen wrote:
> Add a machine driver to support the EVAL-ADAU1373 board connected to a
> Analog Devices BF5XX evaluation board.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

Applied, thanks.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 4/4] Blackfin: bf537: Stamp: Register ASoC EVAL-ADAU1373 board driver
  2011-08-15 18:15   ` Lars-Peter Clausen
@ 2011-08-16 15:56     ` Mark Brown
  -1 siblings, 0 replies; 35+ messages in thread
From: Mark Brown @ 2011-08-16 15:56 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Liam Girdwood, alsa-devel, device-drivers-devel, linux-kernel,
	Mike Frysinger, Mike Frysinger

On Mon, Aug 15, 2011 at 08:15:24PM +0200, Lars-Peter Clausen wrote:
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Mike Frysinger <vapier@gentoo.org>

I'm OK with this but may as well go via the blackfin tree as there's no
code dependency on any of the ASoC stuff and it reduces the risk of any
collisions with other blackfin work.  If you guys would prefer that it
goes via ASoC then I can apply it there though.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 4/4] Blackfin: bf537: Stamp: Register ASoC EVAL-ADAU1373 board driver
@ 2011-08-16 15:56     ` Mark Brown
  0 siblings, 0 replies; 35+ messages in thread
From: Mark Brown @ 2011-08-16 15:56 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: alsa-devel, Mike Frysinger, Mike Frysinger, linux-kernel,
	device-drivers-devel, Liam Girdwood

On Mon, Aug 15, 2011 at 08:15:24PM +0200, Lars-Peter Clausen wrote:
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Mike Frysinger <vapier@gentoo.org>

I'm OK with this but may as well go via the blackfin tree as there's no
code dependency on any of the ASoC stuff and it reduces the risk of any
collisions with other blackfin work.  If you guys would prefer that it
goes via ASoC then I can apply it there though.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]       ` <000601cc5c2a$87a26350$96e729f0$@bossart@linux.intel.com>
@ 2011-08-16 16:09         ` Clemens Ladisch
  2011-08-16 17:19           ` Pierre-Louis Bossart
       [not found]           ` <000001cc5c38$b07abee0$11703ca0$@bossart@linux.intel.com>
  2011-08-16 20:41         ` Torsten Schenk
  1 sibling, 2 replies; 35+ messages in thread
From: Clemens Ladisch @ 2011-08-16 16:09 UTC (permalink / raw)
  To: Pierre-Louis Bossart
  Cc: 'Sarah Sharp', 'ALSA Development Mailing List'

Pierre-Louis Bossart wrote:
> What is this USB frame counter and what would to enable its use, at
> least for playback where there is scope for lots of power
> optimizations.

USB timing is defined by frames, of which there are about 1000 per
second.  The audio driver schedules one packet to be sent/received for
each frame.  Multiple packets are grouped in a URB (USB request buffer);
the controller raises an interrupt when the last packet of the URB has
been completed.

By reading the current frame number, the driver could deduce how many
packets (and therefore, how many samples) have been processed by the
controller.

> > Why more efficient?  You cannot avoid double-buffering when a USB packet
> > wraps around the end of the ALSA ring buffer.
> 
> What I had in mind is a case where the ring buffer is divided in separate
> periods, which would each correspond to an integer number of URBs/packets.

This works only when the driver has control over how many samples
actually are transmitted in each USB frame.  The playback streams of
many USB audio devices work this way, but quite a few others, and
capture streams, don't.

> It's probably less generic than the current driver but a whole lot
> simpler/efficient.

The code wouldn't be simpler because we'd still need the other
algorithm.  This would be just an optimization.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-16 16:09         ` Clemens Ladisch
@ 2011-08-16 17:19           ` Pierre-Louis Bossart
       [not found]           ` <000001cc5c38$b07abee0$11703ca0$@bossart@linux.intel.com>
  1 sibling, 0 replies; 35+ messages in thread
From: Pierre-Louis Bossart @ 2011-08-16 17:19 UTC (permalink / raw)
  To: 'Clemens Ladisch'
  Cc: 'Sarah Sharp', 'ALSA Development Mailing List'


> > What is this USB frame counter and what would to enable its use, at
> > least for playback where there is scope for lots of power
> > optimizations.
> 
> USB timing is defined by frames, of which there are about 1000 per
> second.  The audio driver schedules one packet to be sent/received for
> each frame.  Multiple packets are grouped in a URB (USB request
> buffer);
> the controller raises an interrupt when the last packet of the URB has
> been completed.
> 
> By reading the current frame number, the driver could deduce how many
> packets (and therefore, how many samples) have been processed by the
> controller.


Thanks Clemens, this is valuable information.
If this frame number was used to update the delay field with more accuracy,
I'd be able to use a larger number of packets while allowing for precise a/v
sync. Removing the memcpys, etc, are probably a second-degree optimization.
I agree it'd make the code more complex. 
Any idea on how this frame number can be read, I can try to prototype this
if this is supported by the USB core?
-Pierre

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]       ` <000601cc5c2a$87a26350$96e729f0$@bossart@linux.intel.com>
  2011-08-16 16:09         ` Clemens Ladisch
@ 2011-08-16 20:41         ` Torsten Schenk
  2011-08-16 22:11           ` Pavel Hofman
       [not found]           ` <000001cc5c5a$e7d5c3e0$b7814ba0$@bossart@linux.intel.com>
  1 sibling, 2 replies; 35+ messages in thread
From: Torsten Schenk @ 2011-08-16 20:41 UTC (permalink / raw)
  To: Pierre-Louis Bossart; +Cc: 'ALSA Development Mailing List'

Hi Pierre,

I also spent some time learning about USB and audio while developing the sound/usb/6fire driver. I made the following discoveries:

>What I had in mind is a case where the ring buffer is divided in separate 
>periods, which would each correspond to an integer number of URBs/packets. 
>This way there is _never_ a case of wrap-around and no need for memcpys. 
>It's probably less generic than the current driver but a whole lot 
>simpler/efficient. 

I was also not very happy to implement the double buffering with memcpy's. But from the experience with the 6fire device I can say this:

- The device/driver sends per urb a constant number of packets (as you already also discovered). The number of samples per packet differ however (+- 1 sample per packet). I accuse the asynchronousity between the urb interval and the sound card's internal clock for this fact. The device expects the driver to send back an urb with exactly the same structure. The device produced crackling noise if the samples per packet were not equal. I cannot say to what extend this is true (i.e. if you could wait some packets until catching up the difference without producing crackles) but due to this fact it's at least not a simple task to send urbs exactly corresponding to the period limit.

- Another thing came into my mind while reading your post, but I'm not sure if that would apply in praxis: If sending an urb is scheduled with the alsa buffer, it might be possible that the buffer is overwritten before the urb is sent, leading to complete audio-rubbish.

- And: at least the 6fire requires a header for each packet, which couldn't be placed directly in the alsa buffer. But perhaps this doesn't apply to most cards.

Greets,
Torsten

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-16 20:41         ` Torsten Schenk
@ 2011-08-16 22:11           ` Pavel Hofman
  2011-08-17  6:36             ` Clemens Ladisch
       [not found]           ` <000001cc5c5a$e7d5c3e0$b7814ba0$@bossart@linux.intel.com>
  1 sibling, 1 reply; 35+ messages in thread
From: Pavel Hofman @ 2011-08-16 22:11 UTC (permalink / raw)
  To: Torsten Schenk
  Cc: 'ALSA Development Mailing List', Pierre-Louis Bossart

Dne 16.8.2011 22:41, Torsten Schenk napsal(a):
> Hi Pierre,
> 
> - The device/driver sends per urb a constant number of packets (as
> you already also discovered). The number of samples per packet differ
> however (+- 1 sample per packet). I accuse the asynchronousity
> between the urb interval and the sound card's internal clock for this
> fact. The device expects the driver to send back an urb with exactly
> the same structure. The device produced crackling noise if the
> samples per packet were not equal. I cannot say to what extend this
> is true (i.e. if you could wait some packets until catching up the
> difference without producing crackles) 

Could we attribute this to the adaptive mode and PLL striving to produce
steady stream of well-timed samples to the DAC? IMHO the lower variation
in samples per packet, the lower jitter of the PLL-recovered clock.

Pavel.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]           ` <000001cc5c5a$e7d5c3e0$b7814ba0$@bossart@linux.intel.com>
@ 2011-08-16 22:13             ` Torsten Schenk
  0 siblings, 0 replies; 35+ messages in thread
From: Torsten Schenk @ 2011-08-16 22:13 UTC (permalink / raw)
  To: Pierre-Louis Bossart; +Cc: alsa-devel

 > Thanks for sharing your experience. 
 >  
 > > - The device/driver sends per urb a constant number of packets (as you 
 > > already also discovered). The number of samples per packet differ 
 > > however (+- 1 sample per packet). I accuse the asynchronousity between 
 > > the urb interval and the sound card's internal clock for this fact. The 
 > > device expects the driver to send back an urb with exactly the same 
 > > structure. The device produced crackling noise if the samples per 
 > > packet were not equal. I cannot say to what extend this is true (i.e. 
 > > if you could wait some packets until catching up the difference without 
 > > producing crackles) but due to this fact it's at least not a simple 
 > > task to send urbs exactly corresponding to the period limit. 
 >  
 > I am still unclear on what makes the number of samples per packet vary, I 
 > didn't see this in the code. I understand that it could vary for 44.1 but 
 > for 48kHz (my case) I'd think it should be constant. 

The variation is not directly in the code. I remember that the caiaq driver enlightened me since this behaviour is commented there. The variation comes from the device and these steps lead to this variation:
1. the driver sends n urbs containing m packets with size 0 (i.e. no audio) to the device
2. the device sends back each of the n urbs one-by-one, filled with the captured audio; the urbs will contain m packets, each packet having an individual number of samples s_p ("sample" being defined here as: 1 sample contains one value for each channel)
3. for each urb returned from the device, the driver sends another urb containing again m packets, but this time with s_p samples per packet (corresponding to the packet-dependent s_p received from the device)

I indeed found out that when using 48kHz, I first thought that the number of samples per packet were identical every time. When using this heuristic, crackling noise occured in the sound output and I looked at the number of samples per packet. They varied not very often but sometimes - around once per 2 seconds.

My explanation for this problem is, that the sound card has its own clock, that will not oscillate at exactly an integer multiple as the computer's/usb's internal clock. Even temperature will change the frequency a little bit, meaning that 48kHz is not always exactly 48kHz. I would have to start speculating about which clock drives the usb urb scheduling, so this question should be left for the more experienced USB programmers.


 >  
 > > - Another thing came into my mind while reading your post, but I'm not 
 > > sure if that would apply in praxis: If sending an urb is scheduled with 
 > > the alsa buffer, it might be possible that the buffer is overwritten 
 > > before the urb is sent, leading to complete audio-rubbish. 
 >  
 > Indeed there could be some issues with pulseaudio rewinds, but the fact is 
 > that rewinds are broken anyway when the data is copied... 
I gave this another thought and came to the conclusion: if you'd call snd_pcm_period_elapsed in the output urb retire callback, the theoretical problem wouldn't occur.

Torsten

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
       [not found]           ` <000001cc5c38$b07abee0$11703ca0$@bossart@linux.intel.com>
@ 2011-08-17  6:29             ` Clemens Ladisch
  2011-08-17 15:21               ` Pierre-Louis Bossart
  2011-08-19 22:02               ` Pierre-Louis Bossart
  0 siblings, 2 replies; 35+ messages in thread
From: Clemens Ladisch @ 2011-08-17  6:29 UTC (permalink / raw)
  To: Pierre-Louis Bossart
  Cc: 'Sarah Sharp', 'ALSA Development Mailing List'

Pierre-Louis Bossart wrote:
> Any idea on how this frame number can be read,

With usb_get_current_frame_number().

This is not a technical problem; the problem is that nobody has found
the time to implement this in the audio driver.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-16 22:11           ` Pavel Hofman
@ 2011-08-17  6:36             ` Clemens Ladisch
  0 siblings, 0 replies; 35+ messages in thread
From: Clemens Ladisch @ 2011-08-17  6:36 UTC (permalink / raw)
  To: Pavel Hofman
  Cc: 'ALSA Development Mailing List',
	Torsten Schenk, Pierre-Louis Bossart

Pavel Hofman wrote:
> Dne 16.8.2011 22:41, Torsten Schenk napsal(a):
> > - The device/driver sends per urb a constant number of packets (as
> > you already also discovered). The number of samples per packet differ
> > however (+- 1 sample per packet). I accuse the asynchronousity
> > between the urb interval and the sound card's internal clock for this
> > fact. The device expects the driver to send back an urb with exactly
> > the same structure. The device produced crackling noise if the
> > samples per packet were not equal.
> 
> Could we attribute this to the adaptive mode and PLL striving to produce
> steady stream of well-timed samples to the DAC?

No; those devices are not adaptive, they have their own clock and
excpect the computer to derive the actual rate of the playback stream
from the actual rate of the capture stream.

> IMHO the lower variation in samples per packet, the lower jitter of
> the PLL-recovered clock.

For adaptive devices, the driver has complete control over sent packets,
and thus they already have as little variation as possible.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-17  6:29             ` Clemens Ladisch
@ 2011-08-17 15:21               ` Pierre-Louis Bossart
  2011-08-19 22:02               ` Pierre-Louis Bossart
  1 sibling, 0 replies; 35+ messages in thread
From: Pierre-Louis Bossart @ 2011-08-17 15:21 UTC (permalink / raw)
  To: 'Clemens Ladisch'
  Cc: 'Sarah Sharp', 'ALSA Development Mailing List'


> > Any idea on how this frame number can be read,
> 
> With usb_get_current_frame_number().
> 
> This is not a technical problem; the problem is that nobody has found
> the time to implement this in the audio driver.

Ok, will try to look at this. Thanks for the pointer.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 4/4] Blackfin: bf537: Stamp: Register ASoC EVAL-ADAU1373 board driver
  2011-08-16 15:56     ` Mark Brown
@ 2011-08-18 13:52       ` Lars-Peter Clausen
  -1 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-18 13:52 UTC (permalink / raw)
  To: Mark Brown
  Cc: Liam Girdwood, alsa-devel, device-drivers-devel, linux-kernel,
	Mike Frysinger, Mike Frysinger

On 08/16/2011 05:56 PM, Mark Brown wrote:
> On Mon, Aug 15, 2011 at 08:15:24PM +0200, Lars-Peter Clausen wrote:
>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>> Cc: Mike Frysinger <vapier@gentoo.org>
> 
> I'm OK with this but may as well go via the blackfin tree as there's no
> code dependency on any of the ASoC stuff and it reduces the risk of any
> collisions with other blackfin work.  If you guys would prefer that it
> goes via ASoC then I can apply it there though.

We'll try to let this go through the blackfin tree.

Thanks
- Lars

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 4/4] Blackfin: bf537: Stamp: Register ASoC EVAL-ADAU1373 board driver
@ 2011-08-18 13:52       ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2011-08-18 13:52 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Mike Frysinger, Mike Frysinger, linux-kernel,
	device-drivers-devel, Liam Girdwood

On 08/16/2011 05:56 PM, Mark Brown wrote:
> On Mon, Aug 15, 2011 at 08:15:24PM +0200, Lars-Peter Clausen wrote:
>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>> Cc: Mike Frysinger <vapier@gentoo.org>
> 
> I'm OK with this but may as well go via the blackfin tree as there's no
> code dependency on any of the ASoC stuff and it reduces the risk of any
> collisions with other blackfin work.  If you guys would prefer that it
> goes via ASoC then I can apply it there though.

We'll try to let this go through the blackfin tree.

Thanks
- Lars

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: USB Audio questions
  2011-08-17  6:29             ` Clemens Ladisch
  2011-08-17 15:21               ` Pierre-Louis Bossart
@ 2011-08-19 22:02               ` Pierre-Louis Bossart
  1 sibling, 0 replies; 35+ messages in thread
From: Pierre-Louis Bossart @ 2011-08-19 22:02 UTC (permalink / raw)
  To: alsa-devel

[-- Attachment #1: Type: text/plain, Size: 1113 bytes --]


> > Any idea on how this frame number can be read,
> 
> With usb_get_current_frame_number().

I hacked something to refine the delay information based on the USB
frame number. see attached patch. 
Two points can be improved:
- I am not sure how to convert from USB frames to ALSA frames. I used a
fixed value based what my headset supports
- Every second or so the estimate seems to be off by 96 samples, see
below. Not sure why this happens, how accurate the counter is and if
there a delay in handling retired urbs? Looks good enough though.
Thanks,
-Pierre

[10880.896486] plb-urb -- actual delay 96 estimated delay 0 
[10881.917729] plb-urb -- actual delay 48 estimated delay 0 
[10882.937948] plb-urb -- actual delay 96 estimated delay 0 
[10883.966190] plb-urb -- actual delay 96 estimated delay 0 
[10884.987425] plb-urb -- actual delay 96 estimated delay 0 
[10886.008654] plb-urb -- actual delay 96 estimated delay 0 
[10887.029931] plb-urb -- actual delay 96 estimated delay 0 
[10888.050153] plb-urb -- actual delay 96 estimated delay 0 
[10889.071375] plb-urb -- actual delay 96 estimated delay 0




[-- Attachment #2: usb-delay.patch --]
[-- Type: text/x-patch, Size: 3789 bytes --]

diff --git a/sound/usb/card.h b/sound/usb/card.h
index ae4251d..a39edcc 100644
--- a/sound/usb/card.h
+++ b/sound/usb/card.h
@@ -94,6 +94,8 @@ struct snd_usb_substream {
 	spinlock_t lock;
 
 	struct snd_urb_ops ops;		/* callbacks (must be filled at init) */
+	int last_frame_number;          /* stored frame number */
+	int last_delay;                 /* stored delay */
 };
 
 struct snd_usb_stream {
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index b8dcbf4..ca17711 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -34,6 +34,27 @@
 #include "clock.h"
 #include "power.h"
 
+/* return the estimated delay based on USB frame counters */
+snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs)
+{
+	int current_frame_number;
+	int frame_diff;
+	int est_delay;
+
+	current_frame_number= usb_get_current_frame_number(subs->dev);
+	frame_diff = current_frame_number-subs->last_frame_number;
+	if (frame_diff < 0)
+		frame_diff += 2048; /* handle 11-bit wrap-around */
+
+	/* FIXME: need to make this formula more general */
+	est_delay =  subs->last_delay-frame_diff*48;
+	if (est_delay < 0) {
+		//printk(KERN_ERR "\t est_delay %d last_delay %d frame_diff %d curr_frame %d last_frame %d \n", est_delay, subs->last_delay, frame_diff, current_frame_number, subs->last_frame_number);
+		est_delay = 0;
+	}
+	return est_delay;
+}
+
 /*
  * return the current pcm pointer.  just based on the hwptr_done value.
  */
@@ -45,6 +66,7 @@ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream
 	subs = (struct snd_usb_substream *)substream->runtime->private_data;
 	spin_lock(&subs->lock);
 	hwptr_done = subs->hwptr_done;
+	substream->runtime->delay = snd_usb_pcm_delay(subs);
 	spin_unlock(&subs->lock);
 	return hwptr_done / (substream->runtime->frame_bits >> 3);
 }
diff --git a/sound/usb/pcm.h b/sound/usb/pcm.h
index ed3e283..11b7bd9 100644
--- a/sound/usb/pcm.h
+++ b/sound/usb/pcm.h
@@ -1,6 +1,8 @@
 #ifndef __USBAUDIO_PCM_H
 #define __USBAUDIO_PCM_H
 
+snd_pcm_uframes_t snd_usb_pcm_delay(struct snd_usb_substream *subs);
+
 void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream);
 
 int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
diff --git a/sound/usb/urb.c b/sound/usb/urb.c
index e184349..a0897a5 100644
--- a/sound/usb/urb.c
+++ b/sound/usb/urb.c
@@ -718,7 +718,16 @@ static int prepare_playback_urb(struct snd_usb_substream *subs,
 	subs->hwptr_done += bytes;
 	if (subs->hwptr_done >= runtime->buffer_size * stride)
 		subs->hwptr_done -= runtime->buffer_size * stride;
+
+	/* update delay with exact number of samples queued */
+	runtime->delay = subs->last_delay;
 	runtime->delay += frames;
+	subs->last_delay = runtime->delay;
+
+	subs->last_frame_number = usb_get_current_frame_number(subs->dev);
+
+	//printk(KERN_ERR "\t\tplb-urb: increased delay %d current frame %d \n", runtime->delay, subs->last_frame_number);
+
 	spin_unlock_irqrestore(&subs->lock, flags);
 	urb->transfer_buffer_length = bytes;
 	if (period_elapsed)
@@ -737,12 +746,21 @@ static int retire_playback_urb(struct snd_usb_substream *subs,
 	unsigned long flags;
 	int stride = runtime->frame_bits >> 3;
 	int processed = urb->transfer_buffer_length / stride;
+	int est_delay;
 
 	spin_lock_irqsave(&subs->lock, flags);
-	if (processed > runtime->delay)
-		runtime->delay = 0;
+
+	est_delay = snd_usb_pcm_delay(subs);
+	/* update delay with exact number of samples played*/
+	if (processed > subs->last_delay)
+		subs->last_delay = 0;
 	else
-		runtime->delay -= processed;
+		subs->last_delay -= processed;
+	runtime->delay = subs->last_delay;
+
+	if (runtime->delay != est_delay)
+		printk(KERN_ERR "plb-urb -- actual delay %d estimated delay %d \n", runtime->delay, est_delay);
+
 	spin_unlock_irqrestore(&subs->lock, flags);
 	return 0;
 }

[-- Attachment #3: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply related	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2011-08-19 22:02 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-08-15 18:15 [PATCH 1/4] ASoC: DAPM: Allow multiple mixer sources to be routed via the same switch Lars-Peter Clausen
2011-08-15 18:15 ` Lars-Peter Clausen
2011-08-15 18:15 ` [PATCH 2/4] ASoC: Add ADAU1373 codec support Lars-Peter Clausen
2011-08-15 20:31   ` USB Audio questions Pierre-Louis Bossart
     [not found]   ` <4e498227.854fdf0a.0dd4.6281SMTPIN_ADDED@mx.google.com>
2011-08-15 20:55     ` Daniel Mack
2011-08-15 21:48       ` Pierre-Louis Bossart
     [not found]   ` <000001cc5b8a$4a6577c0$df306740$@bossart@linux.intel.com>
2011-08-16  5:30     ` David Henningsson
2011-08-16  6:47       ` Clemens Ladisch
2011-08-16  6:37     ` Clemens Ladisch
2011-08-16  7:30       ` Daniel Mack
2011-08-16 15:46         ` Clemens Ladisch
2011-08-16 15:38       ` Pierre-Louis Bossart
     [not found]       ` <20110816145353.GB5233@xanatos>
2011-08-16 15:44         ` Pierre-Louis Bossart
     [not found]       ` <000601cc5c2a$87a26350$96e729f0$@bossart@linux.intel.com>
2011-08-16 16:09         ` Clemens Ladisch
2011-08-16 17:19           ` Pierre-Louis Bossart
     [not found]           ` <000001cc5c38$b07abee0$11703ca0$@bossart@linux.intel.com>
2011-08-17  6:29             ` Clemens Ladisch
2011-08-17 15:21               ` Pierre-Louis Bossart
2011-08-19 22:02               ` Pierre-Louis Bossart
2011-08-16 20:41         ` Torsten Schenk
2011-08-16 22:11           ` Pavel Hofman
2011-08-17  6:36             ` Clemens Ladisch
     [not found]           ` <000001cc5c5a$e7d5c3e0$b7814ba0$@bossart@linux.intel.com>
2011-08-16 22:13             ` Torsten Schenk
2011-08-16 15:54   ` [PATCH 2/4] ASoC: Add ADAU1373 codec support Mark Brown
2011-08-16 15:54     ` Mark Brown
2011-08-15 18:15 ` [PATCH 3/4] ASoC: Blackfin: ADAU1373 eval board support Lars-Peter Clausen
2011-08-15 18:15   ` Lars-Peter Clausen
2011-08-16 15:54   ` Mark Brown
2011-08-16 15:54     ` Mark Brown
2011-08-15 18:15 ` [PATCH 4/4] Blackfin: bf537: Stamp: Register ASoC EVAL-ADAU1373 board driver Lars-Peter Clausen
2011-08-15 18:15   ` Lars-Peter Clausen
2011-08-16 15:56   ` Mark Brown
2011-08-16 15:56     ` Mark Brown
2011-08-18 13:52     ` Lars-Peter Clausen
2011-08-18 13:52       ` Lars-Peter Clausen
2011-08-15 23:25 ` [PATCH 1/4] ASoC: DAPM: Allow multiple mixer sources to be routed via the same switch Mark Brown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.