diff options
Diffstat (limited to 'sound/soc/soc-core.c')
-rw-r--r-- | sound/soc/soc-core.c | 611 |
1 files changed, 495 insertions, 116 deletions
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e2bfe1d..c171182 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -38,6 +38,7 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/soc-dsp.h> #include <sound/initval.h> #define CREATE_TRACE_POINTS @@ -45,7 +46,6 @@ #define NAME_SIZE 32 -static DEFINE_MUTEX(pcm_mutex); static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq); #ifdef CONFIG_DEBUG_FS @@ -60,6 +60,7 @@ static LIST_HEAD(platform_list); static LIST_HEAD(codec_list); static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num); +int soc_dsp_debugfs_add(struct snd_soc_pcm_runtime *rtd); /* * This is a timeout to do a DAPM powerdown after a stream is closed(). @@ -123,6 +124,24 @@ static int format_register_str(struct snd_soc_codec *codec, return 0; } +/* ASoC no host IO hardware. + * TODO: fine tune these values for all host less transfers. + */ +static const struct snd_pcm_hardware no_host_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = PAGE_SIZE >> 2, + .period_bytes_max = PAGE_SIZE >> 1, + .periods_min = 2, + .periods_max = 4, + .buffer_bytes_max = PAGE_SIZE, +}; + /* codec register dump */ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf, size_t count, loff_t pos) @@ -527,7 +546,7 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream) * 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) +int soc_pcm_open(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; @@ -538,7 +557,19 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver; int ret = 0; - mutex_lock(&pcm_mutex); + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + + if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) + snd_soc_set_runtime_hwparams(substream, &no_host_hardware); + + if (rtd->dai_link->ops && rtd->dai_link->ops->startup) { + ret = rtd->dai_link->ops->startup(substream); + if (ret < 0) { + printk(KERN_ERR "asoc: %s startup failed\n", + rtd->dai_link->name); + goto machine_err; + } + } /* startup the audio subsystem */ if (cpu_dai->driver->ops->startup) { @@ -546,7 +577,7 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) if (ret < 0) { printk(KERN_ERR "asoc: can't open interface %s\n", cpu_dai->name); - goto out; + goto cpu_err; } } @@ -567,13 +598,9 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) } } - if (rtd->dai_link->ops && rtd->dai_link->ops->startup) { - ret = rtd->dai_link->ops->startup(substream); - if (ret < 0) { - printk(KERN_ERR "asoc: %s startup failed\n", rtd->dai_link->name); - goto machine_err; - } - } + /* DSP DAI links compat checks are different */ + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) + goto dynamic; /* Check that the codec and cpu DAIs are compatible */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { @@ -658,6 +685,7 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max); +dynamic: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active++; codec_dai->playback_active++; @@ -668,14 +696,11 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) cpu_dai->active++; codec_dai->active++; rtd->codec->active++; - mutex_unlock(&pcm_mutex); + rtd->dai_link->active++; + mutex_unlock(&rtd->pcm_mutex); return 0; config_err: - if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) - rtd->dai_link->ops->shutdown(substream); - -machine_err: if (codec_dai->driver->ops->shutdown) codec_dai->driver->ops->shutdown(substream, codec_dai); @@ -686,8 +711,12 @@ codec_dai_err: platform_err: if (cpu_dai->driver->ops->shutdown) cpu_dai->driver->ops->shutdown(substream, cpu_dai); -out: - mutex_unlock(&pcm_mutex); +cpu_err: + if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) + rtd->dai_link->ops->shutdown(substream); + +machine_err: + mutex_unlock(&rtd->pcm_mutex); return ret; } @@ -702,7 +731,7 @@ static void close_delayed_work(struct work_struct *work) container_of(work, struct snd_soc_pcm_runtime, delayed_work.work); struct snd_soc_dai *codec_dai = rtd->codec_dai; - mutex_lock(&pcm_mutex); + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); pr_debug("pop wq checking: %s status: %s waiting: %s\n", codec_dai->driver->playback.stream_name, @@ -717,7 +746,7 @@ static void close_delayed_work(struct work_struct *work) SND_SOC_DAPM_STREAM_STOP); } - mutex_unlock(&pcm_mutex); + mutex_unlock(&rtd->pcm_mutex); } /* @@ -725,7 +754,7 @@ static void close_delayed_work(struct work_struct *work) * freed here. The cpu DAI, codec DAI, machine and platform are also * shutdown. */ -static int soc_codec_close(struct snd_pcm_substream *substream) +int soc_pcm_close(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; @@ -733,7 +762,7 @@ static int soc_codec_close(struct snd_pcm_substream *substream) struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_codec *codec = rtd->codec; - mutex_lock(&pcm_mutex); + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active--; @@ -746,6 +775,7 @@ static int soc_codec_close(struct snd_pcm_substream *substream) cpu_dai->active--; codec_dai->active--; codec->active--; + rtd->dai_link->active--; /* Muting the DAC suppresses artifacts caused during digital * shutdown, for example from stopping clocks. @@ -759,11 +789,12 @@ static int soc_codec_close(struct snd_pcm_substream *substream) if (codec_dai->driver->ops->shutdown) codec_dai->driver->ops->shutdown(substream, codec_dai); + if (platform->driver->ops && platform->driver->ops->close) + platform->driver->ops->close(substream); + if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) rtd->dai_link->ops->shutdown(substream); - if (platform->driver->ops && platform->driver->ops->close) - platform->driver->ops->close(substream); cpu_dai->runtime = NULL; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { @@ -778,7 +809,7 @@ static int soc_codec_close(struct snd_pcm_substream *substream) SND_SOC_DAPM_STREAM_STOP); } - mutex_unlock(&pcm_mutex); + mutex_unlock(&rtd->pcm_mutex); return 0; } @@ -787,7 +818,7 @@ static int soc_codec_close(struct snd_pcm_substream *substream) * 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) +int soc_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; @@ -795,7 +826,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) struct snd_soc_dai *codec_dai = rtd->codec_dai; int ret = 0; - mutex_lock(&pcm_mutex); + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); if (rtd->dai_link->ops && rtd->dai_link->ops->prepare) { ret = rtd->dai_link->ops->prepare(substream); @@ -848,7 +879,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) snd_soc_dai_digital_mute(codec_dai, 0); out: - mutex_unlock(&pcm_mutex); + mutex_unlock(&rtd->pcm_mutex); return ret; } @@ -857,7 +888,7 @@ out: * 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, +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; @@ -866,7 +897,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai = rtd->codec_dai; int ret = 0; - mutex_lock(&pcm_mutex); + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) { ret = rtd->dai_link->ops->hw_params(substream, params); @@ -905,8 +936,21 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, rtd->rate = params_rate(params); + /* malloc a page for hostless IO. + * FIXME: rework with alsa-lib changes so that this malloc is not required. + */ + if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) { + substream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV; + substream->dma_buffer.dev.dev = &rtd->dev; + substream->dma_buffer.dev.dev->coherent_dma_mask = ISA_DMA_THRESHOLD; + substream->dma_buffer.private_data = NULL; + + ret = snd_pcm_lib_malloc_pages(substream, PAGE_SIZE); + if (ret < 0) + goto platform_err; + } out: - mutex_unlock(&pcm_mutex); + mutex_unlock(&rtd->pcm_mutex); return ret; platform_err: @@ -921,14 +965,14 @@ codec_err: if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) rtd->dai_link->ops->hw_free(substream); - mutex_unlock(&pcm_mutex); + mutex_unlock(&rtd->pcm_mutex); return ret; } /* * Frees resources allocated by hw_params, can be called multiple times */ -static int soc_pcm_hw_free(struct snd_pcm_substream *substream) +int soc_pcm_hw_free(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; @@ -936,7 +980,7 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_codec *codec = rtd->codec; - mutex_lock(&pcm_mutex); + mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); /* apply codec digital mute */ if (!codec->active) @@ -957,11 +1001,13 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) if (cpu_dai->driver->ops->hw_free) cpu_dai->driver->ops->hw_free(substream, cpu_dai); - mutex_unlock(&pcm_mutex); + if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) + snd_pcm_lib_free_pages(substream); + mutex_unlock(&rtd->pcm_mutex); return 0; } -static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; @@ -989,12 +1035,40 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } +int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + if (codec_dai->driver->ops->bespoke_trigger) { + ret = codec_dai->driver->ops->bespoke_trigger(substream, cmd, codec_dai); + if (ret < 0) + return ret; + } + + if (platform->driver->bespoke_trigger) { + ret = platform->driver->bespoke_trigger(substream, cmd); + if (ret < 0) + return ret; + } + + if (cpu_dai->driver->ops->bespoke_trigger) { + ret = cpu_dai->driver->ops->bespoke_trigger(substream, cmd, cpu_dai); + if (ret < 0) + return ret; + } + return 0; +} + /* * soc level wrapper for pointer callback * If cpu_dai, codec_dai, platform driver has the delay callback, than * the runtime->delay will be updated accordingly. */ -static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) +snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_platform *platform = rtd->platform; @@ -1021,16 +1095,74 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) return offset; } -/* 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, - .pointer = soc_pcm_pointer, -}; +static int soc_pcm_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_platform *platform = rtd->platform; + + if (platform->driver->ops->ioctl) + return platform->driver->ops->ioctl(substream, cmd, arg); + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +struct snd_soc_codec *snd_soc_card_get_codec(struct snd_soc_card *card, + const char *codec_name) +{ + struct snd_soc_codec *codec = NULL; + + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + if (!strcmp(codec->name, codec_name)) + return codec; + } + + return codec; +} +EXPORT_SYMBOL(snd_soc_card_get_codec); + +int snd_soc_card_active_links(struct snd_soc_card *card) +{ + int i; + int count = 0; + + for (i = 0; i < card->num_rtd; i++) { + /* count FEs: dynamic and legacy */ + if (!card->rtd[i].dai_link->no_pcm) + count += card->rtd[i].dai_link->active; + } + + return count; +} +EXPORT_SYMBOL(snd_soc_card_active_links); + +struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card, + const char *dai_link, int stream) +{ + int i; + + for (i = 0; i < card->num_links; i++) { + if (card->rtd[i].dai_link->no_pcm && + !strcmp(card->rtd[i].dai_link->name, dai_link)) + return card->rtd[i].pcm->streams[stream].substream; + } + dev_dbg(card->dev, "failed to find dai link %s\n", dai_link); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_substream); + +struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, + const char *dai_link) +{ + int i; + + for (i = 0; i < card->num_links; i++) { + if (!strcmp(card->rtd[i].dai_link->name, dai_link)) + return &card->rtd[i]; + } + dev_dbg(card->dev, "failed to find rtd %s\n", dai_link); + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_get_pcm_runtime); #ifdef CONFIG_PM_SLEEP /* powers down audio subsystem for suspend */ @@ -1040,6 +1172,9 @@ int snd_soc_suspend(struct device *dev) struct snd_soc_codec *codec; int i; + /* cancel pending deferred resume if any */ + cancel_work_sync(&card->deferred_resume_work); + /* If the initialization of this soc device failed, there is no codec * associated with it. Just bail out in this case. */ @@ -1061,16 +1196,22 @@ int snd_soc_suspend(struct device *dev) struct snd_soc_dai *dai = card->rtd[i].codec_dai; struct snd_soc_dai_driver *drv = dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 1); + if (card->rtd[i].dai_link->dynamic) + soc_dsp_be_digital_mute(&card->rtd[i], 1); + else { + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 1); + } } /* suspend all pcms */ for (i = 0; i < card->num_rtd; i++) { - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; snd_pcm_suspend_all(card->rtd[i].pcm); @@ -1083,14 +1224,19 @@ int snd_soc_suspend(struct device *dev) struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; struct snd_soc_platform *platform = card->rtd[i].platform; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control) - cpu_dai->driver->suspend(cpu_dai); - if (platform->driver->suspend && !platform->suspended) { - platform->driver->suspend(cpu_dai); - platform->suspended = 1; + if (card->rtd[i].dai_link->dynamic) { + soc_dsp_fe_suspend(&card->rtd[i]); + } else { + if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control) + cpu_dai->driver->suspend(cpu_dai); + if (platform->driver->suspend && !platform->suspended) { + platform->driver->suspend(cpu_dai); + platform->suspended = 1; + } } } @@ -1103,7 +1249,8 @@ int snd_soc_suspend(struct device *dev) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; if (driver->playback.stream_name != NULL) @@ -1137,11 +1284,15 @@ int snd_soc_suspend(struct device *dev) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control) - cpu_dai->driver->suspend(cpu_dai); + if (card->rtd[i].dai_link->dynamic) + soc_dsp_be_ac97_cpu_dai_suspend(&card->rtd[i]); + else + if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control) + cpu_dai->driver->suspend(cpu_dai); } if (card->suspend_post) @@ -1177,11 +1328,15 @@ static void soc_resume_deferred(struct work_struct *work) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->resume && cpu_dai->driver->ac97_control) - cpu_dai->driver->resume(cpu_dai); + if (card->rtd[i].dai_link->dynamic) + soc_dsp_be_ac97_cpu_dai_resume(&card->rtd[i]); + else + if (cpu_dai->driver->resume && cpu_dai->driver->ac97_control) + cpu_dai->driver->resume(cpu_dai); } list_for_each_entry(codec, &card->codec_dev_list, card_list) { @@ -1206,7 +1361,8 @@ static void soc_resume_deferred(struct work_struct *work) for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai_driver *driver = card->rtd[i].codec_dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; if (driver->playback.stream_name != NULL) @@ -1223,25 +1379,35 @@ static void soc_resume_deferred(struct work_struct *work) struct snd_soc_dai *dai = card->rtd[i].codec_dai; struct snd_soc_dai_driver *drv = dai->driver; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, 0); + if (card->rtd[i].dai_link->dynamic) + soc_dsp_be_digital_mute(&card->rtd[i], 0); + else { + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 0); + } } for (i = 0; i < card->num_rtd; i++) { struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; struct snd_soc_platform *platform = card->rtd[i].platform; - if (card->rtd[i].dai_link->ignore_suspend) + if (card->rtd[i].dai_link->ignore_suspend || + card->rtd[i].dai_link->no_pcm) continue; - if (cpu_dai->driver->resume && !cpu_dai->driver->ac97_control) - cpu_dai->driver->resume(cpu_dai); - if (platform->driver->resume && platform->suspended) { - platform->driver->resume(cpu_dai); - platform->suspended = 0; + if (card->rtd[i].dai_link->dynamic) { + soc_dsp_fe_resume(&card->rtd[i]); + } else { + if (cpu_dai->driver->resume && !cpu_dai->driver->ac97_control) + cpu_dai->driver->resume(cpu_dai); + if (platform->driver->resume && platform->suspended) { + platform->driver->resume(cpu_dai); + platform->suspended = 0; + } } } @@ -1286,8 +1452,30 @@ EXPORT_SYMBOL_GPL(snd_soc_resume); #define snd_soc_resume NULL #endif +#define NULL_FORMATS \ + (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 |\ + SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24 |\ + SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32) + static struct snd_soc_dai_ops null_dai_ops = { }; +static struct snd_soc_dai_driver null_codec_dai_drv = { + .name = "null-codec-dai", + .ops = &null_dai_ops, + .capture = { + .channels_min = 1 , + .channels_max = 16, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = NULL_FORMATS, + }, + .playback = { + .channels_min = 1 , + .channels_max = 16, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = NULL_FORMATS, + }, +}; +static struct snd_soc_codec_driver null_codec_drv = {}; static int soc_bind_dai_link(struct snd_soc_card *card, int num) { @@ -1329,7 +1517,7 @@ find_codec: /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/ list_for_each_entry(codec_dai, &dai_list, list) { - if (codec->dev == codec_dai->dev && + if ((codec->dev == codec_dai->dev || codec->driver == &null_codec_drv) && !strcmp(codec_dai->name, dai_link->codec_dai_name)) { rtd->codec_dai = codec_dai; goto find_platform; @@ -1396,7 +1584,7 @@ static void soc_remove_codec(struct snd_soc_codec *codec) module_put(codec->dev->driver->owner); } -static void soc_remove_dai_link(struct snd_soc_card *card, int num) +static void soc_remove_dai_link(struct snd_soc_card *card, int num, int order) { struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; struct snd_soc_codec *codec = rtd->codec; @@ -1413,7 +1601,8 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num) } /* remove the CODEC DAI */ - if (codec_dai && codec_dai->probed) { + if (codec_dai && codec_dai->probed && + codec_dai->driver->remove_order == order) { if (codec_dai->driver->remove) { err = codec_dai->driver->remove(codec_dai); if (err < 0) @@ -1421,10 +1610,12 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num) } codec_dai->probed = 0; list_del(&codec_dai->card_list); + module_put(codec_dai->dev->driver->owner); } /* remove the platform */ - if (platform && platform->probed) { + if (platform && platform->probed && + platform->driver->remove_order == order) { if (platform->driver->remove) { err = platform->driver->remove(platform); if (err < 0) @@ -1436,11 +1627,13 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num) } /* remove the CODEC */ - if (codec && codec->probed) + if (codec && codec->probed && + codec->driver->remove_order == order) soc_remove_codec(codec); /* remove the cpu_dai */ - if (cpu_dai && cpu_dai->probed) { + if (cpu_dai && cpu_dai->probed && + cpu_dai->driver->remove_order == order) { if (cpu_dai->driver->remove) { err = cpu_dai->driver->remove(cpu_dai); if (err < 0) @@ -1454,11 +1647,13 @@ static void soc_remove_dai_link(struct snd_soc_card *card, int num) static void soc_remove_dai_links(struct snd_soc_card *card) { - int i; - - for (i = 0; i < card->num_rtd; i++) - soc_remove_dai_link(card, i); + int dai, order; + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + for (dai = 0; dai < card->num_rtd; dai++) + soc_remove_dai_link(card, dai, order); + } card->num_rtd = 0; } @@ -1575,6 +1770,11 @@ static int soc_post_component_init(struct snd_soc_card *card, rtd->dev.parent = card->dev; rtd->dev.release = rtd_release; rtd->dev.init_name = name; + mutex_init(&rtd->pcm_mutex); + INIT_LIST_HEAD(&rtd->dsp[SNDRV_PCM_STREAM_PLAYBACK].be_clients); + INIT_LIST_HEAD(&rtd->dsp[SNDRV_PCM_STREAM_CAPTURE].be_clients); + INIT_LIST_HEAD(&rtd->dsp[SNDRV_PCM_STREAM_PLAYBACK].fe_clients); + INIT_LIST_HEAD(&rtd->dsp[SNDRV_PCM_STREAM_CAPTURE].fe_clients); ret = device_register(&rtd->dev); if (ret < 0) { dev_err(card->dev, @@ -1596,10 +1796,21 @@ static int soc_post_component_init(struct snd_soc_card *card, dev_err(codec->dev, "asoc: failed to add codec sysfs files: %d\n", ret); +#ifdef CONFIG_DEBUG_FS + /* add DSP sysfs entries */ + if (!dai_link->dynamic) + goto out; + + ret = soc_dsp_debugfs_add(rtd); + if (ret < 0) + dev_err(&rtd->dev, "asoc: failed to add dsp sysfs entries: %d\n", ret); + +out: +#endif return 0; } -static int soc_probe_dai_link(struct snd_soc_card *card, int num) +static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) { struct snd_soc_dai_link *dai_link = &card->dai_link[num]; struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; @@ -1608,19 +1819,22 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num) struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai; int ret; - dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num); + dev_dbg(card->dev, "probe %s dai link %d late %d\n", + card->name, num, order); /* config components */ codec_dai->codec = codec; cpu_dai->platform = platform; codec_dai->card = card; cpu_dai->card = card; + codec->dapm.card = platform->dapm.card = card; /* set default power off timeout */ rtd->pmdown_time = pmdown_time; /* probe the cpu_dai */ - if (!cpu_dai->probed) { + if (!cpu_dai->probed && + cpu_dai->driver->probe_order == order) { if (!try_module_get(cpu_dai->dev->driver->owner)) return -ENODEV; @@ -1639,17 +1853,20 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num) } /* probe the CODEC */ - if (!codec->probed) { + if (!codec->probed && + codec->driver->probe_order == order) { ret = soc_probe_codec(card, codec); if (ret < 0) return ret; } /* probe the platform */ - if (!platform->probed) { + if (!platform->probed && + platform->driver->probe_order == order) { if (!try_module_get(platform->dev->driver->owner)) return -ENODEV; + platform->card = card; if (platform->driver->probe) { ret = platform->driver->probe(platform); if (ret < 0) { @@ -1665,12 +1882,15 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num) } /* probe the CODEC DAI */ - if (!codec_dai->probed) { + if (!codec_dai->probed && codec_dai->driver->probe_order == order) { + if (!try_module_get(codec_dai->dev->driver->owner)) + return -ENODEV; if (codec_dai->driver->probe) { ret = codec_dai->driver->probe(codec_dai); if (ret < 0) { printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n", codec_dai->name); + module_put(codec_dai->dev->driver->owner); return ret; } } @@ -1680,6 +1900,10 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num) list_add(&codec_dai->card_list, &card->dai_dev_list); } + /* complete DAI probe during last probe */ + if (order != SND_SOC_COMP_ORDER_LAST) + return 0; + /* DAPM dai link stream work */ INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); @@ -1820,7 +2044,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) struct snd_soc_codec *codec; struct snd_soc_codec_conf *codec_conf; enum snd_soc_compress_type compress_type; - int ret, i; + int ret, i, order; mutex_lock(&card->mutex); @@ -1876,6 +2100,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) card->dapm.bias_level = SND_SOC_BIAS_OFF; card->dapm.dev = card->dev; card->dapm.card = card; + card->dapm.stream_event = card->stream_event; list_add(&card->dapm.list, &card->dapm_list); #ifdef CONFIG_DEBUG_FS @@ -1898,12 +2123,16 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) goto card_probe_error; } - for (i = 0; i < card->num_links; i++) { - ret = soc_probe_dai_link(card, i); - if (ret < 0) { - pr_err("asoc: failed to instantiate card %s: %d\n", + /* early DAI link probe */ + for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; + order++) { + for (i = 0; i < card->num_links; i++) { + ret = soc_probe_dai_link(card, i, order); + if (ret < 0) { + pr_err("asoc: failed to instantiate card %s: %d\n", card->name, ret); - goto probe_dai_err; + goto probe_dai_err; + } } } @@ -2092,6 +2321,11 @@ int snd_soc_poweroff(struct device *dev) } EXPORT_SYMBOL_GPL(snd_soc_poweroff); +void soc_shutdown(struct platform_device *pdev) +{ + snd_soc_poweroff(&pdev->dev); +} + const struct dev_pm_ops snd_soc_pm_ops = { .suspend = snd_soc_suspend, .resume = snd_soc_resume, @@ -2108,6 +2342,7 @@ static struct platform_driver soc_driver = { }, .probe = soc_probe, .remove = soc_remove, + .shutdown = soc_shutdown, }; /* create a new pcm */ @@ -2117,6 +2352,7 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_substream *substream[2]; struct snd_pcm *pcm; char new_name[64]; int ret = 0, playback = 0, capture = 0; @@ -2125,10 +2361,15 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) snprintf(new_name, sizeof(new_name), "%s %s-%d", rtd->dai_link->stream_name, codec_dai->name, num); - if (codec_dai->driver->playback.channels_min) - playback = 1; - if (codec_dai->driver->capture.channels_min) - capture = 1; + if (rtd->dai_link->dynamic) { + playback = rtd->dai_link->dsp_link->playback; + capture = rtd->dai_link->dsp_link->capture; + } else { + if (codec_dai->driver->playback.channels_min) + playback = 1; + if (codec_dai->driver->capture.channels_min) + capture = 1; + } dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name); ret = snd_pcm_new(rtd->card->snd_card, new_name, @@ -2140,25 +2381,67 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->pcm = pcm; pcm->private_data = rtd; + + substream[SNDRV_PCM_STREAM_PLAYBACK] = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + substream[SNDRV_PCM_STREAM_CAPTURE] = + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + + if (rtd->dai_link->no_pcm) { + if (playback) + substream[SNDRV_PCM_STREAM_PLAYBACK]->private_data = rtd; + if (capture) + substream[SNDRV_PCM_STREAM_CAPTURE]->private_data = rtd; + goto out; + } + + /* setup any hostless PCMs - i.e. no host IO is performed */ + if (rtd->dai_link->no_host_mode) { + substream[SNDRV_PCM_STREAM_PLAYBACK]->hw_no_buffer = 1; + substream[SNDRV_PCM_STREAM_CAPTURE]->hw_no_buffer = 1; + snd_soc_set_runtime_hwparams(substream[SNDRV_PCM_STREAM_PLAYBACK], + &no_host_hardware); + snd_soc_set_runtime_hwparams(substream[SNDRV_PCM_STREAM_CAPTURE], + &no_host_hardware); + } + + /* ASoC PCM operations */ + if (rtd->dai_link->dynamic) { + rtd->ops.open = soc_dsp_fe_dai_open; + rtd->ops.hw_params = soc_dsp_fe_dai_hw_params; + rtd->ops.prepare = soc_dsp_fe_dai_prepare; + rtd->ops.trigger = soc_dsp_fe_dai_trigger; + rtd->ops.hw_free = soc_dsp_fe_dai_hw_free; + rtd->ops.close = soc_dsp_fe_dai_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } else { + rtd->ops.open = soc_pcm_open; + rtd->ops.hw_params = soc_pcm_hw_params; + rtd->ops.prepare = soc_pcm_prepare; + rtd->ops.trigger = soc_pcm_trigger; + rtd->ops.hw_free = soc_pcm_hw_free; + rtd->ops.close = soc_pcm_close; + rtd->ops.pointer = soc_pcm_pointer; + rtd->ops.ioctl = soc_pcm_ioctl; + } + if (platform->driver->ops) { - soc_pcm_ops.mmap = platform->driver->ops->mmap; - soc_pcm_ops.pointer = platform->driver->ops->pointer; - soc_pcm_ops.ioctl = platform->driver->ops->ioctl; - soc_pcm_ops.copy = platform->driver->ops->copy; - soc_pcm_ops.silence = platform->driver->ops->silence; - soc_pcm_ops.ack = platform->driver->ops->ack; - soc_pcm_ops.page = platform->driver->ops->page; + rtd->ops.ack = platform->driver->ops->ack; + rtd->ops.copy = platform->driver->ops->copy; + rtd->ops.silence = platform->driver->ops->silence; + rtd->ops.page = platform->driver->ops->page; + rtd->ops.mmap = platform->driver->ops->mmap; } if (playback) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops); if (capture) - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops); if (platform->driver->pcm_new) { - ret = platform->driver->pcm_new(rtd->card->snd_card, - codec_dai, pcm); + ret = platform->driver->pcm_new(rtd); if (ret < 0) { pr_err("asoc: platform pcm constructor failed\n"); return ret; @@ -2166,6 +2449,7 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) } pcm->private_free = platform->driver->pcm_free; +out: printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name, cpu_dai->name); return ret; @@ -2189,6 +2473,28 @@ int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_codec_volatile_register); +unsigned int snd_soc_platform_read(struct snd_soc_platform *platform, + unsigned int reg) +{ + unsigned int ret; + + ret = platform->driver->read(platform, reg); + dev_dbg(platform->dev, "read %x => %x\n", reg, ret); + trace_snd_soc_preg_read(platform, reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_platform_read); + +unsigned int snd_soc_platform_write(struct snd_soc_platform *platform, + unsigned int reg, unsigned int val) +{ + dev_dbg(platform->dev, "write %x = %x\n", reg, val); + trace_snd_soc_preg_write(platform, reg, val); + return platform->driver->write(platform, reg, val); +} +EXPORT_SYMBOL_GPL(snd_soc_platform_write); + /** * snd_soc_codec_readable_register: Report if a register is readable. * @@ -2411,6 +2717,8 @@ int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, const struct snd_pcm_hardware *hw) { struct snd_pcm_runtime *runtime = substream->runtime; + if (!runtime) + return 0; runtime->hw.info = hw->info; runtime->hw.formats = hw->formats; runtime->hw.period_bytes_min = hw->period_bytes_min; @@ -2504,6 +2812,36 @@ int snd_soc_add_controls(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_add_controls); /** + * snd_soc_add_platform_controls - add an array of controls to a platform. + * Convienience function to add a list of controls. + * + * @platform: platform to add controls to + * @controls: array of controls to add + * @num_controls: number of elements in the array + * + * Return 0 for success, else error. + */ +int snd_soc_add_platform_controls(struct snd_soc_platform *platform, + const struct snd_kcontrol_new *controls, int num_controls) +{ + struct snd_card *card = platform->card->snd_card; + int err, i; + + for (i = 0; i < num_controls; i++) { + const struct snd_kcontrol_new *control = &controls[i]; + err = snd_ctl_add(card, snd_soc_cnew(control, platform, + control->name, NULL)); + if (err < 0) { + dev_err(platform->dev, "Failed to add %s %d\n",control->name, err); + return err; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_add_platform_controls); + +/** * snd_soc_info_enum_double - enumerated double mixer info callback * @kcontrol: mixer control * @uinfo: control element information @@ -2525,7 +2863,8 @@ int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, if (uinfo->value.enumerated.item > e->max - 1) uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item]); + snd_soc_get_enum_text(e, uinfo->value.enumerated.item)); + return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_enum_double); @@ -2689,7 +3028,7 @@ int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol, if (uinfo->value.enumerated.item > e->max - 1) uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, - e->texts[uinfo->value.enumerated.item]); + snd_soc_get_enum_text(e, uinfo->value.enumerated.item)); return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext); @@ -3370,6 +3709,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); int snd_soc_register_card(struct snd_soc_card *card) { int i; + int ret = 0; if (!card->name || !card->dev) return -EINVAL; @@ -3387,12 +3727,43 @@ int snd_soc_register_card(struct snd_soc_card *card) return -ENOMEM; card->rtd_aux = &card->rtd[card->num_links]; - for (i = 0; i < card->num_links; i++) + for (i = 0; i < card->num_links; i++) { card->rtd[i].dai_link = &card->dai_link[i]; + if (card->rtd[i].dai_link->dynamic) { + + card->rtd[i].dai_link->codec_name = "null-codec"; + card->rtd[i].dai_link->codec_dai_name = "null-codec-dai"; + + ret = snd_soc_register_codec(card->dev, &null_codec_drv, + &null_codec_dai_drv, 1); + if (ret < 0) { + printk(KERN_ERR "%s: failed to register dynamic DAI link %d\n", + __func__, ret); + goto out; + } + + continue; + } + if (card->rtd[i].dai_link->no_codec) { + card->rtd[i].dai_link->codec_name = "null-codec"; + + ret = snd_soc_register_codec(card->dev, &null_codec_drv, + &null_codec_dai_drv, 1); + if (ret < 0) { + printk(KERN_ERR "%s: failed to register dynamic DAI link %d\n", + __func__, ret); + goto out; + } + continue; + } + } INIT_LIST_HEAD(&card->list); card->instantiated = 0; mutex_init(&card->mutex); + mutex_init(&card->dapm_mutex); + mutex_init(&card->dsp_mutex); + mutex_init(&card->power_mutex); mutex_lock(&client_mutex); list_add(&card->list, &card_list); @@ -3401,7 +3772,8 @@ int snd_soc_register_card(struct snd_soc_card *card) dev_dbg(card->dev, "Registered card '%s'\n", card->name); - return 0; +out: + return ret; } EXPORT_SYMBOL_GPL(snd_soc_register_card); @@ -3646,7 +4018,10 @@ int snd_soc_register_platform(struct device *dev, } platform->dev = dev; + platform->dapm.platform = platform; platform->driver = platform_drv; + platform->dapm.dev = dev; + platform->dapm.stream_event = platform_drv->stream_event; mutex_lock(&client_mutex); list_add(&platform->list, &platform_list); @@ -3739,7 +4114,10 @@ int snd_soc_register_codec(struct device *dev, return -ENOMEM; /* create CODEC component name */ - codec->name = fmt_single_name(dev, &codec->id); + if (codec_drv == &null_codec_drv) + codec->name = kstrdup("null-codec", GFP_KERNEL); + else + codec->name = fmt_single_name(dev, &codec->id); if (codec->name == NULL) { kfree(codec); return -ENOMEM; @@ -3759,6 +4137,7 @@ int snd_soc_register_codec(struct device *dev, codec->dapm.dev = dev; codec->dapm.codec = codec; codec->dapm.seq_notifier = codec_drv->seq_notifier; + codec->dapm.stream_event = codec_drv->stream_event; codec->dev = dev; codec->driver = codec_drv; codec->num_dai = num_dai; |