[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