[alsa-devel] [PATCH 3/6] ASoC: new ADAU1381 codec driver

Mike Frysinger vapier at gentoo.org
Sat Aug 7 22:28:22 CEST 2010


From: Cliff Cai <cliff.cai at analog.com>

Signed-off-by: Cliff Cai <cliff.cai at analog.com>
Signed-off-by: Mike Frysinger <vapier at gentoo.org>
---
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/adau1381.c |  842 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/adau1381.h |  222 ++++++++++++
 4 files changed, 1070 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/codecs/adau1381.c
 create mode 100644 sound/soc/codecs/adau1381.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f37d5f4..e7195ca 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_AD1980 if SND_SOC_AC97_BUS
 	select SND_SOC_AD73311 if I2C
 	select SND_SOC_ADAU1361 if I2C
+	select SND_SOC_ADAU1381 if I2C
 	select SND_SOC_ADAV80X if SND_SOC_I2C_AND_SPI
 	select SND_SOC_ADS117X
 	select SND_SOC_AK4104 if SPI_MASTER
@@ -108,6 +109,9 @@ config SND_SOC_AD73311
 config SND_SOC_ADAU1361
 	tristate
 
+config SND_SOC_ADAU1381
+	tristate
+
 config SND_SOC_ADAV80X
 	tristate
 
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index d05b672..887ff5d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -4,6 +4,7 @@ snd-soc-ad193x-objs := ad193x.o
 snd-soc-ad1980-objs := ad1980.o
 snd-soc-ad73311-objs := ad73311.o
 snd-soc-adau1361-objs := adau1361.o
+snd-soc-adau1381-objs := adau1381.o
 snd-soc-adav80x-objs := adav80x.o
 snd-soc-ads117x-objs := ads117x.o
 snd-soc-ak4104-objs := ak4104.o
@@ -72,6 +73,7 @@ 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_ADAU1361)	+= snd-soc-adau1361.o
+obj-$(CONFIG_SND_SOC_ADAU1381)	+= snd-soc-adau1381.o
 obj-$(CONFIG_SND_SOC_ADAV80X)	+= snd-soc-adav80x.o
 obj-$(CONFIG_SND_SOC_ADS117X)	+= snd-soc-ads117x.o
 obj-$(CONFIG_SND_SOC_AK4104)	+= snd-soc-ak4104.o
