程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - Machine驱动(simple-audio-card)

 ----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux   :6.3
----------------------------------------------------------------------------------------------------------------------------

Machine driver描述了如何控制platform、codec、cpu dai(Digital Audio Interface,数字音频接口)和codec dai,使得互相配合在一起工作,Machine驱动代码位于sound/soc/generic/simple-card.c文件。

一、设备树配置

1.1 设备节点rt5651-sound

我们在arch/arm64/boot/dts/rockchip/rk3399-evb.dts文件根节点下添加设备节点rt5651-sound;

rt5651_card: rt5651-sound {
    status = "okay";
    compatible = "simple-audio-card";
    pinctrl-names = "default";
    pinctrl-0 = <&hp_det>;

    simple-audio-card,name = "realtek,rt5651-codec";
    simple-audio-card,format = "i2s";
    simple-audio-card,mclk-fs = <256>;
    simple-audio-card,hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_HIGH>;

    simple-audio-card,widgets =
        "Microphone", "Mic Jack",
        "Headphone", "Headphones";
    simple-audio-card,routing =
        "Mic Jack", "micbias1",
        "IN2P", "Mic Jack",
     "IN2N", "Mic Jack",
        "Headphones", "HPOL",
        "Headphones", "HPOR";

    simple-audio-card,cpu {
        sound-dai = <&i2s0>;
    };
    simple-audio-card,codec {
        sound-dai = <&rt5651>;
    };
};
1.1.1 常用属性

(1) status:指定设备状态为“正常”,表示该设备状态为正常运行;

(2) compatible:指定设备驱动程序的兼容性,即告诉内核该设备可以被哪些驱动程序所使用;

(3) pinctrl-names:指定设备pinctrl配置集合,例如“default”表示默认配置;

(4) pinctrl-0:设置default状态对应的引脚配置为hp_det,hp_det引脚配置节将GPIO4_D4配置为基本输入输出、电气特性为上拉配置;

NanoPC-T4开发板中RK3399 GPIO4_D4引脚连接到了耳机插孔的2号引脚,用于检测耳机的插入;当耳机插入时。耳机插头的金属会碰到检测引脚,使得检测引脚的电平发生改变,从而触发中断。这样就可以在中断处理函数中读取GPIO口的值,进一步判断是耳机插入还是拔出。

此外,如果仔细观察上图,我们会发现ACL5651的IRQ引脚也连接到了RK3399 GPIO4_D4引脚;对于ALC5651来说,IRQ引脚可以用来检测麦克风的插入/输出,因此可以通过配置了ALC5651 IRQ引脚用于麦克风的检测,从而将IRQ信号传送到RK3399 GPIO4_D4引脚,从而实现RK3399对麦克风的检测。

1.1.2 simple-audio-card

接下来是simple-audio-card的各个属性设置:

(1) simple-audio-card,name:指定声卡的名称为“realtek,rt5651-codec”;

(2) simple-audio-card,format:指定数字音频接口格式为“I2S”,即使用I2S接口传输音频数据;

此外还支持的数字音频接口格式有:right_j、left_j、dsp_a、dsp_b、ac97、pdm、msb、lsb。

(3) simple-audio-card,mclk-fs:指定主时钟频率MCLK与采样频率之前的比值,例如256表示主时钟频率为系统频率的256倍;

(4) simple-audio-card,hp-det-gpio:用于指定耳机检测使用的引脚,对于NanoPC-T4开发板配置为GPIO4_D4引脚,用于检测耳机的插入和拔出;

(5) simple-audio-card,widgets:在ALSA驱动中,使用widget描述具有路径有电源管理的kcontrol,每个条目都是一对字符串:

  • 第一个是widget模板名称,在Machine驱动中这个是确定的只有那么几种widget,Microphone(表示麦克风)、Headphone(表示耳机)、Speaker(表示扬声器)、Line(线路);
  • 第二个是widget实例名称,可以自由定义;

"Microphone", "Mic Jack":名字为“Microphone”的widget被重命名为“Mic Jack”,该widget定义在Machine驱动中;

SND_SOC_DAPM_MIC("Microphone", NULL),

"Headphone", "Headphones":名字为“Microphone”的widget被重命名为“Headphones”(这个名字不可以随便改,因为在为ASoC声卡中创建一个带有pin的jack时候,指定了pin的名称为"Headphones"),该widget定义在Machine驱动中;

SND_SOC_DAPM_HP("Headphone", NULL)

(6) simple-audio-card,routing:配置与Codec(ALC5651)物理输入引脚、物理输出端引脚连接的路径;每个条目都是一对字符串,第一个是目的(sink),第二个是源(source);

"Mic Jack", "micbias1": 将名字为“micbias1”的widget连接到名字为“Mic Jack”的widget,其中名字为“micbias1”的widget定义在Codec驱动中;

SND_SOC_DAPM_SUPPLY("micbias1", RT5651_PWR_ANLG2,   // ALC5651电源控制寄存器4 地址0x64
                RT5651_PWR_MB1_BIT, 0, NULL, 0)     // 位11用于MICBIAS1 Power Control电源控制,0:下电  1上电

从电路图中知道ALC5651 MICBIAS1引脚(输出引脚)连接到麦克风,并为麦克风提供偏置电压;

"IN2P", "Mic Jack":将名字为“Mic Jack”的widget连接到名字为“IN2P”的widget,其中名字为“IN2P”的widget定义在Codec驱动中; 表示将麦克风连接到音频输入IN2P引脚,这表示通过麦克风输入录制声音时使用此配置;

SND_SOC_DAPM_INPUT("IN2P") // IN2P为ALC5651麦克风2输入引脚

"IN2N", "Mic Jack":将名字为“Mic Jack”的widget连接到名字为“IN2N”的widget,其中名字为“IN2N”的widget定义在Codec驱动中; 表示将麦克风连接到音频输入IN2N引脚,这表示通过麦克风输入录制声音时使用此配置;

SND_SOC_DAPM_INPUT("IN2N") // IN2N为ALC5651麦克风2输入引脚

如果需要使用麦克风3(前提是开发板麦克3连接着耳机麦克风的话)进行录音,则配置:

"IN3P", "Mic Jack"

注意:可以同时配置多路麦克风,那么在录音的时候,多路麦克风音频信号会通过Mixer混音器进行处理。

因此可以在Machine驱动中可以构建输入端路径:micbias1 -->  Mic Jack --> IN2P/IN2N;关于IN2P/IN2N之后的路径在Codec驱动中构建; 

"Headphones", "HPOL":将名字为“HPOL”的widget连接到名字为“Headphones”的widget,其中名字为“HPOL”的widget定义在Codec驱动中;用于将左声道的耳机声音输出连接到耳机;

SND_SOC_DAPM_OUTPUT("HPOL")    // HPOL为ALC5651耳机输出引脚

"Headphones", "HPOR": 将名字为“HPOR”的widget连接到名字为“Headphones”的widget,其中名字为“HPOR”的widget定义在Codec驱动中;用于将左声道的耳机声音输出连接到耳机;

SND_SOC_DAPM_OUTPUT("HPOR")    // HPOR为ALC5651耳机输出引脚

因此可以在Machine驱动中可以构建输出端路径:HPOL/HPOR--> Headphones ;关于HPOL/HPOR之前的路径在Codec驱动中构建;

1.1.3 dai配置

最后配置cpu和codec端点,用于描述cpu dai和code cdai;

(1) simple-audio-card,cpu:指定cpu接入音频编解码的dai;这里配置为&i2s0,即i2s0设备节点的句柄;那么i2s0设备节点是什么呢,这个我们在Rockchip RK3399 - Platform驱动(DMA&i2s0)中介绍。

(2) simple-audio-card,codec:指定编解码音频接入cpu的dai;这里配置为&rt5651,即rt5651设备节点的句柄;那么rt5651设备节点是什么呢?这个我们在Rockchip RK3399 - Codec驱动( Realtek ALC5651)中介绍。

看到这里我们会思考一个问题,sound-dai和sound-dai分别配置了cpu端以及dai端的dai,然而这里配置的内容确是一个设备节点句柄。

这里以rt5651为例,rt5651有两个dai,那问题来了,在创建音频数据链路的时候会选择哪一个dai呢?

  • codec驱动中定义了两个dai,其名字分别为rt5651-aif1、rt5651-aif2,默认会选择第一个dai,也就是rt5651-aif1;
  • 而platform驱动中定义了一个dai,同理默认会选择第一个dai,也就是ff880000.i2s;

如果需要指定第几个dai,可以采用如下写法,其中port值默认从0开始;

"sound-dai = <&phandle port>"

通过上面的配置我们可以得到一条音频数据链路两端的dai分别为:ff880000.i2s和rt5651-aif1。音频数据通过RK3399的I2S0接口传输到ALC5651,再通过耳机、麦克风等端口输出或输入音频信号。

实际上我们在设备树中使用simple-audio-card,cpu、simple-audio-card,codec来描述音频数据链路,这种只适用于Machine中只有一条音频数据链路的情况。

对于Machine中存在多条音频数据链路音频链路,以及更多设备节点属性信息可以参考文档:

  • Documentation/devicetree/bindings/sound/simple-card.yaml;
  • Documentation/devicetree/bindings/sound/widgets.txt;
  • Documentation/devicetree/bindings/sound/rt5651.txt;

1.2 引脚配置节点hp_det

在pinctrl设备节点新增hp_det引脚配置节点:

headphone {
        hp_det: hp-det {
                rockchip,pins =
                        <4 RK_PD4 RK_FUNC_GPIO &pcfg_pull_up>;
        };
};

此处配置GPIO4_D4引脚功能为GPIO,电气特性为pcfg_pull_up,表示上拉配置。

NanoPC-T4开发板中RK3399 GPIO4_D4引脚连接的ALC5651的IRQ引脚,用于检测耳机的插入和拔出。

二、Machine驱动

我们定位到文件sound/soc/generic/simple-card.c,simple-card.c不是单板相关的东西,simple-audio-card 是一个Machine driver。

2.1 platform driver

Machine驱动最重要的事情是:构造并注册struct snd_soc_card。在simple-card.c文件中定义了platform driver:

static const struct of_device_id simple_of_match[] = {  // 用于匹配设备树
        { .compatible = "simple-audio-card", },
        { .compatible = "simple-scu-audio-card",
          .data = (void *)DPCM_SELECTABLE },
        {},
};
MODULE_DEVICE_TABLE(of, simple_of_match);

static struct platform_driver asoc_simple_card = {
        .driver = {
                .name = "asoc-simple-card",
                .pm = &snd_soc_pm_ops,
                .of_match_table = simple_of_match,
        },
        .probe = asoc_simple_probe,
        .remove = asoc_simple_remove,
};

module_platform_driver(asoc_simple_card);  // 注册平台驱动asoc_simple_card

2.2 asoc_simple_probe(重点)

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里也就是asoc_simple_probe函数。

static int asoc_simple_probe(struct platform_device *pdev)
{
        struct asoc_simple_priv *priv;
        struct device *dev = &pdev->dev;
        struct device_node *np = dev->of_node;
        struct snd_soc_card *card;
        struct link_info *li;
        int ret;

        /* Allocate the private data and the DAI link array */
        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);  // 动态分配struct asoc_simple_priv数据结构
        if (!priv)
                return -ENOMEM;

        card = simple_priv_to_card(priv);            // 获取成员变量&priv->snd_card
        card->owner             = THIS_MODULE;
        card->dev               = dev;               // 设置dev,为平台设备的device
        card->probe             = simple_soc_probe;  // 设置probe 该函数会解析设备属性"simple-audio-card,hp-det-gpio",并为之申请GPIO中断
// 并在中断处理函数中检测耳机是插入还是拔出,根据判断结果调用snd_soc_jack_report向上层汇报EV_KEY、EV_SW事件
card
->driver_name = "simple-card"; // 设置驱动名称 li = devm_kzalloc(dev, sizeof(*li), GFP_KERNEL); // 动态分配struct link_info数据结构,用于保存音频数据链路信息 if (!li) return -ENOMEM; ret = simple_get_dais_count(priv, li); //  解析rt5651-sound设备节点中音频数据链路相关节点,获取每条音频数据链路cpu、codec、platfrom的数量 if (ret < 0) return ret; if (!li->link) return -EINVAL; ret = asoc_simple_init_priv(priv, li); // 根据每个音频数据链路中的cpu数量、codec数量、platfom数量来初始化priv的成员 if (ret < 0) return ret; if (np && of_device_is_available(np)) { // 使用设备树,走这里 ret = simple_parse_of(priv, li); // 解析rt5651-sound设备节点的属性信息 if (ret < 0) { dev_err_probe(dev, ret, "parse error\n"); goto err; } } else { // 没有使用设备树 struct asoc_simple_card_info *cinfo; struct snd_soc_dai_link_component *cpus; struct snd_soc_dai_link_component *codecs; struct snd_soc_dai_link_component *platform; struct snd_soc_dai_link *dai_link = priv->dai_link; struct simple_dai_props *dai_props = priv->dai_props; cinfo = dev->platform_data; if (!cinfo) { dev_err(dev, "no info for asoc-simple-card\n"); return -EINVAL; } if (!cinfo->name || !cinfo->codec_dai.name || !cinfo->codec || !cinfo->platform || !cinfo->cpu_dai.name) { dev_err(dev, "insufficient asoc_simple_card_info settings\n"); return -EINVAL; } cpus = dai_link->cpus; cpus->dai_name = cinfo->cpu_dai.name; codecs = dai_link->codecs; codecs->name = cinfo->codec; codecs->dai_name = cinfo->codec_dai.name; platform = dai_link->platforms; platform->name = cinfo->platform; card->name = (cinfo->card) ? cinfo->card : cinfo->name; dai_link->name = cinfo->name; dai_link->stream_name = cinfo->name; dai_link->dai_fmt = cinfo->daifmt; dai_link->init = asoc_simple_dai_init; memcpy(dai_props->cpu_dai, &cinfo->cpu_dai, sizeof(*dai_props->cpu_dai)); memcpy(dai_props->codec_dai, &cinfo->codec_dai, sizeof(*dai_props->codec_dai)); } snd_soc_card_set_drvdata(card, priv); // 设置snd_soc_card的私有数据drvdata为priv asoc_simple_debug_info(priv); // 输出调试信息 ret = devm_snd_soc_register_card(dev, card); // 注册ASoC声卡设备 if (ret < 0) goto err; devm_kfree(dev, li); return 0; err: asoc_simple_clean_reference(card); return ret; }

