From: Alexander Sverdlin <subaparts(a)yandex.ru>
Added support for Cirrus CS4271 codec to ALSA SoC subsystem.
Applies on: 2.6.36-rc6
Signed-off-by: Alexander Sverdlin <subaparts(a)yandex.ru>
---
sound/soc/codecs/Kconfig | 3 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/cs4271.c | 840 +++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/cs4271.h | 27 ++
4 files changed, 872 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 83f5c67..608cf88 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -138,6 +138,9 @@ config SND_SOC_CS4270_VD33_ERRATA
bool
depends on SND_SOC_CS4270
+config SND_SOC_CS4271
+ tristate
+
config SND_SOC_CX20442
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5352409..d7dd676 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -11,6 +11,7 @@ snd-soc-ak4671-objs := ak4671.o
snd-soc-cq93vc-objs := cq93vc.o
snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs4270-objs := cs4270.o
+snd-soc-cs4271-objs := cs4271.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
snd-soc-l3-objs := l3.o
@@ -79,6 +80,7 @@ obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c
new file mode 100644
index 0000000..305281a
--- /dev/null
+++ b/sound/soc/codecs/cs4271.c
@@ -0,0 +1,840 @@
+/*
+ * CS4271 ALSA SoC (ASoC) codec driver
+ *
+ * Copyright (c) 2010 Alexander Sverdlin <subaparts(a)yandex.ru>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * This is an ASoC device driver for the Cirrus Logic CS4271 codec.
+ *
+ * Current features/limitations:
+ *
+ * - I2C and SPI are supported
+ * - Software mode is supported. Stand-alone mode is not supported
+ * - Support for master and slave mode
+ * - The machine driver's 'startup' function must call
+ * cs4271_set_dai_sysclk() with the value of MCLK
+ * - Only I2S and left-justified modes are supported
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+
+#include "cs4271.h"
+
+#define CS4271_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+/*
+ * CS4271 registers addresses
+ * High byte represents SPI address (0x10) + write command (0)
+ */
+#define CS4271_MODE1 0x2001 /* Mode Control 1 */
+#define CS4271_DACCTL 0x2002 /* DAC Control */
+#define CS4271_DACVOL 0x2003 /* DAC Volume & Mixing Control */
+#define CS4271_VOLA 0x2004 /* DAC Channel A Volume Control */
+#define CS4271_VOLB 0x2005 /* DAC Channel B Volume Control */
+#define CS4271_ADCCTL 0x2006 /* ADC Control */
+#define CS4271_MODE2 0x2007 /* Mode Control 2 */
+#define CS4271_CHIPID 0x2008 /* Chip ID */
+
+#define CS4271_FIRSTREG CS4271_MODE1
+#define CS4271_LASTREG CS4271_MODE2
+#define CS4271_NR_REGS ((CS4271_LASTREG & 0xFF) + 1)
+
+/* Bit masks for the CS4271 registers */
+#define CS4271_MODE1_FUNCMODE_MASK 0xC0
+#define CS4271_MODE1_FUNCMODE_1X 0x00
+#define CS4271_MODE1_FUNCMODE_2X 0x80
+#define CS4271_MODE1_FUNCMODE_4X 0xC0
+
+#define CS4271_MODE1_DIV_MASK 0x30
+#define CS4271_MODE1_DIV_1 0x00
+#define CS4271_MODE1_DIV_15 0x10
+#define CS4271_MODE1_DIV_2 0x20
+#define CS4271_MODE1_DIV_3 0x30
+
+#define CS4271_MODE1_MASTER 0x08
+
+#define CS4271_MODE1_DAC_DIF_MASK 0x07
+#define CS4271_MODE1_DAC_DIF_LJ 0x00
+#define CS4271_MODE1_DAC_DIF_I2S 0x01
+#define CS4271_MODE1_DAC_DIF_RJ16 0x02
+#define CS4271_MODE1_DAC_DIF_RJ24 0x03
+#define CS4271_MODE1_DAC_DIF_RJ20 0x04
+#define CS4271_MODE1_DAC_DIF_RJ18 0x05
+
+#define CS4271_DACCTL_AMUTE 0x80
+#define CS4271_DACCTL_IF_SLOW 0x40
+
+#define CS4271_DACCTL_DEM_MASK 0x30
+#define CS4271_DACCTL_DEM_DIS 0x00
+#define CS4271_DACCTL_DEM_441 0x10
+#define CS4271_DACCTL_DEM_48 0x20
+#define CS4271_DACCTL_DEM_32 0x30
+
+#define CS4271_DACCTL_SVRU 0x08
+#define CS4271_DACCTL_SRD 0x04
+#define CS4271_DACCTL_INVA 0x02
+#define CS4271_DACCTL_INVB 0x01
+
+#define CS4271_DACVOL_BEQUA 0x40
+#define CS4271_DACVOL_SOFT 0x20
+#define CS4271_DACVOL_ZEROC 0x10
+
+#define CS4271_DACVOL_ATAPI_MASK 0x0F
+#define CS4271_DACVOL_ATAPI_M_M 0x00
+#define CS4271_DACVOL_ATAPI_M_BR 0x01
+#define CS4271_DACVOL_ATAPI_M_BL 0x02
+#define CS4271_DACVOL_ATAPI_M_BLR2 0x03
+#define CS4271_DACVOL_ATAPI_AR_M 0x04
+#define CS4271_DACVOL_ATAPI_AR_BR 0x05
+#define CS4271_DACVOL_ATAPI_AR_BL 0x06
+#define CS4271_DACVOL_ATAPI_AR_BLR2 0x07
+#define CS4271_DACVOL_ATAPI_AL_M 0x08
+#define CS4271_DACVOL_ATAPI_AL_BR 0x09
+#define CS4271_DACVOL_ATAPI_AL_BL 0x0A
+#define CS4271_DACVOL_ATAPI_AL_BLR2 0x0B
+#define CS4271_DACVOL_ATAPI_ALR2_M 0x0C
+#define CS4271_DACVOL_ATAPI_ALR2_BR 0x0D
+#define CS4271_DACVOL_ATAPI_ALR2_BL 0x0E
+#define CS4271_DACVOL_ATAPI_ALR2_BLR2 0x0F
+
+#define CS4271_VOLA_MUTE 0x80
+#define CS4271_VOLA_VOL_MASK 0x7F
+#define CS4271_VOLB_MUTE 0x80
+#define CS4271_VOLB_VOL_MASK 0x7F
+
+#define CS4271_ADCCTL_DITHER16 0x20
+
+#define CS4271_ADCCTL_ADC_DIF_MASK 0x10
+#define CS4271_ADCCTL_ADC_DIF_LJ 0x00
+#define CS4271_ADCCTL_ADC_DIF_I2S 0x10
+
+#define CS4271_ADCCTL_MUTEA 0x08
+#define CS4271_ADCCTL_MUTEB 0x04
+#define CS4271_ADCCTL_HPFDA 0x02
+#define CS4271_ADCCTL_HPFDB 0x01
+
+#define CS4271_MODE2_LOOP 0x10
+#define CS4271_MODE2_MUTECAEQUB 0x08
+#define CS4271_MODE2_FREEZE 0x04
+#define CS4271_MODE2_CPEN 0x02
+#define CS4271_MODE2_PDN 0x01
+
+#define CS4271_CHIPID_PART_MASK 0xF0
+#define CS4271_CHIPID_REV_MASK 0x0F
+
+/* Private data for the CS4271 */
+struct cs4271_private {
+ struct snd_soc_codec codec;
+ u8 reg_cache[CS4271_NR_REGS];
+ unsigned int mclk; /* Input frequency of the MCLK pin */
+ unsigned int mode; /* The mode (I2S or left-justified) */
+ unsigned int slave_mode;
+};
+
+/*
+ * struct cs4271_mode_ratios - clock ratio tables
+ * @ratio: the ratio of MCLK to the sample rate
+ * @speed_mode: the Speed Mode bits to set in the Mode Control register for
+ * this ratio
+ * @mclk_master: the Ratio Select bits to set in the Mode Control register
+ * for this ratio while in Master mode
+ * @mclk_slave: the Ratio Select bits to set in the Mode Control register
+ * for this ratio while in Slave mode
+ *
+ * This table is used to determine how to program the Mode Control register
+ * It is also used by cs4271_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS4271 currently supports.
+ */
+struct cs4271_mode_ratios {
+ unsigned int ratio;
+ u8 speed_mode;
+ u8 mclk_master;
+ u8 mclk_slave;
+};
+
+static struct cs4271_mode_ratios cs4271_mode_ratios[] = {
+ {64, CS4271_MODE1_FUNCMODE_4X, CS4271_MODE1_DIV_1, CS4271_MODE1_DIV_1},
+ {96, CS4271_MODE1_FUNCMODE_4X, CS4271_MODE1_DIV_15, CS4271_MODE1_DIV_1},
+ {128, CS4271_MODE1_FUNCMODE_2X, CS4271_MODE1_DIV_1, CS4271_MODE1_DIV_1},
+ {192, CS4271_MODE1_FUNCMODE_2X, CS4271_MODE1_DIV_15, CS4271_MODE1_DIV_1},
+ {256, CS4271_MODE1_FUNCMODE_1X, CS4271_MODE1_DIV_1, CS4271_MODE1_DIV_1},
+ {384, CS4271_MODE1_FUNCMODE_1X, CS4271_MODE1_DIV_15, CS4271_MODE1_DIV_1},
+ {512, CS4271_MODE1_FUNCMODE_1X, CS4271_MODE1_DIV_2, CS4271_MODE1_DIV_1},
+ {768, CS4271_MODE1_FUNCMODE_1X, CS4271_MODE1_DIV_3, CS4271_MODE1_DIV_3},
+ {1024, CS4271_MODE1_FUNCMODE_1X, CS4271_MODE1_DIV_3, CS4271_MODE1_DIV_3}
+};
+
+/* The number of MCLK/LRCK ratios supported by the CS4271 */
+#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4271_mode_ratios)
+
+/*
+ * cs4271_set_dai_sysclk - determine the CS4271 MCLK rate.
+ * @codec_dai: the codec DAI
+ * @clk_id: the clock ID (ignored)
+ * @freq: the MCLK input frequency
+ * @dir: the clock direction (ignored)
+ *
+ * This function is used to tell the codec driver what the input MCLK
+ * frequency is.
+ *
+ * The value of MCLK is used to determine which sample rates are supported
+ * by the CS4271. The ratio of MCLK / Fs must be equal to one of
+ * supported values - 64, 96, 128, 192, 256, 384, 512, 768 and
+ * for Slave mode 1024. 256 is the best value and widely recommended.
+ *
+ * This function must be called by the machine driver's 'startup' function,
+ * otherwise the list of supported sample rates will not be available in
+ * time for ALSA.
+ *
+ * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause
+ * theoretically possible sample rates to be enabled. Call it again with a
+ * proper value set one the external clock is set (most probably you would do
+ * that from a machine's driver 'hw_param' hook.
+ */
+static int cs4271_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ unsigned int rates = 0;
+ unsigned int rate_min = -1;
+ unsigned int rate_max = 0;
+ unsigned int i;
+
+ cs4271->mclk = freq;
+
+ if (cs4271->mclk) {
+ for (i = 0; i < NUM_MCLK_RATIOS; i++) {
+ unsigned int rate = freq / cs4271_mode_ratios[i].ratio;
+ rates |= snd_pcm_rate_to_rate_bit(rate);
+ if (rate < rate_min)
+ rate_min = rate;
+ if (rate > rate_max)
+ rate_max = rate;
+ }
+
+ rates &= ~SNDRV_PCM_RATE_KNOT;
+
+ if (!rates) {
+ dev_err(codec->dev, "could not find a valid sample rate\n");
+ return -EINVAL;
+ }
+ } else {
+ /* enable all possible rates */
+ rates = SNDRV_PCM_RATE_8000_96000;
+ rate_min = 8000;
+ rate_max = 96000;
+ }
+
+ codec_dai->playback.rates = rates;
+ codec_dai->playback.rate_min = rate_min;
+ codec_dai->playback.rate_max = rate_max;
+
+ codec_dai->capture.rates = rates;
+ codec_dai->capture.rate_min = rate_min;
+ codec_dai->capture.rate_max = rate_max;
+
+ return 0;
+}
+
+/*
+ * cs4271_set_dai_fmt - configure the codec for the selected audio format
+ * @codec_dai: the codec DAI
+ * @format: a SND_SOC_DAIFMT_x value indicating the data format
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J.
+ */
+static int cs4271_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ /* set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ cs4271->mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ dev_err(codec->dev, "invalid dai format\n");
+ ret = -EINVAL;
+ }
+
+ /* set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs4271->slave_mode = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs4271->slave_mode = 0;
+ break;
+ default:
+ /* all other modes are unsupported by the hardware */
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/*
+ * cs4271_hw_params - program the CS4271 with the given hardware parameters.
+ * @substream: the audio stream
+ * @params: the hardware parameters to set
+ * @dai: the SOC DAI (ignored)
+ */
+static int cs4271_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 cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+ unsigned int i, ratio;
+
+ /* Figure out which MCLK/LRCK ratio to use */
+ ratio = cs4271->mclk / params_rate(params);
+
+ for (i = 0; i < NUM_MCLK_RATIOS; i++)
+ if (cs4271_mode_ratios[i].ratio == ratio)
+ break;
+
+ if ((i == NUM_MCLK_RATIOS) || ((ratio == 1024) && (!cs4271->slave_mode))) {
+ dev_err(codec->dev, "could not find matching ratio\n");
+ return -EINVAL;
+ }
+
+ /* Set the sample rate */
+ ret = snd_soc_read(codec, CS4271_MODE1);
+ ret &= ~(CS4271_MODE1_FUNCMODE_MASK | CS4271_MODE1_DIV_MASK | CS4271_MODE1_DAC_DIF_MASK);
+ ret |= cs4271_mode_ratios[i].speed_mode;
+
+ if (cs4271->slave_mode) {
+ ret &= ~CS4271_MODE1_MASTER;
+ ret |= cs4271_mode_ratios[i].mclk_slave;
+ }
+ else {
+ ret |= CS4271_MODE1_MASTER;
+ ret |= cs4271_mode_ratios[i].mclk_master;
+ }
+
+ switch (cs4271->mode) {
+ case SND_SOC_DAIFMT_I2S:
+ ret |= CS4271_MODE1_DAC_DIF_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ret |= CS4271_MODE1_DAC_DIF_LJ;
+ break;
+ default:
+ dev_err(codec->dev, "unknown dai format\n");
+ return -EINVAL;
+ }
+
+ ret = snd_soc_write(codec, CS4271_MODE1, ret);
+ if (ret < 0) {
+ dev_err(codec->dev, "Codec write failed\n");
+ return ret;
+ }
+
+ /* Set the DAI format */
+ ret = snd_soc_read(codec, CS4271_ADCCTL);
+ ret &= ~CS4271_ADCCTL_ADC_DIF_MASK;
+ ret |= (cs4271->mode == SND_SOC_DAIFMT_I2S) ? CS4271_ADCCTL_ADC_DIF_I2S : CS4271_ADCCTL_ADC_DIF_LJ;
+ ret = snd_soc_write(codec, CS4271_ADCCTL, ret);
+ if (ret < 0)
+ dev_err(codec->dev, "Codec write failed\n");
+
+ return ret;
+}
+
+/*
+ * cs4271_dai_mute - enable/disable the CS4271 external mute
+ * @dai: the SOC DAI
+ * @mute: 0 = disable mute, 1 = enable mute
+ */
+static int cs4271_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int ret1, ret2;
+
+ ret1 = snd_soc_read(codec, CS4271_VOLA);
+ ret2 = snd_soc_read(codec, CS4271_VOLB);
+
+ if (mute) {
+ ret1 |= CS4271_VOLA_MUTE;
+ ret2 |= CS4271_VOLB_MUTE;
+ }
+ else {
+ ret1 &= ~(CS4271_VOLA_MUTE);
+ ret2 &= ~(CS4271_VOLB_MUTE);
+ }
+
+ ret1 = snd_soc_write(codec, CS4271_VOLA, ret1);
+ if (ret1)
+ return ret1;
+ return snd_soc_write(codec, CS4271_VOLB, ret2);
+}
+
+/*
+ * A list of non-DAPM controls that the CS4271 supports
+ */
+static const char *cs4271_de_texts[] = {"None", "44.1KHz", "48KHz", "32KHz"};
+static const struct soc_enum cs4271_de_enum =
+ SOC_ENUM_SINGLE(CS4271_DACCTL, 4, 4, cs4271_de_texts);
+
+static const struct snd_kcontrol_new cs4271_snd_controls[] = {
+ SOC_DOUBLE_R("Master Playback Volume", CS4271_VOLA, CS4271_VOLB, 0, 0x7F, 1),
+ SOC_SINGLE("Digital Loopback Switch", CS4271_MODE2, 4, 1, 0),
+ SOC_SINGLE("Soft Ramp Switch", CS4271_DACVOL, 5, 1, 0),
+ SOC_SINGLE("Zero Cross Switch", CS4271_DACVOL, 4, 1, 0),
+ SOC_ENUM("De-emphasis filter", cs4271_de_enum),
+ SOC_SINGLE("Auto-Mute Switch", CS4271_DACCTL, 7, 1, 0),
+ SOC_SINGLE("Slow Roll Off Filter Switch", CS4271_DACCTL, 6, 1, 0),
+ SOC_SINGLE("Soft Volume Ramp-Up Switch", CS4271_DACCTL, 3, 1, 0),
+ SOC_SINGLE("Soft Ramp-Down Switch", CS4271_DACCTL, 2, 1, 0),
+ SOC_SINGLE("Left Channel Inversion Switch", CS4271_DACCTL, 1, 1, 0),
+ SOC_SINGLE("Right Channel Inversion Switch", CS4271_DACCTL, 0, 1, 0),
+ SOC_DOUBLE("Master Capture Switch", CS4271_ADCCTL, 3, 2, 1, 1),
+ SOC_SINGLE("Dither 16-Bit Data Switch", CS4271_ADCCTL, 5, 1, 0),
+ SOC_DOUBLE("High Pass Filter Switch", CS4271_ADCCTL, 1, 0, 1, 1),
+ SOC_DOUBLE_R("Master Playback Switch", CS4271_VOLA, CS4271_VOLB, 7, 1, 1),
+};
+
+/*
+ * cs4271_codec - global variable to store codec for the ASoC probe function
+ *
+ * For now, we also only allow cs4271_i2c_probe() to be run once. That means
+ * that we do not support more than one cs4271 device in the system, at least
+ * for now.
+ */
+static struct snd_soc_codec *cs4271_codec;
+
+static struct snd_soc_dai_ops cs4271_dai_ops = {
+ .hw_params = cs4271_hw_params,
+ .set_sysclk = cs4271_set_dai_sysclk,
+ .set_fmt = cs4271_set_dai_fmt,
+ .digital_mute = cs4271_dai_mute,
+};
+
+struct snd_soc_dai cs4271_dai = {
+ .name = "cs4271",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4271_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4271_FORMATS,
+ },
+ .ops = &cs4271_dai_ops,
+};
+EXPORT_SYMBOL_GPL(cs4271_dai);
+
+/*
+ * This function writes all writeble registers from cache to codec.
+ * It's used te setup initial config and restore after suspend.
+ */
+static int cs4271_write_cache(struct snd_soc_codec *codec)
+{
+ int i, ret;
+
+ for (i = CS4271_FIRSTREG; i <= CS4271_LASTREG; i++) {
+ ret = snd_soc_write(codec, i, snd_soc_read(codec, i));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * cs4271_probe - ASoC probe function
+ * @pdev: platform device
+ *
+ * This function is called when ASoC has all the pieces it needs to
+ * instantiate a sound driver.
+ * This function actually configure the codec, so MCLK must be enabled
+ * already and reset must be inactive. This is probably done by
+ * machine drivers.
+ */
+static int cs4271_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = cs4271_codec;
+ int ret;
+ u8 *cache;
+
+ /* Turn control port on and set power down mode for a while */
+ ret = snd_soc_write(codec, CS4271_MODE2, CS4271_MODE2_CPEN | CS4271_MODE2_PDN);
+ if (ret < 0) {
+ dev_err(codec->dev, "Codec write failed\n");
+ return ret;
+ }
+
+ cache = codec->reg_cache;
+ /*
+ * Almost default power up configuration of codec, except auto
+ * mute feature which is turned off.
+ * We need to mask register address, because it contains
+ * SPI address also.
+ */
+ cache[CS4271_MODE1 & 0xFF] = 0x00;
+ cache[CS4271_DACCTL & 0xFF] = 0x00;
+ cache[CS4271_DACVOL & 0xFF] = CS4271_DACVOL_ATAPI_AL_BR;
+ cache[CS4271_VOLA & 0xFF] = 0x00;
+ cache[CS4271_VOLB & 0xFF] = 0x00;
+ cache[CS4271_ADCCTL & 0xFF] = 0x00;
+ cache[CS4271_MODE2 & 0xFF] = CS4271_MODE2_CPEN;
+
+ ret = cs4271_write_cache(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Cache write failed\n");
+ return ret;
+ }
+
+ /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */
+ socdev->card->codec = codec;
+
+ /* 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\n");
+ return ret;
+ }
+
+ /* Add the non-DAPM controls */
+ ret = snd_soc_add_controls(codec, cs4271_snd_controls,
+ ARRAY_SIZE(cs4271_snd_controls));
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to add controls\n");
+ goto error_free_pcms;
+ }
+
+ return 0;
+
+error_free_pcms:
+ snd_soc_free_pcms(socdev);
+
+ return ret;
+}
+
+/*
+ * cs4271_remove - ASoC remove function
+ * @pdev: platform device
+ *
+ * This function is the counterpart to cs4271_probe().
+ */
+static int cs4271_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+
+ return 0;
+};
+
+#ifdef CONFIG_PM
+
+/*
+ * The codec's own power saving features are enabled in the suspend callback,
+ * and all registers are written back to the hardware when resuming.
+ */
+static int cs4271_soc_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ struct snd_soc_codec *codec = cs4271_codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = snd_soc_read(codec, CS4271_MODE2);
+ ret |= CS4271_MODE2_PDN;
+
+ return snd_soc_write(codec, CS4271_MODE2, ret);
+}
+
+static int cs4271_soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_codec *codec = cs4271_codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = cs4271_write_cache(codec);
+ if (ret < 0) {
+ dev_err(pdev, "Cache write failed\n");
+ return ret;
+ }
+
+ /* ... then disable the power-down bits */
+ ret = snd_soc_read(codec, CS4271_MODE2);
+ ret &= ~CS4271_MODE2_PDN;
+
+ return snd_soc_write(codec, CS4271_MODE2, ret);
+}
+#else
+#define cs4271_soc_suspend NULL
+#define cs4271_soc_resume NULL
+#endif /* CONFIG_PM */
+
+/*
+ * ASoC codec device structure
+ *
+ * Assign this variable to the codec_dev field of the machine driver's
+ * snd_soc_device structure.
+ */
+struct snd_soc_codec_device soc_codec_device_cs4271 = {
+ .probe = cs4271_probe,
+ .remove = cs4271_remove,
+ .suspend = cs4271_soc_suspend,
+ .resume = cs4271_soc_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_cs4271);
+
+/*
+ * Serial bus independent probe function. It's called both for
+ * I2C and SPI -connected codecs.
+ */
+static int cs4271_bus_probe(struct device *dev, void *ctrl_data, int bus_type)
+{
+ struct cs4271_private *cs4271;
+ struct snd_soc_codec *codec;
+ int ret;
+
+ /*
+ * For now, we only support one cs4271 device in the system. See the
+ * comment for cs4271_codec.
+ */
+ if (cs4271_codec) {
+ dev_err(dev, "Only one CS4271 per board allowed\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Allocate enough space for the snd_soc_codec structure
+ * and our private data together.
+ */
+ cs4271 = kzalloc(sizeof(struct cs4271_private), GFP_KERNEL);
+ if (!cs4271) {
+ dev_err(dev, "Could not allocate codec\n");
+ return -ENOMEM;
+ }
+
+ dev_set_drvdata(dev, cs4271);
+
+ codec = &cs4271->codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->dev = dev;
+ codec->name = "CS4271";
+ codec->owner = THIS_MODULE;
+ codec->dai = &cs4271_dai;
+ codec->num_dai = 1;
+ snd_soc_codec_set_drvdata(codec, cs4271);
+ codec->control_data = ctrl_data;
+ codec->reg_cache = cs4271->reg_cache;
+ codec->reg_cache_size = ARRAY_SIZE(cs4271->reg_cache);
+
+ /*
+ * In case of I2C, chip address specified in board data.
+ * So cache IO operations use 8 bit codec register address.
+ * In case of SPI, chip address and register address
+ * passed together as 16 bit value.
+ * Anyway, register address is masked with 0xFF inside
+ * soc_cache code.
+ */
+ ret = snd_soc_codec_set_cache_io(codec,
+ (bus_type == SND_SOC_SPI) ? 16 : 8, 8, bus_type);
+ if (ret) {
+ dev_err(dev, "Failed to set cache I/O: %d\n", ret);
+ goto error_free_codec;
+ }
+
+ /*
+ * Initialize the DAI. Normally, we'd prefer to have a kmalloc'd DAI
+ * structure for each CS4271 device, but the machine driver needs to
+ * have a pointer to the DAI structure, so for now it must be a global
+ * variable.
+ */
+ cs4271_dai.dev = dev;
+
+ /*
+ * Register the DAI. If all the other ASoC driver have already
+ * registered, then this will call our probe function, so
+ * cs4271_codec needs to be ready.
+ */
+ cs4271_codec = codec;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(dev, "Failed to register codec: %d\n", ret);
+ goto error_free_codec;
+ }
+
+ ret = snd_soc_register_dai(&cs4271_dai);
+ if (ret) {
+ dev_err(dev, "failed to register DAI\n");
+ goto error_reg;
+ }
+
+ return 0;
+
+error_reg:
+ snd_soc_unregister_codec(codec);
+error_free_codec:
+ kfree(cs4271);
+ cs4271_codec = NULL;
+ cs4271_dai.dev = NULL;
+
+ return ret;
+}
+
+static int cs4271_bus_remove(struct device *dev)
+{
+ struct cs4271_private *cs4271 = dev_get_drvdata(dev);
+
+ snd_soc_unregister_dai(&cs4271_dai);
+ kfree(cs4271);
+ cs4271_codec = NULL;
+ cs4271_dai.dev = NULL;
+
+ return 0;
+}
+
+
+#if defined(CONFIG_SPI_MASTER)
+
+static struct spi_device_id cs4271_spi_id[] = {
+ {"cs4271", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, cs4271_spi_id);
+
+static int __devinit cs4271_spi_probe(struct spi_device *spi)
+{
+ return cs4271_bus_probe(&spi->dev, spi, SND_SOC_SPI);
+}
+
+static int __devexit cs4271_spi_remove(struct spi_device *spi)
+{
+ return cs4271_bus_remove(&spi->dev);
+}
+
+static struct spi_driver cs4271_spi_driver = {
+ .driver = {
+ .name = "cs4271",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4271_spi_id,
+ .probe = cs4271_spi_probe,
+ .remove = __devexit_p(cs4271_spi_remove),
+};
+
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+/*
+ * cs4271_id - I2C device IDs supported by this driver
+ */
+static struct i2c_device_id cs4271_i2c_id[] = {
+ {"cs4271", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4271_i2c_id);
+
+static int __devinit cs4271_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ return cs4271_bus_probe(&client->dev, client, SND_SOC_I2C);
+}
+
+static int __devexit cs4271_i2c_remove(struct i2c_client *client)
+{
+ return cs4271_bus_remove(&client->dev);
+}
+
+/*
+ * cs4271_i2c_driver - I2C device identification
+ *
+ * This structure tells the I2C subsystem how to identify and support a
+ * given I2C device type.
+ */
+static struct i2c_driver cs4271_i2c_driver = {
+ .driver = {
+ .name = "cs4271",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4271_i2c_id,
+ .probe = cs4271_i2c_probe,
+ .remove = __devexit_p(cs4271_i2c_remove),
+};
+
+#endif
+
+/*
+ * We only register our serial bus driver here without
+ * assignment to particular chip. So if any of the below
+ * fails, there is some problem with I2C or SPI subsystem.
+ * In most cases this module will be compiled with support
+ * of only one serial bus.
+ */
+static int __init cs4271_modinit(void)
+{
+ int ret;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&cs4271_i2c_driver);
+ if (ret) {
+ pr_err("Failed to register CS4271 I2C driver: %d\n", ret);
+ return ret;
+ }
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&cs4271_spi_driver);
+ if (ret) {
+ pr_err("Failed to register CS4271 SPI driver: %d\n", ret);
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+module_init(cs4271_modinit);
+
+static void __exit cs4271_modexit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&cs4271_spi_driver);
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&cs4271_i2c_driver);
+#endif
+}
+module_exit(cs4271_modexit);
+
+MODULE_AUTHOR("Alexander Sverdlin <subaparts(a)yandex.ru>");
+MODULE_DESCRIPTION("Cirrus Logic CS4271 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs4271.h b/sound/soc/codecs/cs4271.h
new file mode 100644
index 0000000..7cf6af2
--- /dev/null
+++ b/sound/soc/codecs/cs4271.h
@@ -0,0 +1,27 @@
+/*
+ * Cirrus Logic CS4271 ALSA SoC Codec Driver
+ *
+ * Copyright (c) 2010 Alexander Sverdlin <subaparts(a)yandex.ru>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _CS4271_H
+#define _CS4271_H
+
+/*
+ * The ASoC codec DAI structure for the CS4271. Assign this structure to
+ * the .codec_dai field of your machine driver's snd_soc_dai_link structure.
+ */
+extern struct snd_soc_dai cs4271_dai;
+
+/*
+ * The ASoC codec device structure for the CS4271. Assign this structure
+ * to the .codec_dev field of your machine driver's snd_soc_device
+ * structure.
+ */
+extern struct snd_soc_codec_device soc_codec_device_cs4271;
+
+#endif