diff --git a/sound/soc/codecs/adau1381.c b/sound/soc/codecs/adau1381.c
new file mode 100644
index 0000000..359886d
--- /dev/null
+++ b/sound/soc/codecs/adau1381.c
@@ -0,0 +1,842 @@
+/*
+ * Driver for ADAU1381 sound codec
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "adau1381.h"
+
+#define AUDIO_NAME "adau1381"
+#define ADAU1381_VERSION "0.1"
+
+#define CAP_MIC  1
+#define CAP_LINE 2
+#define CAPTURE_SOURCE_NUMBER 2
+#define ADAU1381_DIG_MIC 0
+
+struct snd_soc_codec_device soc_codec_dev_adau1381;
+static struct snd_soc_codec *adau1381_codec;
+/* codec private data */
+struct adau1381_priv {
+	unsigned int sysclk;
+	unsigned int in_source;
+	unsigned int out_route;
+	unsigned int pll_out;
+	struct work_struct resume_work;
+	struct snd_soc_codec codec;
+	int dapm_state_suspend;
+	struct platform_device *pdev;
+	u8 pll_enable;
+	u8 adau1381_pll_reg[6];
+	u8 rate_index;
+	/* dapm */
+	u8 dapm_lineL;
+	u8 dapm_lineR;
+	u8 dapm_hpL;
+	u8 dapm_hpR;
+};
+
+/*
+ * write register cache
+ */
+static inline int adau1381_write_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg, unsigned int value)
+{
+	u8 *cache = codec->reg_cache;
+
+	if (reg < ADAU_FIRSTREG)
+		reg = reg + ADAU_FIRSTREG;
+
+	if ((reg < ADAU_FIRSTREG) || (reg > ADAU_LASTREG))
+		return -1;
+
+	cache[reg - ADAU_FIRSTREG] = value;
+
+	return 0;
+}
+
+/*
+ * read a multi-byte ADAU1381 register (6byte pll reg)
+ */
+static int adau1381_read_reg_block(struct snd_soc_codec *codec,
+	unsigned int reg, u8 len)
+{
+	u8 buf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+	u8 addr[2];
+	unsigned int i;
+
+	if (reg < ADAU_FIRSTREG)
+		reg = reg + ADAU_FIRSTREG;
+
+	if ((reg < ADAU_FIRSTREG) || (reg > ADAU_LASTREG))
+		return -EIO;
+
+	addr[0] = (u8)(reg >> 8);
+	addr[1] = (u8)(reg & 0xFF);
+
+	/* write the 2byte read address */
+	if (codec->hw_write(codec->control_data, addr, 2) != 2) {
+		dev_err(codec->dev, "read_reg_byte:address write failed.");
+		return -EIO;
+	}
+
+	if (i2c_master_recv(codec->control_data, buf, len) != len)
+		return -EIO;
+
+	for (i = 0; i < len; i++)
+		adau1381_write_reg_cache(codec, reg+i, (unsigned int)buf[i]);
+
+	return 0;
+}
+
+/*
+ * write a multibyte ADAU1381 register (6byte pll reg)
+ */
+static int adau1381_write_reg_block(struct snd_soc_codec *codec,
+	unsigned int reg, u8 length, u8 *values)
+{
+	int count = length + 2; /*data plus 16bit register address*/
+	u8 buf[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+	buf[0] = (u8)(reg >> 8);
+	buf[1] = (u8)(reg & 0xFF);
+
+	if (length > 0)
+		memcpy(&buf[2], values, length);
+
+	if (codec->hw_write(codec->control_data, buf, count) == count)
+		return 0;
+	else {
+		dev_err(codec->dev, "address block write failed.");
+		return -EIO;
+	}
+}
+
+static const struct snd_kcontrol_new adau1381_snd_controls[] = {
+SOC_DOUBLE_R("Master Playback Volume", ADAU_LDACATT-ADAU_FIRSTREG,
+	ADAU_RDACATT-ADAU_FIRSTREG, 0, 255, 1),
+SOC_DOUBLE_R("Capture Volume", ADAU_LADCATT-ADAU_FIRSTREG,
+	ADAU_RADCATT-ADAU_FIRSTREG, 0, 255, 1),
+};
+
+/*
+ * _DAPM_
+ */
+
+static const struct snd_soc_dapm_widget adau1381_dapm_widgets[] = {
+
+SND_SOC_DAPM_MIXER("Left Out", ADAU_PLBPWRM-ADAU_FIRSTREG, 0, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("LHPOUT"),
+
+SND_SOC_DAPM_MIXER("Right Out", ADAU_PLBPWRM-ADAU_FIRSTREG, 1, 0, NULL, 0),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("RHPOUT"),
+
+SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_MIXER("HP Bias Left", ADAU_PLBPWRM-ADAU_FIRSTREG, 6, 1, NULL, 0),
+
+SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_MIXER("ADC Left", ADAU_ADCCTL0-ADAU_FIRSTREG, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("ADC Right", ADAU_ADCCTL0-ADAU_FIRSTREG, 1, 0, NULL, 0),
+
+#if !defined(ADAU1381_DIG_MIC)
+SND_SOC_DAPM_MICBIAS("Mic Bias", ADAU_RECMBIA-ADAU_FIRSTREG, 0, 0),
+SND_SOC_DAPM_MIXER("Left Mic Mixer", ADAU_RECVLCL-ADAU_FIRSTREG, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Right Mic Mixer", ADAU_RECVLCR-ADAU_FIRSTREG, 0, 0, NULL, 0),
+#else
+SND_SOC_DAPM_MICBIAS("Mic Bias Left", SND_SOC_NOPM, 1, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias Right", SND_SOC_NOPM, 1, 0),
+SND_SOC_DAPM_MIXER("Left Mic Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Right Mic Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+#endif
+
+SND_SOC_DAPM_MIXER("Left Input", ADAU_RECPWRM-ADAU_FIRSTREG, 1, 1, NULL, 0),
+SND_SOC_DAPM_MIXER("Right Input", ADAU_RECPWRM-ADAU_FIRSTREG, 2, 1, NULL, 0),
+
+SND_SOC_DAPM_INPUT("LMICIN"),
+SND_SOC_DAPM_INPUT("RMICIN"),
+SND_SOC_DAPM_INPUT("LLINEIN"),
+SND_SOC_DAPM_INPUT("RLINEIN"),
+};
+
+static const struct snd_soc_dapm_route audio_conns[] = {
+	/* DAC */
+	{"DAC Enable Left", NULL, "DAC"},
+	{"DAC Enable Right", NULL, "DAC"},
+
+	/* mixers */
+	{"Left Mixer", NULL, "DAC Enable Left"},
+	{"Right Mixer", NULL, "DAC Enable Right"},
+
+	/* outputs */
+	{"Left Out", NULL, "Left Mixer"},
+	{"Right Out", NULL, "Right Mixer"},
+
+	/* line Out */
+	{"Left Line Mixer", NULL, "Left Out"},
+	{"Right Line Mixer", NULL, "Right Out"},
+	{"LOUT", "LineLeft Bypass Switch", "Left Line Mixer"},
+	{"ROUT", "LineRight Bypass Switch", "Right Line Mixer"},
+
+	/* headphone out */
+	{"HP Bias Left", NULL, "Left Out"},
+	{"HP Bias Right", NULL, "Right Out"},
+	{"LHPOUT", "HPLeft Bypass Switch", "HP Bias Left"},
+	{"RHPOUT", "HPRight Bypass Switch", "HP Bias Right"},
+
+	/* inputs */
+	{"Left Input", NULL, "LLINEIN"},
+	{"Right Input", NULL, "RLINEIN"},
+	{"Mic Bias", NULL, "LMICIN"},
+	{"Mic Bias", NULL, "RMICIN"},
+	{"Left Mic Mixer", NULL, "Mic Bias"},
+	{"Right Mic Mixer", NULL, "Mic Bias"},
+	{"ADC Left", NULL, "Left Input"},
+	{"ADC Right", NULL, "Right Input"},
+	{"ADC Left", NULL, "Left Mic Mixer"},
+	{"ADC Right", NULL, "Right Mic Mixer"},
+	{"ADC", NULL, "ADC Left"},
+	{"ADC", NULL, "ADC Right"},
+
+};
+
+static int adau1381_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, adau1381_dapm_widgets,
+				  ARRAY_SIZE(adau1381_dapm_widgets));
+
+	snd_soc_dapm_add_routes(codec, audio_conns, ARRAY_SIZE(audio_conns));
+	return 0;
+}
+
+/* PLL dividors */
+struct _pll_div {
+	u32 mclk;
+	u32 pll_freq;
+	u16 den;
+	u16 num;
+	u8  param;
+};
+
+static const struct _pll_div clock_dividers[] = {
+	{ 12000000, 45158400, 625, 477, /*44.1kHz*/
+		(PLLCTRL_INTPART_R3|PLLCTRL_INPUT_DIV1|PLLCTRL_TYPE_FRAC) },
+	{12000000, 49152000, 125, 12, /*48kHz*/
+		(PLLCTRL_INTPART_R4|PLLCTRL_INPUT_DIV1|PLLCTRL_TYPE_FRAC) },
+	{12288000, 45158400, 40, 27, /*44.1Khz*/
+		(PLLCTRL_INTPART_R3|PLLCTRL_INPUT_DIV1|PLLCTRL_TYPE_FRAC) },
+	{12288000, 49152000, 0, 0, /*48kHz*/
+		(PLLCTRL_INTPART_R4|PLLCTRL_INPUT_DIV1|PLLCTRL_TYPE_INT) },
+};
+
+static inline int get_pll_settings(int mclk, int pll_out)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(clock_dividers); i++) {
+		if (clock_dividers[i].mclk == mclk
+			&& clock_dividers[i].pll_freq == pll_out)
+			return i;
+	}
+	return 0;
+}
+
+static int adau1381_pll_init(struct snd_soc_codec *codec)
+{
+	struct adau1381_priv *adau1381 = codec->private_data;
+	u8 *pll_reg = adau1381->adau1381_pll_reg;
+	int ix = 0;
+
+	/* Init ADAU1381 clocking */
+	snd_soc_write(codec, ADAU_CLKCTRL,
+		(CLKCTRL_SRC_PLL | CLKCTRL_FRQ_1024 | CLKCTRL_DISABLE));
+
+	ix = get_pll_settings(adau1381->sysclk, adau1381->pll_out);
+
+	pll_reg[0] = (clock_dividers[ix].den >> 8);
+	pll_reg[1] = (clock_dividers[ix].den & 0xFF);
+	pll_reg[2] = (clock_dividers[ix].num >> 8);
+	pll_reg[3] = (clock_dividers[ix].num & 0xFF);
+	pll_reg[4] = clock_dividers[ix].param;
+	pll_reg[5] = PLLCTRL_DISABLE;
+	adau1381_write_reg_block(codec, ADAU_PLLCTRL, 6, pll_reg);
+
+	adau1381->pll_enable = 0;
+
+	return 0;
+}
+
+static int adau1381_pll_enable(struct snd_soc_codec *codec, int enable)
+{
+	struct adau1381_priv *adau1381 = codec->private_data;
+	u8 *pll_reg = adau1381->adau1381_pll_reg;
+	int counter = 0;
+
+	if (enable) {
+		pll_reg[5]  = PLLCTRL_ENABLE;
+		adau1381_write_reg_block(codec, ADAU_PLLCTRL, 6, pll_reg);
+
+		/* wait for PLL lock*/
+		do {
+			++counter;
+			schedule_timeout_interruptible(msecs_to_jiffies(1));
+			adau1381_read_reg_block(codec, ADAU_PLLCTRL, 6);
+		} while (0 == (snd_soc_read(codec, ADAU_PLLCTRL + 5) & 0x2)
+			&& counter < 20);
+		if (counter >= 20)
+			return -1;
+
+		adau1381->pll_enable = 1;
+
+		/* Init ADAU1381 clocking */
+		snd_soc_write(codec, ADAU_CLKCTRL,
+			(CLKCTRL_SRC_PLL | CLKCTRL_FRQ_1024 | CLKCTRL_ENABLE));
+	}
+
+	return 0;
+
+}
+
+static int adau1381_reg_init(struct snd_soc_codec *codec)
+{
+	struct adau1381_mode_register regdata;
+	struct adau1381_mode_register *registers = 0;
+	int i;
+#ifdef ADAU1381_DIG_MIC
+	int mode = 1;
+#else /* analog mic */
+	int mode = 0;
+#endif
+
+	/* Load deault regsiter settings */
+	for (i = 0; i < RESET_REGISTER_COUNT; ++i) {
+		regdata = adau1381_reset[i];
+		snd_soc_write(codec, regdata.regaddress, regdata.regvalue);
+	}
+	/* Load mode registers */
+	registers = adau1381_mode_registers[mode];
+	for (i = 0; i < MODE_REGISTER_COUNT; ++i) {
+		regdata = registers[i];
+		snd_soc_write(codec, regdata.regaddress, regdata.regvalue);
+	}
+
+	snd_soc_write(codec, ADAU_SPRTCT1, 0x00);
+
+
+
+	return 0;
+}
+
+struct _srate_set {
+	int fs;
+	u8 reg;
+};
+
+static const struct _srate_set srate_iface[] = {
+	{8000, 0x1},
+	{11025, 0x2},
+	{12000, 0x2},
+	{16000, 0x3},
+	{22050, 0x4},
+	{24000, 0x4},
+	{32000, 0x5},
+	{44100, 0x0},
+	{48000, 0x0},
+	{88200, 0x6},
+	{96000, 0x6},
+};
+
+static const struct _srate_set erate_iface[] = {
+	{8000, 0x6},
+	{11025, 0x5},
+	{12000, 0x5},
+	{16000, 0x4},
+	{22050, 0x3},
+	{24000, 0x3},
+	{32000, 0x2},
+	{44100, 0x1},
+	{48000, 0x1},
+	{88200, 0x0},
+	{96000, 0x0},
+};
+
+static int adau1381_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct adau1381_priv *adau1381 = codec->private_data;
+	int rate = params_rate(params);
+	int i;
+
+	/* initialize the PLL */
+	if (adau1381_pll_init(codec) != 0)
+		return -EINVAL;
+	for (i = 0; i < ARRAY_SIZE(srate_iface); i++) {
+		if (srate_iface[i].fs == rate) {
+			adau1381->rate_index = i;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int adau1381_pcm_prepare(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct adau1381_priv *adau1381 = codec->private_data;
+	u8 reg = 0;
+	int ret = 0;
+
+	reg = srate_iface[adau1381->rate_index].reg;
+	ret = adau1381_pll_enable(codec, 1);
+	if (ret)
+		dev_err(codec->dev, "Failed to initialize PLL");
+	snd_soc_write(codec, ADAU_DIGPWR0, 0xF3);
+	snd_soc_write(codec, ADAU_DIGPWR0, 0x0D);
+	reg = (snd_soc_read(codec, ADAU_CONVCT0) & 0xF8) | reg;
+	snd_soc_write(codec, ADAU_CONVCT0, reg);
+	reg = (snd_soc_read(codec, ADAU_SAMRATE)) | reg;
+	snd_soc_write(codec, ADAU_SAMRATE, reg);
+
+	reg = (snd_soc_read(codec, ADAU_FRMRATE)) | reg;
+	snd_soc_write(codec, ADAU_FRMRATE, reg);
+	snd_soc_write(codec, ADAU_ENGIRUN, 0x01);
+
+	return ret;
+}
+
+static void adau1381_shutdown(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	u8 reg;
+
+	snd_soc_write(codec, ADAU_DIGPWR0, 0x0);
+	snd_soc_write(codec, ADAU_DIGPWR0, 0x0);
+	reg = snd_soc_read(codec, ADAU_CLKCTRL);
+	snd_soc_write(codec, ADAU_CLKCTRL, reg & ~0x1);
+	snd_soc_write(codec, ADAU_FRMRATE, 0x7F);
+	msleep(3);
+	snd_soc_write(codec, ADAU_ENGIRUN, 0x0);
+
+}
+
+static int adau1381_set_dai_fmt(struct snd_soc_dai *codec_dai,
+		unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	u8 reg = 0;
+
+	/* set master/slave audio interface */
+	reg = (snd_soc_read(codec, ADAU_SPRTCT0) & 0xFE);
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:  /*master*/
+		reg |= 0x1;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS: /*slave*/
+		reg &= ~0x1;
+		break;
+	default:
+		return 0;
+	}
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	/* TODO: support TDM */
+	default:
+		return 0;
+	}
+
+	/* clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	/* TODO: support signal inversions */
+	default:
+		return 0;
+	}
+
+	/* set I2S iface format*/
+	snd_soc_write(codec, ADAU_SPRTCT0, reg);
+	return 0;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int adau1381_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+		int clk_id, int source, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct adau1381_priv *adau1381 = codec->private_data;
+
+	switch (freq) {
+	case 12000000:
+		adau1381->sysclk = freq;
+		return 0;
+	case 12288000:
+		adau1381->sysclk = freq;
+		return 0;
+	}
+
+	/* supported 12MHz MCLK only for now */
+	return -EINVAL;
+}
+
+static int adau1381_set_dai_pll(struct snd_soc_dai *codec_dai,
+		int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct adau1381_priv *adau1381 = codec->private_data;
+
+	/* fixed MCLK only supported for now */
+	if (adau1381->sysclk != freq_in)
+		return -EINVAL;
+
+	/* Only update pll when freq changes */
+	if (adau1381->pll_enable && adau1381->pll_out == freq_out)
+		return 0;
+
+	switch (freq_out) {
+	case 45158400:
+		adau1381->pll_out = freq_out;
+		break;
+	case 49152000:
+		adau1381->pll_out = freq_out;
+		break;
+	default:
+		dev_err(codec->dev, "adau1381_set_dai_pll: undefined pll freq:%d", freq_out);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+static int adau1381_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_write(codec, ADAU_CLKCTRL,
+			(CLKCTRL_SRC_PLL | CLKCTRL_FRQ_1024 | CLKCTRL_DISABLE));
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* everything off, dac mute, inactive */
+		snd_soc_write(codec, ADAU_RECPWRM, RECPWRM_LOW_PWR);
+		snd_soc_write(codec, ADAU_PLBPWRM, PLBPWRM_LOW_PWR);
+		snd_soc_write(codec, ADAU_CLKCTRL,
+			(CLKCTRL_SRC_PLL | CLKCTRL_FRQ_1024 | CLKCTRL_DISABLE));
+		break;
+
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+#define ADAU1381_RATES (SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 |\
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\
+		SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
+		SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\
+		SNDRV_PCM_RATE_96000)
+
+#define ADAU1381_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_ops adau1381_dai_ops = {
+	.hw_params	= adau1381_hw_params,
+	.prepare	= adau1381_pcm_prepare,
+	.shutdown	= adau1381_shutdown,
+	.set_fmt	= adau1381_set_dai_fmt,
+	.set_sysclk	= adau1381_set_dai_sysclk,
+	.set_pll	= adau1381_set_dai_pll,
+};
+
+struct snd_soc_dai adau1381_dai = {
+	.name = "ADAU1381",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = ADAU1381_RATES,
+		.formats = ADAU1381_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = ADAU1381_RATES,
+		.formats = ADAU1381_FORMATS,
+	},
+	.ops = &adau1381_dai_ops,
+};
+EXPORT_SYMBOL_GPL(adau1381_dai);
+
+static int adau1381_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec = socdev->card->codec;
+
+	adau1381_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+static void adau1381_resume_wq_handler(struct work_struct *work)
+{
+	struct adau1381_priv *adau1381 = container_of(work, struct adau1381_priv, resume_work);
+	struct snd_soc_codec *codec = &adau1381->codec;
+	unsigned int i, v;
+
+	adau1381_pll_init(codec);
+	adau1381_pll_enable(codec, 1);
+
+	/* sync reg_cache with the hardware */
+	for (i = ADAU_FIRSTREG; i <= ADAU_LASTREG; ++i) {
+
+		v = snd_soc_read(codec, i);
+		if (snd_soc_write(codec, i, v) != 0) {
+			dev_err(codec->dev, "ERROR WRITING %.4X AT REG %x\n", v, i);
+			return;
+		}
+	}
+
+	snd_soc_write(codec, ADAU_RECPWRM, RECPWRM_RUN_PWR);
+	snd_soc_write(codec, ADAU_PLBPWRM, PLBPWRM_RUN_PWR);
+
+	adau1381_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+}
+
+static int adau1381_resume(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct adau1381_priv *adau1381 = codec->private_data;
+
+	adau1381->pdev = pdev;
+	schedule_work(&adau1381->resume_work);
+	return 0;
+}
+
+/*
+ * initialise the adau1381 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+static int adau1381_register(struct adau1381_priv *adau1381, enum snd_soc_control_type control)
+{
+	struct snd_soc_codec *codec = &adau1381->codec;
+	int ret = 0;
+
+	mutex_init(&codec->mutex);
+	INIT_LIST_HEAD(&codec->dapm_widgets);
+	INIT_LIST_HEAD(&codec->dapm_paths);
+	codec->name = "adau1381";
+	codec->owner = THIS_MODULE;
+	codec->set_bias_level = adau1381_set_bias_level;
+	codec->dai = &adau1381_dai;
+	codec->num_dai = 1;
+	codec->reg_cache_size = ADAU_NUMCACHEREG;
+	codec->reg_cache = kzalloc(ADAU_NUMCACHEREG, GFP_KERNEL);
+	if (codec->reg_cache == NULL)
+		return -ENOMEM;
+
+	ret = snd_soc_codec_set_cache_io(codec, 16, 8, control);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_register_codec(codec);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_register_dai(&adau1381_dai);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+		snd_soc_unregister_codec(codec);
+		return ret;
+	}
+
+	return ret;
+}
+
+static void adau1381_unregister(struct adau1381_priv *adau1381)
+{
+	struct snd_soc_codec *codec = &adau1381->codec;
+
+	adau1381_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	kfree(codec->reg_cache);
+	snd_soc_unregister_dai(&adau1381_dai);
+	snd_soc_unregister_codec(codec);
+	kfree(adau1381);
+	adau1381_codec = NULL;
+}
+
+static int adau1381_probe(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec;
+	struct adau1381_priv *adau1381;
+	int ret = 0;
+
+	socdev->card->codec = adau1381_codec;
+	codec = adau1381_codec;
+	adau1381 = codec->private_data;
+	adau1381->in_source = CAP_MIC; /*default is mic input*/
+	adau1381->sysclk = ADAU1381_MCLK_RATE;
+	adau1381->pll_out = ADAU1381_PLL_FREQ_48;
+	adau1381->dapm_lineL = DAPM_LINE_DEF;
+	adau1381->dapm_lineR = DAPM_LINE_DEF;
+	adau1381->dapm_hpL = DAPM_HP_DEF;
+	adau1381->dapm_hpR = DAPM_HP_DEF;
+	adau1381->pdev = pdev;
+
+	ret = adau1381_reg_init(codec);
+	if (ret < 0)
+		dev_err(codec->dev, "failed to initialize\n");
+	/* register pcms */
+	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+	if (ret < 0) {
+		dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+		goto pcm_err;
+	}
+	snd_soc_add_controls(codec, adau1381_snd_controls,
+			     ARRAY_SIZE(adau1381_snd_controls));
+	adau1381_add_widgets(codec);
+pcm_err:
+	return ret;
+}
+
+/* remove everything here */
+static int adau1381_remove(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+	snd_soc_free_pcms(socdev);
+	snd_soc_dapm_free(socdev);
+
+	return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_adau1381 = {
+	.probe =	adau1381_probe,
+	.remove =	adau1381_remove,
+	.suspend =	adau1381_suspend,
+	.resume =	adau1381_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_adau1381);
+
+
+static __devinit int adau1381_i2c_probe(struct i2c_client *i2c,
+			      const struct i2c_device_id *id)
+{
+	struct adau1381_priv *adau1381;
+	struct snd_soc_codec *codec;
+	int ret = 0;
+
+	adau1381 = kzalloc(sizeof(struct adau1381_priv), GFP_KERNEL);
+	if (adau1381 == NULL)
+		return -ENOMEM;
+	codec = &adau1381->codec;
+	codec->private_data = adau1381;
+	codec->hw_write = (hw_write_t)i2c_master_send;
+
+	i2c_set_clientdata(i2c, adau1381);
+	codec->control_data = i2c;
+
+	codec->dev = &i2c->dev;
+	adau1381_codec = codec;
+
+	INIT_WORK(&adau1381->resume_work, adau1381_resume_wq_handler);
+	ret = adau1381_register(adau1381, SND_SOC_I2C);
+	if (ret < 0)
+		dev_err(&i2c->dev, "failed to initialize\n");
+
+	return ret;
+}
+
+static __devexit int adau1381_i2c_remove(struct i2c_client *client)
+{
+	struct adau1381_priv *adau1381 = i2c_get_clientdata(client);
+	adau1381_unregister(adau1381);
+	return 0;
+}
+
+static const struct i2c_device_id adau1381_i2c_id[] = {
+	{ "adau1381", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, adau1381_i2c_id);
+
+/* corgi i2c codec control layer */
+static struct i2c_driver adau1381_i2c_driver = {
+	.driver = {
+		.name = "adau1381",
+		.owner = THIS_MODULE,
+	},
+	.probe    = adau1381_i2c_probe,
+	.remove   = __devexit_p(adau1381_i2c_remove),
+	.id_table = adau1381_i2c_id,
+};
+
+static int __init adau1381_modinit(void)
+{
+	int ret;
+
+	ret = i2c_add_driver(&adau1381_i2c_driver);
+	if (ret != 0) {
+		printk(KERN_ERR "Failed to register adau1381 I2C driver: %d\n",
+		       ret);
+	}
+
+	return ret;
+}
+module_init(adau1381_modinit);
+
+static void __exit adau1381_exit(void)
+{
+	i2c_del_driver(&adau1381_i2c_driver);
+}
+module_exit(adau1381_exit);
+
+MODULE_DESCRIPTION("ASoC ADAU1381 driver");
+MODULE_AUTHOR("Cliff Cai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/adau1381.h b/sound/soc/codecs/adau1381.h
new file mode 100644
index 0000000..e11c5cd
--- /dev/null
+++ b/sound/soc/codecs/adau1381.h
@@ -0,0 +1,222 @@
+/*
+ * header file fortone adau1381 sound chip
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+
+#ifndef __ADAU1381_H__
+#define __ADAU1381_H__
+
+struct adau1381_setup_data {
+	unsigned short i2c_bus;
+	unsigned short i2c_address;
+};
+
+struct adau1381_mode_register {
+	u16  regaddress;
+	u16  regvalue;
+};
+
+#define RESET_REGISTER_COUNT 40
+#define MODE_REGISTER_COUNT 1
+
+#define MASTER_MODE 1
+#ifdef MASTER_MODE
+/* IIS mater mode*/
+#define ADAU_SRPT_CTRL0		0x01
+#else
+/* IIS slave mode*/
+#define ADAU_SRPT_CTRL0		0x00
+#endif
+
+/* adau1381_set_dai_sysclk clk_id */
+#define ADAU1381_MCLK_ID	0
+#define ADAU1381_BCLK_ID	0x33
+
+#define ADAU1381_MCLK_RATE	12288000
+
+#define ADAU1381_PLL_FREQ_441	45158400
+#define ADAU1381_PLL_FREQ_48	49152000
+
+
+/* ADAU1381 control registers */
+#define ADAU_FIRSTREG	0x4000
+
+#define ADAU_CLKCTRL	0x4000
+#define ADAU_RGUCTRL	0x4001
+#define ADAU_PLLCTRL	0x4002
+#define ADAU_RECCTRL	0x4008
+#define ADAU_RECPWRM	0x4009
+#define ADAU_RECGAIL	0x400E
+#define ADAU_RECGAIR	0x400F
+#define ADAU_RECMBIA	0x4010
+#define ADAU_SPRTCT0	0x4015
+#define ADAU_SPRTCT1	0x4016
+#define ADAU_CONVCT0	0x4017
+#define ADAU_CONVCT1	0x4018
+#define ADAU_ADCCTL0	0x4019
+#define ADAU_LADCATT	0x401A
+#define ADAU_RADCATT	0x401B
+#define ADAU_PLMLCTL	0x401C
+#define ADAU_PLMRCTL	0x401E
+#define ADAU_PLBMCTL	0x401F
+#define ADAU_PLBCAMP	0x4020
+#define ADAU_RLOMUTE	0x4025
+#define ADAU_LLOMUTE	0x4026
+#define ADAU_PLSPCTL	0x4027
+#define ADAU_ZXDETCT	0x4028
+#define ADAU_PLBPWRM	0x4029
+#define ADAU_DACCTRL	0x402A
+#define ADAU_LDACATT	0x402B
+#define ADAU_RDACATT	0x402C
+#define ADAU_SERPAD0	0x402D
+#define ADAU_SERPAD1	0x402E
+#define ADAU_COMPAD0	0x402F
+#define ADAU_COMPAD1	0x4030
+#define ADAU_MCKOCTL	0x4031
+#define ADAU_DIGPWR0	0x4080
+#define ADAU_DIGPWR1	0x4081
+#define ADAU_FRMRATE	0x40EB
+#define ADAU_INPRCON	0x40F2
+#define ADAU_OUPRCON	0x40F3
+#define ADAU_PINCONF	0x40F4
+#define ADAU_ENGIRUN	0x40F6
+#define ADAU_SAMRATE	0x40F8
+
+
+#define ADAU_LASTREG	0x40F8
+
+#define ADAU_NUMCACHEREG	40
+
+/* Register field definitions */
+/* Clock Control */
+#define CLKCTRL_SRC_MCLK	0x0
+#define CLKCTRL_SRC_PLL		0x8
+#define CLKCTRL_FRQ_256		0x0
+#define CLKCTRL_FRQ_512		0x2
+#define CLKCTRL_FRQ_768		0x4
+#define CLKCTRL_FRQ_1024	0x6
+#define CLKCTRL_DISABLE		0x0
+#define CLKCTRL_ENABLE		0x1
+
+/* PLL Control -- 6 bytes*/
+/*Bytes 5-6*/
+#define PLLCTRL_DEN_MSB		0x00
+#define PLLCTRL_DEN_LSB		0x00
+/*Bytes 3-4*/
+#define PLLCTRL_NUM_MSB		0x00
+#define PLLCTRL_NUM_LSB		0x00
+/*Byte 2*/
+#define PLLCTRL_INTPART_R2	0x10
+#define PLLCTRL_INTPART_R3	0x18
+#define PLLCTRL_INTPART_R4	0x20
+#define PLLCTRL_INTPART_R5	0x28
+#define PLLCTRL_INTPART_R6	0x30
+#define PLLCTRL_INTPART_R7	0x38
+#define PLLCTRL_INTPART_R8	0x40
+#define PLLCTRL_INPUT_DIV1	0x00
+#define PLLCTRL_INPUT_DIV2	0x02
+#define PLLCTRL_INPUT_DIV3	0x04
+#define PLLCTRL_INPUT_DIV4	0x06
+#define PLLCTRL_TYPE_INT	0x0
+#define PLLCTRL_TYPE_FRAC	0x1
+/*Byte 1*/
+#define PLLCTRL_DISABLE		0x0
+#define PLLCTRL_ENABLE		0x1
+
+/*ADC*/
+#define ADCCTL_DISABLE_MASK	0xFC
+#define ADCCTL_ENABLE_MASK	0x03
+
+/*MIC*/
+#define RECMBIA_DISABLE		0x00
+#define RECMBIA_ENABLE		0x01
+#define RECVLC_DISABLE_MASK	0xFC
+#define RECVLC_ENABLE_MASK	0x03
+
+#define RECMLC_MIC_0DB		0x08
+#define RECMLC_MIC_20DB		0x10
+#define RECMLC_LINE_0DB		0x05
+
+/* PWN MNGMNT */
+#define RECPWRM_LOW_PWR		0x0E
+#define PLBPWRM_LOW_PWR		0x5C
+#define PLBCTRL_POP_LPWR	0x10
+#define PLBCTRL_POP_OFF		0x06
+#define PLBCTRL_POP_ON		0x00
+#define RECPWRM_RUN_PWR		0x00
+#define PLBPWRM_RUN_PWR		0x03
+#define DAPM_LINE_DEF		0xE6
+#define DAPM_HP_DEF		0xE7
+#define PLB_MUTE_MASK		0x03
+
+#define ADAU1381_BITSFRAM_32	0x4000
+#define ADAU1381_BITSFRAM_48	0x8000
+
+/*playback output control*/
+#define ADAU1381_VOLUME_MASK 0xFC
+#define ADAU1381_VOLUME_BITS 0x2
+#define ADAU1381_MUTE_MASK 0x02
+#define ADAU1381_MUTE_BITS 0x1
+#define ADAU1381_ADVOL_MASK 0xff
+
+/*
+ * Reset Mode - ADC capture/DAC playback
+ * (AInput mixers 0db, AOuput mixers 0db, HP out ON)
+*/
+static struct adau1381_mode_register adau1381_reset[RESET_REGISTER_COUNT] = {
+	/* mute outputs */
+	{ADAU_RGUCTRL, 0x00},
+	{ADAU_RECCTRL, 0x00},
+	{ADAU_RECPWRM, 0x00},
+	{ADAU_RECGAIL, 0x07},
+	{ADAU_RECGAIR, 0x07},
+	{ADAU_RECMBIA, RECMBIA_DISABLE},
+	{ADAU_SPRTCT0, ADAU_SRPT_CTRL0},
+	{ADAU_SPRTCT1, 0x21}, /*0x21 = 32bclocks frame, 0x41 = 48*/
+	{ADAU_CONVCT0, 0x00},
+	{ADAU_CONVCT1, 0x00},
+	{ADAU_ADCCTL0, 0x00},
+	{ADAU_LADCATT, 0x00},
+	{ADAU_RADCATT, 0x00},
+	{ADAU_PLMLCTL, 0x20},
+	{ADAU_PLMRCTL, 0x20},
+	{ADAU_PLBMCTL, 0x00},
+	{ADAU_PLBCAMP, 0x00},
+	{ADAU_RLOMUTE, 0x02},
+	{ADAU_LLOMUTE, 0x02},
+	{ADAU_PLSPCTL, 0x01},
+	{ADAU_ZXDETCT, 0x01},
+	{ADAU_PLBPWRM, 0x00},
+	{ADAU_DACCTRL, 0x03},
+	{ADAU_LDACATT, 0x00},
+	{ADAU_RDACATT, 0x00},
+	{ADAU_SERPAD0, 0xAA},
+	{ADAU_SERPAD1, 0x00},
+	{ADAU_COMPAD0, 0xAA},
+	{ADAU_COMPAD1, 0x00},
+	{ADAU_MCKOCTL, 0x00},
+};
+
+static struct adau1381_mode_register adau1381_mode0[MODE_REGISTER_COUNT] = {
+	/*analog mic*/
+	{ADAU_RECCTRL, 0x00},
+};
+
+static struct adau1381_mode_register adau1381_mode1[MODE_REGISTER_COUNT] = {
+	/*digital mic*/
+	{ADAU_RECCTRL, 0x10},
+};
+
+static struct adau1381_mode_register *adau1381_mode_registers[] = {
+	adau1381_mode0,
+	adau1381_mode1,
+};
+
+extern struct snd_soc_dai adau1381_dai;
+extern struct snd_soc_codec_device soc_codec_dev_adau1381;
+
+#endif
-- 
1.7.2



More information about the Alsa-devel mailing list