该函数的主要作用是解析设备树节点数据或用户传递进来的配置信息,初始化并注册 ASoC声卡设备。

具体来说,该函数的主要执行步骤如下:

  • 分配asoc_simple_priv结构体,并将通过宏simple_priv_to_card获取其成员变量snd_card,其类型为snd_soc_card;
  • 设置snd_soc_card结构体各个参数的值,包括card名称、所属设备、probe函数等;
  • 调用 simple_get_dais_count,通过simple_for_each_link函数解析rt5651-sound设备节点,遍历每条音频数据链路并调用两个不同的计数回调函数simple_count_noml和 simple_count_dpcm,用于对不同类型的音频数据链路中的cpu、codec、platfrom数量进行统计,并保存在li变量中;
  • 调用 asoc_simple_init_priv根据每个音频数据链路中的cpu数量、codec数量、platfom数量来初始化priv的成员,比如:
    • dai_props(同时会初始化每个元素的成员cpus、num.cpus、cpu_dai、codec、num.codecs、codev_dai、platforms、num.platforms);
    • dai_link(同时会初始化每个元素的成员cpus、num_cpus、codec、num_codecs、platforms、num_platforms)、dais、dlcs、codec_conf等;
    • 此外设置card->dai_link = priv->dai_link;
    • 需要注意的是这里仅仅是进行的动态内存分配的操作,并未对具体结构体的成员内容进行初始化
  • 由于使用了设备树,因此调用simple_parse_of函数解析rt5651-sound设备节点的属性信息,比如:
    • 解析simple-audio-card,widgets 、simple-audio-card,routing、simple-audio-card,name属性,并将这些信息保存到card数据结构的成员中中;
    • 解析simple-audio-card,cpu、simple-audio-card,codec、simple-audio-card,format等属性,并初始化priv->dai_link[i]成员,由于card->dai_link = priv->dai_link ,因此也就是初始化声卡card的音频数据链路dai_link[i]成员cpus、codec、dai_fmt;
    • 解析simple-audio-card,mclk-fs属性,并初始化 priv->dai_props[i]->mclk_fs;
  • 调用snd_soc_card_set_drvdata设置snd_soc_card的私有数据drvdata为priv;
  • 调用devm_snd_soc_register_card注册 ASoC声卡设备,并返回执行结果;
  • 如果执行失败,则会进行清理工作,如清理已经注册的资源,释放资源,返回错误码;
2.2.1 simple_soc_probe

ASoC声卡card的probe函数被设置为了simple_soc_probe,在注册ASoC声卡的时候会回调用card->probe函数;

static int simple_soc_probe(struct snd_soc_card *card)
{
        struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card);
        int ret;

        ret = asoc_simple_init_hp(card, &priv->hp_jack, PREFIX); // 创建带有pin的jack,用于耳机检测
        if (ret < 0)
                return ret;

        ret = asoc_simple_init_mic(card, &priv->mic_jack, PREFIX);  // 创建带有pin的jack,用于麦克风检测
        if (ret < 0)
                return ret;

        ret = asoc_simple_init_aux_jacks(priv, PREFIX);
        if (ret < 0)
                return ret;

        return 0;
}

其中asoc_simple_init_hp是对耳机的初始化,asoc_simple_init_mic是对麦克风的初始化,其均定义在include/sound/simple_card_utils.h:

#define asoc_simple_init_hp(card, sjack, prefix) \
        asoc_simple_init_jack(card, sjack, 1, prefix, NULL)
#define asoc_simple_init_mic(card, sjack, prefix) \
        asoc_simple_init_jack(card, sjack, 0, prefix, NULL)

这里都调用asoc_simple_init_jack,只是传入参数不一样。

asoc_simple_init_jack函数位于sound/soc/generic/simple-card-utils.c,下面我们以HP为例进行介绍;

int asoc_simple_init_jack(struct snd_soc_card *card, // 传入ASoC声卡设备
                          struct asoc_simple_jack *sjack, // 插孔,该函数会进行其成员的初始化
                          int is_hp, char *prefix,  // is_hp为1表示耳机,为0表示麦克风 prefix="simple-audio-card,"
                          char *pin) // 传入NULL
{
        struct device *dev = card->dev;  // 获取平台设备的设备
        struct gpio_desc *desc;
        char prop[128];
        char *pin_name;
        char *gpio_name;
        int mask;
        int error;

        if (!prefix)
                prefix = "";

        sjack->gpio.gpio = -ENOENT;
     // 下面通过参数来判断是hp还是mic
        if (is_hp) {          // 这里做了字符串拼接,所以dts中要按照这样设置:simple-audio-card,hp-det-gpio
                snprintf(prop, sizeof(prop), "%shp-det", prefix);    // prop="simple-audio-card,hp-det"
                pin_name        = pin ? pin : "Headphones";  // "Headphones"
                gpio_name       = "Headphone detection";     // GPIO口的名称
                mask            = SND_JACK_HEADPHONE;        // 0x0001
        } else {
                snprintf(prop, sizeof(prop), "%smic-det", prefix);
                pin_name        = pin ? pin : "Mic Jack";
                gpio_name       = "Mic detection";
                mask            = SND_JACK_MICROPHONE;
        }
        // 根据dts中dev设备节点属性名prop获取属性中描述的GPIO信息,同时配置GPIO口为输入 optional表示可选的,也就是说如果没有指定prop属性,也不会报错
        desc = gpiod_get_optional(dev, prop, GPIOD_IN); // 第三个参数表示配置GPIO口为输入
        error = PTR_ERR_OR_ZERO(desc);
        if (error)
                return error;
       if (desc) {
                error = gpiod_set_consumer_name(desc, gpio_name); // 设置GPIO口的label为"Headphone detection"
                if (error)
                        return error;

                sjack->pin.pin          = pin_name;  // "Headphones",根据该名称定为widget
                sjack->pin.mask         = mask;      // jack pin可以上报的类型,SND_JACK_HEADPHONE

                sjack->gpio.name        = gpio_name;// GPIO的名称,也是GPIO中断的名称"Headphone detection"
                sjack->gpio.report      = mask;     // GPIO可以上报的类型,SND_JACK_HEADPHONE
                sjack->gpio.desc        = desc;    
                sjack->gpio.debounce_time = 150;   // 延迟时间
          // 创建一个带有pin的jack
                snd_soc_card_jack_new_pins(card, pin_name, mask, &sjack->jack,  // jack可以检测的类型为SND_JACK_HEADPHONE
                                           &sjack->pin, 1);
          // 通过request irq申请耳机插拔中断,在中断处理函数中通过检测上升沿/下降沿判断耳机是插入还是拔出,根据判断结果调用snd_soc_jack_report发送输入事件
                snd_soc_jack_add_gpios(&sjack->jack, 1, &sjack->gpio);
        }

        return 0;
}

这里调用了gpiod_get_optional、snd_soc_card_jack_new_pins、snd_soc_jack_add_gpios等函数,关于jack设备我们在声卡之Jack设备已经介绍过了。

2.2.2 总结

Machine驱动主要做的就是两件事情:

  • 构造一个struct snd_soc_dai_link,比如变量名为dai_link,将cpu和codec关联起来;需要初始化成员cpus、codecs、name、stream_name、dai_fmt、init;
  • 构造一个struct snd_soc_card,比如变量名为card,并将其注册到ASoC中;

simple_parse_of函数通过解析设备节点rt5651-sound的属性,从而可以初始化card、以及dai_link数据结构,函数执行完毕,各个数据结构之间的关系大致如下图所示:

2.2.3 伪实现

如果我们不使用设备树的话,就可以构建一个platform device、其平台数据platform_data设置为struct asoc_simple_card_info,代码实现大致如下:

/* A Minimal ASoC Sound Card - Single DAI Link */
struct asoc_simple_card_info cinfo = {
        .name = "ff880000.i2s-rt5651-aif1",
        .card = "realtek,rt5651-codec",
        .codec = "rt5651",
        .platform = NULL,
        .daifmt = 1,
     .cpu_dai = {
      .name = "ff880000.i2s",
.sysclk = xxxx,
}
.codec_dai = {
      .name = "rt5651",
.sysclk = xxxx,
    } };

然后asoc_simple_probe函数走通过cinfo去初始化dai_link、card的流程,最后再将card注册到ASoC代码大致如下,因此asoc_simple_probe应该是可以简化为

/* A Minimal ASoC Sound Card - Single DAI Link */
struct snd_soc_dai_link dai_link = {
        .name = "ff880000.i2s-rt5651-aif1",
        .stream_name = "ff880000.i2s-rt5651-aif1",
        .dai_fmt = 1,
        .init = asoc_simple_dai_init,
        .cpus = {
       .dai_name = "ff880000.i2s",  // 用于匹配component dai_list链表中的dai,dai_name设置pdev->dev的名称,pdev为i2s0设备节点转换得到的platform_device
     },
.num_cpus = 1, .codec
= { .name = "rt5651", // 用于匹配全局链表component_list中的component; name设置为pdev->dev的名称,pdev为rt5651设备节点转换得到的platform_device       .dai_name = "rt5651-aif1", // 用于匹配component dai_list链表中的dai;dai_name设置为codec dai的名称      },
.num_codecs = 1, .platform
= { .name = NULL,     }
.num_platforms = 1,
.ops = simple_ops, };
struct snd_soc_card card = { .name = "realtek,rt5651-codec", .dai_link = &dai_link, .num_links = 1,
.num_dapm_widgets = 2,
.dapm_widgers = xxx,
.num_dapm_routes = 4,
.dapm_routes = xxx, }; snd_soc_register_card(
&card);

关于非设备树方式的实现,更多详情可以参考理解ALSA(三):从零写ASoC驱动

后面我们将对asoc_simple_probe函数源码进行的分析,如果对此不感兴趣,可以忽略后面的内容。

三、相关数据结构

在分析源码之前,需要介绍一下Machine驱动中涉及到的数据结构,主要有struct asoc_simple_priv、struct link_info。

3.1  asoc_simple_priv

数据结构asoc_simple_priv定义在include/sound/simple_card_utils.h;

struct asoc_simple_priv {
        struct snd_soc_card snd_card;
        struct simple_dai_props {  // 每个成员,代表一个音频数据链路,用于描述每个音频链路上所有属性
                struct asoc_simple_dai *cpu_dai;          // 存储当前链路上所有的cpu_dai  指向一个数组,数组长度由num.cpus决定
                struct asoc_simple_dai *codec_dai;        // 存储当前链路上所有的codec_dai 指向一个数组,数组长度由num.codecs决定
                struct snd_soc_dai_link_component *cpus;  // 存储当前链路上所有的cpu设备 指向一个数组,数组长度由num.cpus决定
                struct snd_soc_dai_link_component *codecs;     // 存储当前链路上的所有codec设备 指向一个数组,数组长度由num.codecss决定
                struct snd_soc_dai_link_component *platforms;  // 存储当前链路上所有的platform设备 指向一个数组,数组长度由num.platforms决定
                struct asoc_simple_data adata;
                struct snd_soc_codec_conf *codec_conf;   // 这个我们不会使用
                struct prop_nums num;   // 保存当前音频数据链路的cpu、codec、platform数量
                unsigned int mclk_fs;   // MCLK频率 存放simple-audio-card,mclk-fs属性的值
        } *dai_props;
        struct asoc_simple_jack hp_jack;
        struct asoc_simple_jack mic_jack;
        struct snd_soc_jack *aux_jacks;
        struct snd_soc_dai_link *dai_link;
        struct asoc_simple_dai *dais;
        struct snd_soc_dai_link_component *dlcs;
        struct snd_soc_dai_link_component dummy;
        struct snd_soc_codec_conf *codec_conf;
        struct gpio_desc *pa_gpio;
        const struct snd_soc_ops *ops;
        unsigned int dpcm_selectable:1;
        unsigned int force_dpcm:1;
};

其中:

  • snd_card:保存ASoC中的SoC声卡设备;
  • dai_props:指向动态分配得到的数组,每个元素都是一个struct simple_dai_props,保存每个音频数据链路上的属性;数组的长度等于音频数据链路的长度;
  • dai_link:指向动态分配得到的数组,每个元素都一个struct snd_soc_dai_link,即描述一个音频数据链路;数组的长度等于音频数据链路的长度;
  • dais:指向动态分配得到的数组,每个元素都是一个struct asoc_simple_dai,数组的长度为每个音频数据链路上的DAI个数的累计和;
  • dlcs:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component,数组的长度为每个音频数据链路上的设备个数的累计和;
  • dummy:虚拟的CPU/Codec设备; 例如在音频回环测试中,可以使用虚拟的设备来模拟真实的硬件设备,从而方便测试和调试;
  • codec_conf:指向动态分配得到的数组,每个元素都是一个struct snd_soc_codec_conf,在这里,针对rt5651-sound设备节点,该数组长度为0;

3.2 struct asoc_simple_dai 

struct asoc_simple_dai是machine driver层定义的用来描述dai的数据结构,不要和struct snd_soc_dai数据结构搞混了,其定义在include/sound/simple_card_utils.h:

struct asoc_simple_dai {
        const char *name;
        unsigned int sysclk;
        int clk_direction;
        int slots;
        int slot_width;
        unsigned int tx_slot_mask;
        unsigned int rx_slot_mask;
        struct clk *clk;
        bool clk_fixed;
        struct asoc_simple_tdm_width_map *tdm_width_map;
        int n_tdm_widths;
};

其中:

  • name:dai的名称;
  • sysclck:系统时钟频率;
  • clk_direction:设置时钟方向,输入or输出;SND_SOC_CLOCK_OUT表示输出,值为1;SND_SOC_CLOCK_IN表示输入,值为0;
  • clk:系统时钟;
    • 对于cpi dai来说系统时钟指的是RK3399 i2s_clk的时钟,即i2s0模块输出的MCLK时钟(对应的时钟名称为clk_i2s0);
    • 对于codec dai来说系统时钟指的是ALC5651 i2s1接口的MCLK信号线输入的时钟,同时也是RK3399 i2s0接口的MCLK信号线输出的时钟(对应的时钟名称为clk_i2sout);

3.3 link_info

link_info定义在include/sound/simple_card_utils.h:

struct link_info {
        int link; /* number of link */
        int cpu;  /* turn for CPU / Codec */
        struct prop_nums num[SNDRV_MAX_LINKS];
};

struct prop_nums {
        int cpus;
        int codecs;
        int platforms;
};

该结构体用于保存音频设备中link、cpu 和num三个参数的信息。具体来说:

  • link:表示音频数据链路的数量;
  • cpu:表示当前已经处理过的CPU或Codec的数量,即正在进行处理的是第几个CPU或Codec;
  • num:是一个数组,一个大小为SNDRV_MAX_LINKS的数组,用于保存每条音频数据链路所需的信息。每个数组元素是一个struct prop_nums 结构体;

四、asoc_simple_probe函数分析

4.1 simple_priv_to_card

simple_priv_to_card宏定义在include/sound/simple_card_utils.h:

#define simple_priv_to_card(priv)       (&(priv)->snd_card)

可以看到这个宏内容很简单,就是获取ASoC中定义的数据类型struct snd_soc_card。

4.2 simple_get_dais_count

simple_get_dais_count函数定义在sound/soc/generic/simple-card.c,这是一个用于获取音频数据链路信息的函数。该函数接受一个指向asoc_simple_priv数据结构的指针以及一个指向link_info结构的指针。

static int simple_get_dais_count(struct asoc_simple_priv *priv,
                                 struct link_info *li)
{
        struct device *dev = simple_priv_to_dev(priv);  // 获取&priv->snd_card.dev,即获取平台设备的device
        struct device_node *top = dev->of_node;         // 获取设备树中设备节点 ,在我们这里也就是rt5651-sound设备节点

        /*
         * link_num :   number of links.
         *              CPU-Codec / CPU-dummy / dummy-Codec
         * dais_num :   number of DAIs                          等于CPU数量 + Codec数量
         * ccnf_num :   number of codec_conf                    和dummy-Codec数量一致
         *              same number for "dummy-Codec"
         *
         * ex1)
         * CPU0 --- Codec0      link : 5
         * CPU1 --- Codec1      dais : 7
         * CPU2 -/              ccnf : 1
         * CPU3 --- Codec2
         *
         *      => 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec
         *      => 7 DAIs  = 4xCPU + 3xCodec
         *      => 1 ccnf  = 1xdummy-Codec
         *
         * ex2)
         * CPU0 --- Codec0      link : 5
         * CPU1 --- Codec1      dais : 6
         * CPU2 -/              ccnf : 1
         * CPU3 -/
         *
         *      => 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec
         *      => 6 DAIs  = 4xCPU + 2xCodec
         *      => 1 ccnf  = 1xdummy-Codec
         *
         * ex3)
         * CPU0 --- Codec0      link : 6
         * CPU1 -/              dais : 6
         * CPU2 --- Codec1      ccnf : 2
         * CPU3 -/
         *
         *      => 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec
         *      => 6 DAIs  = 4xCPU + 2xCodec
         *      => 2 ccnf  = 2xdummy-Codec
         *
         * ex4)
         * CPU0 --- Codec0 (convert-rate)       link : 3
         * CPU1 --- Codec1                      dais : 4
         *                                      ccnf : 1
         *
         *      => 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec
         */
        if (!top) {  // 如果没有设备节点才会进入,该函数我们不会进入
                li->num[0].cpus         = 1;    // 描述第0个音频链路的cpu、codecs、platforms数量
                li->num[0].codecs       = 1;
                li->num[0].platforms    = 1;

                li->link = 1;    // 音频链路数量
                return 0;
        }

        return simple_for_each_link(priv, li,
                                    simple_count_noml,
                                    simple_count_dpcm);
}
View Code

该函数通过调用simple_for_each_link函数来计算音频数据链路的数量和以及每条音频数据链路中cpu、codec、platform的数量

simple_for_each_link用于解析设备树中音频数据链路节点:

如果我们的Machine中只有一条音频数据链路,在设备树中使用simple-audio-card,cpu、simple-audio-card,codec子节点进行描述;比如我们的设备节点rt5651-sound就是single DAI link:

    simple-audio-card,cpu {
        sound-dai = <&i2s0>;
    };
    simple-audio-card,codec {
        sound-dai = <&rt5651>;
    };

如果存在多条音频数据链路,在设备树中使用simple-audio-card,dai-link子节点进行描述;比如:

simple-audio-card,dai-link@0 {
        format = "i2s";
        cpu {
                sound-dai = <&i2s0>;
        };

        codec {
                sound-dai = <&rt5651>;
        };
};

simple-audio-card,dai-link@1 {
        format = "i2s2";
        cpu {
                sound-dai = <&i2s>;
        };

        codec {
                sound-dai = <&hdmi>;
        };
};

此外还有多个CPU,1个Codec的复杂音频数据链路;比如:

simple-audio-card,dai-link@0 {
    reg = <0>;
    format = "left_j";
    bitclock-master = <&sndcpu0>;
    frame-master = <&sndcpu0>;

    sndcpu0: cpu {
        sound-dai = <&rcar_sound 0>;
    };
    codec {
        sound-dai = <&ak4613>;
    };
};

simple-audio-card,dai-link@1 {
    reg = <1>;
    format = "i2s";
    bitclock-master = <&sndcpu1>;
    frame-master = <&sndcpu1>;

    convert-channels = <8>; /* TDM Split */

    sndcpu1: cpu-0 {
        sound-dai = <&rcar_sound 1>;
    };
    cpu-1 {
        sound-dai = <&rcar_sound 2>;
    };
    cpu-2 {
        sound-dai = <&rcar_sound 3>;
    };
    cpu-3 {
        sound-dai = <&rcar_sound 4>;
    };
    codec {
        mclk-fs = <512>;
        prefix = "pcm3168a";
        dai-tdm-slot-num = <8>;
        sound-dai = <&pcm3168a 0>;
    };
};
View Code

在文档Documentation/devicetree/bindings/sound/simple-card.yaml给出了single DAI link、 Multi DAI links、Sampling Rate Conversion、2 CPU 1 Codec (Mixing)、Multi DAI links with DPCM等不同类型音频数据链路的示例,有兴趣可以看看;

4.2.1 simple_for_each_link

simple_for_each_link函数会解析设备节点,遍历每条音频数据链路并调用两个不同的计数回调函数simple_count_noml和 simple_count_dpcm,用于对不同类型的音频数据链路中的cpu、codec、platfrom数量进行统计,并保存在li变量中。

static int simple_for_each_link(struct asoc_simple_priv *priv,
                                struct link_info *li,
                                int (*func_noml)(struct asoc_simple_priv *priv,
                                                 struct device_node *np,
                                                 struct device_node *codec,
                                                 struct link_info *li, bool is_top),
                                int (*func_dpcm)(struct asoc_simple_priv *priv,
                                                 struct device_node *np,
                                                 struct device_node *codec,
                                                 struct link_info *li, bool is_top))
{
        int ret;
        /*
         * Detect all CPU first, and Detect all Codec 2nd.
         *
         * In Normal sound case, all DAIs are detected
         * as "CPU-Codec".
         *
         * In DPCM sound case,
         * all CPUs   are detected as "CPU-dummy", and
         * all Codecs are detected as "dummy-Codec".
         * To avoid random sub-device numbering,
         * detect "dummy-Codec" in last;
         */
        for (li->cpu = 1; li->cpu >= 0; li->cpu--) {  // 设置li->cpu=1、0,然后执行__simple_for_each_link函数
                ret = __simple_for_each_link(priv, li, func_noml, func_dpcm);
                if (ret < 0)
                        break;
        }

        return ret;
}

这里又调用了__simple_for_each_link函数:

static int __simple_for_each_link(struct asoc_simple_priv *priv,
                        struct link_info *li,
                        int (*func_noml)(struct asoc_simple_priv *priv,
                                         struct device_node *np,
                                         struct device_node *codec,
                                         struct link_info *li, bool is_top),
                        int (*func_dpcm)(struct asoc_simple_priv *priv,
                                         struct device_node *np,
                                         struct device_node *codec,
                                         struct link_info *li, bool is_top))
{
        struct device *dev = simple_priv_to_dev(priv);    // 获取&priv->snd_card.dev,即获取平台设备的device
        struct device_node *top = dev->of_node;           // 获取设备树中设备节点 ,在我们这里也就是rt5651-sound设备节点 
        struct device_node *node;
        uintptr_t dpcm_selectable = (uintptr_t)of_device_get_match_data(dev);  // 获取与设备相关的驱动程序私有数据结构,其是在设备树中由设备的compatible
                                                                               // 属性匹配到的of_device_id 实例中定义的。这里我们并为定义,所以获取到的为NUL。
        bool is_top = 0;
        int ret = 0;

        /* Check if it has dai-link */
        node = of_get_child_by_name(top, PREFIX "dai-link");   // 获取simple-audio-card,dai-link设备节点,由于rt5651-sound设备节点中并未定义该节点,所以获取到的位NULL
        if (!node) {                    // 进入,设置is_top标志位
                node = of_node_get(top);
                is_top = 1;
        }

        /* loop for all dai-link */
        do {
                struct asoc_simple_data adata;
                struct device_node *codec;
                struct device_node *plat;
                struct device_node *np;
                int num = of_get_child_count(node);

                /* get codec */
                codec = of_get_child_by_name(node, is_top ?     // 获取simple-audio-card,codec设备节点,即{sound-dai = <&rt5651>;};
                                             PREFIX "codec" : "codec");
                if (!codec) {         // codec设备节点是必须的
                        ret = -ENODEV;
                        goto error;
                }
                /* get platform */
                plat = of_get_child_by_name(node, is_top ?    // 获取simple-audio-card,plat设备节点,我们也未定义
                                            PREFIX "plat" : "plat");

                /* get convert-xxx property */
                memset(&adata, 0, sizeof(adata));     // 全部填充0
                for_each_child_of_node(node, np)      // 处理simple-audio-card,convert-channels等属性,由于这些属性我们没有使用到,所以可以不用关注
                        simple_parse_convert(dev, np, &adata);

                /* loop for all CPU/Codec node */
                for_each_child_of_node(node, np) {    // 遍历node中每个子节点,rt5651-sound设备节点中只有两个子节点simple-audio-card,cpu、simple-audio-card,codec
                        if (plat == np)
                                continue;
                        /*
                         * It is DPCM
                         * if it has many CPUs,
                         * or has convert-xxx property
                         */
                        if (dpcm_selectable &&
                            (num > 2 || asoc_simple_is_convert_required(&adata))) {   // 不会进入
                                /*
                                 * np
                                 *       |1(CPU)|0(Codec)  li->cpu
                                 * CPU   |Pass  |return
                                 * Codec |return|Pass
                                 */
                                if (li->cpu != (np == codec))
                                        ret = func_dpcm(priv, np, codec, li, is_top);
                        /* else normal sound */
                        } else {
                                /*
                                 * np
                                 *       |1(CPU)|0(Codec)  li->cpu
                                 * CPU   |Pass  |return
                                 * Codec |return|return
                                 */
                                if (li->cpu && (np != codec))    // 当np为simple-audio-card,cpu节点时进入
                                        ret = func_noml(priv, np, codec, li, is_top);  // 最终执行simple_count_noml函数
                        }

                        if (ret < 0) {
                                of_node_put(codec);
                                of_node_put(np);
                                goto error;
                        }
                }

                of_node_put(codec);
                node = of_get_next_child(top, node);  // 从top设备节中获取下一个子节点,并且该子节点在在node
        } while (!is_top && node);

 error:
        of_node_put(node);
        return ret;
}
View Code

这里我们以rt5651-sound设备节点为例介绍一下这段代码流程:li->cpu=1时;

  • 获取子设备节点simple-audio-card,codec;
  • 获取子设备节点simple-audio-card,cpu,并调用simple_count_noml函数将当前音频数据链路信息添加到li->num[li->link]中;

当li->cpu=0时,__simple_for_each_link函数将不会做任何事情。

4.2.2 simple_count_noml

simple_count_noml函数定义在sound/soc/generic/simple-card.c:

static int simple_count_noml(struct asoc_simple_priv *priv,
                             struct device_node *np,
                             struct device_node *codec,
                             struct link_info *li, bool is_top)
{
        if (li->link >= SNDRV_MAX_LINKS) {  // 链路数已经达到上限  
                struct device *dev = simple_priv_to_dev(priv);

                dev_err(dev, "too many links\n");
                return -EINVAL;
        }

        li->num[li->link].cpus          = 1;   // cpu数量
        li->num[li->link].codecs        = 1;   // codec数量
        li->num[li->link].platforms     = 1;   // platform数量 

