#include <linux/workqueue.h>
#include <linux/export.h>
#include <linux/debugfs.h>
+#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
return codec_stream->channels_min;
}
+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,
+ /*
+ * Increase the max buffer bytes as PAGE_SIZE bytes is
+ * not enough to encompass all the scenarios sent by
+ * userspapce.
+ */
+ .buffer_bytes_max = PAGE_SIZE * 4,
+};
+
/**
* snd_soc_runtime_activate() - Increment active count for PCM runtime components
* @rtd: ASoC PCM runtime that is activated
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;
be->dai_link->name, event, dir);
if ((event == SND_SOC_DAPM_STREAM_STOP) &&
- (be->dpcm[dir].users >= 1))
+ (be->dpcm[dir].users >= 1)) {
+ pr_debug("%s Don't close BE \n", __func__);
continue;
+ }
snd_soc_dapm_stream_event(be, dir, event);
}
pm_runtime_get_sync(platform->dev);
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);
/* startup the audio subsystem */
if (cpu_dai->driver->ops && cpu_dai->driver->ops->startup) {
platform->driver->ops->close(substream);
platform_err:
- if (cpu_dai->driver->ops->shutdown)
+ if (cpu_dai->driver->ops && cpu_dai->driver->ops->shutdown)
cpu_dai->driver->ops->shutdown(substream, cpu_dai);
out:
mutex_unlock(&rtd->pcm_mutex);
snd_soc_dai_digital_mute(cpu_dai, 1, substream->stream);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (snd_soc_runtime_ignore_pmdown_time(rtd)) {
+ /* powered down playback stream now */
+ snd_soc_dapm_stream_event(rtd,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ SND_SOC_DAPM_STREAM_STOP);
+ } else {
+ /* start delayed pop wq here for playback streams */
+ rtd->pop_wait = 1;
+ queue_delayed_work(system_power_efficient_wq,
+ &rtd->delayed_work,
+ msecs_to_jiffies(rtd->pmdown_time));
+ }
+ }
if (cpu_dai->driver->ops->shutdown)
cpu_dai->driver->ops->shutdown(substream, cpu_dai);
if (platform->driver->ops && platform->driver->ops->close)
platform->driver->ops->close(substream);
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- if (snd_soc_runtime_ignore_pmdown_time(rtd)) {
- /* powered down playback stream now */
- snd_soc_dapm_stream_event(rtd,
- SNDRV_PCM_STREAM_PLAYBACK,
- SND_SOC_DAPM_STREAM_STOP);
- } else {
- /* start delayed pop wq here for playback streams */
- rtd->pop_wait = 1;
- queue_delayed_work(system_power_efficient_wq,
- &rtd->delayed_work,
- msecs_to_jiffies(rtd->pmdown_time));
- }
- } else {
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) {
/* capture streams can be powered down now */
snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE,
SND_SOC_DAPM_STREAM_STOP);
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dapm_stream_event(rtd,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ SND_SOC_DAPM_STREAM_START);
+
if (rtd->dai_link->ops && rtd->dai_link->ops->prepare) {
ret = rtd->dai_link->ops->prepare(substream);
if (ret < 0) {
cancel_delayed_work(&rtd->delayed_work);
}
- snd_soc_dapm_stream_event(rtd, substream->stream,
- SND_SOC_DAPM_STREAM_START);
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ for (i = 0; i < rtd->num_codecs; i++) {
+ codec_dai = rtd->codec_dais[i];
+ if (codec_dai->capture_active == 1)
+ snd_soc_dapm_stream_event(rtd,
+ SNDRV_PCM_STREAM_CAPTURE,
+ SND_SOC_DAPM_STREAM_START);
+ }
+ }
for (i = 0; i < rtd->num_codecs; i++)
snd_soc_dai_digital_mute(rtd->codec_dais[i], 0,
snd_soc_dai_digital_mute(cpu_dai, 0, substream->stream);
out:
+ if (ret < 0 && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ pr_err("%s: Issue stop stream for codec_dai due to op failure %d = ret\n",
+ __func__, ret);
+ snd_soc_dapm_stream_event(rtd,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ SND_SOC_DAPM_STREAM_STOP);
+ }
mutex_unlock(&rtd->pcm_mutex);
return ret;
}
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
+ /* perform any hw_params fixups */
+ if ((rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) &&
+ rtd->dai_link->be_hw_params_fixup) {
+ ret = rtd->dai_link->be_hw_params_fixup(rtd,
+ params);
+ if (ret < 0)
+ dev_err(rtd->card->dev, "ASoC: fixup failed for %s\n",
+ rtd->dai_link->name);
+ }
+
ret = soc_pcm_params_symmetry(substream, params);
if (ret)
goto out;
+ /* perform any hw_params fixups */
+ if ((rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) &&
+ rtd->dai_link->be_hw_params_fixup) {
+ ret = rtd->dai_link->be_hw_params_fixup(rtd,
+ params);
+ if (ret < 0) {
+ dev_err(rtd->card->dev, "ASoC: fixup failed for %s\n",
+ rtd->dai_link->name);
+ }
+ }
+
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
ret = rtd->dai_link->ops->hw_params(substream, params);
if (ret < 0) {
cpu_dai->channels = params_channels(params);
cpu_dai->sample_bits =
snd_pcm_format_physical_width(params_format(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 =
+ DMA_BIT_MASK(sizeof(dma_addr_t) * 8);
+ substream->dma_buffer.private_data = NULL;
+
+ arch_setup_dma_ops(substream->dma_buffer.dev.dev,
+ 0, 0, NULL, 0);
+ ret = snd_pcm_lib_malloc_pages(substream, PAGE_SIZE);
+ if (ret < 0)
+ goto platform_err;
+ }
out:
mutex_unlock(&rtd->pcm_mutex);
if (cpu_dai->driver->ops && cpu_dai->driver->ops->hw_free)
cpu_dai->driver->ops->hw_free(substream, cpu_dai);
+ 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;
}
if (platform->driver->ops && platform->driver->ops->pointer)
offset = platform->driver->ops->pointer(substream);
+ if (platform->driver->delay_blk)
+ return offset;
+
if (cpu_dai->driver->ops && cpu_dai->driver->ops->delay)
delay += cpu_dai->driver->ops->delay(substream, cpu_dai);
return offset;
}
+static int soc_pcm_delay_blk(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_platform *platform = rtd->platform;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_sframes_t delay = 0;
+
+ if (platform->driver->delay_blk)
+ delay = platform->driver->delay_blk(substream,
+ rtd->codec_dais[0]);
+
+ runtime->delay = delay;
+
+ return 0;
+}
+
/* connect a FE and BE */
static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe,
struct snd_soc_pcm_runtime *be, int stream)
if (!be->dai_link->no_pcm)
continue;
- if (be->cpu_dai->playback_widget == widget)
+ if ((be->cpu_dai->playback_widget == widget &&
+ (be->dai_link->stream_name &&
+ !strcmp(be->dai_link->stream_name,
+ be->cpu_dai->playback_widget->sname))) ||
+ be->codec_dai->playback_widget == widget)
return be;
for (j = 0; j < be->num_codecs; j++) {
if (!be->dai_link->no_pcm)
continue;
- if (be->cpu_dai->capture_widget == widget)
+ if ((be->cpu_dai->capture_widget == widget &&
+ (be->dai_link->stream_name &&
+ !strcmp(be->dai_link->stream_name,
+ be->cpu_dai->capture_widget->sname))) ||
+ be->codec_dai->capture_widget == widget)
return be;
for (j = 0; j < be->num_codecs; j++) {
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
- /* shutdown the BEs */
- dpcm_be_dai_shutdown(fe, substream->stream);
-
dev_dbg(fe->dev, "ASoC: close FE %s\n", fe->dai_link->name);
/* now shutdown the frontend */
soc_pcm_close(substream);
+ /* shutdown the BEs */
+ dpcm_be_dai_shutdown(fe, substream->stream);
+
/* run the stream event for each BE */
dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP);
return 0;
}
+int dpcm_fe_dai_hw_params_be(struct snd_soc_pcm_runtime *fe,
+ struct snd_soc_pcm_runtime *be,
+ struct snd_pcm_hw_params *params, int stream)
+{
+ int ret;
+ struct snd_soc_dpcm *dpcm;
+ struct snd_pcm_substream *be_substream =
+ snd_soc_dpcm_get_substream(be, stream);
+
+ /* is this op for this BE ? */
+ if (!snd_soc_dpcm_be_can_update(fe, be, stream))
+ return 0;
+
+ /* only allow hw_params() if no connected FEs are running */
+ if (!snd_soc_dpcm_can_be_params(fe, be, stream))
+ return 0;
+
+ if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) &&
+ (be->dpcm[stream].state !=
+ SND_SOC_DPCM_STATE_HW_PARAMS) &&
+ (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE))
+ return 0;
+
+ dev_dbg(be->dev, "ASoC: hw_params BE %s\n",
+ fe->dai_link->name);
+
+ /* perform any hw_params fixups */
+ if (be->dai_link->be_hw_params_fixup) {
+ ret = be->dai_link->be_hw_params_fixup(be,
+ params);
+ if (ret < 0) {
+ dev_err(be->dev,
+ "ASoC: hw_params BE fixup failed %d\n",
+ ret);
+ goto unwind;
+ }
+ }
+
+ ret = soc_pcm_hw_params(be_substream, params);
+ if (ret < 0) {
+ dev_err(be->dev, "ASoC: hw_params BE failed %d\n", ret);
+ goto unwind;
+ }
+
+ be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS;
+ return 0;
+
+unwind:
+ /* disable any enabled and non active backends */
+ list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_pcm_substream *be_substream =
+ snd_soc_dpcm_get_substream(be, stream);
+
+ if (!snd_soc_dpcm_be_can_update(fe, be, stream))
+ continue;
+
+ /* only allow hw_free() if no connected FEs are running */
+ if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream))
+ continue;
+
+ if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) &&
+ (be->dpcm[stream].state
+ != SND_SOC_DPCM_STATE_HW_PARAMS) &&
+ (be->dpcm[stream].state
+ != SND_SOC_DPCM_STATE_HW_FREE) &&
+ (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
+ continue;
+
+ soc_pcm_hw_free(be_substream);
+ }
+
+ return ret;
+}
+
int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream)
{
struct snd_soc_dpcm *dpcm;
return ret;
}
+int dpcm_fe_dai_prepare_be(struct snd_soc_pcm_runtime *fe,
+ struct snd_soc_pcm_runtime *be, int stream)
+{
+ struct snd_pcm_substream *be_substream =
+ snd_soc_dpcm_get_substream(be, stream);
+ int ret = 0;
+
+ /* is this op for this BE ? */
+ if (!snd_soc_dpcm_be_can_update(fe, be, stream))
+ return 0;
+
+ if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) &&
+ (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
+ return 0;
+
+ dev_dbg(be->dev, "ASoC: prepare BE %s\n",
+ fe->dai_link->name);
+
+ ret = soc_pcm_prepare(be_substream);
+ if (ret < 0) {
+ dev_err(be->dev, "ASoC: backend prepare failed %d\n",
+ ret);
+ return ret;
+ }
+
+ be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
+ return ret;
+}
+
static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *fe = substream->private_data;
return ret;
}
+static void dpcm_be_async_prepare(void *data, async_cookie_t cookie)
+{
+ struct snd_soc_dpcm *dpcm = data;
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ int stream = dpcm->stream;
+ struct snd_pcm_substream *be_substream =
+ snd_soc_dpcm_get_substream(be, stream);
+ int ret;
+
+ dev_dbg(be->dev, "%s ASoC: prepare BE %s\n", __func__,
+ dpcm->fe->dai_link->name);
+ ret = soc_pcm_prepare(be_substream);
+ if (ret < 0) {
+ be->err_ops = ret;
+ dev_err(be->dev, "ASoC: backend prepare failed %d\n",
+ ret);
+ return;
+ }
+ be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
+}
+
+void dpcm_be_dai_prepare_async(struct snd_soc_pcm_runtime *fe, int stream,
+ struct async_domain *domain)
+{
+ struct snd_soc_dpcm *dpcm;
+ struct snd_soc_dpcm *dpcm_async[DPCM_MAX_BE_USERS];
+ int i = 0, j;
+
+ list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+
+ be->err_ops = 0;
+ /* is this op for this BE ? */
+ if (!snd_soc_dpcm_be_can_update(fe, be, stream))
+ continue;
+
+ if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) &&
+ (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP))
+ continue;
+
+ /* does this BE support async op ?*/
+ if ((fe->dai_link->async_ops & ASYNC_DPCM_SND_SOC_PREPARE) &&
+ (be->dai_link->async_ops & ASYNC_DPCM_SND_SOC_PREPARE)) {
+ dpcm->stream = stream;
+ async_schedule_domain(dpcm_be_async_prepare,
+ dpcm, domain);
+ } else {
+ dpcm_async[i++] = dpcm;
+ if (i == DPCM_MAX_BE_USERS) {
+ dev_dbg(fe->dev, "ASoC: MAX backend users!\n");
+ break;
+ }
+ }
+ }
+
+ for (j = 0; j < i; j++) {
+ struct snd_soc_dpcm *dpcm = dpcm_async[j];
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_pcm_substream *be_substream =
+ snd_soc_dpcm_get_substream(be, stream);
+ int ret;
+
+ dev_dbg(be->dev, "ASoC: prepare BE %s\n",
+ dpcm->fe->dai_link->name);
+
+ ret = soc_pcm_prepare(be_substream);
+ if (ret < 0) {
+ dev_err(be->dev, "ASoC: backend prepare failed %d\n",
+ ret);
+ be->err_ops = ret;
+ return;
+ }
+
+ be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE;
+ }
+}
+
static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *fe = substream->private_data;
+ struct snd_soc_dpcm *dpcm;
int stream = substream->stream, ret = 0;
+ ASYNC_DOMAIN_EXCLUSIVE(async_domain);
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
+ fe->err_ops = 0;
+
dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name);
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
goto out;
}
- ret = dpcm_be_dai_prepare(fe, substream->stream);
- if (ret < 0)
- goto out;
+ if (!(fe->dai_link->async_ops & ASYNC_DPCM_SND_SOC_PREPARE)) {
+ ret = dpcm_be_dai_prepare(fe, substream->stream);
+ if (ret < 0)
+ goto out;
+ /* call prepare on the frontend */
+ ret = soc_pcm_prepare(substream);
+ if (ret < 0) {
+ dev_err(fe->dev, "ASoC: prepare FE %s failed\n",
+ fe->dai_link->name);
+ goto out;
+ }
+ } else {
+ dpcm_be_dai_prepare_async(fe, substream->stream,
+ &async_domain);
- /* call prepare on the frontend */
- ret = soc_pcm_prepare(substream);
- if (ret < 0) {
- dev_err(fe->dev,"ASoC: prepare FE %s failed\n",
- fe->dai_link->name);
- goto out;
+ /* call prepare on the frontend */
+ ret = soc_pcm_prepare(substream);
+ if (ret < 0) {
+ fe->err_ops = ret;
+ dev_err(fe->dev, "ASoC: prepare FE %s failed\n",
+ fe->dai_link->name);
+ }
+
+ async_synchronize_full_domain(&async_domain);
+
+ /* check if any BE failed */
+ list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients,
+ list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+
+ if (be->err_ops < 0) {
+ ret = be->err_ops;
+ goto out;
+ }
+ }
+
+ /* check if FE failed */
+ if (fe->err_ops < 0) {
+ ret = fe->err_ops;
+ goto out;
+ }
}
/* run the stream event for each BE */
return ret;
}
+static int soc_pcm_compat_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->compat_ioctl)
+ return platform->driver->ops->compat_ioctl(substream,
+ cmd, arg);
+ return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
static int soc_pcm_ioctl(struct snd_pcm_substream *substream,
unsigned int cmd, void *arg)
{
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
if (capture)
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
+ if (platform->driver->pcm_new)
+ rtd->platform->driver->pcm_new(rtd);
goto out;
}
+ /* setup any hostless PCMs - i.e. no host IO is performed */
+ if (rtd->dai_link->no_host_mode) {
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->hw_no_buffer = 1;
+ snd_soc_set_runtime_hwparams(
+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
+ &no_host_hardware);
+ }
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->hw_no_buffer = 1;
+ snd_soc_set_runtime_hwparams(
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
+ &no_host_hardware);
+ }
+ }
+
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_fe_dai_close;
rtd->ops.pointer = soc_pcm_pointer;
+ rtd->ops.delay_blk = soc_pcm_delay_blk;
rtd->ops.ioctl = soc_pcm_ioctl;
+ rtd->ops.compat_ioctl = soc_pcm_compat_ioctl;
} else {
rtd->ops.open = soc_pcm_open;
rtd->ops.hw_params = soc_pcm_hw_params;
rtd->ops.hw_free = soc_pcm_hw_free;
rtd->ops.close = soc_pcm_close;
rtd->ops.pointer = soc_pcm_pointer;
+ rtd->ops.delay_blk = soc_pcm_delay_blk;
rtd->ops.ioctl = soc_pcm_ioctl;
+ rtd->ops.compat_ioctl = soc_pcm_compat_ioctl;
}
if (platform->driver->ops) {
pcm->private_free = platform->driver->pcm_free;
out:
- dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",
+ dev_dbg(rtd->card->dev, "%s <-> %s mapping ok\n",
(rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
cpu_dai->name);
return ret;