linux音频子系统 - ASoC-PCM之machine

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l289123557/article/details/79028255

对于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

猜你喜欢

转载自blog.csdn.net/l289123557/article/details/79028255