        li->link += 1;   // 音频数据链路计数+1

        return 0;
}

4.3 asoc_simple_init_priv

asoc_simple_init_priv定义在sound/soc/generic/simple-card-utils.c,该函数用来初始化priv数据成员,实际就是根据每个音频数据链路中的cpu数量、codec数量、platfom数量来为priv的成员动态分配内存的过程;

int asoc_simple_init_priv(struct asoc_simple_priv *priv,
                          struct link_info *li)
{
        struct snd_soc_card *card = simple_priv_to_card(priv);    // 获取成员变量&priv->snd_card
        struct device *dev = simple_priv_to_dev(priv);            // 获取&priv->snd_card.dev,即获取平台设备的device
        struct snd_soc_dai_link *dai_link;                        // 用于保存音频数据链路信息,指向一个struct snd_soc_dai_link数组
        struct simple_dai_props *dai_props;
        struct asoc_simple_dai *dais;
        struct snd_soc_dai_link_component *dlcs;                  // 用于保存音频数据链路中的设备
        struct snd_soc_codec_conf *cconf = NULL;
        int i, dai_num = 0, dlc_num = 0, cnf_num = 0;

        dai_props = devm_kcalloc(dev, li->link, sizeof(*dai_props), GFP_KERNEL); // 动态申请数组,即为每一个音频数据链路分配一个struct simple_dai_props
        dai_link  = devm_kcalloc(dev, li->link, sizeof(*dai_link),  GFP_KERNEL); // 动态申请数组,即为每一个音频数据链路分配一个struct snd_soc_dai_link
        if (!dai_props || !dai_link)
                return -ENOMEM;

        /*
         * dais (= CPU+Codec)      dai数量 = CPU数量 + Codec数量
         * dlcs (= CPU+Codec+Platform) 音频数据链路中设备数量 = CPU数量 + Codec数量 + Platform数量
         */
        for (i = 0; i < li->link; i++) {    // 遍历每条音频数据链路,统计dai、condec_conf等数量
                int cc = li->num[i].cpus + li->num[i].codecs;  // 计算当前数据链路dai数量

                dai_num += cc;          // 累计dai的总数量
                dlc_num += cc + li->num[i].platforms; // 累计dlc的总数量

                if (!li->num[i].cpus)  
                        cnf_num += li->num[i].codecs; // 累计codec_conf的总数量
        }

        dais = devm_kcalloc(dev, dai_num, sizeof(*dais), GFP_KERNEL);  // 动态申请数组,即为每一个dai分配一个struct asoc_simple_dai
        dlcs = devm_kcalloc(dev, dlc_num, sizeof(*dlcs), GFP_KERNEL);  // 动态申请数组,即为每一个dlcs分配一个struct snd_soc_dai_link_component
        if (!dais || !dlcs)
                return -ENOMEM;

        if (cnf_num) {
                cconf = devm_kcalloc(dev, cnf_num, sizeof(*cconf), GFP_KERNEL);  // 动态申请数组,即为每一个condec_conf分配一个struct snd_soc_codec_conf
                if (!cconf)
                        return -ENOMEM;
        }

        dev_dbg(dev, "link %d, dais %d, ccnf %d\n",
                li->link, dai_num, cnf_num);

        /* dummy CPU/Codec 虚拟设备  */
        priv->dummy.of_node     = NULL;         
        priv->dummy.dai_name    = "snd-soc-dummy-dai";
        priv->dummy.name        = "snd-soc-dummy";

        priv->dai_props         = dai_props; // 设置dai_props 
        priv->dai_link          = dai_link;  // 设置音频数据链路
        priv->dais              = dais;      // 设置dai 
        priv->dlcs              = dlcs;      // 设置dlcs
        priv->codec_conf        = cconf;     // 设置codec_conf 

        card->dai_link          = priv->dai_link;  // 设置音频数据链路
        card->num_links         = li->link;        // 设置音频数据链路数量   
        card->codec_conf        = cconf;           // 设置codec_conf 
        card->num_configs       = cnf_num;         // 设置codec_conf数量 
        for (i = 0; i < li->link; i++) {  // 遍历每条音频数据链路
                if (li->num[i].cpus) {    // 走这里
                        /* Normal CPU */
                        dai_props[i].cpus       =
                        dai_link[i].cpus        = dlcs;             // 设置cpu设备 
                        dai_props[i].num.cpus   =
                        dai_link[i].num_cpus    = li->num[i].cpus;  // 设置cpu设备数量
                        dai_props[i].cpu_dai    = dais;             // 设置cpu_dai

                        dlcs += li->num[i].cpus;     // 指向向后移动cpus
                        dais += li->num[i].cpus;     // 指向向后移动cpus 
                } else {
                        /* DPCM Be's CPU = dummy */
                        dai_props[i].cpus       =
                        dai_link[i].cpus        = &priv->dummy;
                        dai_props[i].num.cpus   =
                        dai_link[i].num_cpus    = 1;
                }

                if (li->num[i].codecs) {  // 走这里
                        /* Normal Codec */
                        dai_props[i].codecs     =
                        dai_link[i].codecs      = dlcs;       // 设置codec设备
                        dai_props[i].num.codecs = 
                        dai_link[i].num_codecs  = li->num[i].codecs;   // 设置codec设备数量
                        dai_props[i].codec_dai  = dais;                // 设置cpu_dai 

                        dlcs += li->num[i].codecs;   // 指针向后移动codecs
                        dais += li->num[i].codecs;   // 指针向后移动codecs

                        if (!li->num[i].cpus) {  // 并不会进入这里 
                                /* DPCM Be's Codec */
                                dai_props[i].codec_conf = cconf;
                                cconf += li->num[i].codecs;
                        }
                } else {
                        /* DPCM Fe's Codec = dummy */
                        dai_props[i].codecs     =
                        dai_link[i].codecs      = &priv->dummy;
                        dai_props[i].num.codecs =
                        dai_link[i].num_codecs  = 1;
                }

                if (li->num[i].platforms) {  // 走这里
                        /* Have Platform */
                        dai_props[i].platforms          =
                        dai_link[i].platforms           = dlcs; // 设置platform设备
                        dai_props[i].num.platforms      = 
                        dai_link[i].num_platforms       = li->num[i].platforms;   // 设置platform设备数量

                        dlcs += li->num[i].platforms;   // 指针向后移动platforms
                } else {
                        /* Doesn't have Platform */
                        dai_props[i].platforms          =
                        dai_link[i].platforms           = NULL;
                        dai_props[i].num.platforms      =
                        dai_link[i].num_platforms       = 0;
                }
        }

        return 0;
}
View Code

4.4 simple_parse_of

simple_parse_of函数定义在sound/soc/generic/simple-card.c,其用来解析rt5651-sound设备节点的属性信息,主要是simple-audio-card,widgets 、simple-audio-card,routing属性,并将这些信息应用到 snd_soc_card 数据结构中;

static int simple_parse_of(struct asoc_simple_priv *priv, struct link_info *li)
{
        struct snd_soc_card *card = simple_priv_to_card(priv);
        int ret;

        ret = asoc_simple_parse_widgets(card, PREFIX);
        if (ret < 0)
                return ret;

        ret = asoc_simple_parse_routing(card, PREFIX);
        if (ret < 0)
                return ret;

        ret = asoc_simple_parse_pin_switches(card, PREFIX);
        if (ret < 0)
                return ret;

        /* Single/Muti DAI link(s) & New style of DT node */
        memset(li, 0, sizeof(*li));
        ret = simple_for_each_link(priv, li,
                                   simple_dai_link_of,
                                   simple_dai_link_of_dpcm);
        if (ret < 0)
                return ret;

        ret = asoc_simple_parse_card_name(card, PREFIX);
        if (ret < 0)
                return ret;

        ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs");

        return ret;
}

该函数主要完成以下工作:

  • 调用 asoc_simple_parse_widgets函数解析析控件(widgets)的信息(即simple-audio-card,widgets属性),并将解析得到的信息保存到snd_soc_card成员of_dapm_widgets、num_of_dapm_widgets 中;
  • 调用 asoc_simple_parse_routing函数解析路由(routing)的信息(即simple-audio-card,routing属性),并将解析得到的信息保存到snd_soc_card成员of_dapm_routes、num_of_dapm_routes中;
  • 调用 asoc_simple_parse_pin_switches函数解析引脚开关(pin switches)的信息(即simple-audio-card,pin-switches属性属性),并将解析得到的信息保存到snd_soc_card成员of_dapm_routes、num_of_dapm_routes中;
  • 调用simple_dai_link_of函数解析simple-audio-card,cpu、simple-audio-card,codec属性指向的设备节点,并将这些信息存储在音频数据链路的snd_soc_dai_link的成员cpus、或codec;此外还会解析simple-audio-card,mclk-fs、simple-audio-card,format属性等
  • 调用 asoc_simple_parse_card_name函数解析声卡的名称,用于解析设备节点rt5651-sound中的label以及simple-audio-card,name属性,并将解析得到的信息保存到snd_soc_card成员name中;
  • 调用 snd_soc_of_parse_aux_devs函数解析设备节点rt5651-sound中的lsimple-audio-card,aux-devs属性,并将解析得到的信息保存到snd_soc_card成员aux_devs、num_aux_devs中;

这里我们介绍一下simple_parse_of函数内部调用的子函数,asoc_simple_parse_widgets、asoc_simple_parse_routing、asoc_simple_parse_pin_switches、simple_for_each_link、asoc_simple_parse_card_name、snd_soc_of_parse_aux_devs。

4.4.1 asoc_simple_parse_widgets

asoc_simple_parse_widgets函数定义在sound/soc/generic/simple-card-utils.c,用于解析设备节点rt5651-sound中的simple-audio-card,widgets属性,并将解析得到的信息保存到snd_soc_card成员of_dapm_widgets、num_of_dapm_widgets 中;

simple-audio-card,widgets =
    "Microphone", "Mic Jack",
    "Headphone", "Headphones";

每个条目都是一对字符串:

  • 第一个是控件模板名称,在Machine驱动中这个是确定的只有那么几种widget,Microphone(表示麦克风)、Headphone(表示耳机)、Speaker(表示扬声器)、Line(线路);
  • 第二个是控件实例名称,可以自由定义,可以将其看看做控件模板名称的别名,比如“Microphone”被重命名为“Mic Jack”;

函数内容如下:

int asoc_simple_parse_widgets(struct snd_soc_card *card,
                              char *prefix)    // "simple-audio-card,"
{
        struct device_node *node = card->dev->of_node;    // 获取平台设备的设备节点,即rt5651-sound
        char prop[128];

        if (!prefix)
                prefix = "";

        snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); // simple-audio-card,widgets

        if (of_property_read_bool(node, prop))  // 如果该属性存在,进入
                return snd_soc_of_parse_audio_simple_widgets(card, prop);

        /* no widgets is not error */
        return 0;
}

可以这里先是检查simple-audio-card,widgets属性是否存在,如果存在则调用snd_soc_of_parse_audio_simple_widgets函数,该函数定义在sound/soc/soc-core.c:

int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card,
                                          const char *propname)
{
        struct device_node *np = card->dev->of_node;    // 获取平台设备的设备节点,即rt5651-sound
        struct snd_soc_dapm_widget *widgets;
        const char *template, *wname;
        int i, j, num_widgets;

        num_widgets = of_property_count_strings(np, propname);  // 获取simple-audio-card,widgets属性值中字符串的数量
        if (num_widgets < 0) {
                dev_err(card->dev,
                        "ASoC: Property '%s' does not exist\n", propname);
                return -EINVAL;
        }
        if (!num_widgets) {
                dev_err(card->dev, "ASoC: Property '%s's length is zero\n",
                        propname);
                return -EINVAL;
        }
        if (num_widgets & 1) {
                dev_err(card->dev,
                        "ASoC: Property '%s' length is not even\n", propname);
                return -EINVAL;
        }

        num_widgets /= 2;               // 计算控件的数量

        widgets = devm_kcalloc(card->dev, num_widgets, sizeof(*widgets),     // 动态分配数组,为每一个控件分配一个struct snd_soc_dapm_widget 
                               GFP_KERNEL);
        if (!widgets) {
                dev_err(card->dev,
                        "ASoC: Could not allocate memory for widgets\n");
                return -ENOMEM;
        }

        for (i = 0; i < num_widgets; i++) {  // 保存每一个控件的信息到snd_soc_dapm_widget 
                int ret = of_property_read_string_index(np, propname,
                                                        2 * i, &template);  // 读取设备节点属性propname的第2*i个值,保存到template
                if (ret) {
                        dev_err(card->dev,
                                "ASoC: Property '%s' index %d read error:%d\n",
                                propname, 2 * i, ret);
                        return -EINVAL;
                }

                for (j = 0; j < ARRAY_SIZE(simple_widgets); j++) {      // 遍历simple_widgets数组
                        if (!strncmp(template, simple_widgets[j].name,  // 根据name查找。找到则保存到widgets[i]
                                     strlen(simple_widgets[j].name))) {
                                widgets[i] = simple_widgets[j];
                                break;
                        }
                }

                if (j >= ARRAY_SIZE(simple_widgets)) {  // 没有找到,即设备节点中定义了不支持的控件模板名称
                        dev_err(card->dev,
                                "ASoC: DAPM widget '%s' is not supported\n",
                                template);
                        return -EINVAL;
                }

                ret = of_property_read_string_index(np, propname,
                                                    (2 * i) + 1,
                                                    &wname);  // 读取设备节点属性propname的第2*i+1个值,保存到wname  
                if (ret) {
                        dev_err(card->dev,
                                "ASoC: Property '%s' index %d read error:%d\n",
                                propname, (2 * i) + 1, ret);
                        return -EINVAL;
                }

                widgets[i].name = wname;   // 设置name
        }

        card->of_dapm_widgets = widgets;           // 保存音频控件 
        card->num_of_dapm_widgets = num_widgets;   // 音频控件数量

        return 0;
}
View Code

