• Linux音频驱动学习之:(1)ASOC详解


    一、音频架构概述

    (1)ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构,想了解更多的
    关于ALSA的这一开源项目的信息和知识,请查看以下网址:http://www.alsa-project.org/。
    在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用
    alsa-lib提供的API,即可以完成对底层音频硬件的控制。

    (2)PCM是英文Pulse-code modulation的缩写,中文译名是脉冲编码调制。我们知道在现实生活中,人耳听到的声音
    是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率
    对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲,把这些脉冲的幅值按一定的精
    度进行量化,这些量化后的数值被连续地输出、传输、处理或记录到存储介质中,所有这些组成了数字音频的产
    生过程。

      PCM信号的两个重要指标是采样频率和量化精度,目前,CD音频的采样频率通常为44100Hz,量化精度是
    16bit。通常,播放音乐时,应用程序从存储介质中读取音频数据(MP3、WMA、AAC......),经过解码后,最终
    送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得的PCM数据送回给应用程
    序,由应用程序完成压缩、存储等任务。所以,音频驱动的两大核心任务就是:
    playback 如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频
    capture 把mic拾取到得模拟信号,经过采样、量化,转换为PCM信号送回给用户空间的应用程序

    (3)ASoC--ALSA System on Chip:
      建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备
    中的音频Codec的一套软件体系。在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有
    一些局限性:
    Codec驱动与SoC CPU的底层耦合过于紧密,这种不理想会导致代码的重复,例如,仅是wm8731的驱
    动,当时Linux中有分别针对4个平台的驱动代码。
    音频事件没有标准的方法来通知用户,例如耳机、麦克风的插拔和检测,这些事件在移动设备中是非常
    普通的,而且通常都需要特定于机器的代码进行重新对音频路劲进行配置。
    当进行播放或录音时,驱动会让整个codec处于上电状态,这对于PC没问题,但对于移动设备来说,这意
    味着浪费大量的电量。同时也不支持通过改变过取样频率和偏置电流来达到省电的目的。
    ASoC正是为了解决上述种种问题而提出的,目前已经被整合至内核的代码树中:sound/soc。ASoC不能单
    独存在,他只是建立在标准ALSA驱动上的一个它必须和标准的ALSA驱动框架相结合才能工作。

    Linux ALSA音频系统架构简图

    • Alsa application:aplay,arecord,amixer,是alsa alsa-tools中提供的上层调试工具,用户可以直接将其移植到自己所需要的平台,这些应用可以用来实现playback,capture,controls等。
    • alsa library API:alsa 用户库接口,常见有alsa-lib.(alsa-tools中的应用程序基于alsa-lib提供的api来实现)
    • alsa core:alsa 核心层,向上提供逻辑设备(pcm/ctl/midi/timer/..)系统调用,向下驱动硬件设备(Machine/i2s/dma/codec)
    • asoc core:asoc是建立在标准alsa core基础上,为了更好支持嵌入式系统和应用于移动设备的音频codec的一套软件体系。
    • hardware driver:音频硬件设备驱动,由三大部分组成,分别是machine,platform,codec.

    (4)ASOC硬件架构:

      通常,就像软件领域里的抽象和重用一样,嵌入式设备的音频系统可以被划分为板载硬件(Machine)、
    Soc(Platform)、Codec三大部分,如下图所示:

    ①Machine 是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。ASoC的一切都从Machine驱动开始,通过配置dai_link把cpu_dai,codec_dai,modem_dai各个音频接口给链结成一条条音频链路(绑定Platform和Codec驱动),然后注册snd_soc_card。

    ②Platform 一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。在具体实现上,ASoC有把Platform驱动分为两个部分:snd_soc_platform_driversnd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

    ③Codec 字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。

    功能主要以下4种:

    -对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号

    -对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号

    -对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的

    -对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等

    回放时PCM大致数据流:

    (5)ASOC软件架构:
    在软件层面,ASoC也把嵌入式设备的音频系统同样分为3大部分,Machine,Platform和Codec。
    Codec驱动 ASoC中的一个重要设计原则就是要求Codec驱动是平台无关的,它包含了一些音频的控件
    (Controls),音频接口,DAMP(动态音频电源管理)的定义和某些Codec IO功能。为了保证硬件无关
    性,任何特定于平台和机器的代码都要移到Platform和Machine驱动中。所有的Codec驱动都要提供以下特
    性:
    Codec DAI 和 PCM的配置信息;
    Codec的IO控制方式(I2C,SPI等);
    Mixer和其他的音频控件;
    Codec的ALSA音频操作接口;
    必要时,也可以提供以下功能:
    DAPM描述信息;
    DAPM事件处理程序;
    DAC数字静音控制
    Platform驱动它包含了该SoC平台的音频DMA和音频接口的配置和控制(I2S,PCM,AC97等等);它
    也不能包含任何与板子或机器相关的代码。
    Machine驱动 Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打
    开一个放大器);单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才
    能完成整个设备的音频处理工作。

    (6)数据结构

      整个ASoC是由一些列数据结构组成,要搞清楚ASoC的工作机理,必须要理解这一系列数据结构之间的关系和作用,下面的关系图展示了ASoC中重要的数据结构之间的关联方式:

                                                                                                         Kernel-2.6.35-ASoC中各个结构的静态关系

       ASoC把声卡实现为一个Platform Device,然后利用Platform_device结构中的dev字段:dev.drvdata,它实际上指向一个snd_soc_device结构。可以认为snd_soc_device是整个ASoC数据结构的根本,由他开始,引出一系列的数据结构用于表述音频的各种特性和功能。snd_soc_device结构引出了snd_soc_card和soc_codec_device两个结构,然后snd_soc_card又引出了snd_soc_platform、snd_soc_dai_link和snd_soc_codec结构。如上所述,ASoC被划分为Machine、Platform和Codec三大部分,如果从这些数据结构看来,snd_codec_device和snd_soc_card代表着Machine驱动,snd_soc_platform则代表着Platform驱动,snd_soc_codec和soc_codec_device则代表了Codec驱动,而snd_soc_dai_link则负责连接Platform和Codec。

      时序图:

          

    ---> 5. 3.0版内核对ASoC的改进
      数据结构的静态关系图:

           

                                                                                                Kernel 3.0中的ASoC数据结构

      由上图我们可以看出,3.0中的数据结构更为合理和清晰,取消了snd_soc_device结构,直接用snd_soc_card取代了它,并且强化了snd_soc_pcm_runtime的作用,同时还增加了另外两个数据结构snd_soc_codec_driver和snd_soc_platform_driver,用于明确代表Codec驱动和Platform驱动。

      时序图:

          

     (7)核心函数说明:

      1. Machine部分:

      • snd_soc_dai_link:音频链路描述及板级操作函数,指定了Platform、Codec、codec_dai、cpu_dai的名字,Machine利用这些名字去匹配系统中注册的platform,codec,dai模块。
      • snd_soc_register_card:注册platform_driver时触发prob函数,其中调用 snd_soc_register_card 注册Machine驱动,它正是整个ASoC驱动初始化的入口。
        •   soc_bind_dai_link:扫描三个全局的链表头变量:codec_list、dai_list、platform_list,根据card->dai_link[]中的名称进行匹配。

      2. Platform驱动:

      • snd_soc_platform_driver:负责管理音频数据,简单的说就是对音频DMA的设置。name要和前面snd_soc_dai_link 结构中定义的相同,snd_soc_platform_driver才会被关联。
      • snd_soc_dai_driver:主要完成cpu一侧的dai的参数配置,也就是对cpu端音频控制器的寄存器的设置,例如时钟频率、采样率、数据格式等等的设置。
      • struct snd_pcm_ops
        .open :打开设备,准备开始播放的时候调用,这个函数主要是调用snd_soc_set_runtime_hwparams设置支持的音频参数。snd_dmaengine_pcm_open打开DMA引擎。
        .close:关闭播放设备的时候回调。该函数负责关闭DMA引擎。释放相关的资源。
        .ioctl:应用层调用的ioctl会调用这个回调。
        .hw_params:在open后,应用设置播放参数的时候调用,根据设置的参数,设置DMA,例如数据宽度,传输块大小,DMA地址等。
        .hw_free :  关闭设备前被调用,释放缓冲。
        .trigger:  DAM开始时传输,结束传输,暂停传世,恢复传输的时候被回调。
        .pointer: 返回DMA缓冲的当前指针。
        .mmap :   建立内存映射。

      3. codec音频操作接口分为5大部分:时钟配置,格式配置,数字静音,pcm音频接口,FIFO延迟。着重说下时钟配置及格式配置接口:

      • set_sysclk:codec_dai系统时钟设置,当上层打开pcm设备时,需要回调该接口设置codec的系统时钟,codec才能正常工作;
      • set_pll:codec FLL设置,codec一般接了一个MCKL输入时钟,回调该接口基于mclk来产生Codec FLL时钟,接着codec_dai的sysclk,bclk,lrclk均可从FLL分频出来(假设codec作为master);
      • set_fmt:codec_dai格式设置,具体见soc-dai.h;
      • SND_SOC_DAIFMT_I2S:音频数据是I2S格式,常用于多媒体音频;
      • SND_SOC_DAIFMT_DSP_A:音频数据是PCM格式,常用于通话语音;
      • SND_SOC_DAIFMT_CBM_CFM:codec作为master,BCLK和LRCLK由codec提供;
      • SND_SOC_DAIFMT_CBS_CFS:codec作为slave,BCLK和LRCLK由Soc/CPU提供;
      • hw_params:codec_dai硬件参数设置,根据上层设定的声道数,采样率,数据格式,来配置codec_dai相关寄存器。
      • struct snd_kcontrol_new:codec驱动工作的核心,通过这个结构控制许多开关(switch)和调节器(slider)等等,从而读写Codec相关寄存器,实现几乎codec支持的所有功能。
        .iface : 定义了control的类型,形式为SNDRV_CTL_ELEM_IFACE_XXX,对于mixer是SNDRV_CTL_ELEM_IFACE_MIXER,对于不属于mixer的全局控制,使用CARD;如果关联到某类设备,则是PCM、RAWMIDI、TIMER或SEQUENCER。
        .name :名称标识,这个字段非常重要,因为control的作用由名称来区分(如果名称相同需要通过index来区分,且后加的index的值要大于之前的index)。上层应用就是根据name名称标识来找到底层相应的control(上层应用也可以通过id来匹配,id对应的就是每一个control的下标)。name定义的标准是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”,SOURCE定义了control的源,如“Master”、“PCM”等;DIRECTION 则为“Playback”、“Capture”等,如果DIRECTION忽略,意味着Playback和capture双向;FUNCTION则可以是“Switch”、“Volume”和“Route”等。
        .access :访问控制权限。SNDRV_CTL_ELEM_ACCESS_READ意味着只读,这时put()函数不必实 现;SNDRV_CTL_ELEM_ACCESS_WRITE意味着只写,这时get()函数不必实现。若control值频繁变化,则需定义 VOLATILE标志。当control处于非激活状态时,应设置INACTIVE标志。
        .private_value:包含1个长整型值,可以通过它给info()、get()和put()函数传递参数。在通常的使用中是一个指针。
        .info : 函数指针,获取相应的控制项的参数,例如取值范围
        .get :函数指正,获取相关控制项的值
        .put :函数指正,设置相关的寄存器。

         snd_soc_codec_driver:音频编解码芯片描述及操作函数,如控件/微件/音频路由的描述信息,时钟配置,IO控制等

        platform driver(i2c)的name要和前面 snd_soc_dai_link 结构中定义的codec_name相同,codec驱动才会被关联,然后在这个驱动的probe函数中调用snd_soc_register_codec(&pdev->dev, &soc_codec_dev_sndpcm , &sndcodec_dai , 1);  就完成了snd_soc_dai_driver的注册。最后一个参数是snd_soc_dai_driver数组个数。

        到这里驱动都已经注册好了,还需要添加control才能让codec工作,添加control的方法:
        方法一:直接在snd_soc_codec_driver 的结构中添加两个字段,这样调用snd_soc_register_codec 的时候control就添加了。

            .controls =  codec_controls,
            .num_controls = ARRAY_SIZE(codec_controls),

        方法二: 在snd_soc_codec_driver 结构定义的probe函数中添加,probe函数会在调用snd_soc_register_codec后被系统回调,实现下面的代码即可。

            static int sndpcm_soc_probe(struct snd_soc_codec *codec)
            {
                /* Add virtual switch */
                snd_soc_add_codec_controls(codec, codec_controls, ARRAY_SIZE(codec_controls));
                return 0;
            }    

        声卡测试:
        ① 编译工具:mmm external/tinyalsa/
        ② 播放:tinyplay file.wav [-D card] [-d device] [-p period_size] [-n n_periods]
                  tinyplay /sdcard/test.wav -D 0 -d 0 -p 1024 -n 3
        ③ 录音:tinycap /sdcard/rec.wav -D 0 -d 0 -c 2 -r 44100 -b 16 -p 1024 -n  3

    二、代码流程分析(jz2440移植uda1341):

    1. platform:
    1.1 s3c24xx-i2s.c : 把s3c24xx_i2s_dai放入链表dai_list, .name = "s3c24xx-iis",
      s3c24xx_iis_dev_probe
        snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
          list_add(&dai->list, &dai_list);

    1.2 sound/soc/samsung/dma.c : 把samsung_asoc_platform放入了链表platform_list, .name = "samsung-audio",
      samsung_asoc_platform_probe
        snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
          list_add(&platform->list, &platform_list);

    2. codec: uda134x.c
      uda134x_codec_probe
        snd_soc_register_codec(&pdev->dev,&soc_codec_dev_uda134x, &uda134x_dai, 1);
          struct snd_soc_codec *codec;
            codec->driver = codec_drv; = &soc_codec_dev_uda134x

            snd_soc_register_dais(dev, dai_drv, num_dai); // uda134x_dai
              list_add(&dai->list, &dai_list); : 把uda134x_dai放入了链表dai_list
            list_add(&codec->list, &codec_list);

    3. machine:
    s3c24xx_uda134x_probe
    s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
    platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x);
    platform_device_add(s3c24xx_uda134x_snd_device);

    .....
    soc_probe
      snd_soc_register_card(card); // card = &snd_soc_s3c24xx_uda134x

      card->rtd = devm_kzalloc(card->dev,...

      card->rtd[i].dai_link = &card->dai_link[i]; // &s3c24xx_uda134x_dai_link

      list_add(&card->list, &card_list);

      snd_soc_instantiate_cards(); // 实例化声卡
        snd_soc_instantiate_card(card);
    3.1 /* bind DAIs */
        for (i = 0; i < card->num_links; i++)
          soc_bind_dai_link(card, i);
    3.1.1 /* find CPU DAI */
        rtd->cpu_dai = cpu_dai; = //&s3c24xx_i2s_dai
    3.1.2 /* find_codec */
        rtd->codec = codec; = // codec, codec->driver=&soc_codec_dev_uda134x
    3.1.3 /* find CODEC DAI */
        rtd->codec_dai = codec_dai; // = &uda134x_dai
    3.1.4 /* find_platform */
        rtd->platform = platform; // = &samsung_asoc_platform
    3.2 /* initialize the register cache for each available codec */
        ret = snd_soc_init_codec_cache(codec, compress_type);

    3.3 snd_card_create

    3.4 /* early DAI link probe */
    soc_probe_dai_link
    /* probe the cpu_dai */
    /* probe the CODEC */
    /* probe the platform */
    /* probe the CODEC DAI */
    /* create the pcm */
    ret = soc_new_pcm(rtd, num);
    struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
    soc_pcm_ops->open = soc_pcm_open;
    soc_pcm_ops->close = soc_pcm_close;
    soc_pcm_ops->hw_params = soc_pcm_hw_params;
    soc_pcm_ops->hw_free = soc_pcm_hw_free;
    soc_pcm_ops->prepare = soc_pcm_prepare;
    soc_pcm_ops->trigger = soc_pcm_trigger;
    soc_pcm_ops->pointer = soc_pcm_pointer;

    snd_pcm_new
    3.5 snd_card_register



    strace分析: aplay Windows.wav
    1. /dev/snd/controlC0 对应的file_operations是snd_ctl_f_ops
    open : snd_ctl_open
    SNDRV_CTL_IOCTL_PVERSION : snd_ctl_ioctl -> put_user(SNDRV_CTL_VERSION, ip)
    SNDRV_CTL_IOCTL_CARD_INFO : snd_ctl_ioctl -> snd_ctl_card_info(card, ctl, cmd, argp);
    copy_to_user

    SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE : snd_ctl_ioctl -> snd_pcm_control_ioctl -> control->prefer_pcm_subdevice = val;
    close
    上述三个ioctl不涉及硬件操作

    2. /dev/snd/pcmC0D0p 对应的file_operations是snd_pcm_f_ops[0]
    open : snd_pcm_playback_open
      snd_pcm_open
        snd_pcm_open_file
          struct snd_pcm_substream *substream;
            snd_pcm_open_substream
              err = snd_pcm_hw_constraints_init(substream);
                snd_mask_any
                snd_interval_any
      .....
      err = substream->ops->open(substream) // substream->ops : snd_pcm_ops结构体
        soc_pcm_open
        依次调用cpu_dai, dma, codec_dai, machine的open或startup函数
    uda134x_startup 里:snd_pcm_hw_constraint_minmax(SNDRV_PCM_HW_PARAM_RATE),snd_pcm_hw_constraint_minmax(SNDRV_PCM_HW_PARAM_SAMPLE_BITS)
    dma_open里: snd_pcm_hw_constraint_integer,snd_soc_set_runtime_hwparams
    runtime->hw.info = hw->info; = SNDRV_PCM_INFO_INTERLEAVED |
      SNDRV_PCM_INFO_BLOCK_TRANSFER |
      SNDRV_PCM_INFO_MMAP |
      SNDRV_PCM_INFO_MMAP_VALID |
      SNDRV_PCM_INFO_PAUSE |
      SNDRV_PCM_INFO_RESUME,
        snd_pcm_hw_constraints_complete
      pcm_file->substream = substream;
    file->private_data = pcm_file;

    注意:substream->ops = soc_new_pcm函数里的soc_pcm_ops

    以下的ioctl入口都是:snd_pcm_playback_ioctl
    SNDRV_PCM_IOCTL_INFO : snd_pcm_info_user(substream, arg);
    substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
    snd_pcm_lib_ioctl

    SNDRV_PCM_IOCTL_PVERSION : put_user(SNDRV_PCM_VERSION, (int __user *)arg)
    SNDRV_PCM_IOCTL_TTSTAMP : snd_pcm_tstamp(substream, arg);

    SNDRV_PCM_IOCTL_SYNC_PTR : snd_pcm_sync_ptr(substream, arg); 先不管

    SNDRV_PCM_IOCTL_HW_REFINE .... : snd_pcm_hw_refine_user(substream, arg);
    memdup_user
    snd_pcm_hw_refine(substream, params); 先不管
    copy_to_user
    SNDRV_PCM_IOCTL_HW_PARAMS : snd_pcm_hw_params_user(substream, arg);
    snd_pcm_hw_params
    substream->ops->hw_params(substream, params);
    soc_pcm_hw_params
    依次调用machine,codec_dai,cpu_dai,platform(dma)的hw_params函数
    SNDRV_PCM_IOCTL_SYNC_PTR
    SNDRV_PCM_IOCTL_SW_PARAMS : snd_pcm_sw_params_user(substream, arg);
    snd_pcm_sw_params 不涉及硬件操作

    SNDRV_PCM_IOCTL_SYNC_PTR
    SNDRV_PCM_IOCTL_PREPARE : snd_pcm_prepare(substream, file);
    snd_power_wait // 电源管理相关,先不管
    .... 调用到platform里的prepare

    SNDRV_PCM_IOCTL_SYNC_PTR
    SNDRV_PCM_IOCTL_SW_PARAMS

    循环:
    SNDRV_PCM_IOCTL_WRITEI_FRAMES : copy_from_user
    snd_pcm_lib_write
    snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer)
    snd_pcm_lib_write_transfer
    copy_from_user
    snd_pcm_start(substream); // 启动传输


    SNDRV_PCM_IOCTL_SYNC_PTR

    SNDRV_PCM_IOCTL_DRAIN
    SNDRV_PCM_IOCTL_DROP
    SNDRV_PCM_IOCTL_HW_FREE
    close

    strace分析: amixer cset numid=1 30 (设置音量)
    /dev/snd/controlC0
    open
    SNDRV_CTL_IOCTL_CARD_INFO
    SNDRV_CTL_IOCTL_PVERSION
    SNDRV_CTL_IOCTL_ELEM_INFO
    SNDRV_CTL_IOCTL_ELEM_READ
    SNDRV_CTL_IOCTL_ELEM_WRITE : snd_ctl_elem_write_user
    snd_ctl_elem_write
    // 找到一个snd_kcontrol
    kctl = snd_ctl_find_id(card, &control->id);
    // 调用它的put
    result = kctl->put(kctl, control);


    附:
    static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
    .name = "S3C24XX_UDA134X",
    .owner = THIS_MODULE,
    .dai_link = &s3c24xx_uda134x_dai_link,
    .num_links = 1,
    };

    static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
    .name = "UDA134X",
    .stream_name = "UDA134X",
    .codec_name = "uda134x-codec",
    .codec_dai_name = "uda134x-hifi",
    .cpu_dai_name = "s3c24xx-iis",
    .ops = &s3c24xx_uda134x_ops,
    .platform_name = "samsung-audio",
    };

  • 相关阅读:
    用python实现简单的调度场算法
    数据结构顺序表python
    数据结构顺序表C
    python绘制5角形,6角星方法
    TsinghuaX+00740043_2X C++程序设计进阶 C7-3
    Struts2开发环境搭建,及一个简单登录功能实例
    Javascript进度条
    java.util.Date与java.sql.Date
    Error while performing database login with the sqljdbc driver:Unable to create connection. Check your URL.
    java.sql.SQLException: [Microsoft][ODBC 驱动程序管理器] 在指定的 DSN 中,驱动程序和应用程序之间的体系结构不匹配
  • 原文地址:https://www.cnblogs.com/blogs-of-lxl/p/6538769.html
Copyright © 2020-2023  润新知