cregit-Linux how code gets into the kernel

Release 4.7 sound/soc/soc-pcm.c

Directory: sound/soc
/*
 * soc-pcm.c  --  ALSA SoC PCM
 *
 * Copyright 2005 Wolfson Microelectronics PLC.
 * Copyright 2005 Openedhand Ltd.
 * Copyright (C) 2010 Slimlogic Ltd.
 * Copyright (C) 2010 Texas Instruments Inc.
 *
 * Authors: Liam Girdwood <lrg@ti.com>
 *          Mark Brown <broonie@opensource.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.
 *
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/export.h>
#include <linux/debugfs.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dpcm.h>
#include <sound/initval.h>


#define DPCM_MAX_BE_USERS	8

/*
 * snd_soc_dai_stream_valid() - check if a DAI supports the given stream
 *
 * Returns true if the DAI supports the indicated stream type.
 */

static bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int stream) { struct snd_soc_pcm_stream *codec_stream; if (stream == SNDRV_PCM_STREAM_PLAYBACK) codec_stream = &dai->driver->playback; else codec_stream = &dai->driver->capture; /* If the codec specifies any rate at all, it supports the stream. */ return codec_stream->rates; }

Contributors

PersonTokensPropCommitsCommitProp
ricard wanderlofricard wanderlof50100.00%1100.00%
Total50100.00%1100.00%

/** * snd_soc_runtime_activate() - Increment active count for PCM runtime components * @rtd: ASoC PCM runtime that is activated * @stream: Direction of the PCM stream * * Increments the active count for all the DAIs and components attached to a PCM * runtime. Should typically be called when a stream is opened. * * Must be called with the rtd->pcm_mutex being held */
void snd_soc_runtime_activate(struct snd_soc_pcm_runtime *rtd, int stream) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int i; lockdep_assert_held(&rtd->pcm_mutex); if (stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active++; for (i = 0; i < rtd->num_codecs; i++) rtd->codec_dais[i]->playback_active++; } else { cpu_dai->capture_active++; for (i = 0; i < rtd->num_codecs; i++) rtd->codec_dais[i]->capture_active++; } cpu_dai->active++; cpu_dai->component->active++; for (i = 0; i < rtd->num_codecs; i++) { rtd->codec_dais[i]->active++; rtd->codec_dais[i]->component->active++; } }

Contributors

PersonTokensPropCommitsCommitProp
lars-peter clausenlars-peter clausen8051.61%375.00%
benoit coussonbenoit cousson7548.39%125.00%
Total155100.00%4100.00%

/** * snd_soc_runtime_deactivate() - Decrement active count for PCM runtime components * @rtd: ASoC PCM runtime that is deactivated * @stream: Direction of the PCM stream * * Decrements the active count for all the DAIs and components attached to a PCM * runtime. Should typically be called when a stream is closed. * * Must be called with the rtd->pcm_mutex being held */
void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) { struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int i; lockdep_assert_held(&rtd->pcm_mutex); if (stream == SNDRV_PCM_STREAM_PLAYBACK) { cpu_dai->playback_active--; for (i = 0; i < rtd->num_codecs; i++) rtd->codec_dais[i]->playback_active--; } else { cpu_dai->capture_active--; for (i = 0; i < rtd->num_codecs; i++) rtd->codec_dais[i]->capture_active--; } cpu_dai->active--; cpu_dai->component->active--; for (i = 0; i < rtd->num_codecs; i++) { rtd->codec_dais[i]->component->active--; rtd->codec_dais[i]->active--; } }

Contributors

PersonTokensPropCommitsCommitProp
lars-peter clausenlars-peter clausen8051.61%375.00%
benoit coussonbenoit cousson7548.39%125.00%
Total155100.00%4100.00%

/** * snd_soc_runtime_ignore_pmdown_time() - Check whether to ignore the power down delay * @rtd: The ASoC PCM runtime that should be checked. * * This function checks whether the power down delay should be ignored for a * specific PCM runtime. Returns true if the delay is 0, if it the DAI link has * been configured to ignore the delay, or if none of the components benefits * from having the delay. */
bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) { int i; bool ignore = true; if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) return true; for (i = 0; i < rtd->num_codecs; i++) ignore &= rtd->codec_dais[i]->component->ignore_pmdown_time; return rtd->cpu_dai->component->ignore_pmdown_time && ignore; }

Contributors

PersonTokensPropCommitsCommitProp
lars-peter clausenlars-peter clausen3953.42%266.67%
benoit coussonbenoit cousson3446.58%133.33%
Total73100.00%3100.00%