其中simple_widgets是一个全局静态数组, 定义了Machine域widget,包括麦克风(MIC),耳机(HP),扬声器(SPK),线路输入接口(LINE)。

static const struct snd_soc_dapm_widget simple_widgets[] = {
        SND_SOC_DAPM_MIC("Microphone", NULL),
        SND_SOC_DAPM_LINE("Line", NULL),
        SND_SOC_DAPM_HP("Headphone", NULL),
        SND_SOC_DAPM_SPK("Speaker", NULL),
};
4.4.2 asoc_simple_parse_routing

asoc_simple_parse_routing函数定义在sound/soc/generic/simple-card-utils.c,用于解析设备节点rt5651-sound中的simple-audio-card,routing属性,并将解析得到的信息保存到snd_soc_card成员of_dapm_routes、num_of_dapm_routes中;

simple-audio-card,routing =
    "Mic Jack", "micbias1",
    "IN2P", "Mic Jack",
    "IN2N""Mic Jack", 
    "Headphones", "HPOL",
    "Headphones", "HPOR";

用于置与Codec(ALC5651)物理输入引脚、物理输出端引脚连接的路径;每个条目都是一对字符串,第一个是目的(sink),第二个是源(source):

函数内容如下:

int asoc_simple_parse_routing(struct snd_soc_card *card,
                              char *prefix)
{
        struct device_node *node = card->dev->of_node;  // 获取平台设备的设备节点,即rt5651-sound
        char prop[128];

        if (!prefix)
                prefix = "";

        snprintf(prop, sizeof(prop), "%s%s", prefix, "routing");  // simple-audio-card,routing

        if (!of_property_read_bool(node, prop))  // 如果属性不存在,则进入
                return 0;

        return snd_soc_of_parse_audio_routing(card, prop);
}

可以这里先是检查simple-audio-card,routing属性是否存在,如果存在则调用snd_soc_of_parse_audio_routing函数,该函数定义在sound/soc/soc-core.c:

int snd_soc_of_parse_audio_routing(struct snd_soc_card *card,
                                   const char *propname)
{
        struct device_node *np = card->dev->of_node;  // 获取平台设备的设备节点,即rt5651-sound
        int num_routes;
        struct snd_soc_dapm_route *routes;
        int i;

        num_routes = of_property_count_strings(np, propname);  // 获取simple-audio-card,routing属性值中字符串的数量
        if (num_routes < 0 || num_routes & 1) {
                dev_err(card->dev,
                        "ASoC: Property '%s' does not exist or its length is not even\n",
                        propname);
                return -EINVAL;
        }
        num_routes /= 2;    // 计算路由的数量

        routes = devm_kcalloc(card->dev, num_routes, sizeof(*routes),  // 动态分配数组,为每一个路由分配一个struct snd_soc_dapm_route 
                              GFP_KERNEL);
        if (!routes) {
                dev_err(card->dev,
                        "ASoC: Could not allocate DAPM route table\n");
                return -ENOMEM;
        }

        for (i = 0; i < num_routes; i++) {  // 保存每一个路由的信息到snd_soc_dapm_route
                int ret = of_property_read_string_index(np, propname,
                                                        2 * i, &routes[i].sink);  // 读取设备节点属性propname的第2*i个值,保存到sink
                if (ret) {
                        dev_err(card->dev,
                                "ASoC: Property '%s' index %d could not be read: %d\n",
                                propname, 2 * i, ret);
                        return -EINVAL;
                }
                ret = of_property_read_string_index(np, propname,
                        (2 * i) + 1, &routes[i].source);       // 读取设备节点属性propname的第2*i+1个值,保存到source  
                if (ret) {
                        dev_err(card->dev,
                                "ASoC: Property '%s' index %d could not be read: %d\n",
                                propname, (2 * i) + 1, ret);
                        return -EINVAL;
                }
        }

        card->num_of_dapm_routes = num_routes;   // 保存路由
        card->of_dapm_routes = routes;           // 路由数量

        return 0;
}
View Code
4.4.3 asoc_simple_parse_pin_switches

asoc_simple_parse_routing函数定义在sound/soc/generic/simple-card-utils.c,用于解析设备节点rt5651-sound中的simple-audio-card,pin-switches属性,并将解析得到的信息保存到snd_soc_card成员of_dapm_routes、num_of_dapm_routes中;比如:

simple-audio-card,pin-switches = "Speaker";

由于rt5651-sound设备节点并没有使用该属性,所以这里不做过多深究;

int asoc_simple_parse_pin_switches(struct snd_soc_card *card,
                                   char *prefix)
{
        char prop[128];

        if (!prefix)
                prefix = "";

        snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches");

        return snd_soc_of_parse_pin_switches(card, prop);
}

这里直接调用snd_soc_of_parse_pin_switches函数,该函数定义在sound/soc/soc-core.c:

int snd_soc_of_parse_pin_switches(struct snd_soc_card *card, const char *prop)
{
        const unsigned int nb_controls_max = 16;
        const char **strings, *control_name;
        struct snd_kcontrol_new *controls;
        struct device *dev = card->dev;
        unsigned int i, nb_controls;
        int ret;

        if (!of_property_read_bool(dev->of_node, prop))      // 属性不存在,直接返回
                return 0;

        strings = devm_kcalloc(dev, nb_controls_max,         // 动态分配数组,数组长度为16个,每个元素都是一个指针
                               sizeof(*strings), GFP_KERNEL);
        if (!strings)
                return -ENOMEM;

        ret = of_property_read_string_array(dev->of_node, prop,
                                            strings, nb_controls_max);  // 读取属性值,保存到strings指针数组中
        if (ret < 0)
                return ret;

        nb_controls = (unsigned int)ret;

        controls = devm_kcalloc(dev, nb_controls,
                                sizeof(*controls), GFP_KERNEL);  // 动态分配数组,数组长度为nb_controls,每一个元素都是struct snd_kcontrol_new
        if (!controls)
                return -ENOMEM;

        for (i = 0; i < nb_controls; i++) {        // 保存每一个control的信息到snd_kcontrol_new
                control_name = devm_kasprintf(dev, GFP_KERNEL,
                                              "%s Switch", strings[i]); // 设置control名称
                if (!control_name)
                        return -ENOMEM;

                controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
                controls[i].name = control_name;
                controls[i].info = snd_soc_dapm_info_pin_switch;
                controls[i].get = snd_soc_dapm_get_pin_switch;
                controls[i].put = snd_soc_dapm_put_pin_switch;
                controls[i].private_value = (unsigned long)strings[i];
        }

        card->controls = controls;       // 保存controls
        card->num_controls = nb_controls; // controls数量

        return 0;
}
View Code
4.4.4 simple_dai_link_of(重要)

这个函数上面已经介绍过了,simple_for_each_link函数会解析设备树rt5651-sound设备节点,遍历每条音频数据链路并调用两个不同的计数回调函数simple_dai_link_of和simple_dai_link_of_dpcm,用于对不同类型的音频数据链路中的cpu、codec、platfrom数量进行统计,并保存在li变量中。

这里我们只需要看一下simple_dai_link_of函数即可,当li->cpu=1时,执行该函数,函数定义在sound/soc/generic/simple-card.c:

static int simple_dai_link_of(struct asoc_simple_priv *priv,
                              struct device_node *np,    // 为simple-audio-card,cpu属性指向的设备节点,即{sound-dai = <&i2s0>;};
                              struct device_node *codec, // 为simple-audio-card,codec属性指向的设备节点,即{sound-dai = <&rt5651>;};
                              struct link_info *li,      // 音频数据链路信息 此时该数据结构内存全部被设置为了0
                              bool is_top)   // 1
{
        struct device *dev = simple_priv_to_dev(priv);  // 获取&priv->snd_card.dev,即获取平台设备的device
        struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);      //  获取成员变量priv->dai_link[li->link]  获取第0个音频数据链路
        struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);      // 获取当前链路上第0个cpu设备&(dai_link)->cpus[0]
        struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);  // 获取当前链路上第0个的codec设备 &(dai_link)->codecs[0]
        struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0); // 获取当前链路上第0个的platform设备 &(dai_link)->platforms[0]
        struct device_node *cpu = NULL;
        struct device_node *node = NULL;
        struct device_node *plat = NULL;
        char dai_name[64];
        char prop[128];
        char *prefix = "";
        int ret, single_cpu = 0;

        cpu  = np;                  // 设置为simple-audio-card,cpu设备节点
        node = of_get_parent(np);   // 获取父设备节点,即rt5651-sound设备节点

        dev_dbg(dev, "link_of (%pOF)\n", node);

        /* For single DAI link & old style of DT node */
        if (is_top)
                prefix = PREFIX;  // prefix = "simple-audio-card,"

        snprintf(prop, sizeof(prop), "%splat", prefix);  // "simple-audio-card,plat"
        plat = of_get_child_by_name(node, prop);   // 未定义该设备节点,所以返回NULL

        ret = simple_parse_node(priv, cpu, li, prefix, &single_cpu);  // 这个函数很重要,单独介绍,主要目的解析simple-audio-card,cpu设备节点
        if (ret < 0)
                goto dai_link_of_err;

        ret = simple_parse_node(priv, codec, li, prefix, NULL);    // 这个函数很重要,单独介绍,主要目的simple-audio-card,codec设备节点
        if (ret < 0)
                goto dai_link_of_err;

        ret = asoc_simple_parse_platform(plat, platforms);  // 初始化platforms->of_node为rt5651-sound设备节点
        if (ret < 0)
                goto dai_link_of_err;

        snprintf(dai_name, sizeof(dai_name), // 得到dai_name为ff880000.i2s-rt5651-aif1
                 "%s-%s", cpus->dai_name, codecs->dai_name);  // cpu->dai_name为"ff880000.i2s",codec->dai_name为"rt5651-aif1"

        asoc_simple_canonicalize_cpu(cpus, single_cpu);
        asoc_simple_canonicalize_platform(platforms, cpus);

        ret = simple_link_init(priv, node, codec, li, prefix, dai_name); // 初始化音频数据链路snd_soc_dai_link的部分成员

dai_link_of_err:
        of_node_put(plat);
        of_node_put(node);

        li->link++; // 自增

        return ret;
}

这里唯一值得关注的函数就是simple_parse_node、simple_link_init,这两个函数比较复杂,我们后面单独介绍。

当li->cpu=0时,__simple_for_each_link函数将不会做任何事情,因此simple_dai_link_of_dpcm这个函数不会执行到:

static int simple_dai_link_of_dpcm(struct asoc_simple_priv *priv,
                                   struct device_node *np,   // np为simple-audio-card,cpu节点
                                   struct device_node *codec,  // simple-audio-card,codec设备节点,即{sound-dai = <&rt5651>;};
                                   struct link_info *li,
                                   bool is_top)
{
        struct device *dev = simple_priv_to_dev(priv);                               // 获取&priv->snd_card.dev,即获取平台设备的device
        struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);     // 获取成员变量priv->dai_link[li->link]
        struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);   // 获取priv->dai_props[li->link]
        struct device_node *top = dev->of_node;          // 获取设备树中设备节点 ,在我们这里也就是rt5651-sound设备节点 
        struct device_node *node = of_get_parent(np);    // 获取父设备节点
        char *prefix = "";
        char dai_name[64];
        int ret;

        dev_dbg(dev, "link_of DPCM (%pOF)\n", np);

        /* For single DAI link & old style of DT node */
        if (is_top)
                prefix = PREFIX;

        if (li->cpu) {  // 为1时进入
                struct snd_soc_dai_link_component *cpus = asoc_link_to_cpu(dai_link, 0);           // 获取当前链路上第0个cpu设备&(dai_link)->cpus[0]
                struct snd_soc_dai_link_component *platforms = asoc_link_to_platform(dai_link, 0); // 存储当前链路上第0个的platform设备 &(dai_link)->platforms[0]
                int is_single_links = 0;

                /* Codec is dummy */

                /* FE settings */                      // front end dai link配置  
                dai_link->dynamic               = 1;   // 动态PCM 标记
                dai_link->dpcm_merged_format    = 1;   // 标记是否使用合并格式的 DPCM

                ret = simple_parse_node(priv, np, li, prefix, &is_single_links);
                if (ret < 0)
                        goto out_put_node;

                snprintf(dai_name, sizeof(dai_name), "fe.%s", cpus->dai_name);

                asoc_simple_canonicalize_cpu(cpus, is_single_links);
                asoc_simple_canonicalize_platform(platforms, cpus);
        } else {  // 为0时进入
                struct snd_soc_dai_link_component *codecs = asoc_link_to_codec(dai_link, 0);
                struct snd_soc_codec_conf *cconf;

                /* CPU is dummy */

                /* BE settings */
                dai_link->no_pcm                = 1;
                dai_link->be_hw_params_fixup    = asoc_simple_be_hw_params_fixup;

                cconf   = simple_props_to_codec_conf(dai_props, 0);

                ret = simple_parse_node(priv, np, li, prefix, NULL);
                if (ret < 0)
                        goto out_put_node;

                snprintf(dai_name, sizeof(dai_name), "be.%s", codecs->dai_name);

                /* check "prefix" from top node */
                snd_soc_of_parse_node_prefix(top, cconf, codecs->of_node,
                                              PREFIX "prefix");
                snd_soc_of_parse_node_prefix(node, cconf, codecs->of_node,
                                             "prefix");
                snd_soc_of_parse_node_prefix(np, cconf, codecs->of_node,
                                             "prefix");
        }

        simple_parse_convert(dev, np, &dai_props->adata);

        snd_soc_dai_link_set_capabilities(dai_link);

        ret = simple_link_init(priv, node, codec, li, prefix, dai_name);

