1. 程式人生 > >linux音訊子系統 - ASoC-PCM之machine

linux音訊子系統 - ASoC-PCM之machine

對於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裝置

進行音效卡註冊前,首先要把下面這個結構體完善,裡面各個欄位所對應的資訊下圖所示

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