/** * snd_soc_set_runtime_hwparams - set the runtime hardware parameters * @substream: the pcm substream * @hw: the hardware parameters * * Sets the substream runtime hardware parameters. */
int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, const struct snd_pcm_hardware *hw) { struct snd_pcm_runtime *runtime = substream->runtime; runtime->hw.info = hw->info; runtime->hw.formats = hw->formats; runtime->hw.period_bytes_min = hw->period_bytes_min; runtime->hw.period_bytes_max = hw->period_bytes_max; runtime->hw.periods_min = hw->periods_min; runtime->hw.periods_max = hw->periods_max; runtime->hw.buffer_bytes_max = hw->buffer_bytes_max; runtime->hw.fifo_size = hw->fifo_size; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
lars-peter clausenlars-peter clausen108100.00%1100.00%
Total108100.00%1100.00%

EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams); /* DPCM stream event, send event to FE and all active BEs. */
int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event) { struct snd_soc_dpcm *dpcm; list_for_each_entry(dpcm, &fe->dpcm[dir].be_clients, list_be) { struct snd_soc_pcm_runtime *be = dpcm->be; dev_dbg(be->dev, "ASoC: BE %s event %d dir %d\n", be->dai_link->name, event, dir); snd_soc_dapm_stream_event(be, dir, event); } snd_soc_dapm_stream_event(fe, dir, event); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
liam girdwoodliam girdwood80100.00%2100.00%
Total80100.00%2100.00%


static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, struct snd_soc_dai *soc_dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; int ret; if (soc_dai->rate && (soc_dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates)) { dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n", soc_dai->rate); ret = snd_pcm_hw_constraint_single(substream->runtime, SNDRV_PCM_HW_PARAM_RATE, soc_dai->rate); if (ret < 0) { dev_err(soc_dai->dev, "ASoC: Unable to apply rate constraint: %d\n", ret); return ret; } } if (soc_dai->channels && (soc_dai->driver->symmetric_channels || rtd->dai_link->symmetric_channels)) { dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d channel(s)\n", soc_dai->channels); ret = snd_pcm_hw_constraint_single(substream->runtime, SNDRV_PCM_HW_PARAM_CHANNELS, soc_dai->channels); if (ret < 0) { dev_err(soc_dai->dev, "ASoC: Unable to apply channel symmetry constraint: %d\n", ret); return ret; } } if (soc_dai->sample_bits && (soc_dai->driver->symmetric_samplebits || rtd->dai_link->symmetric_samplebits)) { dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d sample bits\n", soc_dai->sample_bits); ret = snd_pcm_hw_constraint_single(substream->runtime, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, soc_dai->sample_bits); if (ret < 0) { dev_err(soc_dai->dev, "ASoC: Unable to apply sample bits symmetry constraint: %d\n", ret); return ret; } } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
nicolin chennicolin chen13153.04%125.00%
liam girdwoodliam girdwood10040.49%125.00%
dong aishengdong aisheng135.26%125.00%
lars-peter clausenlars-peter clausen31.21%125.00%
Total247100.00%4100.00%


static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; unsigned int rate, channels, sample_bits, symmetry, i; rate = params_rate(params); channels = params_channels(params); sample_bits = snd_pcm_format_physical_width(params_format(params)); /* reject unmatched parameters when applying symmetry */ symmetry = cpu_dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates; for (i = 0; i < rtd->num_codecs; i++) symmetry |= rtd->codec_dais[i]->driver->symmetric_rates; if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) { dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n", cpu_dai->rate, rate); return -EINVAL; } symmetry = cpu_dai->driver->symmetric_channels || rtd->dai_link->symmetric_channels; for (i = 0; i < rtd->num_codecs; i++) symmetry |= rtd->codec_dais[i]->driver->symmetric_channels; if (symmetry && cpu_dai->channels && cpu_dai->channels != channels) { dev_err(rtd->dev, "ASoC: unmatched channel symmetry: %d - %d\n", cpu_dai->channels, channels); return -EINVAL; } symmetry = cpu_dai->driver->symmetric_samplebits || rtd->dai_link->symmetric_samplebits; for (i = 0; i < rtd->num_codecs; i++) symmetry |= rtd->codec_dais[i]->driver->symmetric_samplebits; if (symmetry && cpu_dai->sample_bits && cpu_dai->sample_bits != sample_bits) { dev_err(rtd->dev, "ASoC: unmatched sample bits symmetry: %d - %d\n", cpu_dai->sample_bits, sample_bits); return -EINVAL; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
nicolin chennicolin chen18159.34%133.33%
benoit coussonbenoit cousson8728.52%133.33%
liam girdwoodliam girdwood3712.13%133.33%
Total305100.00%3100.00%


static bool soc_pcm_has_symmetry(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_driver *cpu_driver = rtd->cpu_dai->driver; struct snd_soc_dai_link *link = rtd->dai_link; unsigned int symmetry, i; symmetry = cpu_driver->symmetric_rates || link->symmetric_rates || cpu_driver->symmetric_channels || link->symmetric_channels || cpu_driver->symmetric_samplebits || link->symmetric_samplebits; for (i = 0; i < rtd->num_codecs; i++) symmetry = symmetry || rtd->codec_dais[i]->driver->symmetric_rates || rtd->codec_dais[i]->driver->symmetric_channels || rtd->codec_dais[i]->driver->symmetric_samplebits; return symmetry; }

Contributors

PersonTokensPropCommitsCommitProp
lars-peter clausenlars-peter clausen6651.97%150.00%
benoit coussonbenoit cousson6148.03%150.00%
Total127100.00%2100.00%


static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) { struct snd_soc_pcm_runtime *rtd = substream->private_data; int ret; if (!bits) return; ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, bits); if (ret != 0) dev_warn(rtd->dev, "ASoC: Failed to set MSB %d: %d\n", bits, ret); }