out_put_node:
        li->link++;

        of_node_put(node);
        return ret;
}
View Code
4.4.5 asoc_simple_parse_card_name

asoc_simple_parse_card_name函数定义在sound/soc/generic/simple-card-utils.c,用于解析设备节点rt5651-sound中的label以及simple-audio-card,name属性,并将解析得到的信息保存到snd_soc_card成员name中;

int asoc_simple_parse_card_name(struct snd_soc_card *card,
                                char *prefix)
{
        int ret;

        if (!prefix)
                prefix = "";

        /* Parse the card name from DT */
        ret = snd_soc_of_parse_card_name(card, "label");  // 获取rt5651-sound设备节点label属性
        if (ret < 0 || !card->name) {  // 属性不存在
                char prop[128];

                snprintf(prop, sizeof(prop), "%sname", prefix);  // simple-audio-card,name
                ret = snd_soc_of_parse_card_name(card, prop);    // 获取rt5651-sound设备节点simple-audio-card,name属性值"realtek,rt5651-codec",保存到card->name
                if (ret < 0)
                        return ret;
        }

        if (!card->name && card->dai_link)
                card->name = card->dai_link->name;

        return 0;
}

这里直接调用snd_soc_of_parse_card_name函数,该函数定义在sound/soc/soc-core.c:

/* Retrieve a card's name from device tree */
int snd_soc_of_parse_card_name(struct snd_soc_card *card,
                               const char *propname)  // "label"
{
        struct device_node *np;
        int ret;

        if (!card->dev) {
                pr_err("card->dev is not set before calling %s\n", __func__);
                return -EINVAL;
        }

        np = card->dev->of_node;     // 获取平台设备的设备节点,即rt5651-sound

        ret = of_property_read_string_index(np, propname, 0, &card->name);  // 读取设备节点属性propname的第0个值,保存到card->name
        /*
         * EINVAL means the property does not exist. This is fine providing
         * card->name was previously set, which is checked later in
         * snd_soc_register_card.
         */
        if (ret < 0 && ret != -EINVAL) {
                dev_err(card->dev,
                        "ASoC: Property '%s' could not be read: %d\n",
                        propname, ret);
                return ret;
        }

        return 0;
}
View Code
4.4.6 snd_soc_of_parse_aux_devs

asoc_simple_parse_card_name函数定义在sound/soc/soc-core.c,用于解析设备节点rt5651-sound中的lsimple-audio-card,aux-devs属性,并将解析得到的信息保存到snd_soc_card成员aux_devs、num_aux_devs中;

int snd_soc_of_parse_aux_devs(struct snd_soc_card *card, const char *propname)  // "simple-audio-card,aux-devs"
{
        struct device_node *node = card->dev->of_node;
        struct snd_soc_aux_dev *aux;
        int num, i;

        num = of_count_phandle_with_args(node, propname, NULL); //  根据属性名和参数的要求,统计节点中符合条件的 phandle 的数量  这里找不到匹配的属性,返回负数
        if (num == -ENOENT) {
                return 0;
        } else if (num < 0) {
                dev_err(card->dev, "ASOC: Property '%s' could not be read: %d\n",
                        propname, num);
                return num;
        }

        aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL);  // 动态分配数组,每个元素都是一个struct snd_soc_aux_dev
        if (!aux)
                return -ENOMEM;
        card->aux_dev = aux;
        card->num_aux_devs = num;

        for_each_card_pre_auxs(card, i, aux) {
                aux->dlc.of_node = of_parse_phandle(node, propname, i);
                if (!aux->dlc.of_node)
                        return -EINVAL;
        }
}

4.5 snd_soc_card_set_drvdata

snd_soc_card_set_drvdata函数定义在include/sound/soc-card.h,用于设置snd_soc_card的私有数据drvdata为前面申请的asoc_simple_priv类型的变量priv;

/* device driver data */
static inline void snd_soc_card_set_drvdata(struct snd_soc_card *card,
                                            void *data)
{
        card->drvdata = data;
}

4.6 asoc_simple_debug_info

asoc_simple_debug_info函数定义在include/sound/simple_card_utils.h,如果定义了DEBUG模式,则该函数如下:

static inline void asoc_simple_debug_info(struct asoc_simple_priv *priv)
{
        struct snd_soc_card *card = simple_priv_to_card(priv);
        struct device *dev = simple_priv_to_dev(priv);

        int i;

        if (card->name)
                dev_dbg(dev, "Card Name: %s\n", card->name);

        for (i = 0; i < card->num_links; i++) {   // 遍历每一个音频数据链路
                struct simple_dai_props *props = simple_priv_to_props(priv, i);   // 获取priv->dai_props[i]
                struct snd_soc_dai_link *link = simple_priv_to_link(priv, i);     // 获取成员变量priv->dai_link[i]
                struct asoc_simple_dai *dai;
                struct snd_soc_codec_conf *cnf;
                int j;

                dev_dbg(dev, "DAI%d\n", i);

                dev_dbg(dev, "cpu num = %d\n", link->num_cpus);
                for_each_prop_dai_cpu(props, j, dai)     // 遍历当前链路上每一个cpu_dai
                        asoc_simple_debug_dai(priv, "cpu", dai);        // 输出dai各个字段信息
                dev_dbg(dev, "codec num = %d\n", link->num_codecs);
                for_each_prop_dai_codec(props, j, dai)    // 遍历当前链路上每一个codec_dai
                        asoc_simple_debug_dai(priv, "codec", dai);       // 输出dai各个字段信息

                if (link->name)
                        dev_dbg(dev, "dai name = %s\n", link->name);
                if (link->dai_fmt)
                        dev_dbg(dev, "dai format = %04x\n", link->dai_fmt);
                if (props->adata.convert_rate)
                        dev_dbg(dev, "convert_rate = %d\n", props->adata.convert_rate);
                if (props->adata.convert_channels)
                        dev_dbg(dev, "convert_channels = %d\n", props->adata.convert_channels);
                for_each_prop_codec_conf(props, j, cnf)
                        if (cnf->name_prefix)
                                dev_dbg(dev, "name prefix = %s\n", cnf->name_prefix);
                if (props->mclk_fs)
                        dev_dbg(dev, "mclk-fs = %d\n", props->mclk_fs);
        }
}

可以看到这个函数主要是用来输出每一个音频数据链路的信息。

4.7 devm_snd_soc_register_card

evm_snd_soc_register_card函数用于注册 ASoC声卡设备,其内部调用了snd_soc_register_card,函数定义在sound/soc/soc-devres.c:

/**
 * devm_snd_soc_register_card - resource managed card registration
 * @dev: Device used to manage card
 * @card: Card to register
 *
 * Register a card with automatic unregistration when the device is
 * unregistered.
 */
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{
        struct snd_soc_card **ptr;
        int ret;

        ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);   // 动态分配内存,指向struct *snd_soc_card,在设备释放时自动调用devm_card_release函数
        if (!ptr)
                return -ENOMEM;

        ret = snd_soc_register_card(card);  // 注册ASoC声卡设备
        if (ret == 0) {                     // 注册成功,则将声卡指针存储到之前分配的指针中,并通过devres_add函数将指针添加到设备的资源列表中;
// 这样,当设备注销时,资源管理机制会自动调用释放函数,从而卸载声卡。
*ptr = card; devres_add(dev, ptr); } else { devres_free(ptr); } return ret; }

关于ASoC 声卡的注册函数snd_soc_register_card我们已经在Rockchip RK3399 - ASoC Machine驱动基础中单独介绍了,由于声卡注册流程是非常复杂的,这里就不再重复介绍。

五、simple_parse_node函数分析

simple_parse_node是在simple_dai_link_of中被调用的,函数定义在sound/soc/generic/simple-card.c,该函数用于解析simple-audio-card,cpu属性指向的设备节点、或simple-audio-card,codec属性指向的设备节点,主要目的是为了初始化音频数据链路的snd_soc_dai_link的成员cpus、或codec;

static int simple_parse_node(struct asoc_simple_priv *priv,
                             struct device_node *np,   //simple-audio-card,cpu属性指向的设备节点、或simple-audio-card,codec属性指向的设备节点
                             struct link_info *li,
                             char *prefix, // simple-audio-card,
                             int *cpu)
{
        struct device *dev = simple_priv_to_dev(priv);            // 获取&priv->snd_card.dev,即获取平台设备的device
        struct device_node *top = dev->of_node;                   // rt5651-sound设备节点
        struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);       //  获取成员变量priv->dai_link[li->link]  即获取第0个音频数据链路
        struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link);     //  获取priv->dai_props[li->link] 即获取第0个音频数据链路上的属性
        struct snd_soc_dai_link_component *dlc;                   // 存放音频链路中的设备
        struct asoc_simple_dai *dai;
        int ret;

        if (cpu) { // np如果是simple-audio-card,cpu设备节点
                dlc = asoc_link_to_cpu(dai_link, 0);                // 获取dai_link链路上第0个cpu设备&(dai_link)->cpus[0]
                dai = simple_props_to_dai_cpu(dai_props, 0);        // dai_props->cpu_dai[0] 
        } else {  // np如果是imple-audio-card,codec设备节点
                dlc = asoc_link_to_codec(dai_link, 0);             // 获取dai_link链路上第0个codec设备&(dai_link)->codec[0]
                dai = simple_props_to_dai_codec(dai_props, 0);     // dai_props->codec_dai[0] 
        }

        simple_parse_mclk_fs(top, np, dai_props, prefix);  // 从设备节点解析simple-audio-card,mclk-fs属性的值,保存到dai_props->mclk_fs

        ret = asoc_simple_parse_dai(np, dlc, cpu);         // 解析设备节点np,初始化dlc成员of_node、dai_name
        if (ret)
                return ret;

        ret = asoc_simple_parse_clk(dev, np, dai, dlc);   // 解析i2s0或者rt5651设备节点获取时钟,初始化dai->sysclk
        if (ret)
                return ret;

        ret = asoc_simple_parse_tdm(np, dai);
        if (ret)
                return ret;

        return 0;
}

以解析simple-audio-card,cpu设备节点为例,函数实参:

  • np为simple-audio-card,cpu设备节点,即{sound-dai = <&i2s0>;};
  • li数据结构内存全为0;
  • prefix为“simple-audio-card,”;
  • cpu指向的内存值为0;

5.1 simple_parse_mclk_fs

simple_parse_mclk_fs函数定义在sound/soc/generic/simple-card.c,该函数获取rt5651-sound设备节点的simple-audio-card,mclk-fs属性的值,保存到props->mclk_fs;

static void simple_parse_mclk_fs(struct device_node *top,    // rt5651-sound设备节点 
                                 struct device_node *np,     //simple-audio-card,cpu属性指向的设备节点、或simple-audio-card,codec属性指向的设备节点
                                 struct simple_dai_props *props,  // 第0个音频数据链路上的属性
                                 char *prefix)                    // simple-audio-card,
{ 
        struct device_node *node = of_get_parent(np); // rt5651-sound设备节点 
        char prop[128];

        snprintf(prop, sizeof(prop), "%smclk-fs", PREFIX);    // simple-audio-card,mclk-fs
        of_property_read_u32(top,       prop, &props->mclk_fs);  // 获取top节点simple-audio-card,mclk-fs属性的值,保存到props->mclk_fs

        snprintf(prop, sizeof(prop), "%smclk-fs", prefix);     // simple-audio-card,mclk-fs
        of_property_read_u32(node,      prop, &props->mclk_fs);  // 获取node节点simple-audio-card,mclk-fs属性的值,保存到props->mclk_fs
        of_property_read_u32(np,        prop, &props->mclk_fs);  // np不存在simple-audio-card,mclk-fs属性

        of_node_put(node);
}

5.2 asoc_simple_parse_dai

asoc_simple_parse_dai函数定义在sound/soc/generic/simple-card.c,该函数解析设备节点node,使用设备节点信息初始化dlc成员of_node、dai_name;

static int asoc_simple_parse_dai(struct device_node *node,   //simple-audio-card,cpu属性指向的设备节点、或simple-audio-card,codec属性指向的设备节点
                                 struct snd_soc_dai_link_component *dlc, // 音频数据链路中的设备
                                 int *is_single_link)
{
        struct of_phandle_args args;
        int ret;

        if (!node)
                return 0;

        /*
         * Get node via "sound-dai = <&phandle port>"
         * it will be used as xxx_of_node on soc_bind_dai_link()
         */
        ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args); // 如果是simple-audio-card,cpu属性指向的设备节点,DAI为"sound-dai",CELL为"#sound-dai-cells", 
// 即获取phandle列表中的第0个元素i2s0
if (ret) return ret; /* * FIXME * * Here, dlc->dai_name is pointer to CPU/Codec DAI name. * If user unbinded CPU or Codec driver, but not for Sound Card, * dlc->dai_name is keeping unbinded CPU or Codec * driver's pointer. * * If user re-bind CPU or Codec driver again, ALSA SoC will try * to rebind Card via snd_soc_try_rebind_card(), but because of * above reason, it might can't bind Sound Card. * Because Sound Card is pointing to released dai_name pointer. * * To avoid this rebind Card issue, * 1) It needs to alloc memory to keep dai_name eventhough * CPU or Codec driver was unbinded, or * 2) user need to rebind Sound Card everytime * if he unbinded CPU or Codec. */ ret = snd_soc_of_get_dai_name(node, &dlc->dai_name); // 设置dlc->dai_name,以为例simple-audio-card,codec {sound-dai = <&rt5651>;};
// 查找rt5651设备节点所属的component,遍历其dai_list链表,设置dai_name为其中某一个dai driver的名称
if (ret < 0) return ret; dlc->of_node = args.np; // 设置成员of_node,指向i2s0设备节点 if (is_single_link) *is_single_link = !args.args_count; return 0; }

