linux音訊子系統 - ASoC-PCM之machine
阿新 • • 發佈:2018-11-03
對於ASoC框架來說,machine就相當於card,soc-card的註冊就在machine的相關檔案中操作,machine相當於整個音效卡,而platform和codec是音效卡的附屬部件
1. struct snd_soc_card
soc音效卡用這個結構體來表示
struct snd_soc_card {
const char *name;
const char *long_name;
const char *driver_name;
struct device *dev;
struct snd_card *snd_card;--------------------card結構體,最終是要註冊card
struct module *owner;
struct list_head list;
struct mutex mutex;
struct mutex dapm_mutex;
bool instantiated;
int (*probe)(struct snd_soc_card *card);
int (*late_probe)(struct snd_soc_card *card);
int (*remove)(struct snd_soc_card *card);
/* the pre and post PM functions are used to do any PM work before and
* after the codec and DAI's do any PM work. */
int (*suspend_pre)(struct snd_soc_card *card);
int (*suspend_post)(struct snd_soc_card *card);
int (*resume_pre)(struct snd_soc_card *card);
int (*resume_post)(struct snd_soc_card *card);
/* callbacks */
int (*set_bias_level)(struct snd_soc_card *,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level);
int (*set_bias_level_post)(struct snd_soc_card *,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level);
long pmdown_time;
/* CPU <--> Codec DAI links */----------------附屬部件連結表
struct snd_soc_dai_link *dai_link;
int num_links;
struct snd_soc_pcm_runtime *rtd;
int num_rtd;
/* optional codec specific configuration */
struct snd_soc_codec_conf *codec_conf;
int num_configs;
/*
* optional auxiliary devices such as amplifiers or codecs with DAI
* link unused
*/
struct snd_soc_aux_dev *aux_dev;
int num_aux_devs;
struct snd_soc_pcm_runtime *rtd_aux;
int num_aux_rtd;
const struct snd_kcontrol_new *controls;
int num_controls;
/*
* Card-specific routes and widgets.----------------------DAPM內容
*/
const struct snd_soc_dapm_widget *dapm_widgets;
int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
int num_dapm_routes;
bool fully_routed;
struct work_struct deferred_resume_work;
/* lists of probed devices belonging to this card */--------附屬部件連結串列
struct list_head codec_dev_list;
struct list_head platform_dev_list;
struct list_head dai_dev_list;
struct list_head widgets;
struct list_head paths;
struct list_head dapm_list;
struct list_head dapm_dirty;
/* Generic DAPM context for the card */---------------DAPM內容
struct snd_soc_dapm_context dapm;
struct snd_soc_dapm_stats dapm_stats;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_card_root;
struct dentry *debugfs_pop_time;
#endif
u32 pop_time;
void *drvdata;
};
2. card註冊
音效卡的註冊函式
int snd_soc_register_card(struct snd_soc_card *card);
註冊流程如下:
- A:繫結系統中相關的platform/cedec
- B:音效卡物件的建立
- C:音效卡註冊
- control:這是control相關的一些初始化函式
3. 掃描platform/codec裝置
3.1 struct snd_soc_dai_link
進行音效卡註冊前,首先要把下面這個結構體完善,裡面各個欄位所對應的資訊下圖所示
struct snd_soc_dai_link {
/* config - must be set by machine driver */
const char *name; /* Codec name */
const char *stream_name; /* Stream name */
/*
* You MAY specify the link's CPU-side device, either by device name,
* or by DT/OF node, but not both. If this information is omitted,
* the CPU-side DAI is matched using .cpu_dai_name only, which hence
* must be globally unique. These fields are currently typically used
* only for codec to codec links, or systems using device tree.
*/
const char *cpu_name;
const struct device_node *cpu_of_node;
/*
* You MAY specify the DAI name of the CPU DAI. If this information is
* omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
* only, which only works well when that device exposes a single DAI.
*/
const char *cpu_dai_name;
/*
* You MUST specify the link's codec, either by device name, or by
* DT/OF node, but not both.
*/
const char *codec_name;
const struct device_node *codec_of_node;
/* You MUST specify the DAI name within the codec */
const char *codec_dai_name;
/*
* You MAY specify the link's platform/PCM/DMA driver, either by
* device name, or by DT/OF node, but not both. Some forms of link
* do not need a platform.
*/
const char *platform_name;
const struct device_node *platform_of_node;
int be_id; /* optional ID for machine driver BE identification */
const struct snd_soc_pcm_stream *params;
unsigned int dai_fmt; /* format to set on init */
enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;
/* Symmetry requirements */
unsigned int symmetric_rates:1;
/* Do not create a PCM for this DAI link (Backend link) */
unsigned int no_pcm:1;
/* This DAI link can route to other DAI links at runtime (Frontend)*/
unsigned int dynamic:1;
/* pmdown_time is ignored at stop */
unsigned int ignore_pmdown_time:1;
/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_pcm_runtime *rtd);
/* optional hw_params re-writing for BE and FE sync */
int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params);
/* machine stream operations */
const struct snd_soc_ops *ops;
const struct snd_soc_compr_ops *compr_ops;
};
3.2 遍歷裝置:soc_bind_dai_link
函式soc_bind_dai_link用來遍歷系統中的裝置
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct snd_soc_dai *codec_dai, *cpu_dai;
const char *platform_name;
dev_dbg(card->dev, "ASoC: binding %s at idx %d\n", dai_link->name, num);
/* Find CPU DAI from registered DAIs*/--------從dai_list連結串列尋找cpu-codec
list_for_each_entry(cpu_dai, &dai_list, list) {
if (dai_link->cpu_of_node &&
(cpu_dai->dev->of_node != dai_link->cpu_of_node))
continue;
if (dai_link->cpu_name &&
strcmp(dev_name(cpu_dai->dev), dai_link->cpu_name))
continue;
if (dai_link->cpu_dai_name &&
strcmp(cpu_dai->name, dai_link->cpu_dai_name))
continue;
rtd->cpu_dai = cpu_dai;
}
if (!rtd->cpu_dai) {
dev_err(card->dev, "ASoC: CPU DAI %s not registered\n",
dai_link->cpu_dai_name);
return -EPROBE_DEFER;
}
/* Find CODEC from registered CODECs */--------從codec_list連結串列找尋codec
list_for_each_entry(codec, &codec_list, list) {
if (dai_link->codec_of_node) {
if (codec->dev->of_node != dai_link->codec_of_node)
continue;
} else {
if (strcmp(codec->name, dai_link->codec_name))
continue;
}
rtd->codec = 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 &&
!strcmp(codec_dai->name,
dai_link->codec_dai_name)) {
rtd->codec_dai = codec_dai;
}
}
if (!rtd->codec_dai) {
dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n",
dai_link->codec_dai_name);
return -EPROBE_DEFER;
}
}
if (!rtd->codec) {
dev_err(card->dev, "ASoC: CODEC %s not registered\n",
dai_link->codec_name);
return -EPROBE_DEFER;
}
/* if there's no platform we match on the empty platform */
platform_name = dai_link->platform_name;
if (!platform_name && !dai_link->platform_of_node)
platform_name = "snd-soc-dummy";
/* find one from the set of registered platforms */------從platform_list找尋platform
list_for_each_entry(platform, &platform_list, list) {
if (dai_link->platform_of_node) {
if (platform->dev->of_node !=
dai_link->platform_of_node)
continue;
} else {
if (strcmp(platform->name, platform_name))
continue;
}
rtd->platform = platform;
}
if (!rtd->platform) {
dev_err(card->dev, "ASoC: platform %s not registered\n",
dai_link->platform_name);
return -EPROBE_DEFER;
}
card->num_rtd++;
return 0;
}
4.soc-pcm設備註冊
在card註冊時候,soc_probe_link_dais中會進行soc-pcm的註冊,註冊函式為:
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 *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
if (cpu_dai->driver->playback.channels_min)
playback = 1;
if (cpu_dai->driver->capture.channels_min)
capture = 1;
} else {
if (codec_dai->driver->playback.channels_min &&
cpu_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min &&
cpu_dai->driver->capture.channels_min)
capture = 1;
}
/* create the PCM */
if (rtd->dai_link->no_pcm) {
snprintf(new_name, sizeof(new_name), "(%s)",
rtd->dai_link->stream_name);
ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
playback, capture, &pcm);
} else {
if (rtd->dai_link->dynamic)
snprintf(new_name, sizeof(new_name), "%s (*)",
rtd->dai_link->stream_name);
else
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
capture, &pcm);--------------建立pcm裝置到card中,之後會進行pcm設備註冊
}
if (ret < 0) {
dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n",
rtd->dai_link->name);
return ret;
}
dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name);
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
rtd->pcm = pcm;
pcm->private_data = rtd;------pcm和soc-pcm的紐帶
if (rtd->dai_link->no_pcm) {
if (playback)
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
if (capture)
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
goto out;
}
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_params = dpcm_fe_dai_hw_params;
rtd->ops.prepare = dpcm_fe_dai_prepare;
rtd->ops.trigger = dpcm_fe_dai_trigger;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_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) {
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, &rtd->ops);-------soc-pcm裝置的操作函式賦值給pcm
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
if (ret < 0) {
dev_err(platform->dev,
"ASoC: pcm constructor failed: %d\n",
ret);
return ret;
}
}
pcm->private_free = platform->driver->pcm_free;
out:
dev_info(rtd->card->dev, " %s <-> %s mapping ok\n", codec_dai->name,
cpu_dai->name);
return ret;
}
在ASoC層的soc-pcm只是對pcm進行了再次’封裝‘,最終還是要進行pcm裝置的註冊
4.1soc-pcm執行流程
pcm裝置的執行過程大致為:
先找到字元裝置,然後在open的過程中找到private_data,這個欄位是一個結構體snd_pcm_file,裡面主要的欄位是一個結構體snd_pcm_substream,這樣就從一個字元裝置過度到pcm結構層。soc-pcm會在pcm層上進一步封裝,封裝為一個機構體snd_soc_pcm_runtime,在soc-pcm註冊的時候,會把其ops操作函式賦值給pcm,這樣就從pcm過度到soc-pcm了,相關過程如上面的函式soc_new_pcm中所示。
我們來看一下pcm-ops中一個操作hw_params:
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_platform *platform = rtd->platform;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret = 0;
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);
if (ret < 0) {
dev_err(rtd->card->dev, "ASoC: machine hw_params"
" failed: %d\n", ret);
goto out;
}
}
if (codec_dai->driver->ops->hw_params) {
ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai);
if (ret < 0) {
dev_err(codec_dai->dev, "ASoC: can't set %s hw params:"
" %d\n", codec_dai->name, ret);
goto codec_err;
}
}
if (cpu_dai->driver->ops->hw_params) {
ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai);
if (ret < 0) {
dev_err(cpu_dai->dev, "ASoC: %s hw params failed: %d\n",
cpu_dai->name, ret);
goto interface_err;
}
}
if (platform->driver->ops && platform->driver->ops->hw_params) {
ret = platform->driver->ops->hw_params(substream, params);
if (ret < 0) {
dev_err(platform->dev, "ASoC: %s hw params failed: %d\n",
platform->name, ret);
goto platform_err;
}
}
/* store the rate for each DAIs */
cpu_dai->rate = params_rate(params);
codec_dai->rate = params_rate(params);
out:
mutex_unlock(&rtd->pcm_mutex);
return ret;
platform_err:
if (cpu_dai->driver->ops->hw_free)
cpu_dai->driver->ops->hw_free(substream, cpu_dai);
interface_err:
if (codec_dai->driver->ops->hw_free)
codec_dai->driver->ops->hw_free(substream, codec_dai);
codec_err:
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free)
rtd->dai_link->ops->hw_free(substream);
mutex_unlock(&rtd->pcm_mutex);
return ret;
}
可以看到,執行的過程依次為:codec_dai->cpu_dai->platform。其他比如trigger/prepare/hw_free等都是類似的過程。
ref
version | date |
---|---|
linux4.1 | 2018.2.1 |