Contributors

PersonTokensPropCommitsCommitProp
mark brownmark brown5177.27%125.00%
benoit coussonbenoit cousson1319.70%250.00%
lars-peter clausenlars-peter clausen23.03%125.00%
Total66100.00%4100.00%


static void soc_pcm_apply_msb(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dai *codec_dai; int i; unsigned int bits = 0, cpu_bits; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < rtd->num_codecs; i++) { codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->playback.sig_bits == 0) { bits = 0; break; } bits = max(codec_dai->driver->playback.sig_bits, bits); } cpu_bits = cpu_dai->driver->playback.sig_bits; } else { for (i = 0; i < rtd->num_codecs; i++) { codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->capture.sig_bits == 0) { bits = 0; break; } bits = max(codec_dai->driver->capture.sig_bits, bits); } cpu_bits = cpu_dai->driver->capture.sig_bits; } soc_pcm_set_msb(substream, bits); soc_pcm_set_msb(substream, cpu_bits); }

Contributors

PersonTokensPropCommitsCommitProp
benoit coussonbenoit cousson19591.98%240.00%
lars-peter clausenlars-peter clausen167.55%240.00%
daniel mackdaniel mack10.47%120.00%
Total212100.00%5100.00%


static void soc_pcm_init_runtime_hw(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_hardware *hw = &runtime->hw; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai_driver *cpu_dai_drv = rtd->cpu_dai->driver; struct snd_soc_dai_driver *codec_dai_drv; struct snd_soc_pcm_stream *codec_stream; struct snd_soc_pcm_stream *cpu_stream; unsigned int chan_min = 0, chan_max = UINT_MAX; unsigned int rate_min = 0, rate_max = UINT_MAX; unsigned int rates = UINT_MAX; u64 formats = ULLONG_MAX; int i; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) cpu_stream = &cpu_dai_drv->playback; else cpu_stream = &cpu_dai_drv->capture; /* first calculate min/max only for CODECs in the DAI link */ for (i = 0; i < rtd->num_codecs; i++) { /* * Skip CODECs which don't support the current stream type. * Otherwise, since the rate, channel, and format values will * zero in that case, we would have no usable settings left, * causing the resulting setup to fail. * At least one CODEC should match, otherwise we should have * bailed out on a higher level, since there would be no * CODEC to support the transfer direction in that case. */ if (!snd_soc_dai_stream_valid(rtd->codec_dais[i], substream->stream)) continue; codec_dai_drv = rtd->codec_dais[i]->driver; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) codec_stream = &codec_dai_drv->playback; else codec_stream = &codec_dai_drv->capture; chan_min = max(chan_min, codec_stream->channels_min); chan_max = min(chan_max, codec_stream->channels_max); rate_min = max(rate_min, codec_stream->rate_min); rate_max = min_not_zero(rate_max, codec_stream->rate_max); formats &= codec_stream->formats; rates = snd_pcm_rate_mask_intersect(codec_stream->rates, rates); } /* * chan min/max cannot be enforced if there are multiple CODEC DAIs * connected to a single CPU DAI, use CPU DAI's directly and let * channel allocation be fixed up later */ if (rtd->num_codecs > 1) { chan_min = cpu_stream->channels_min; chan_max = cpu_stream->channels_max; } hw->channels_min = max(chan_min, cpu_stream->channels_min); hw->channels_max = min(chan_max, cpu_stream->channels_max); if (hw->formats) hw->formats &= formats & cpu_stream->formats; else hw->formats = formats & cpu_stream->formats; hw->rates = snd_pcm_rate_mask_intersect(rates, cpu_stream->rates); snd_pcm_limit_hw_rates(runtime); hw->rate_min = max(hw->rate_min, cpu_stream->rate_min); hw->rate_min = max(hw->rate_min, rate_min); hw->rate_max = min_not_zero(hw->rate_max, cpu_stream->rate_max); hw->rate_max = min_not_zero(hw->rate_max, rate_max); }