snd_soc_of_get_dai_name函数定义在sound/soc/soc-core.c:

int snd_soc_of_get_dai_name(struct device_node *of_node,
                            const char **dai_name)
{
        struct of_phandle_args args;
        int ret;

        ret = of_parse_phandle_with_args(of_node, "sound-dai",
                                         "#sound-dai-cells", 0, &args);// 如果是simple-audio-card,cpu设备节点,DAI为"sound-dai",CELL为"#sound-dai-cells",
                                            // 即获取phandle列表中的第0个元素i2s0
        if (ret)
                return ret;

        ret = snd_soc_get_dai_name(&args, dai_name); // 获取dai名称

        of_node_put(args.np);

        return ret;
}
View Code

snd_soc_get_dai_name函数定义在sound/soc/soc-core.c:

int snd_soc_get_dai_name(const struct of_phandle_args *args,
                                const char **dai_name)
{
        struct snd_soc_component *pos;
        int ret = -EPROBE_DEFER;

        mutex_lock(&client_mutex);
        for_each_component(pos) {  // 遍历全局链表component_list,Platform/Codec驱动注册时会向该链表添加元素
                struct device_node *component_of_node = soc_component_to_node(pos); // 获取对应的设备节点 比如i2s0、rt5651

                if (component_of_node != args->np || !pos->num_dai) // 设备节点不匹配,跳过
                        continue;

                ret = snd_soc_component_of_xlate_dai_name(pos, args, dai_name);
                if (ret == -ENOTSUPP) {  // 如果没设置,走这里
                        struct snd_soc_dai *dai;
                        int id = -1;

                        switch (args->args_count) {   // 获取sound-dai属性值参数个数,如果参数个数为1,说明指定了使用第几个dai,即值格式 <&phandle port>
                        case 0:            // 我们没有配置port走这里
                                id = 0; /* same as dai_drv[0] */
                                break;
                        case 1:
                                id = args->args[0];
                                break;
                        default:
                                /* not supported */
                                break;
                        }

                        if (id < 0 || id >= pos->num_dai) {
                                ret = -EINVAL;
                                continue;
                        }

                        ret = 0;

                        /* find target DAI */
                        for_each_component_dais(pos, dai) {  // 遍历component的dai_list链表,赋值给dai
                                if (id == 0)
                                        break;
                                id--;
                        }

                        *dai_name = dai->driver->name;   // 设置dai_name为dai driver的名称
                        if (!*dai_name)   // 如果没有设置dai driver的名称,则设置为pos->name(其值最初来自component->dev的名称)
                                *dai_name = pos->name;
                } else if (ret) {
                        /*
                         * if another error than ENOTSUPP is returned go on and
                         * check if another component is provided with the same
                         * node. This may happen if a device provides several
                         * components
                         */
                        continue;
                }

                break;
        }
        mutex_unlock(&client_mutex);
        return ret;
}

snd_soc_component_of_xlate_dai_name定义为;

int snd_soc_component_of_xlate_dai_name(struct snd_soc_component *component,
                                        const struct of_phandle_args *args,
                                        const char **dai_name)
{
        if (component->driver->of_xlate_dai_name)
                return component->driver->of_xlate_dai_name(component,
                                                            args, dai_name);
        /*
         * Don't use soc_component_ret here because we may not want to report
         * the error just yet. If a device has more than one component, the
         * first may not match and we don't want spam the log with this.
         */
        return -ENOTSUPP;
}
View Code

5.3 asoc_simple_parse_clk

asoc_simple_parse_clk函数定义在sound/soc/generic/simple-card-utils.c,通过解析i2s0或者rt5651设备节点获取时钟,初始化simple_dai->sysclk;

int asoc_simple_parse_clk(struct device *dev,        // 平台设备的device
                          struct device_node *node,  // simple-audio-card,cpu属性指向的设备节点、或simple-audio-card,codec属性指向的设备节点
                          struct asoc_simple_dai *simple_dai,
                          struct snd_soc_dai_link_component *dlc) // 音频链路中的设备
{
        struct clk *clk;
        u32 val;

        /*
         * Parse dai->sysclk come from "clocks = <&xxx>"
         * (if system has common clock)
         *  or "system-clock-frequency = <xxx>"
         *  or device's module clock.
         */
        clk = devm_get_clk_from_child(dev, node, NULL);  // 扫描node设备节点,获取索引为0的时钟, 即获取i2s0/rt5651设备节点的时钟
        simple_dai->clk_fixed = of_property_read_bool(
                node, "system-clock-fixed");
        if (!IS_ERR(clk)) {         // 走这里
                simple_dai->sysclk = clk_get_rate(clk);   // 获取系统时钟频率,保存到simple_dai->sysclk

                simple_dai->clk = clk;  // 设置系统时钟
        } else if (!of_property_read_u32(node, "system-clock-frequency", &val)) {
                simple_dai->sysclk = val;
                simple_dai->clk_fixed = true;
        } else {  
                clk = devm_get_clk_from_child(dev, dlc->of_node, NULL);if (!IS_ERR(clk))
                        simple_dai->sysclk = clk_get_rate(clk); 
        }

        if (of_property_read_bool(node, "system-clock-direction-out"))
                simple_dai->clk_direction = SND_SOC_CLOCK_OUT;

        return 0;
}

这里我们简单说一下devm_get_clk_from_child这个函数,函数定义在 drivers/clk/clk-devres.c:

struct clk *devm_get_clk_from_child(struct device *dev,
                                    struct device_node *np, const char *con_id)
{
        struct clk **ptr, *clk;

        ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL);
        if (!ptr)
                return ERR_PTR(-ENOMEM);

        clk = of_clk_get_by_name(np, con_id);  // 重点
        if (!IS_ERR(clk)) {
                *ptr = clk;
                devres_add(dev, ptr);
        } else {
                devres_free(ptr);
        }

        return clk;
}

调用of_clk_get_by_name通过扫描设备节点np属性“clock-names”中所有的值,和传入的con_id比较,如果相同,获得它的index(即“clock-names”中的第几个),调用of_clk_get,取得clock指针;如果未指定con_id,默认就是获取第一个时钟。

(1) 对于i2s0设备节点,时钟配置如下:

clock-names = "i2s_clk", "i2s_hclk";
clocks = <&cru SCLK_I2S0_8CH>, <&cru HCLK_I2S0_8CH>;

因此获取到的是RK3399 i2s_clk的时钟,即i2s0模块输出的MCLK时钟(对应的时钟名称为clk_i2s0)。

(2) 对于rt5651设备节点,时钟配置为:

clocks = <&cru SCLK_I2S_8CH_OUT>;
clock-names = "mclk";

因此获取到的是mclk的时钟,即ALC5651 is21接口的MCLK信号线输入的时钟,同时也是RK3399 i2s0接口的MCLK信号线输出的时钟(对应的时钟名称为clk_i2sout)。

六、simple_link_init函数分析

simple_link_init是在simple_dai_link_of中被调用的,主要目的是初始化音频数据链路snd_soc_dai_link的部分成员,函数定义在sound/soc/generic/simple-card.c:

static int simple_link_init(struct asoc_simple_priv *priv,
                            struct device_node *node,     // rt5651-sound设备节点
                            struct device_node *codec,    // simple-audio-card,codec设备节点,即{sound-dai = <&rt5651>;};
                            struct link_info *li,         // 音频数据链路信息 此时该数据结构内存全部被设置为了0
                            char *prefix, char *name)     // "simple-audio-card,"   "ff880000.i2s-rt5651-aif1"    
{
        struct device *dev = simple_priv_to_dev(priv);       // 获取&priv->snd_card.dev,即获取平台设备的device
        struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link);  //  获取成员变量priv->dai_link[li->link]  即获取第0个音频数据链路
        int ret;

        ret = asoc_simple_parse_daifmt(dev, node, codec,
                                       prefix, &dai_link->dai_fmt);  // 解析设备节点simple-audio-card,format属性,获取数字音频接口格式保存到dai_link->dai_fmt
        if (ret < 0)
                return 0;

        dai_link->init                  = asoc_simple_dai_init;      // 初始化函数
        dai_link->ops                   = &simple_ops;               // 音频相关的操作集

        return asoc_simple_set_dailink_name(dev, dai_link, name);  // 设置dai_link成员name、stream_name
}

6.1 asoc_simple_parse_daifmt

asoc_simple_parse_daifmt函数定义在sound/soc/generic/simple-card-utils.c,通过解析simple-audio-card,format属性获取数字音频接口格式,转换为数值存储到retfmt;

int asoc_simple_parse_daifmt(struct device *dev,
                             struct device_node *node,   // rt5651-sound设备节点
                             struct device_node *codec,
                             char *prefix,
                             unsigned int *retfmt)
{
        struct device_node *bitclkmaster = NULL;
        struct device_node *framemaster = NULL;
        unsigned int daifmt;

        daifmt = snd_soc_daifmt_parse_format(node, prefix);  // 解析simple-audio-card,format属性,获取i2s接口对应的数值1

        snd_soc_daifmt_parse_clock_provider_as_phandle(node, prefix, &bitclkmaster, &framemaster); // 解析simple-audio-card,bitclock-master、simple-audio-card,frame-master属性,这俩属性我们都未使用
        if (!bitclkmaster && !framemaster) {  // 走这里
                /*
                 * No dai-link level and master setting was not found from
                 * sound node level, revert back to legacy DT parsing and
                 * take the settings from codec node.
                 */
                dev_dbg(dev, "Revert to legacy daifmt parsing\n");

                daifmt |= snd_soc_daifmt_parse_clock_provider_as_flag(codec, NULL);
        } else {
                daifmt |= snd_soc_daifmt_clock_provider_from_bitmap(
                                ((codec == bitclkmaster) << 4) | (codec == framemaster));
        }

        of_node_put(bitclkmaster);
        of_node_put(framemaster);

        *retfmt = daifmt;

        return 0;
}

snd_soc_daifmt_parse_format函数定义在sound/soc/soc-core.c,数字音频接口主要包括PCM、I2S、AC97、PDM这几种,snd_soc_daifmt_parse_format函数实际上是解析simple-audio-card,format属性,获取到属性值i2s,然后将其转换为int类型;

unsigned int snd_soc_daifmt_parse_format(struct device_node *np,   // rt5651-sound设备节点
                                         const char *prefix)
{
        int ret;
        char prop[128];
        unsigned int format = 0;
        int bit, frame;
        const char *str;
        struct {    // 定义数字音频接口种类
                char *name;
                unsigned int val;
        } of_fmt_table[] = {
                { "i2s",        SND_SOC_DAIFMT_I2S },
                { "right_j",    SND_SOC_DAIFMT_RIGHT_J },
                { "left_j",     SND_SOC_DAIFMT_LEFT_J },
                { "dsp_a",      SND_SOC_DAIFMT_DSP_A },
                { "dsp_b",      SND_SOC_DAIFMT_DSP_B },
                { "ac97",       SND_SOC_DAIFMT_AC97 },
                { "pdm",        SND_SOC_DAIFMT_PDM},
                { "msb",        SND_SOC_DAIFMT_MSB },
                { "lsb",        SND_SOC_DAIFMT_LSB },
        };

        if (!prefix)
                prefix = "";

        /*
         * check "dai-format = xxx"
         * or    "[prefix]format = xxx"
         * SND_SOC_DAIFMT_FORMAT_MASK area
         */
        ret = of_property_read_string(np, "dai-format", &str);   // dai-format
        if (ret < 0) {
                snprintf(prop, sizeof(prop), "%sformat", prefix);  // simple-audio-card,format
                ret = of_property_read_string(np, prop, &str);  // 获取属性值,存储到str
        }
        if (ret == 0) {
                int i;

                for (i = 0; i < ARRAY_SIZE(of_fmt_table); i++) {  // 遍历数组of_fmt_table,根据名称查找匹配项
                        if (strcmp(str, of_fmt_table[i].name) == 0) {
                                format |= of_fmt_table[i].val;
                                break;
                        }
                }
        }

        /*
         * check "[prefix]continuous-clock"
         * SND_SOC_DAIFMT_CLOCK_MASK area
         */
        snprintf(prop, sizeof(prop), "%scontinuous-clock", prefix);
        if (of_property_read_bool(np, prop))
                format |= SND_SOC_DAIFMT_CONT;
        else
                format |= SND_SOC_DAIFMT_GATED;

        /*
         * check "[prefix]bitclock-inversion"
         * check "[prefix]frame-inversion"
         * SND_SOC_DAIFMT_INV_MASK area
         */
        snprintf(prop, sizeof(prop), "%sbitclock-inversion", prefix);
        bit = !!of_get_property(np, prop, NULL);     // 找不到

        snprintf(prop, sizeof(prop), "%sframe-inversion", prefix);
        frame = !!of_get_property(np, prop, NULL);   // 找不到

        switch ((bit << 4) + frame) {
        case 0x11:
                format |= SND_SOC_DAIFMT_IB_IF;
                break;
        case 0x10:
                format |= SND_SOC_DAIFMT_IB_NF;
                break;
        case 0x01:
                format |= SND_SOC_DAIFMT_NB_IF;
                break;
        default:
                /* SND_SOC_DAIFMT_NB_NF is default */
                break;  // 这里
        }

        return format;
}
View Code

数字音频接口格式对应的数值定义在include/uapi/sound/asoc.h,因此采用i2s总线对应的数值为1;

/* DAI physical PCM data formats.
 * Add new formats to the end of the list.
 */
