[alsa-devel] [PATCH v2] ASoC CS4270 codec device driver

Timur Tabi timur at freescale.com
Mon Jul 30 22:22:10 CEST 2007


This patch adds ALSA SoC support for the Cirrus Logic CS4270 codec.  The
following features are suppored:

1) Stand-alone and software mode
2) Software mode via I2C only
3) Master mode, not Slave
4) No power management

Signed-off-by: Timur Tabi <timur at freescale.com>
---

This code is based on the Liam Girdwood's ASoC tree.

Please note that I was unable to clone Liam's ASoC git repositories because
the http interface does not work for me, and my corporate firewall blocks the
git protocol.  I had to download a snapshot and create a bare git repository
from it.  Therefore, git may complain when trying to apply this patch.

 include/linux/i2c-id.h    |    1 +
 sound/soc/codecs/Kconfig  |   20 ++
 sound/soc/codecs/Makefile |    2 +
 sound/soc/codecs/cs4270.c |  812 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/cs4270.h |   28 ++
 5 files changed, 863 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/codecs/cs4270.c
 create mode 100644 sound/soc/codecs/cs4270.h

diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h
index 0051203..6b3d207 100644
--- a/include/linux/i2c-id.h
+++ b/include/linux/i2c-id.h
@@ -119,6 +119,7 @@
 #define I2C_DRIVERID_WM8750		90	/* Wolfson WM8750 audio codec */
 #define I2C_DRIVERID_WM8753		91	/* Wolfson WM8753 audio codec */
 #define I2C_DRIVERID_WM8951             92      /* Wolfson WM8753 audio codec */
+#define I2C_DRIVERID_CS4270	93	/* Cirrus Logic 4270 audio codec */

 #define I2C_DRIVERID_I2CDEV	900
 #define I2C_DRIVERID_ARP        902    /* SMBus ARP Client              */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index e71667f..9c84eb2 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -78,4 +78,24 @@ config SND_SOC_WM9713
 	tristate
 	depends on SND_SOC

+# Cirrus Logic CS4270 Codec
+config SND_SOC_CS4270
+	tristate
+	depends on SND_SOC
+
+# Cirrus Logic CS4270 Codec Hardware Mute Support
+# Select if you have external muting circuitry attached to your CS4270.
+config SND_SOC_CS4270_HWMUTE
+	bool
+	depends on SND_SOC_CS4270
+
+# Cirrus Logic CS4270 Codec VD = 3.3V Errata
+# Select if you are affected by the errata where the part will not function
+# if MCLK divide-by-1.5 is selected and VD is set to 3.3V.  The driver will
+# not select any sample rates that require MCLK to be divided by 1.5.
+config SND_SOC_CS4270_VD33_ERRATA
+	bool
+	depends on SND_SOC_CS4270
+
+

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 07d0b59..d9237a1 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -18,6 +18,7 @@ snd-soc-wm8976-objs := wm8976.o
 snd-soc-wm8980-objs := wm8980.o
 snd-soc-wm9712-objs := wm9712.o
 snd-soc-wm9713-objs := wm9713.o
+snd-soc-cs4270-objs := cs4270.o

 obj-$(CONFIG_SND_SOC_AC97_CODEC)	+= snd-soc-ac97.o
 obj-$(CONFIG_SND_SOC_AK4535)	+= snd-soc-ak4535.o
@@ -39,3 +40,4 @@ obj-$(CONFIG_SND_SOC_WM8976)	+= snd-soc-wm8976.o
 obj-$(CONFIG_SND_SOC_WM8980)	+= snd-soc-wm8980.o
 obj-$(CONFIG_SND_SOC_WM9712)	+= snd-soc-wm9712.o
 obj-$(CONFIG_SND_SOC_WM9713)	+= snd-soc-wm9713.o
