aboutsummaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorFrank Mandarino <fmandarino@endrelia.com>2006-10-06 18:31:09 +0200
committerJaroslav Kysela <perex@suse.cz>2007-02-09 09:00:16 +0100
commitdb2a416556af0313db028147e4a22fef6f214f2f (patch)
treed5b05b3ad7af4b6023237eee4eac2c6375248dce /sound
parent808db4a4512bedd45b62de255f7eedb5d5b788b9 (diff)
[ALSA] ASoC: core code
This patch is the core of ASoC functionality. The ASoC core is designed to provide the following features :- o Codec independence. Allows reuse of codec drivers on other platforms and machines. o Platform driver code reuse. Reuse of platform specific audio DMA and DAI drivers on different machines. o Easy I2S/PCM digital audio interface configuration between codec and SoC. Each SoC interface and codec registers their audio interface capabilities with the core at initialisation. The capabilities are subsequently matched and configured at run time for best power and performance when the application hw params are known. o Machine specific controls/operations: Allow machines to add controls and operations to the audio subsystem. e.g. volume control for speaker amp. To achieve all this, ASoC splits an embedded audio system into 3 components :- 1. Codec driver: The codec driver is platform independent and contains audio controls, audio interface capabilities, codec dapm and codec IO functions. 2. Platform driver: The platform driver contains the audio dma engine and audio interface drivers (e.g. I2S, AC97, PCM) for that platform. 3. Machine driver: The machine driver handles any machine specific controls and audio events. i.e. turning on an amp at start of playback. Signed-off-by: Frank Mandarino <fmandarino@endrelia.com> Signed-off-by: Richard Purdie <rpurdie@rpsys.net> Signed-off-by: Liam Girdwood <liam.Girdwood@wolfsonmicro.com> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/soc-core.c1920
1 files changed, 1920 insertions, 0 deletions
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
new file mode 100644
index 00000000000..e841ad46c75
--- /dev/null
+++ b/sound/soc/soc-core.c
@@ -0,0 +1,1920 @@
+/*
+ * soc-core.c -- ALSA SoC Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Revision history
+ * 12th Aug 2005 Initial version.
+ * 25th Oct 2005 Working Codec, Interface and Platform registration.
+ *
+ * TODO:
+ * o Add hw rules to enforce rates, etc.
+ * o More testing with other codecs/machines.
+ * o Add more codecs and platforms to ensure good API coverage.
+ * o Support TDM on PCM and I2S
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <sound/driver.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>
+
+/* debug */
+#define SOC_DEBUG 0
+#if SOC_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+/* debug DAI capabilities matching */
+#define SOC_DEBUG_DAI 0
+#if SOC_DEBUG_DAI
+#define dbgc(format, arg...) printk(format, ## arg)
+#else
+#define dbgc(format, arg...)
+#endif
+
+static DEFINE_MUTEX(pcm_mutex);
+static DEFINE_MUTEX(io_mutex);
+static struct workqueue_struct *soc_workq;
+static struct work_struct soc_stream_work;
+static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
+
+/* supported sample rates */
+/* ATTENTION: these values depend on the definition in pcm.h! */
+static const unsigned int rates[] = {
+ 5512, 8000, 11025, 16000, 22050, 32000, 44100,
+ 48000, 64000, 88200, 96000, 176400, 192000
+};
+
+/*
+ * This is a timeout to do a DAPM powerdown after a stream is closed().
+ * It can be used to eliminate pops between different playback streams, e.g.
+ * between two audio tracks.
+ */
+static int pmdown_time = 5000;
+module_param(pmdown_time, int, 0);
+MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
+
+#ifdef CONFIG_SND_SOC_AC97_BUS
+/* unregister ac97 codec */
+static int soc_ac97_dev_unregister(struct snd_soc_codec *codec)
+{
+ if (codec->ac97->dev.bus)
+ device_unregister(&codec->ac97->dev);
+ return 0;
+}
+
+/* stop no dev release warning */
+static void soc_ac97_device_release(struct device *dev){}
+
+/* register ac97 codec to bus */
+static int soc_ac97_dev_register(struct snd_soc_codec *codec)
+{
+ int err;
+
+ codec->ac97->dev.bus = &ac97_bus_type;
+ codec->ac97->dev.parent = NULL;
+ codec->ac97->dev.release = soc_ac97_device_release;
+
+ snprintf(codec->ac97->dev.bus_id, BUS_ID_SIZE, "%d-%d:%s",
+ codec->card->number, 0, codec->name);
+ err = device_register(&codec->ac97->dev);
+ if (err < 0) {
+ snd_printk(KERN_ERR "Can't register ac97 bus\n");
+ codec->ac97->dev.bus = NULL;
+ return err;
+ }
+ return 0;
+}
+#endif
+
+static inline const char* get_dai_name(int type)
+{
+ switch(type) {
+ case SND_SOC_DAI_AC97:
+ return "AC97";
+ case SND_SOC_DAI_I2S:
+ return "I2S";
+ case SND_SOC_DAI_PCM:
+ return "PCM";
+ }
+ return NULL;
+}
+
+/* get rate format from rate */
+static inline int soc_get_rate_format(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rates); i++) {
+ if (rates[i] == rate)
+ return 1 << i;
+ }
+ return 0;
+}
+
+/* gets the audio system mclk/sysclk for the given parameters */
+static unsigned inline int soc_get_mclk(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_clock_info *info)
+{
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_machine *machine = socdev->machine;
+ int i;
+
+ /* find the matching machine config and get it's mclk for the given
+ * sample rate and hardware format */
+ for(i = 0; i < machine->num_links; i++) {
+ if (machine->dai_link[i].cpu_dai == rtd->cpu_dai &&
+ machine->dai_link[i].config_sysclk)
+ return machine->dai_link[i].config_sysclk(rtd, info);
+ }
+ return 0;
+}
+
+/* changes a bitclk multiplier mask to a divider mask */
+static u16 soc_bfs_mult_to_div(u16 bfs, int rate, unsigned int mclk,
+ unsigned int pcmfmt, unsigned int chn)
+{
+ int i, j;
+ u16 bfs_ = 0;
+ int size = snd_pcm_format_physical_width(pcmfmt), min = 0;
+
+ if (size <= 0)
+ return 0;
+
+ /* the minimum bit clock that has enough bandwidth */
+ min = size * rate * chn;
+ dbgc("mult --> div min bclk %d with mclk %d\n", min, mclk);
+
+ for (i = 0; i < 16; i++) {
+ if ((bfs >> i) & 0x1) {
+ j = rate * SND_SOC_FSB_REAL(1<<i);
+
+ if (j >= min) {
+ bfs_ |= SND_SOC_FSBD(mclk/j);
+ dbgc("mult --> div support mult %d\n",
+ SND_SOC_FSB_REAL(1<<i));
+ }
+ }
+ }
+
+ return bfs_;
+}
+
+/* changes a bitclk divider mask to a multiplier mask */
+static u16 soc_bfs_div_to_mult(u16 bfs, int rate, unsigned int mclk,
+ unsigned int pcmfmt, unsigned int chn)
+{
+ int i, j;
+ u16 bfs_ = 0;
+
+ int size = snd_pcm_format_physical_width(pcmfmt), min = 0;
+
+ if (size <= 0)
+ return 0;
+
+ /* the minimum bit clock that has enough bandwidth */
+ min = size * rate * chn;
+ dbgc("div to mult min bclk %d with mclk %d\n", min, mclk);
+
+ for (i = 0; i < 16; i++) {
+ if ((bfs >> i) & 0x1) {
+ j = mclk / (SND_SOC_FSBD_REAL(1<<i));
+ if (j >= min) {
+ bfs_ |= SND_SOC_FSB(j/rate);
+ dbgc("div --> mult support div %d\n",
+ SND_SOC_FSBD_REAL(1<<i));
+ }
+ }
+ }
+
+ return bfs_;
+}
+
+/* Matches codec DAI and SoC CPU DAI hardware parameters */
+static int soc_hw_match_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai_mode *codec_dai_mode = NULL;
+ struct snd_soc_dai_mode *cpu_dai_mode = NULL;
+ struct snd_soc_clock_info clk_info;
+ unsigned int fs, mclk, codec_bfs, cpu_bfs, rate = params_rate(params),
+ chn, j, k, cpu_bclk, codec_bclk, pcmrate;
+ u16 fmt = 0;
+
+ dbg("asoc: match version %s\n", SND_SOC_VERSION);
+ clk_info.rate = rate;
+ pcmrate = soc_get_rate_format(rate);
+
+ /* try and find a match from the codec and cpu DAI capabilities */
+ for (j = 0; j < rtd->codec_dai->caps.num_modes; j++) {
+ for (k = 0; k < rtd->cpu_dai->caps.num_modes; k++) {
+ codec_dai_mode = &rtd->codec_dai->caps.mode[j];
+ cpu_dai_mode = &rtd->cpu_dai->caps.mode[k];
+
+ if (!(codec_dai_mode->pcmrate & cpu_dai_mode->pcmrate &
+ pcmrate)) {
+ dbgc("asoc: DAI[%d:%d] failed to match rate\n", j, k);
+ continue;
+ }
+
+ fmt = codec_dai_mode->fmt & cpu_dai_mode->fmt;
+ if (!(fmt & SND_SOC_DAIFMT_FORMAT_MASK)) {
+ dbgc("asoc: DAI[%d:%d] failed to match format\n", j, k);
+ continue;
+ }
+
+ if (!(fmt & SND_SOC_DAIFMT_CLOCK_MASK)) {
+ dbgc("asoc: DAI[%d:%d] failed to match clock masters\n",
+ j, k);
+ continue;
+ }
+
+ if (!(fmt & SND_SOC_DAIFMT_INV_MASK)) {
+ dbgc("asoc: DAI[%d:%d] failed to match invert\n", j, k);
+ continue;
+ }
+
+ if (!(codec_dai_mode->pcmfmt & cpu_dai_mode->pcmfmt)) {
+ dbgc("asoc: DAI[%d:%d] failed to match pcm format\n", j, k);
+ continue;
+ }
+
+ if (!(codec_dai_mode->pcmdir & cpu_dai_mode->pcmdir)) {
+ dbgc("asoc: DAI[%d:%d] failed to match direction\n", j, k);
+ continue;
+ }
+
+ /* todo - still need to add tdm selection */
+ rtd->cpu_dai->dai_runtime.fmt =
+ rtd->codec_dai->dai_runtime.fmt =
+ 1 << (ffs(fmt & SND_SOC_DAIFMT_FORMAT_MASK) -1) |
+ 1 << (ffs(fmt & SND_SOC_DAIFMT_CLOCK_MASK) - 1) |
+ 1 << (ffs(fmt & SND_SOC_DAIFMT_INV_MASK) - 1);
+ clk_info.bclk_master =
+ rtd->cpu_dai->dai_runtime.fmt & SND_SOC_DAIFMT_CLOCK_MASK;
+
+ /* make sure the ratio between rate and master
+ * clock is acceptable*/
+ fs = (cpu_dai_mode->fs & codec_dai_mode->fs);
+ if (fs == 0) {
+ dbgc("asoc: DAI[%d:%d] failed to match FS\n", j, k);
+ continue;
+ }
+ clk_info.fs = rtd->cpu_dai->dai_runtime.fs =
+ rtd->codec_dai->dai_runtime.fs = fs;
+
+ /* calculate audio system clocking using slowest clocks possible*/
+ mclk = soc_get_mclk(rtd, &clk_info);
+ if (mclk == 0) {
+ dbgc("asoc: DAI[%d:%d] configuration not clockable\n", j, k);
+ dbgc("asoc: rate %d fs %d master %x\n", rate, fs,
+ clk_info.bclk_master);
+ continue;
+ }
+
+ /* calculate word size (per channel) and frame size */
+ rtd->codec_dai->dai_runtime.pcmfmt =
+ rtd->cpu_dai->dai_runtime.pcmfmt =
+ 1 << params_format(params);
+
+ chn = params_channels(params);
+ /* i2s always has left and right */
+ if (params_channels(params) == 1 &&
+ rtd->cpu_dai->dai_runtime.fmt & (SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_LEFT_J))
+ chn <<= 1;
+
+ /* Calculate bfs - the ratio between bitclock and the sample rate
+ * We must take into consideration the dividers and multipliers
+ * used in the codec and cpu DAI modes. We always choose the
+ * lowest possible clocks to reduce power.
+ */
+ if (codec_dai_mode->flags & cpu_dai_mode->flags &
+ SND_SOC_DAI_BFS_DIV) {
+ /* cpu & codec bfs dividers */
+ rtd->cpu_dai->dai_runtime.bfs =
+ rtd->codec_dai->dai_runtime.bfs =
+ 1 << (fls(codec_dai_mode->bfs & cpu_dai_mode->bfs) - 1);
+ } else if (codec_dai_mode->flags & SND_SOC_DAI_BFS_DIV) {
+ /* normalise bfs codec divider & cpu mult */
+ codec_bfs = soc_bfs_div_to_mult(codec_dai_mode->bfs, rate,
+ mclk, rtd->codec_dai->dai_runtime.pcmfmt, chn);
+ rtd->cpu_dai->dai_runtime.bfs =
+ 1 << (ffs(codec_bfs & cpu_dai_mode->bfs) - 1);
+ cpu_bfs = soc_bfs_mult_to_div(cpu_dai_mode->bfs, rate, mclk,
+ rtd->codec_dai->dai_runtime.pcmfmt, chn);
+ rtd->codec_dai->dai_runtime.bfs =
+ 1 << (fls(codec_dai_mode->bfs & cpu_bfs) - 1);
+ } else if (cpu_dai_mode->flags & SND_SOC_DAI_BFS_DIV) {
+ /* normalise bfs codec mult & cpu divider */
+ codec_bfs = soc_bfs_mult_to_div(codec_dai_mode->bfs, rate,
+ mclk, rtd->codec_dai->dai_runtime.pcmfmt, chn);
+ rtd->cpu_dai->dai_runtime.bfs =
+ 1 << (fls(codec_bfs & cpu_dai_mode->bfs) -1);
+ cpu_bfs = soc_bfs_div_to_mult(cpu_dai_mode->bfs, rate, mclk,
+ rtd->codec_dai->dai_runtime.pcmfmt, chn);
+ rtd->codec_dai->dai_runtime.bfs =
+ 1 << (ffs(codec_dai_mode->bfs & cpu_bfs) -1);
+ } else {
+ /* codec & cpu bfs rate multipliers */
+ rtd->cpu_dai->dai_runtime.bfs =
+ rtd->codec_dai->dai_runtime.bfs =
+ 1 << (ffs(codec_dai_mode->bfs & cpu_dai_mode->bfs) -1);
+ }
+
+ /* make sure the bit clock speed is acceptable */
+ if (!rtd->cpu_dai->dai_runtime.bfs ||
+ !rtd->codec_dai->dai_runtime.bfs) {
+ dbgc("asoc: DAI[%d:%d] failed to match BFS\n", j, k);
+ dbgc("asoc: cpu_dai %x codec %x\n",
+ rtd->cpu_dai->dai_runtime.bfs,
+ rtd->codec_dai->dai_runtime.bfs);
+ dbgc("asoc: mclk %d hwfmt %x\n", mclk, fmt);
+ continue;
+ }
+
+ goto found;
+ }
+ }
+ printk(KERN_ERR "asoc: no matching DAI found between codec and CPU\n");
+ return -EINVAL;
+
+found:
+ /* we have matching DAI's, so complete the runtime info */
+ rtd->codec_dai->dai_runtime.pcmrate =
+ rtd->cpu_dai->dai_runtime.pcmrate =
+ soc_get_rate_format(rate);
+
+ rtd->codec_dai->dai_runtime.priv = codec_dai_mode->priv;
+ rtd->cpu_dai->dai_runtime.priv = cpu_dai_mode->priv;
+ rtd->codec_dai->dai_runtime.flags = codec_dai_mode->flags;
+ rtd->cpu_dai->dai_runtime.flags = cpu_dai_mode->flags;
+
+ /* for debug atm */
+ dbg("asoc: DAI[%d:%d] Match OK\n", j, k);
+ if (rtd->codec_dai->dai_runtime.flags == SND_SOC_DAI_BFS_DIV) {
+ codec_bclk = (rtd->codec_dai->dai_runtime.fs * params_rate(params)) /
+ SND_SOC_FSBD_REAL(rtd->codec_dai->dai_runtime.bfs);
+ dbg("asoc: codec fs %d mclk %d bfs div %d bclk %d\n",
+ rtd->codec_dai->dai_runtime.fs, mclk,
+ SND_SOC_FSBD_REAL(rtd->codec_dai->dai_runtime.bfs), codec_bclk);
+ } else {
+ codec_bclk = params_rate(params) *
+ SND_SOC_FSB_REAL(rtd->codec_dai->dai_runtime.bfs);
+ dbg("asoc: codec fs %d mclk %d bfs mult %d bclk %d\n",
+ rtd->codec_dai->dai_runtime.fs, mclk,
+ SND_SOC_FSB_REAL(rtd->codec_dai->dai_runtime.bfs), codec_bclk);
+ }
+ if (rtd->cpu_dai->dai_runtime.flags == SND_SOC_DAI_BFS_DIV) {
+ cpu_bclk = (rtd->cpu_dai->dai_runtime.fs * params_rate(params)) /
+ SND_SOC_FSBD_REAL(rtd->cpu_dai->dai_runtime.bfs);
+ dbg("asoc: cpu fs %d mclk %d bfs div %d bclk %d\n",
+ rtd->cpu_dai->dai_runtime.fs, mclk,
+ SND_SOC_FSBD_REAL(rtd->cpu_dai->dai_runtime.bfs), cpu_bclk);
+ } else {
+ cpu_bclk = params_rate(params) *
+ SND_SOC_FSB_REAL(rtd->cpu_dai->dai_runtime.bfs);
+ dbg("asoc: cpu fs %d mclk %d bfs mult %d bclk %d\n",
+ rtd->cpu_dai->dai_runtime.fs, mclk,
+ SND_SOC_FSB_REAL(rtd->cpu_dai->dai_runtime.bfs), cpu_bclk);
+ }
+
+ /*
+ * Check we have matching bitclocks. If we don't then it means the
+ * sysclock returned by either the codec or cpu DAI (selected by the
+ * machine sysclock function) is wrong compared with the supported DAI
+ * modes for the codec or cpu DAI.
+ */
+ if (cpu_bclk != codec_bclk){
+ printk(KERN_ERR
+ "asoc: codec and cpu bitclocks differ, audio may be wrong speed\n"
+ );
+ printk(KERN_ERR "asoc: codec %d != cpu %d\n", codec_bclk, cpu_bclk);
+ }
+
+ switch(rtd->cpu_dai->dai_runtime.fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ dbg("asoc: DAI codec BCLK master, LRC master\n");
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ dbg("asoc: DAI codec BCLK slave, LRC master\n");
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ dbg("asoc: DAI codec BCLK master, LRC slave\n");
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ dbg("asoc: DAI codec BCLK slave, LRC slave\n");
+ break;
+ }
+ dbg("asoc: mode %x, invert %x\n",
+ rtd->cpu_dai->dai_runtime.fmt & SND_SOC_DAIFMT_FORMAT_MASK,
+ rtd->cpu_dai->dai_runtime.fmt & SND_SOC_DAIFMT_INV_MASK);
+ dbg("asoc: audio rate %d chn %d fmt %x\n", params_rate(params),
+ params_channels(params), params_format(params));
+
+ return 0;
+}
+
+static inline u32 get_rates(struct snd_soc_dai_mode *modes, int nmodes)
+{
+ int i;
+ u32 rates = 0;
+
+ for(i = 0; i < nmodes; i++)
+ rates |= modes[i].pcmrate;
+
+ return rates;
+}
+
+static inline u64 get_formats(struct snd_soc_dai_mode *modes, int nmodes)
+{
+ int i;
+ u64 formats = 0;
+
+ for(i = 0; i < nmodes; i++)
+ formats |= modes[i].pcmfmt;
+
+ return formats;
+}
+
+/*
+ * Called by ALSA when a PCM substream is opened, the runtime->hw record is
+ * then initialized and any private data can be allocated. This also calls
+ * startup for the cpu DAI, platform, machine and codec DAI.
+ */
+static int soc_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_cpu_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ /* startup the audio subsystem */
+ if (rtd->cpu_dai->ops.startup) {
+ ret = rtd->cpu_dai->ops.startup(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open interface %s\n",
+ rtd->cpu_dai->name);
+ goto out;
+ }
+ }
+
+ if (platform->pcm_ops->open) {
+ ret = platform->pcm_ops->open(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
+ goto platform_err;
+ }
+ }
+
+ if (machine->ops && machine->ops->startup) {
+ ret = machine->ops->startup(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: %s startup failed\n", machine->name);
+ goto machine_err;
+ }
+ }
+
+ if (rtd->codec_dai->ops.startup) {
+ ret = rtd->codec_dai->ops.startup(substream);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't open codec %s\n",
+ rtd->codec_dai->name);
+ goto codec_dai_err;
+ }
+ }
+
+ /* create runtime params from DMA, codec and cpu DAI */
+ if (runtime->hw.rates)
+ runtime->hw.rates &=
+ get_rates(codec_dai->caps.mode, codec_dai->caps.num_modes) &
+ get_rates(cpu_dai->caps.mode, cpu_dai->caps.num_modes);
+ else
+ runtime->hw.rates =
+ get_rates(codec_dai->caps.mode, codec_dai->caps.num_modes) &
+ get_rates(cpu_dai->caps.mode, cpu_dai->caps.num_modes);
+ if (runtime->hw.formats)
+ runtime->hw.formats &=
+ get_formats(codec_dai->caps.mode, codec_dai->caps.num_modes) &
+ get_formats(cpu_dai->caps.mode, cpu_dai->caps.num_modes);
+ else
+ runtime->hw.formats =
+ get_formats(codec_dai->caps.mode, codec_dai->caps.num_modes) &
+ get_formats(cpu_dai->caps.mode, cpu_dai->caps.num_modes);
+
+ /* Check that the codec and cpu DAI's are compatible */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ runtime->hw.rate_min =
+ max(rtd->codec_dai->playback.rate_min,
+ rtd->cpu_dai->playback.rate_min);
+ runtime->hw.rate_max =
+ min(rtd->codec_dai->playback.rate_max,
+ rtd->cpu_dai->playback.rate_max);
+ runtime->hw.channels_min =
+ max(rtd->codec_dai->playback.channels_min,
+ rtd->cpu_dai->playback.channels_min);
+ runtime->hw.channels_max =
+ min(rtd->codec_dai->playback.channels_max,
+ rtd->cpu_dai->playback.channels_max);
+ } else {
+ runtime->hw.rate_min =
+ max(rtd->codec_dai->capture.rate_min,
+ rtd->cpu_dai->capture.rate_min);
+ runtime->hw.rate_max =
+ min(rtd->codec_dai->capture.rate_max,
+ rtd->cpu_dai->capture.rate_max);
+ runtime->hw.channels_min =
+ max(rtd->codec_dai->capture.channels_min,
+ rtd->cpu_dai->capture.channels_min);
+ runtime->hw.channels_max =
+ min(rtd->codec_dai->capture.channels_max,
+ rtd->cpu_dai->capture.channels_max);
+ }
+
+ snd_pcm_limit_hw_rates(runtime);
+ if (!runtime->hw.rates) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching rates\n",
+ rtd->codec_dai->name, rtd->cpu_dai->name);
+ goto codec_dai_err;
+ }
+ if (!runtime->hw.formats) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching formats\n",
+ rtd->codec_dai->name, rtd->cpu_dai->name);
+ goto codec_dai_err;
+ }
+ if (!runtime->hw.channels_min || !runtime->hw.channels_max) {
+ printk(KERN_ERR "asoc: %s <-> %s No matching channels\n",
+ rtd->codec_dai->name, rtd->cpu_dai->name);
+ goto codec_dai_err;
+ }
+
+ dbg("asoc: %s <-> %s info:\n", rtd->codec_dai->name, rtd->cpu_dai->name);
+ dbg("asoc: rate mask 0x%x \nasoc: min ch %d max ch %d\n
+ asoc: min rate %d max rate %d\n",
+ runtime->hw.rates, runtime->hw.channels_min,
+ runtime->hw.channels_max, runtime->hw.rate_min, runtime->hw.rate_max);
+
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rtd->cpu_dai->playback.active = rtd->codec_dai->playback.active = 1;
+ else
+ rtd->cpu_dai->capture.active = rtd->codec_dai->capture.active = 1;
+ rtd->cpu_dai->active = rtd->codec_dai->active = 1;
+ rtd->cpu_dai->runtime = runtime;
+ socdev->codec->active++;
+ mutex_unlock(&pcm_mutex);
+ return 0;
+
+codec_dai_err:
+ if (machine->ops && machine->ops->shutdown)
+ machine->ops->shutdown(substream);
+
+machine_err:
+ if (platform->pcm_ops->close)
+ platform->pcm_ops->close(substream);
+
+platform_err:
+ if (rtd->cpu_dai->ops.shutdown)
+ rtd->cpu_dai->ops.shutdown(substream);
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Power down the audio subsytem pmdown_time msecs after close is called.
+ * This is to ensure there are no pops or clicks in between any music tracks
+ * due to DAPM power cycling.
+ */
+static void close_delayed_work(void *data)
+{
+ struct snd_soc_device *socdev = data;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec_dai *codec_dai;
+ int i;
+
+ mutex_lock(&pcm_mutex);
+ for(i = 0; i < codec->num_dai; i++) {
+ codec_dai = &codec->dai[i];
+
+ dbg("pop wq checking: %s status: %s waiting: %s\n",
+ codec_dai->playback.stream_name,
+ codec_dai->playback.active ? "active" : "inactive",
+ codec_dai->pop_wait ? "yes" : "no");
+
+ /* are we waiting on this codec DAI stream */
+ if (codec_dai->pop_wait == 1) {
+
+ codec_dai->pop_wait = 0;
+ snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name,
+ SND_SOC_DAPM_STREAM_STOP);
+
+ /* power down the codec power domain if no longer active */
+ if (codec->active == 0) {
+ dbg("pop wq D3 %s %s\n", codec->name,
+ codec_dai->playback.stream_name);
+ if (codec->dapm_event)
+ codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot);
+ }
+ }
+ }
+ mutex_unlock(&pcm_mutex);
+}
+
+/*
+ * Called by ALSA when a PCM substream is closed. Private data can be
+ * freed here. The cpu DAI, codec DAI, machine and platform are also
+ * shutdown.
+ */
+static int soc_codec_close(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec *codec = socdev->codec;
+
+ mutex_lock(&pcm_mutex);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rtd->cpu_dai->playback.active = rtd->codec_dai->playback.active = 0;
+ else
+ rtd->cpu_dai->capture.active = rtd->codec_dai->capture.active = 0;
+
+ if (rtd->codec_dai->playback.active == 0 &&
+ rtd->codec_dai->capture.active == 0) {
+ rtd->cpu_dai->active = rtd->codec_dai->active = 0;
+ }
+ codec->active--;
+
+ if (rtd->cpu_dai->ops.shutdown)
+ rtd->cpu_dai->ops.shutdown(substream);
+
+ if (rtd->codec_dai->ops.shutdown)
+ rtd->codec_dai->ops.shutdown(substream);
+
+ if (machine->ops && machine->ops->shutdown)
+ machine->ops->shutdown(substream);
+
+ if (platform->pcm_ops->close)
+ platform->pcm_ops->close(substream);
+ rtd->cpu_dai->runtime = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ /* start delayed pop wq here for playback streams */
+ rtd->codec_dai->pop_wait = 1;
+ queue_delayed_work(soc_workq, &soc_stream_work,
+ msecs_to_jiffies(pmdown_time));
+ } else {
+ /* capture streams can be powered down now */
+ snd_soc_dapm_stream_event(codec, rtd->codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_STOP);
+
+ if (codec->active == 0 && rtd->codec_dai->pop_wait == 0){
+ if (codec->dapm_event)
+ codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot);
+ }
+ }
+
+ mutex_unlock(&pcm_mutex);
+ return 0;
+}
+
+/*
+ * Called by ALSA when the PCM substream is prepared, can set format, sample
+ * rate, etc. This function is non atomic and can be called multiple times,
+ * it can refer to the runtime info.
+ */
+static int soc_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec *codec = socdev->codec;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+ if (platform->pcm_ops->prepare) {
+ ret = platform->pcm_ops->prepare(substream);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (rtd->codec_dai->ops.prepare) {
+ ret = rtd->codec_dai->ops.prepare(substream);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (rtd->cpu_dai->ops.prepare)
+ ret = rtd->cpu_dai->ops.prepare(substream);
+
+ /* we only want to start a DAPM playback stream if we are not waiting
+ * on an existing one stopping */
+ if (rtd->codec_dai->pop_wait) {
+ /* we are waiting for the delayed work to start */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ snd_soc_dapm_stream_event(codec,
+ rtd->codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ else {
+ rtd->codec_dai->pop_wait = 0;
+ cancel_delayed_work(&soc_stream_work);
+ if (rtd->codec_dai->digital_mute)
+ rtd->codec_dai->digital_mute(codec, rtd->codec_dai, 0);
+ }
+ } else {
+ /* no delayed work - do we need to power up codec */
+ if (codec->dapm_state != SNDRV_CTL_POWER_D0) {
+
+ if (codec->dapm_event)
+ codec->dapm_event(codec, SNDRV_CTL_POWER_D1);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dapm_stream_event(codec,
+ rtd->codec_dai->playback.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ else
+ snd_soc_dapm_stream_event(codec,
+ rtd->codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+
+ if (codec->dapm_event)
+ codec->dapm_event(codec, SNDRV_CTL_POWER_D0);
+ if (rtd->codec_dai->digital_mute)
+ rtd->codec_dai->digital_mute(codec, rtd->codec_dai, 0);
+
+ } else {
+ /* codec already powered - power on widgets */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dapm_stream_event(codec,
+ rtd->codec_dai->playback.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ else
+ snd_soc_dapm_stream_event(codec,
+ rtd->codec_dai->capture.stream_name,
+ SND_SOC_DAPM_STREAM_START);
+ if (rtd->codec_dai->digital_mute)
+ rtd->codec_dai->digital_mute(codec, rtd->codec_dai, 0);
+ }
+ }
+
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Called by ALSA when the hardware params are set by application. This
+ * function can also be called multiple times and can allocate buffers
+ * (using snd_pcm_lib_* ). It's non-atomic.
+ */
+static int soc_pcm_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_platform *platform = socdev->platform;
+ struct snd_soc_machine *machine = socdev->machine;
+ int ret = 0;
+
+ mutex_lock(&pcm_mutex);
+
+ /* we don't need to match any AC97 params */
+ if (rtd->cpu_dai->type != SND_SOC_DAI_AC97) {
+ ret = soc_hw_match_params(substream, params);
+ if (ret < 0)
+ goto out;
+ } else {
+ struct snd_soc_clock_info clk_info;
+ clk_info.rate = params_rate(params);
+ ret = soc_get_mclk(rtd, &clk_info);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (rtd->codec_dai->ops.hw_params) {
+ ret = rtd->codec_dai->ops.hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't set codec %s hw params\n",
+ rtd->codec_dai->name);
+ goto out;
+ }
+ }
+
+ if (rtd->cpu_dai->ops.hw_params) {
+ ret = rtd->cpu_dai->ops.hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't set interface %s hw params\n",
+ rtd->cpu_dai->name);
+ goto interface_err;
+ }
+ }
+
+ if (platform->pcm_ops->hw_params) {
+ ret = platform->pcm_ops->hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: can't set platform %s hw params\n",
+ platform->name);
+ goto platform_err;
+ }
+ }
+
+ if (machine->ops && machine->ops->hw_params) {
+ ret = machine->ops->hw_params(substream, params);
+ if (ret < 0) {
+ printk(KERN_ERR "asoc: machine hw_params failed\n");
+ goto machine_err;
+ }
+ }
+
+out:
+ mutex_unlock(&pcm_mutex);
+ return ret;
+
+machine_err:
+ if (platform->pcm_ops->hw_free)
+ platform->pcm_ops->hw_free(substream);
+
+platform_err:
+ if (rtd->cpu_dai->ops.hw_free)
+ rtd->cpu_dai->ops.hw_free(substream);
+
+interface_err:
+ if (rtd->codec_dai->ops.hw_free)
+ rtd->codec_dai->ops.hw_free(substream);
+
+ mutex_unlock(&pcm_mutex);
+ return ret;
+}
+
+/*
+ * Free's resources allocated by hw_params, can be called multiple times
+ */
+static int soc_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_machine *machine = socdev->machine;
+
+ mutex_lock(&pcm_mutex);
+
+ /* apply codec digital mute */
+ if (!codec->active && rtd->codec_dai->digital_mute)
+ rtd->codec_dai->digital_mute(codec, rtd->codec_dai, 1);
+
+ /* free any machine hw params */
+ if (machine->ops && machine->ops->hw_free)
+ machine->ops->hw_free(substream);
+
+ /* free any DMA resources */
+ if (platform->pcm_ops->hw_free)
+ platform->pcm_ops->hw_free(substream);
+
+ /* now free hw params for the DAI's */
+ if (rtd->codec_dai->ops.hw_free)
+ rtd->codec_dai->ops.hw_free(substream);
+
+ if (rtd->cpu_dai->ops.hw_free)
+ rtd->cpu_dai->ops.hw_free(substream);
+
+ mutex_unlock(&pcm_mutex);
+ return 0;
+}
+
+static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_platform *platform = socdev->platform;
+ int ret;
+
+ if (rtd->codec_dai->ops.trigger) {
+ ret = rtd->codec_dai->ops.trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (platform->pcm_ops->trigger) {
+ ret = platform->pcm_ops->trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (rtd->cpu_dai->ops.trigger) {
+ ret = rtd->cpu_dai->ops.trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/* ASoC PCM operations */
+static struct snd_pcm_ops soc_pcm_ops = {
+ .open = soc_pcm_open,
+ .close = soc_codec_close,
+ .hw_params = soc_pcm_hw_params,
+ .hw_free = soc_pcm_hw_free,
+ .prepare = soc_pcm_prepare,
+ .trigger = soc_pcm_trigger,
+};
+
+#ifdef CONFIG_PM
+/* powers down audio subsystem for suspend */
+static int soc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec_device *codec_dev = socdev->codec_dev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+
+ /* mute any active DAC's */
+ for(i = 0; i < machine->num_links; i++) {
+ struct snd_soc_codec_dai *dai = machine->dai_link[i].codec_dai;
+ if (dai->digital_mute && dai->playback.active)
+ dai->digital_mute(codec, dai, 1);
+ }
+
+ if (machine->suspend_pre)
+ machine->suspend_pre(pdev, state);
+
+ for(i = 0; i < machine->num_links; i++) {
+ struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->suspend && cpu_dai->type != SND_SOC_DAI_AC97)
+ cpu_dai->suspend(pdev, cpu_dai);
+ if (platform->suspend)
+ platform->suspend(pdev, cpu_dai);
+ }
+
+ /* close any waiting streams and save state */
+ flush_workqueue(soc_workq);
+ codec->suspend_dapm_state = codec->dapm_state;
+
+ for(i = 0; i < codec->num_dai; i++) {
+ char *stream = codec->dai[i].playback.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_SUSPEND);
+ stream = codec->dai[i].capture.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_SUSPEND);
+ }
+
+ if (codec_dev->suspend)
+ codec_dev->suspend(pdev, state);
+
+ for(i = 0; i < machine->num_links; i++) {
+ struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->suspend && cpu_dai->type == SND_SOC_DAI_AC97)
+ cpu_dai->suspend(pdev, cpu_dai);
+ }
+
+ if (machine->suspend_post)
+ machine->suspend_post(pdev, state);
+
+ return 0;
+}
+
+/* powers up audio subsystem after a suspend */
+static int soc_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_machine *machine = socdev->machine;
+ struct snd_soc_platform *platform = socdev->platform;
+ struct snd_soc_codec_device *codec_dev = socdev->codec_dev;
+ struct snd_soc_codec *codec = socdev->codec;
+ int i;
+
+ if (machine->resume_pre)
+ machine->resume_pre(pdev);
+
+ for(i = 0; i < machine->num_links; i++) {
+ struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai;
+ if (cpu_dai->resume && cpu_dai->type == SND_SOC_DAI_AC97)
+ cpu_dai->resume(pdev, cpu_dai);
+ }
+
+ if (codec_dev->resume)
+ codec_dev->resume(pdev);
+
+ for(i = 0; i < codec->num_dai; i++) {
+ char* stream = codec->dai[i].playback.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_RESUME);
+ stream = codec->dai[i].capture.stream_name;
+ if (stream != NULL)
+ snd_soc_dapm_stream_event(codec, stream,
+ SND_SOC_DAPM_STREAM_RESUME);
+ }
+
+ /* unmute any active DAC's */
+ for(i = 0; i < machine->num_links; i++) {
+