#define SND_SOC_DAI_FORMAT_I2S          1 /* I2S mode */
#define SND_SOC_DAI_FORMAT_RIGHT_J      2 /* Right Justified mode */
#define SND_SOC_DAI_FORMAT_LEFT_J       3 /* Left Justified mode */
#define SND_SOC_DAI_FORMAT_DSP_A        4 /* L data MSB after FRM LRC */
#define SND_SOC_DAI_FORMAT_DSP_B        5 /* L data MSB during FRM LRC */
#define SND_SOC_DAI_FORMAT_AC97         6 /* AC97 */
#define SND_SOC_DAI_FORMAT_PDM          7 /* Pulse density modulation */

/* left and right justified also known as MSB and LSB respectively */
#define SND_SOC_DAI_FORMAT_MSB          SND_SOC_DAI_FORMAT_LEFT_J
#define SND_SOC_DAI_FORMAT_LSB          SND_SOC_DAI_FORMAT_RIGHT_J

6.2 asoc_simple_dai_init

音频数据链路的的成员init被设置为了asoc_simple_dai_init,函数定义在sound/soc/generic/simple-card-utils.c,这个回调函数在ASoC声卡注册初始化cpm runtime阶段(soc_init_pcm_runtime)会调用;

int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd)
{
        struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card);
        struct simple_dai_props *props = simple_priv_to_props(priv, rtd->num);  //   获取priv->dai_props[rtd->num] 即获取第rtd->num个音频数据链路上的属性
        struct asoc_simple_dai *dai;
        int i, ret;

        for_each_prop_dai_codec(props, i, dai) {  // 遍历音频数据链路上的codec_dai,即dai = props->codec_dai[i]
// 第一个参数为(rtd)->dais[i + (rtd)->dai_link->num_cpus],即获取第i个codec dai,数据结构类型为struct snd_soc_dai ret
= asoc_simple_init_dai(asoc_rtd_to_codec(rtd, i), dai);
if (ret < 0) return ret; } for_each_prop_dai_cpu(props, i, dai) { // 遍历音频数据链路上的cpu_dai,即dai = props->cpu_dai[i]
// 第一个参数(rtd)->dais[i],即获取第i个cpu dai,数据结构类型为struct snd_soc_dai
ret
= asoc_simple_init_dai(asoc_rtd_to_cpu(rtd, i), dai); if (ret < 0) return ret; } ret = asoc_simple_init_for_codec2codec(rtd, props); if (ret < 0) return ret; return 0; }

该函数内部主要做了两件事情:

  • 设置cpu dai、codec dai系统时钟,我们知道一个模块要工作的前提,时钟必须工作;比如像rt5651这种声卡芯片,我们就需要为其进行时钟配置,使其能够工作在设定的时钟频率;
  • 调用asoc_simple_init_for_codec2codec,不过这个函数没执行什么动作;
6.2.1 asoc_simple_init_dai

asoc_simple_init_dai函数定义在sound/soc/generic/simple-card-utils.c,函数内部调用dai driver操作集中的set_sysclk函数来设置系统时钟频率;

static int asoc_simple_init_dai(struct snd_soc_dai *dai,
                                     struct asoc_simple_dai *simple_dai)
{
        int ret;

        if (!simple_dai)
                return 0;

        if (simple_dai->sysclk) {        // 已经在asoc_simple_parse_clk函数中初始化
                ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk,  // clk_id传入的0
                                             simple_dai->clk_direction);  // clk方向传入的0,表示输if (ret && ret != -ENOTSUPP) {
                        dev_err(dai->dev, "simple-card: set_sysclk error\n");
                        return ret;
                }
        }

        if (simple_dai->slots) {  // 不会进入
                ret = snd_soc_dai_set_tdm_slot(dai,
                                               simple_dai->tx_slot_mask,
                                               simple_dai->rx_slot_mask,
                                               simple_dai->slots,
                                               simple_dai->slot_width);
                if (ret && ret != -ENOTSUPP) {
                        dev_err(dai->dev, "simple-card: set_tdm_slot error\n");
                        return ret;
                }
        }

        return 0;
}

snd_soc_dai_set_sysclk调用dai driver操作集里的set_sysclk函数(底层实现就是配置时钟相关寄存器)来设置cpu dai或者codec dai的系统时钟,函数定义为:

/**
 * snd_soc_dai_set_sysclk - configure DAI system or master clock.
 * @dai: DAI
 * @clk_id: DAI specific clock ID
 * @freq: new clock frequency in Hz
 * @dir: new clock direction - input/output.
 *
 * Configures the DAI master (MCLK) or system (SYSCLK) clocking.
 */
int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, // clk_id传入0
                           unsigned int freq, int dir)
{
        int ret;

        if (dai->driver->ops &&  // 走这里  这里实际上就是回调dai driver操作集里的set_sysclk来设置系统频率
            dai->driver->ops->set_sysclk)
                ret = dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
        else
                ret = snd_soc_component_set_sysclk(dai->component, clk_id, 0,
                                                   freq, dir);

        return soc_dai_ret(dai, ret);
}
View Code
6.2.2 asoc_simple_init_for_codec2codec

asoc_simple_init_for_codec2codec函数定义在sound/soc/generic/simple-card-utils.c:

static int asoc_simple_init_for_codec2codec(struct snd_soc_pcm_runtime *rtd,    // pcm runtime
                                            struct simple_dai_props *dai_props) // dai信息
{
        struct snd_soc_dai_link *dai_link = rtd->dai_link;    // 获取音频数据链路
        struct snd_soc_component *component;
        struct snd_soc_pcm_stream *params;
        struct snd_pcm_hardware hw;
        int i, ret, stream;

        /* Do nothing if it already has Codec2Codec settings */
        if (dai_link->params)
                return 0;

        /* Do nothing if it was DPCM :: BE */
        if (dai_link->no_pcm)
                return 0;

        /* Only Codecs */
        for_each_rtd_components(rtd, i, component) { // 遍历rtd->components数组,为所有用到的component调用asoc_simple_component_is_codec函数
                if (!asoc_simple_component_is_codec(component))  // 函数内部直接返回component->driver->endianness,对于描述codec设备的component,该属性被设置为了1
                        return 0;                                // 而对于描述platform设备的component,该属性被设置为0   
        }

        /* Assumes the capabilities are the same for all supported streams */
        for_each_pcm_streams(stream) {
                ret = snd_soc_runtime_calc_hw(rtd, &hw, stream);
                if (ret == 0)
                        break;
        }

        if (ret < 0) {
                dev_err(rtd->dev, "simple-card: no valid dai_link params\n");
                return ret;
        }

        params = devm_kzalloc(rtd->dev, sizeof(*params), GFP_KERNEL);
        if (!params)
                return -ENOMEM;

        params->formats = hw.formats;
        params->rates = hw.rates;
        params->rate_min = hw.rate_min;
        params->rate_max = hw.rate_max;
        params->channels_min = hw.channels_min;
        params->channels_max = hw.channels_max;

        dai_link->params = params;
        dai_link->num_params = 1;

        return 0;
}

6.3 simple_ops

simple_ops函数定义在sound/soc/generic/simple-card.c,这三个回调函数当前阶段并不会调用,可以等被调用的时候再来查看;

static const struct snd_soc_ops simple_ops = {
        .startup        = asoc_simple_startup,
        .shutdown       = asoc_simple_shutdown,
        .hw_params      = asoc_simple_hw_params,
};
6.3.1 asoc_simple_startup

asoc_simple_startup函数定义在sound/soc/generic/simple-card-utils.c:

int asoc_simple_startup(struct snd_pcm_substream *substream)
{
        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
        struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card);
        struct simple_dai_props *props = simple_priv_to_props(priv, rtd->num);
        struct asoc_simple_dai *dai;
        unsigned int fixed_sysclk = 0;
        int i1, i2, i;
        int ret;

        for_each_prop_dai_cpu(props, i1, dai) {
                ret = asoc_simple_clk_enable(dai);
                if (ret)
                        goto cpu_err;
                ret = asoc_simple_check_fixed_sysclk(rtd->dev, dai, &fixed_sysclk);
                if (ret)
                        goto cpu_err;
        }

        for_each_prop_dai_codec(props, i2, dai) {
                ret = asoc_simple_clk_enable(dai);
                if (ret)
                        goto codec_err;
                ret = asoc_simple_check_fixed_sysclk(rtd->dev, dai, &fixed_sysclk);
                if (ret)
                        goto codec_err;
        }

        if (fixed_sysclk && props->mclk_fs) {
                unsigned int fixed_rate = fixed_sysclk / props->mclk_fs;

                if (fixed_sysclk % props->mclk_fs) {
                        dev_err(rtd->dev, "fixed sysclk %u not divisible by mclk_fs %u\n",
                                fixed_sysclk, props->mclk_fs);
                        return -EINVAL;
                }
                ret = snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_RATE,
                        fixed_rate, fixed_rate);
                if (ret)
                        goto codec_err;
        }

        return 0;

codec_err:
        for_each_prop_dai_codec(props, i, dai) {
                if (i >= i2)
                        break;
                asoc_simple_clk_disable(dai);
        }
cpu_err:
        for_each_prop_dai_cpu(props, i, dai) {
                if (i >= i1)
                        break;
                asoc_simple_clk_disable(dai);
        }
        return ret;
}
6.3.2 asoc_simple_shutdown

asoc_simple_shutdown函数定义在sound/soc/generic/simple-card-utils.c:

void asoc_simple_shutdown(struct snd_pcm_substream *substream)
{
        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
        struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card);
        struct simple_dai_props *props = simple_priv_to_props(priv, rtd->num);
        struct asoc_simple_dai *dai;
        int i;

        for_each_prop_dai_cpu(props, i, dai) {
                struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, i);

                if (props->mclk_fs && !dai->clk_fixed && !snd_soc_dai_active(cpu_dai))
                        snd_soc_dai_set_sysclk(cpu_dai,
                                               0, 0, SND_SOC_CLOCK_OUT);

                asoc_simple_clk_disable(dai);
        }
        for_each_prop_dai_codec(props, i, dai) {
                struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, i);

                if (props->mclk_fs && !dai->clk_fixed && !snd_soc_dai_active(codec_dai))
                        snd_soc_dai_set_sysclk(codec_dai,
                                               0, 0, SND_SOC_CLOCK_IN);

                asoc_simple_clk_disable(dai);
        }
}
6.3.3 asoc_simple_hw_params

asoc_simple_hw_params函数定义在sound/soc/generic/simple-card-utils.c:

int asoc_simple_hw_params(struct snd_pcm_substream *substream,
                          struct snd_pcm_hw_params *params)
{
        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
        struct asoc_simple_dai *pdai;
        struct snd_soc_dai *sdai;
        struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card);
        struct simple_dai_props *props = simple_priv_to_props(priv, rtd->num);
        unsigned int mclk, mclk_fs = 0;
        int i, ret;

        if (props->mclk_fs)
                mclk_fs = props->mclk_fs;

        if (mclk_fs) {
                struct snd_soc_component *component;
                mclk = params_rate(params) * mclk_fs;

                for_each_prop_dai_codec(props, i, pdai) {
                        ret = asoc_simple_set_clk_rate(rtd->dev, pdai, mclk);
                        if (ret < 0)
                                return ret;
                }

                for_each_prop_dai_cpu(props, i, pdai) {
                        ret = asoc_simple_set_clk_rate(rtd->dev, pdai, mclk);
                        if (ret < 0)
                                return ret;
                }

                /* Ensure sysclk is set on all components in case any
                 * (such as platform components) are missed by calls to
                 * snd_soc_dai_set_sysclk.
                 */
                for_each_rtd_components(rtd, i, component) {
                        ret = snd_soc_component_set_sysclk(component, 0, 0,
                                                           mclk, SND_SOC_CLOCK_IN);
                        if (ret && ret != -ENOTSUPP)
                                return ret;
                }

                for_each_rtd_codec_dais(rtd, i, sdai) {
                        ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, SND_SOC_CLOCK_IN);
                        if (ret && ret != -ENOTSUPP)
                                return ret;
                }

                for_each_rtd_cpu_dais(rtd, i, sdai) {
                        ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, SND_SOC_CLOCK_OUT);
                        if (ret && ret != -ENOTSUPP)
                                return ret;
                }
        }

        for_each_prop_dai_codec(props, i, pdai) {
                sdai = asoc_rtd_to_codec(rtd, i);
                ret = asoc_simple_set_tdm(sdai, pdai, params);
                if (ret < 0)
                        return ret;
        }

        for_each_prop_dai_cpu(props, i, pdai) {
                sdai = asoc_rtd_to_cpu(rtd, i);
                ret = asoc_simple_set_tdm(sdai, pdai, params);
                if (ret < 0)
                        return ret;
        }

        return 0;
}

6.4 asoc_simple_set_dailink_name

asoc_simple_set_dailink_name函数定义在sound/soc/generic/simple-card-utils.c,用于初始化数据音频链路dai_link的name、stream_name为ff880000.i2s-rt5651-aif1;

int asoc_simple_set_dailink_name(struct device *dev,
                                 struct snd_soc_dai_link *dai_link,
                                 const char *fmt, ...)   //   "ff880000.i2s-rt5651-aif1"
{
        va_list ap;
        char *name = NULL;
        int ret = -ENOMEM;

        va_start(ap, fmt);
        name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
        va_end(ap);

        if (name) {
                ret = 0;

                dai_link->name          = name;  // 设置名称
                dai_link->stream_name   = name;  // 设置流名称
        }

        return ret;
}

参考文章

[1] 理解ALSA(三):从零写ASoC驱动

[2] RK3399平台适配TI-tlv320aic3111音频芯片

[3] rk3399调试alc5651(audio模块)之操作方法

posted @ 2023-07-30 14:27  大奥特曼打小怪兽  阅读(2555)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步