+obj-$(CONFIG_SND_SOC_CS4270)	+= snd-soc-cs4270.o
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
new file mode 100644
index 0000000..e1cf104
--- /dev/null
+++ b/sound/soc/codecs/cs4270.c
@@ -0,0 +1,812 @@
+/**
+ * CS4270 ALSA SoC (ASoC) codec driver
+ *
+ * Author: Timur Tabi <timur at freescale.com>
+ *
+ * Copyright 2007 Freescale Semiconductor, Inc.  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 CS4270 codec.
+ *
+ * Current features/limitations:
+ *
+ * 1) Stand-alone and software mode is supported.  Stand-alone is
+ *    automatically selected if I2C is disabled or if a CS4270 is not found
+ *    on the I2C bus.
+ * 2) Only I2C is supported, not SPI
+ * 3) Only Master mode is supported, not Slave.
+ * 4) The machine driver's 'startup' function must call
+ *    cs4270_set_dai_sysclk() with the value of MCLK.
+ * 5) Only I2S and left-justified modes are supported
+ * 6) Power management is not supported
+ * 7) The only supported control is volume and hardware mute (if enabled)
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/i2c.h>
+
+#include "cs4270.h"
+
+/* Private data for the CS4270 */
+struct cs4270_private {
+	unsigned int mclk; /* Input frequency of the MCLK pin */
+	unsigned int mode; /* The mode (I2S or left-justified) */
+};
+
+/**
+ * The codec isn't really big-endian or little-endian, since the I2S
+ * interface requires data to be sent serially with the MSbit first.
+ * However, to support BE and LE I2S devices, we specify both here.  That
+ * way, ALSA will always match the bit patterns.  Also, it appears that the
+ * CS4270 doesn't like S8.
+ *
+ * FIXME: This list should contain only signed values, not unsigned. The I2S
+ * protocol requires signed samples.
+ */
+#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8      | \
+			SNDRV_PCM_FMTBIT_S16_LE  | SNDRV_PCM_FMTBIT_S16_BE  | \
+			SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \
+			SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \
+			SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \
+			SNDRV_PCM_FMTBIT_S24_LE  | SNDRV_PCM_FMTBIT_S24_BE)
+
+#ifdef CONFIG_I2C
+
+/* CS4270 registers addresses */
+#define CS4270_CHIPID	0x01	/* Chip ID */
+#define CS4270_PWRCTL	0x02	/* Power Control */
+#define CS4270_MODE	0x03	/* Mode Control */
+#define CS4270_FORMAT	0x04	/* Serial Format, ADC/DAC Control */
+#define CS4270_TRANS	0x05	/* Transition Control */
+#define CS4270_MUTE	0x06	/* Mute Control */
+#define CS4270_VOLA	0x07	/* DAC Channel A Volume Control */
+#define CS4270_VOLB	0x08	/* DAC Channel B Volume Control */
+
+#define CS4270_FIRSTREG	0x01
+#define CS4270_LASTREG	0x08
+#define CS4270_NUMREGS	(CS4270_LASTREG - CS4270_FIRSTREG + 1)
+
+/* Bit masks for the CS4270 registers */
+#define CS4270_CHIPID_ID	0xF0
+#define CS4270_CHIPID_REV	0x0F
+#define CS4270_PWRCTL_FREEZE	0x80
+#define CS4270_PWRCTL_PDN_ADC	0x20
+#define CS4270_PWRCTL_PDN_DAC	0x02
+#define CS4270_PWRCTL_PDN	0x01
+#define CS4270_MODE_SPEED_MASK	0x30
+#define CS4270_MODE_1X		0x00
+#define CS4270_MODE_2X		0x10
+#define CS4270_MODE_4X		0x20
+#define CS4270_MODE_SLAVE	0x30
+#define CS4270_MODE_DIV_MASK	0x0E
+#define CS4270_MODE_DIV1	0x00
+#define CS4270_MODE_DIV15	0x02
+#define CS4270_MODE_DIV2	0x04
+#define CS4270_MODE_DIV3	0x06
+#define CS4270_MODE_DIV4	0x08
+#define CS4270_MODE_POPGUARD	0x01
+#define CS4270_FORMAT_FREEZE_A	0x80
+#define CS4270_FORMAT_FREEZE_B	0x40
+#define CS4270_FORMAT_LOOPBACK	0x20
+#define CS4270_FORMAT_DAC_MASK	0x18
+#define CS4270_FORMAT_DAC_LJ	0x00
+#define CS4270_FORMAT_DAC_I2S	0x08
+#define CS4270_FORMAT_DAC_RJ16	0x18
+#define CS4270_FORMAT_DAC_RJ24	0x10
+#define CS4270_FORMAT_ADC_MASK	0x01
+#define CS4270_FORMAT_ADC_LJ	0x00
+#define CS4270_FORMAT_ADC_I2S	0x01
+#define CS4270_TRANS_ONE_VOL	0x80
+#define CS4270_TRANS_SOFT	0x40
+#define CS4270_TRANS_ZERO	0x20
+#define CS4270_TRANS_INV_ADC_A	0x08
+#define CS4270_TRANS_INV_ADC_B	0x10
+#define CS4270_TRANS_INV_DAC_A	0x02
+#define CS4270_TRANS_INV_DAC_B	0x04
+#define CS4270_TRANS_DEEMPH	0x01
+#define CS4270_MUTE_AUTO	0x20
+#define CS4270_MUTE_ADC_A	0x08
+#define CS4270_MUTE_ADC_B	0x10
+#define CS4270_MUTE_POLARITY	0x04
+#define CS4270_MUTE_DAC_A	0x01
+#define CS4270_MUTE_DAC_B	0x02
+
+/**
+ * A list of addresses on which this CS4270 could use.  I2C addresses are
+ * 7 bits.  For the CS4270, the upper four bits are always 1001, and the
+ * lower three bits are determined via the AD2, AD1, and AD0 pins
+ * (respectively).
+ */
+static unsigned short normal_i2c[] = {
+	0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, I2C_CLIENT_END
+};
+I2C_CLIENT_INSMOD;
+
+/**
+ * Pre-fill the CS4270 register cache.
+ *
+ * We use the auto-increment feature of the CS4270 to read all registers in
+ * one shot.
+ */
+static int cs4270_fill_cache(struct snd_soc_codec *codec)
+{
+	u8 *cache = codec->reg_cache;
+	struct i2c_client *i2c_client = codec->control_data;
+	s32 length;
+
+	length = i2c_smbus_read_i2c_block_data(i2c_client,
+		CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache);
+
+	if (length != CS4270_NUMREGS) {
+		printk(KERN_ERR "cs4270: I2C read failure, addr=%u\n",
+		       i2c_client->addr);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/**
+ * Read from the CS4270 register cache.
+ *
+ * This CS4270 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS4270 never updates
+ * the register values, so we won't have a cache coherncy problem.
+ */
+static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec,
+	unsigned int reg)
+{
+	u8 *cache = codec->reg_cache;
+
+	if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+		return -EIO;
+
+	return cache[reg - CS4270_FIRSTREG];
+}
+
+/**
+ * Write to a CS4270 register via the I2C bus.
+ *
+ * This function writes the given value to the given CS4270 register, and
+ * also updates the register cache.
+ *
+ * Note that we don't use the hw_write function pointer of snd_soc_codec.
+ * That's because it's too clunky: the hw_write_t prototype does not match
+ * i2c_smbus_write_byte_data(), and it's just another layer of overhead.
+ */
+static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg,
+			    unsigned int value)
+{
+	if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
+		return -EIO;
+
+	if (i2c_smbus_write_byte_data(codec->control_data, reg, value) == 0) {
+		/* We've written to the hardware, so update the cache */
+		u8 *cache = codec->reg_cache;
+		cache[reg - CS4270_FIRSTREG] = value;
+		return 0;
+	} else {
+		printk(KERN_ERR "cs4270: I2C write failed\n");
+		return -EIO;
+	}
+}
+
+/**
+ * Clock Ratio Selection for Master Mode.
+ *
+ * The data for this chart is taken from Table 5 of the CS4270 reference
+ * manual.
+ *
+ * This table is used to determine how to program the Mode Control register.
+ * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS4270 currently supports.
+ *
+ * 'ratio' is the MCLK/LRCK ratio.  MCLK is usually a fixed input frequency,
+ * and LRCK is equal to the sampling rate. The CS4270 only supports sampling
+ * rates where this ratio is one of: 64, 96, 128, 192, 256, 384, 512, 768 or
+ * 1024.
+ *
+ * 'speed_mode' is the corresponding bit pattern to be written to the
+ * MODE bits of the Mode Control Register
+ *
+ * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of
+ * the Mode Control Register.
+ *
+ * In situations where a single ratio is represented by multiple speed
+ * modes, we favor the slowest speed.  E.g, for a ratio of 128, we pick
+ * double-speed instead of quad-speed.  However, the CS4270 errata states
+ * that Divide-By-1.5 can cause failures, so we avoid that mode where
+ * possible.
+ *
+ * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not
+ * work if VD = 3.3V.  If this effects you, select the
+ * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will
+ * never select any sample rates that require divide-by-1.5.
+ */
+static struct {
+	unsigned int ratio;
+	u8 speed_mode;
+	u8 mclk;
+} cs4270_mode_ratios[] = {
+	{64, CS4270_MODE_4X, CS4270_MODE_DIV1},
+#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA
+	{96, CS4270_MODE_4X, CS4270_MODE_DIV15},
+#endif
+	{128, CS4270_MODE_2X, CS4270_MODE_DIV1},
+	{192, CS4270_MODE_4X, CS4270_MODE_DIV3},
+	{256, CS4270_MODE_1X, CS4270_MODE_DIV1},
+	{384, CS4270_MODE_2X, CS4270_MODE_DIV3},
+	{512, CS4270_MODE_1X, CS4270_MODE_DIV2},
+	{768, CS4270_MODE_1X, CS4270_MODE_DIV3},
+	{1024, CS4270_MODE_1X, CS4270_MODE_DIV4}
+};
+
+/**
+ * Program the CS4270 with the given hardware parameters.
+ *
+ * The .dai_ops functions are used to provide board-specific data, like
+ * input frequencies, to this driver.  This function takes that information,
+ * combines it with the hardware parameters provided, and programs the
+ * hardware accordingly.
+ */
+static int cs4270_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_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->codec;
+	struct cs4270_private *cs4270 = codec->private_data;
+	unsigned int ret = 0;
+	unsigned int i;
+	unsigned int rate;
+	unsigned int ratio;
+	int reg;
+
+	/* Figure out which MCLK/LRCK ratio to use */
+
+	rate = params_rate(params);	/* Sampling rate, in Hz */
+	ratio = cs4270->mclk / rate;	/* MCLK/LRCK ratio */
+
+	for (i = 0; i < ARRAY_SIZE(cs4270_mode_ratios); i++) {
+		if (cs4270_mode_ratios[i].ratio == ratio)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(cs4270_mode_ratios)) {
+		/* We did not find a matching ratio */
+		printk(KERN_ERR "cs4270: could not find matching ratio\n");
+		return -EINVAL;
+	}
+
+	/* Freeze and power-down the codec */
+
+	ret = snd_soc_write(codec, CS4270_PWRCTL, CS4270_PWRCTL_FREEZE |
+			    CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC |
+			    CS4270_PWRCTL_PDN);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: I2C write failed\n");
+		return ret;
+	}
+
+	/* Program the mode control register */
+
+	reg = snd_soc_read(codec, CS4270_MODE);
+	reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK);
+	reg |= cs4270_mode_ratios[i].speed_mode | cs4270_mode_ratios[i].mclk;
+
+	ret = snd_soc_write(codec, CS4270_MODE, reg);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: I2C write failed\n");
+		return ret;
+	}
+
+	/* Program the format register */
+
+	reg = snd_soc_read(codec, CS4270_FORMAT);
+	reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK);
+
+	switch (cs4270->mode) {
+	case SND_SOC_DAIFMT_I2S:
+		reg |= CS4270_FORMAT_DAC_I2S | CS4270_FORMAT_ADC_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ;
+		break;
+	default:
+		printk(KERN_ERR "cs4270: unknown format\n");
+		return -EINVAL;
+	}
+
+	ret = snd_soc_write(codec, CS4270_FORMAT, reg);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: I2C write failed\n");
+		return ret;
+	}
+
+	/* Disable auto-mute.  This feature appears to be buggy, because in
+	   some situations, auto-mute will not deactivate when it should. */
+
+	reg = snd_soc_read(codec, CS4270_MUTE);
+	reg &= ~CS4270_MUTE_AUTO;
+	ret = snd_soc_write(codec, CS4270_MUTE, reg);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: I2C write failed\n");
+		return ret;
+	}
+
+	/* Thaw and power-up the codec */
+
+	ret = snd_soc_write(codec, CS4270_PWRCTL, 0);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: I2C write failed\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
+
+/**
+ * Set the CS4270 external mute
+ *
+ * This function toggles the mute bits in the MUTE register.  The CS4270's
+ * mute capability is intended for external muting circuitry, so if the
+ * board does not have the MUTEA or MUTEB pins connected to such circuitry,
+ * then this function will do nothing.
+ */
+static int cs4270_mute(struct snd_soc_codec_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	int reg6;
+
+	reg6 = snd_soc_read(codec, CS4270_MUTE);
+
+	if (mute)
+		reg6 |= CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
+			CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
+	else
+		reg6 &= ~(CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
+			  CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+
+	return snd_soc_write(codec, CS4270_MUTE, reg6);
+}
+
+#endif
+
+/**
+ * Sampling rate <-> bit patter mapping
+ *
+ * This array maps sampling rates to their SNDRV_PCM_RATE_x equivalent.
+ *
+ * This is really something that ALSA should provide.
+ *
+ * This table is used by cs4270_set_dai_sysclk() to tell ALSA which sampling
+ * rates the CS4270 currently supports.
+ */
+static struct {
+	unsigned int rate;
+	unsigned int bit;
+} rate_map[] = {
+	{5512, SNDRV_PCM_RATE_5512},
+	{8000, SNDRV_PCM_RATE_8000},
+	{11025, SNDRV_PCM_RATE_11025},
+	{16000, SNDRV_PCM_RATE_16000},
+	{22050, SNDRV_PCM_RATE_22050},
+	{32000, SNDRV_PCM_RATE_32000},
+	{44100, SNDRV_PCM_RATE_44100},
+	{48000, SNDRV_PCM_RATE_48000},
+	{64000, SNDRV_PCM_RATE_64000},
+	{88200, SNDRV_PCM_RATE_88200},
+	{96000, SNDRV_PCM_RATE_96000},
+	{176400, SNDRV_PCM_RATE_176400},
+	{192000, SNDRV_PCM_RATE_192000}
+};
+
+/**
+ * Determine the CS4270 samples rates.
+ *
+ * 'freq' is the input frequency to MCLK.  The other parameters are ignored.
+ *
+ * The value of MCLK is used to determine which sample rates are supported
+ * by the CS4270.  The ratio of MCLK / Fs must be equal to one of nine
+ * support values: 64, 96, 128, 192, 256, 384, 512, 768, and 1024.
+ *
+ * This function calculates the nine ratios and determines which ones match
+ * a standard sample rate.  If there's a match, then it is added to the list
+ * of support sample rates.
+ *
+ * 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.
+ */
+static int cs4270_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs4270_private *cs4270 = codec->private_data;
+	unsigned int rates = 0;
+	unsigned int rate_min = -1;
+	unsigned int rate_max = 0;
+	unsigned int i;
+
+	cs4270->mclk = freq;
+
+	for (i = 0; i < ARRAY_SIZE(cs4270_mode_ratios); i++) {
+		unsigned int rate;
+		unsigned int j;
+		rate = freq / cs4270_mode_ratios[i].ratio;
+		for (j = 0; j < ARRAY_SIZE(rate_map); j++) {
+			if (rate == rate_map[j].rate) {
+				rates |= rate_map[j].bit;
+				if (rate < rate_min)
+					rate_min = rate;
+				if (rate > rate_max)
+					rate_max = rate;
+			}
+		}
+	}
+
+	if (!rate_max) {
+		printk(KERN_ERR "cs4270: could not find a valid rate\n");
+		return -EINVAL;
+	}
+
+	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;
+}
+
+/**
+ * Configure the codec for the selected audio format
+ *
+ * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
+ * codec accordingly.
+ *
+ * Currently, this function only supports SND_SOC_DAIFMT_I2S and
+ * SND_SOC_DAIFMT_LEFT_J.  The CS4270 codec also supports right-justified
+ * data for playback only, but ASoC currently does not support different
+ * formats for playback vs. record.
+ */
+static int cs4270_set_dai_fmt(struct snd_soc_codec_dai *codec_dai,
+			      unsigned int format)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct cs4270_private *cs4270 = codec->private_data;
+	int ret = 0;
+
+	switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+	case SND_SOC_DAIFMT_LEFT_J:
+		cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
+		break;
+	default:
+		printk(KERN_ERR "cs4270: invalid DAI format\n");
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int cs4270_i2c_probe(struct i2c_adapter *adap, int addr, int kind);
+
+/**
+ * Notify the driver that a new I2C bus has been found.
+ *
+ * This function is called for each I2C bus in the system.  The function
+ * then asks the I2C subsystem to probe that bus at the addresses on which
+ * our device (the CS4270) could exist.  If a device is found at one of
+ * those addresses, then our probe function (cs4270_i2c_probe) is called.
+ */
+static int cs4270_i2c_attach(struct i2c_adapter *adapter)
+{
+	return i2c_probe(adapter, &addr_data, cs4270_i2c_probe);
+}
+
+static int cs4270_i2c_detach(struct i2c_client *client)
+{
+	struct snd_soc_codec *codec = i2c_get_clientdata(client);
+
+	i2c_detach_client(client);
+	codec->control_data = NULL;
+
+	kfree(codec->reg_cache);
+	codec->reg_cache = NULL;
+
+	kfree(client);
+	return 0;
+}
+
+/* A list of non-DAPM controls that the CS4270 supports */
+static const struct snd_kcontrol_new cs4270_snd_controls[] = {
+	SOC_DOUBLE_R("Master Playback Volume",
+		CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 0)
+};
+
+static struct i2c_driver cs4270_i2c_driver = {
+	.driver = {
+		.name = "CS4270 I2C",
+		.owner = THIS_MODULE,
+	},
+	.id =             I2C_DRIVERID_CS4270,
+	.attach_adapter = cs4270_i2c_attach,
+	.detach_client =  cs4270_i2c_detach,
+};
+
+/**
+ * Global variable to store socdev for i2c probe function.
+ *
+ * If struct i2c_driver had a private_data field, we wouldn't need to use
+ * cs4270_socdec.  This is the only way to pass the socdev structure to
+ * cs4270_i2c_probe().
+ *
+ * The real solution to cs4270_socdev is to create a mechanism
+ * that maps I2C addresses to snd_soc_device structures.  Perhaps the
+ * creation of the snd_soc_device object should be moved out of
+ * cs4270_probe() and into cs4270_i2c_probe(), but that would make this
+ * driver dependent on I2C.  The CS4270 supports "stand-alone" mode, whereby
+ * the chip is *not* connected to the I2C bus, but is instead configured via
+ * input pins.
+ */
+static struct snd_soc_device *cs4270_socdev;
+
+/**
+ * Initialize the I2C interface of the CS4270
+ *
+ * This function is called for whenever the I2C subsystem finds a device
+ * at a particular address.
+ *
+ * Note: snd_soc_new_pcms() must be called before this function can be called,
+ * because of snd_ctl_add().
+ */
+static int cs4270_i2c_probe(struct i2c_adapter *adapter, int addr, int kind)
+{
+	struct snd_soc_device *socdev = cs4270_socdev;
+	struct snd_soc_codec *codec = socdev->codec;
+	struct i2c_client *i2c_client = NULL;
+	int i;
+	int ret = 0;
+
+	/* Probing all possible addresses has one drawback: if there are
+	   multiple CS4270s on the bus, then you cannot specify which
+	   socdev is matched with which CS4270.  For now, we just reject
+	   this I2C device if the socdev already has one attached. */
+	if (codec->control_data)
+		return -ENODEV;
+
+	/* Note: codec_dai->codec is NULL here */
+
+	i2c_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
+	if (!i2c_client) {
+		printk(KERN_ERR "cs4270: could not allocate I2C client\n");
+		return -ENOMEM;
+	}
+
+	codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL);
+	if (!codec->reg_cache) {
+		printk(KERN_ERR "cs4270: could not allocate register cache\n");
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	i2c_set_clientdata(i2c_client, codec);
+	strcpy(i2c_client->name, "CS4270");
+
+	i2c_client->driver = &cs4270_i2c_driver;
+	i2c_client->adapter = adapter;
+	i2c_client->addr = addr;
+
+	/* Verify that we have a CS4270 */
+
+	ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: failed to read I2C\n");
+		goto error;
+	}
+	/* The top four bits of the chip ID should be 1100. */
+	if ((ret & 0xF0) != 0xC0) {
+		/* The device at this address is not a CS4270 codec */
+		ret = -ENODEV;
+		goto error;
+	}
+
+	printk(KERN_INFO "cs4270: found device at I2C address %X\n", addr);
+	printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF);
+
+	/* Tell the I2C layer a new client has arrived */
+
+	ret = i2c_attach_client(i2c_client);
+	if (ret) {
+		printk(KERN_ERR "cs4270: could not attach codec, "
+			"I2C address %x, error code %i\n", addr, ret);
+		goto error;
+	}
+
+	codec->control_data = i2c_client;
+	codec->read = cs4270_read_reg_cache;
+	codec->write = cs4270_i2c_write;
+	codec->reg_cache_size = CS4270_NUMREGS;
+
+	/* The I2C interface is set up, so pre-fill our register cache */
+
+	ret = cs4270_fill_cache(codec);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: failed to fill register cache\n");
+		goto error;
+	}
+
+	/* Add the non-DAPM controls */
+
+	for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) {
+		struct snd_kcontrol *kctrl =
+		snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL);
+
+		ret = snd_ctl_add(codec->card, kctrl);
+		if (ret < 0)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	if (codec->control_data) {
+		i2c_detach_client(i2c_client);
+		codec->control_data = NULL;
+	}
+
+	kfree(codec->reg_cache);
+	codec->reg_cache = NULL;
+	codec->reg_cache_size = 0;
+
+	kfree(i2c_client);
+
+	return ret;
+}
+
+#endif
+
+struct snd_soc_codec_dai cs4270_dai = {
+	.name = "CS4270",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = 0,
+		.formats = CS4270_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = 0,
+		.formats = CS4270_FORMATS,
+	},
+	.dai_ops = {
+		.set_sysclk = cs4270_set_dai_sysclk,
+		.set_fmt = cs4270_set_dai_fmt,
+	}
+};
+EXPORT_SYMBOL_GPL(cs4270_dai);
+
+/**
+ * ASoC probe function
+ *
+ * This function is called when the machine driver calls
+ * platform_device_add().
+ */
+static int cs4270_probe(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec;
+	int ret = 0;
+
+	printk(KERN_INFO "CS4270 ALSA SoC Codec\n");
+
+	/* Allocate enough space for the snd_soc_codec structure
+	   and our private data together. */
+	codec = kzalloc(ALIGN(sizeof(struct snd_soc_codec), 4) +
+			sizeof(struct cs4270_private), GFP_KERNEL);
+	if (!codec) {
+		printk(KERN_ERR "cs4270: Could not allocate codec structure\n");
+		return -ENOMEM;
+	}
+
+	mutex_init(&codec->mutex);
+	INIT_LIST_HEAD(&codec->dapm_widgets);
+	INIT_LIST_HEAD(&codec->dapm_paths);
+
+	codec->name = "CS4270";
+	codec->owner = THIS_MODULE;
+	codec->dai = &cs4270_dai;
+	codec->num_dai = 1;
+	codec->private_data = codec + ALIGN(sizeof(struct snd_soc_codec), 4);
+
+	socdev->codec = codec;
+
+	/* Register PCMs */
+
+	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: failed to create PCMs\n");
+		return ret;
+	}
+
+#ifdef CONFIG_I2C
+	cs4270_socdev = socdev;
+
+	ret = i2c_add_driver(&cs4270_i2c_driver);
+	if (ret) {
+		printk(KERN_ERR "cs4270: failed to attach driver");
+		snd_soc_free_pcms(socdev);
+		return ret;
+	}
+
+	/* Did we find a CS4270 on the I2C bus? */
+	if (codec->control_data) {
+		/* Initialize codec ops */
+		cs4270_dai.ops.hw_params = cs4270_hw_params;
+#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
+		cs4270_dai.dai_ops.digital_mute = cs4270_mute;
+#endif
+	} else
+		printk(KERN_INFO "cs4270: no I2C device found, "
+			"using stand-alone mode\n");
+#else
+	printk(KERN_INFO "cs4270: I2C disabled, using stand-alone mode\n");
+#endif
+
+	ret = snd_soc_register_card(socdev);
+	if (ret < 0) {
+		printk(KERN_ERR "cs4270: failed to register card\n");
+		snd_soc_free_pcms(socdev);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int cs4270_remove(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+	snd_soc_free_pcms(socdev);
+
+#ifdef CONFIG_I2C
+	if (socdev->codec->control_data)
+		i2c_del_driver(&cs4270_i2c_driver);
+#endif
+
+	kfree(socdev->codec);
+	socdev->codec = NULL;
+
+	return 0;
+}
+
+/**
+ * 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_cs4270 = {
+	.probe = 	cs4270_probe,
+	.remove = 	cs4270_remove
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_cs4270);
+
+MODULE_AUTHOR("Timur Tabi <timur at freescale.com>");
+MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs4270.h b/sound/soc/codecs/cs4270.h
new file mode 100644
index 0000000..35e5fe3
--- /dev/null
+++ b/sound/soc/codecs/cs4270.h
@@ -0,0 +1,28 @@
+/*
+ * Cirrus Logic CS4270 ALSA SoC Codec Driver
+ *
+ * Author: Timur Tabi <timur at freescale.com>
+ *
+ * Copyright 2007 Freescale Semiconductor, Inc.  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 _CS4270_H
+#define _CS4270_H
+
+/**
+ * The ASoC codec DAI structure for the CS4270.  Assign this structure to
+ * the .codec_dai field of your machine driver's snd_soc_dai_link structure.
+ */
+extern struct snd_soc_codec_dai cs4270_dai;
+
+/**
+ * The ASoC codec device structure for the CS4270.  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_cs4270;
+
+#endif
--
1.5.2.4



More information about the Alsa-devel mailing list