zoukankan      html  css  js  c++  java
  • ASOC 驱动架构

    前言

    针对嵌入式设备,linux在ALSA的基础上又封装了一层,名为ASOC(ALSA system on chip)。使用ASOC就不需要自己调用snd_card_create、snd_ctrl_create等函数自己来创建声卡。ASOC将驱动程序分为3部分:

    1)machine:单板相关的内容。抽象出一个snd_soc_card结构体 ,重点关注snd_soc_dai_link结构体 ,此结构体表明以下两点:

    a. 表明platform是哪一个, CPU DAI是哪一个,DMA是哪一个,即哪个DMA负责数据传输。

    b. 表明codec是哪一个,codec DAI是哪一个

    2)platform

    a. DAI:抽象出snd_soc_dai_driver结构体

    b. DMA:抽象出snd_soc_platform_driver结构体 

    3)codec

    a. DAI:抽象出一个snd_soc_dai_driver结构体 

    b. 控制接口:抽象出一个snd_soc_codec_driver结构体。

    在Linux驱动中,肯定有多个platform和codec。对于某个单板,使用哪款主芯片(platform),哪款codec芯片在machine中指定。

    内核中带有UDA1341的驱动程序,但是没有WM8976的驱动程序。首先分析UDA1341的驱动程序

    1. machine

    /sound/soc/samsung/S3c24xx_uda134x.c

    static struct platform_driver s3c24xx_uda134x_driver = {
        .probe  = s3c24xx_uda134x_probe,
        .remove = s3c24xx_uda134x_remove,
        .driver = {
            .name = "s3c24xx_uda134x",
            .owner = THIS_MODULE,
        },
    };

    有平台driver,肯定有同名的平台device(这是一种老的方式,现在都使用设备树了),搜索s3c24xx_uda134x

    arch/arm/mach-s3c24xx/Mach-mini2440.c

    static struct platform_device mini2440_audio = {
        .name        = "s3c24xx_uda134x",
        .id        = 0,
        .dev        = {
            .platform_data    = &mini2440_audio_pins,
        },
    };

    当内核中有同名的平台driver和平台device时,platform_driver 中的probe函数将会调用。

    static int s3c24xx_uda134x_probe(struct platform_device *pdev)
    {
        int ret;
    
        printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver
    ");
        
        ......
        s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
        
        platform_set_drvdata(s3c24xx_uda134x_snd_device,
                     &snd_soc_s3c24xx_uda134x);
        platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
        ret = platform_device_add(s3c24xx_uda134x_snd_device);
        
        return ret;
    }

    又出现了平台device soc-audio,在内核中搜索soc-audio

    /sound/soc/Soc-core.c

    /* ASoC platform driver */
    static struct platform_driver soc_driver = {
        .driver        = {
            .name        = "soc-audio",
            .owner        = THIS_MODULE,
            .pm        = &snd_soc_pm_ops,
        },
        .probe        = soc_probe,
        .remove        = soc_remove,
    };

    soc_probe函数将被调用

    /* probes a new socdev */
    static int soc_probe(struct platform_device *pdev)
    {
        struct snd_soc_card *card = platform_get_drvdata(pdev);
        int ret = 0;
        ........
        card->dev = &pdev->dev;
    
        ret = snd_soc_register_card(card);
    
        return 0;
    }

    注册一个snd_soc_card的结构体。该结构体具体指哪一个?就是snd_soc_s3c24xx_uda134x

    /sound/soc/samsung/S3c24xx_uda134x.c

    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
        .codec_dai_name = "uda134x-hifi",//codec芯片中的哪一个DAI,因为有些codec中有多个数字接口
        .cpu_dai_name = "s3c24xx-iis",//2440的DAI接口
        .ops = &s3c24xx_uda134x_ops,
        .platform_name    = "samsung-audio",//DMA
    };
    static struct snd_soc_ops s3c24xx_uda134x_ops = {
        .startup = s3c24xx_uda134x_startup,
        .shutdown = s3c24xx_uda134x_shutdown,
        .hw_params = s3c24xx_uda134x_hw_params,
    };

    machine部分到此结束,它主要就是构造了一个snd_soc_card结构体 。在该结构体中有一个dai_link,在dai_link中指定了codec_name,code_dai_name,cpu_dai_name以及platform_name.

    2. platform

    2.1 cpu dai

    搜索s3c24xx-iis

    /sound/soc/samsung/S3c24xx-i2s.c

    static struct platform_driver s3c24xx_iis_driver = {
        .probe  = s3c24xx_iis_dev_probe,
        .remove = __devexit_p(s3c24xx_iis_dev_remove),
        .driver = {
            .name = "s3c24xx-iis",
            .owner = THIS_MODULE,
        },
    };

    有平台driver,必然有同名的平台device

    /arch/arm/plat-samsung/Devs.c

    struct platform_device s3c_device_iis = {
        .name        = "s3c24xx-iis",
        .id        = -1,
        .num_resources    = ARRAY_SIZE(s3c_iis_resource),
        .resource    = s3c_iis_resource,
        .dev        = {
            .dma_mask        = &samsung_device_dma_mask,
            .coherent_dma_mask    = DMA_BIT_MASK(32),
        }
    };

    因此函数s3c24xx_iis_dev_probe将被调用,

    static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)
    {
        return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
    }
    static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
        .probe = s3c24xx_i2s_probe,
        .suspend = s3c24xx_i2s_suspend,
        .resume = s3c24xx_i2s_resume,
        .playback = {
            .channels_min = 2,
            .channels_max = 2,
            .rates = S3C24XX_I2S_RATES,
            .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
        .capture = {
            .channels_min = 2,
            .channels_max = 2,
            .rates = S3C24XX_I2S_RATES,
            .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
        .ops = &s3c24xx_i2s_dai_ops,
    };

    至此,platform中最重要的cpu DAI接口——> snd_soc_dai_driver 就出现了

    static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
        .trigger    = s3c24xx_i2s_trigger,
        .hw_params    = s3c24xx_i2s_hw_params,
        .set_fmt    = s3c24xx_i2s_set_fmt,
        .set_clkdiv    = s3c24xx_i2s_set_clkdiv,
        .set_sysclk    = s3c24xx_i2s_set_sysclk,
    };

    2.2 DMA

    根据结构体snd_soc_dai_link中的  platform_name = "samsung-audio",在代码中搜索samsung-audio

    /sound/soc/samsung/Dma.c

    static struct platform_driver asoc_dma_driver = {
        .driver = {
            .name = "samsung-audio",
            .owner = THIS_MODULE,
        },
    
        .probe = samsung_asoc_platform_probe,
        .remove = __devexit_p(samsung_asoc_platform_remove),
    };

    又出现了一个平台driver,要想这个平台driver起作用,必定会有一个平台device.

    /arch/arm/plat-samsung/Devs.c

    /* ASOC DMA */
    
    struct platform_device samsung_asoc_dma = {
        .name        = "samsung-audio",
        .id        = -1,
        .dev        = {
            .dma_mask        = &samsung_device_dma_mask,
            .coherent_dma_mask    = DMA_BIT_MASK(32),
        }
    };

    /sound/soc/samsung/Dma.c

    static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
    {
        return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
    }
    static struct snd_soc_platform_driver samsung_asoc_platform = {
        .ops        = &dma_ops,
        .pcm_new    = dma_new,
        .pcm_free    = dma_free_dma_buffers,
    };
    static struct snd_pcm_ops dma_ops = {
        .open        = dma_open,
        .close        = dma_close,
        .ioctl        = snd_pcm_lib_ioctl,
        .hw_params    = dma_hw_params,
        .hw_free    = dma_hw_free,
        .prepare    = dma_prepare,
        .trigger    = dma_trigger,
        .pointer    = dma_pointer,
        .mmap        = dma_mmap,
    };

    至此,platform的DMA这部分分析完毕,这部分主要就是注册了一个snd_soc_platform_driver的结构体。

    3. codec

     搜索 uda134x-codec

    /sound/soc/codecs/Uda134x.c

    static struct platform_driver uda134x_codec_driver = {
        .driver = {
            .name = "uda134x-codec",
            .owner = THIS_MODULE,
        },
        .probe = uda134x_codec_probe,
        .remove = __devexit_p(uda134x_codec_remove),
    };

    内核中肯定存在名为uda134x-codec的platform device

    /arch/arm/mach-s3c24xx/Mach-mini2440.c

    static struct platform_device uda1340_codec = {
            .name = "uda134x-codec",
            .id = -1,
    };

    函数uda134x_codec_probe将被调用

    static int __devinit uda134x_codec_probe(struct platform_device *pdev)
    {
        return snd_soc_register_codec(&pdev->dev,
                &soc_codec_dev_uda134x, &uda134x_dai, 1);
    }

    3.1 soc_codec_dev_uda134x

    这个结构体其实就是codec中的控制接口。

    static struct snd_soc_codec_driver soc_codec_dev_uda134x = {
        .probe =        uda134x_soc_probe,
        .remove =       uda134x_soc_remove,
        .suspend =      uda134x_soc_suspend,
        .resume =       uda134x_soc_resume,
        .reg_cache_size = sizeof(uda134x_reg),
        .reg_word_size = sizeof(u8),
        .reg_cache_default = uda134x_reg,
        .reg_cache_step = 1,
        .read = uda134x_read_reg_cache,
        .write = uda134x_write,
        .set_bias_level = uda134x_set_bias_level,
    };

    3.2 uda134x_dai

    static struct snd_soc_dai_driver uda134x_dai = {
        .name = "uda134x-hifi",//这个地方正好对应s3c24xx_uda134x_dai_link结构体中的codec_dai_name
    /* playback capabilities */ .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = UDA134X_RATES, .formats = UDA134X_FORMATS, }, /* capture capabilities */ .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = UDA134X_RATES, .formats = UDA134X_FORMATS, }, /* pcm operations */ .ops = &uda134x_dai_ops, };

    3.2.1 uda134x_dai_ops

    static const struct snd_soc_dai_ops uda134x_dai_ops = {
        .startup    = uda134x_startup,
        .shutdown    = uda134x_shutdown,
        .hw_params    = uda134x_hw_params,
        .digital_mute    = uda134x_mute,
        .set_sysclk    = uda134x_set_dai_sysclk,
        .set_fmt    = uda134x_set_dai_fmt,
    };

     4. ASOC如何与ALSA建立联系

    4.1 platform

    4.1.1 s3c244-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);

    4.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);

    4.2 uda134x.c

    .name = "uda134x-codec",

    uda134x_codec_probe
        snd_soc_register_codec(&pdev->dev,&soc_codec_dev_uda134x, &uda134x_dai, 1);
            struct snd_soc_codec *codec;
            //分配一个snd_soc_codec结构体
            codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
            codec->driver = codec_drv; //codec_drv即soc_codec_dev_uda134x
            snd_soc_register_dais(dev, dai_drv, num_dai); //dai_drv即uda134x_dai
                //把结构体uda134x_dai放入了链表dai_list中。在链表dai_list中既有s3c24xx_i2s_dai,也有uda134x_dai
                list_add(&dai->list, &dai_list); 
            
            list_add(&codec->list, &codec_list); //把上面分配的codec放入链表codec_list中。

     它们之间是如何联系起来的,就是通过machine中的snd_soc_dai_link中的名字把它们联系起来。下面具体来分析

    4.3 s3c24xx_uda134x_probe

    s3c24xx_uda134x_probe
        //分配一个名为soc-audio的平台设备
        s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
        //设置该设备的私有数据
        platform_set_drvdata(s3c24xx_uda134x_snd_device, &snd_soc_s3c24xx_uda134x);    
        //添加该设备,此时将导致soc-core.c中的soc_driver中的soc_probe函数将被调用。因为在该结构体中有同名soc-audio platform driver.
        platform_device_add(s3c24xx_uda134x_snd_device)
        
        .....
        soc_probe
            //card其实就是snd_soc_s3c24xx_uda134x
            struct snd_soc_card *card = platform_get_drvdata(pdev);
            snd_soc_register_card(card);
                snd_soc_instantiate_cards();
                    struct snd_soc_card *card;
                    list_for_each_entry(card, &card_list, list)//对card_list中的每一个card,执行snd_soc_instantiate_card(card)
                            snd_soc_instantiate_card(card);
                                4.3.1/* bind DAIs */
                                for (i = 0; i < card->num_links; i++)
                                    soc_bind_dai_link(card, i);
                                        /*find CPU DAI from registered DAIs*/
                                            list_for_each_entry(cpu_dai, &dai_list, list) {
                                                if (strcmp(cpu_dai->name, dai_link->cpu_dai_name))
                                            rtd->cpu_dai = cpu_dai //即s3c24xx_i2s_dai
                                            ....
                                        /*find CODEC from registered CODECs*/
                                            list_for_each_entry(codec, &codec_list, list) {
                                                if (dai_link->codec_of_node) {
                                                if (strcmp(codec->name, dai_link->codec_name))
                                            rtd->codec = codec //即code->codec_drv = soc_codec_dev_uda134x
                                            
                                        .....
                                            /*
                                         * CODEC found, so find CODEC DAI from registered DAIs from
                                         * this CODEC
                                         */
                                        list_for_each_entry(codec_dai, &dai_list, list) {
                                            if (codec->dev == codec_dai->dev &&
                                                !strcmp(codec_dai->name,
                                                    dai_link->codec_dai_name)) {
    
                                            rtd->codec_dai = codec_dai;//即s3c24xx_i2s_dai
                                        .......
                                        /* no, then find one from the set of registered platforms */
                                        list_for_each_entry(platform, &platform_list, list) {
                                                if (strcmp(platform->name, platform_name))
                                            
                                            rtd->platform = platform;//即samsung_asoc_platform
                                            
                                ........
                                
                                4.3.2 /* initialize the register cache for each available codec */
                                list_for_each_entry(codec, &codec_list, list) {
                                    //初始化codec_list链表中的每个codec中的寄存器,这不是我们所关心的点
                                    ret = snd_soc_init_codec_cache(codec, compress_type);
                                }
                                
                                4.3.3/* card bind complete so register a sound card */
                                ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
                                        card->owner, 0, &card->snd_card);
                                .....
                                4.3.4/* early DAI link probe */
                                soc_probe_dai_link(card, i, order)
                                    /* probe the cpu_dai,调用cpu_dai中的probe函数 */
                                    /* probe the CODEC,调用codec中的probe函数*/
                                    /* probe the platform,调用platform中的probe函数 */
                                    /* probe the CODEC DAI */
                                    * create the pcm */
                                    ret = soc_new_pcm(rtd, num);
                                            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
                                4.3.5 snd_card_register(card->snd_card);

    在文章的开始,曾经说过当使用ASOC驱动时,它将自动调用snd_card_create、snd_pcm_new、以及snd_card_register。从这个地方也可以看出ASOC是在ALSA的基础上,进行了封装。

    附:

    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",
    };    
  • 相关阅读:
    记录docker 安装sonarqube和安装的一些坑
    Docker安装部署ELK教程 (Elasticsearch+Kibana+Logstash+Filebeat)
    使用docker创建rabbitmq服务
    Centos7下安装Docker
    使用 docker 搭建 MySQL 主从同步/读写分离
    sql优化建议
    记使用docker配置kafka
    6种@Transactional注解的失效场景
    linux定时任务之清理tomcat catalina.out日志
    持久层框架JPA与Mybatis该如何选型
  • 原文地址:https://www.cnblogs.com/-glb/p/14359899.html
Copyright © 2011-2022 走看看