Contributors

PersonTokensPropCommitsCommitProp
benoit coussonbenoit cousson23457.92%116.67%
lars-peter clausenlars-peter clausen15137.38%466.67%
ricard wanderlofricard wanderlof194.70%116.67%
Total404100.00%6100.00%

/* * 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_pcm_runtime *runtime = substream->runtime; struct snd_soc_platform *platform = rtd->platform; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dai *codec_dai; const char *codec_dai_name = "multicodec"; int i, ret = 0; pinctrl_pm_select_default_state(cpu_dai->dev); for (i = 0; i < rtd->num_codecs; i++) pinctrl_pm_select_default_state(rtd->codec_dais[i]->dev); pm_runtime_get_sync(cpu_dai->dev); for (i = 0; i < rtd->num_codecs; i++) pm_runtime_get_sync(rtd->codec_dais[i]->dev); pm_runtime_get_sync(platform->dev); mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); /* startup the audio subsystem */ if (cpu_dai->driver->ops && cpu_dai->driver->ops->startup) { ret = cpu_dai->driver->ops->startup(substream, cpu_dai); if (ret < 0) { dev_err(cpu_dai->dev, "ASoC: can't open interface" " %s: %d\n", cpu_dai->name, ret); goto out; } } if (platform->driver->ops && platform->driver->ops->open) { ret = platform->driver->ops->open(substream); if (ret < 0) { dev_err(platform->dev, "ASoC: can't open platform" " %s: %d\n", platform->component.name, ret); goto platform_err; } } for (i = 0; i < rtd->num_codecs; i++) { codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->ops && codec_dai->driver->ops->startup) { ret = codec_dai->driver->ops->startup(substream, codec_dai); if (ret < 0) { dev_err(codec_dai->dev, "ASoC: can't open codec %s: %d\n", codec_dai->name, ret); goto codec_dai_err; } } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) codec_dai->tx_mask = 0; else codec_dai->rx_mask = 0; } if (rtd->dai_link->ops && rtd->dai_link->ops->startup) { ret = rtd->dai_link->ops->startup(substream); if (ret < 0) { pr_err("ASoC: %s startup failed: %d\n", rtd->dai_link->name, ret); goto machine_err; } } /* Dynamic PCM DAI links compat checks use dynamic capabilities */ if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) goto dynamic; /* Check that the codec and cpu DAIs are compatible */ soc_pcm_init_runtime_hw(substream); if (rtd->num_codecs == 1) codec_dai_name = rtd->codec_dai->name; if (soc_pcm_has_symmetry(substream)) runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; ret = -EINVAL; if (!runtime->hw.rates) { printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n", codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.formats) { printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n", codec_dai_name, cpu_dai->name); goto config_err; } if (!runtime->hw.channels_min || !runtime->hw.channels_max || runtime->hw.channels_min > runtime->hw.channels_max) { printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n", codec_dai_name, cpu_dai->name); goto config_err; } soc_pcm_apply_msb(substream); /* Symmetry only applies if we've already got an active stream. */ if (cpu_dai->active) { ret = soc_pcm_apply_symmetry(substream, cpu_dai); if (ret != 0) goto config_err; } for (i = 0; i < rtd->num_codecs; i++) { if (rtd->codec_dais[i]->active) { ret = soc_pcm_apply_symmetry(substream, rtd->codec_dais[i]); if (ret != 0) goto config_err; } } pr_debug("ASoC: %s <-> %s info:\n", codec_dai_name, cpu_dai->name); pr_debug("ASoC: rate mask 0x%x\n", runtime->hw.rates); pr_debug("ASoC: min ch %d max ch %d\n", runtime->hw.channels_min, runtime->hw.channels_max); pr_debug("ASoC: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max); dynamic: snd_soc_runtime_activate(rtd, substream->stream); 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: i = rtd->num_codecs; codec_dai_err: while (--i >= 0) { codec_dai = rtd->codec_dais[i]; if (codec_dai->driver->ops->shutdown) codec_dai->driver->ops->shutdown(substream, codec_dai); } if (platform->driver->ops && platform->driver->ops->close