Chinaunix首页 | 论坛 | 博客
  • 博客访问: 309438
  • 博文数量: 101
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 774
  • 用 户 组: 普通用户
  • 注册时间: 2018-10-15 14:13
个人简介

搭建一个和linux开发者知识共享和学习的平台

文章分类

全部博文(101)

文章存档

2024年(15)

2023年(24)

2022年(27)

2019年(8)

2018年(27)

分类: 嵌入式

2023-02-13 14:45:05

1.  Codec简介
在移动设备中,Codec的作用可以归结为4种,分别是:

对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号
对Mic、Linein或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号
对音频通路进行控制,比如播放音乐,收听调频收音机,又或者接听电话时,音频信号在codec内的流通路线是不一样的
对音频信号做出相应的处理,例如音量控制,功率放大,EQ控制等等
ASoC对Codec的这些功能都定义好了一些列相应的接口,以方便地对Codec进行控制。ASoC对Codec驱动的一个基本要求是:驱动程序的代码必须要做到平台无关性,以方便同一个Codec的代码不经修改即可用在不同的平台上。以下的讨论基于wolfson的Codec芯片WM8994,kernel的版本3.3.x。

2.  ASoC中对Codec的数据抽象
描述Codec的{BANNED}最佳主要的几个数据结构分别是:snd_soc_codec,snd_soc_codec_driver,snd_soc_dai,snd_soc_dai_driver,其中的snd_soc_dai和snd_soc_dai_driver在ASoC的Platform驱动中也会使用到,Platform和Codec的DAI通过snd_soc_dai_link结构,在Machine驱动中进行绑定连接。下面我们先看看这几个结构的定义,这里我只贴出我要关注的字段,详细的定义请参照:/include/sound/soc.h。
snd_soc_codec:

  1. /* SoC Audio Codec device */
  2. struct snd_soc_codec {
  3.     const char *name;  /* Codec的名字*/
  4.     struct device *dev; /* 指向Codec设备的指针 */
  5.     const struct snd_soc_codec_driver *driver; /* 指向该codec的驱动的指针 */
  6.     struct snd_soc_card *card;    /* 指向Machine驱动的card实例 */
  7.     int num_dai; /* 该Codec数字接口的个数,目前越来越多的Codec带有多个I2S或者是PCM接口 */
  8.     int (*volatile_register)(...);  /* 用于判定某一寄存器是否是volatile */
  9.     int (*readable_register)(...);  /* 用于判定某一寄存器是否可读 */
  10.     int (*writable_register)(...);  /* 用于判定某一寄存器是否可写 */
  11.  
  12.     /* runtime */
  13.     ......
  14.     /* codec IO */
  15.     void *control_data; /* 该指针指向的结构用于对codec的控制,通常和read,write字段联合使用 */
  16.     enum snd_soc_control_type control_type;/* 可以是SND_SOC_SPI,SND_SOC_I2C,SND_SOC_REGMAP中的一种 */
  17.     unsigned int (*read)(struct snd_soc_codec *, unsigned int);  /* 读取Codec寄存器的函数 */
  18.     int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);  /* 写入Codec寄存器的函数 */
  19.     /* dapm */
  20.     struct snd_soc_dapm_context dapm;  /* 用于DAPM控件 */
  21. };

snd_soc_codec_driver:

  1. /* codec driver */
  2. struct snd_soc_codec_driver {
  3.     /* driver ops */
  4.     int (*probe)(struct snd_soc_codec *);  /* codec驱动的probe函数,由snd_soc_instantiate_card回调 */
  5.     int (*remove)(struct snd_soc_codec *);  
  6.     int (*suspend)(struct snd_soc_codec *);  /* 电源管理 */
  7.     int (*resume)(struct snd_soc_codec *);  /* 电源管理 */
  8.  
  9.     /* Default control and setup, added after probe() is run */
  10.     const struct snd_kcontrol_new *controls;  /* 音频控件指针 */
  11.     const struct snd_soc_dapm_widget *dapm_widgets;  /* dapm部件指针 */
  12.     const struct snd_soc_dapm_route *dapm_routes;  /* dapm路由指针 */
  13.  
  14.     /* codec wide operations */
  15.     int (*set_sysclk)(...);  /* 时钟配置函数 */
  16.     int (*set_pll)(...);  /* 锁相环配置函数 */
  17.  
  18.     /* codec IO */
  19.     unsigned int (*read)(...);  /* 读取codec寄存器函数 */
  20.     int (*write)(...);  /* 写入codec寄存器函数 */
  21.     int (*volatile_register)(...);  /* 用于判定某一寄存器是否是volatile */
  22.     int (*readable_register)(...);  /* 用于判定某一寄存器是否可读 */
  23.     int (*writable_register)(...);  /* 用于判定某一寄存器是否可写 */
  24.  
  25.     /* codec bias level */
  26.     int (*set_bias_level)(...);  /* 偏置电压配置函数 */
  27.  
  28. };


snd_soc_dai:

  1. /*
  2.  * Digital Audio Interface runtime data.
  3.  *
  4.  * Holds runtime data for a DAI.
  5.  */
  6. struct snd_soc_dai {
  7.     const char *name;  /* dai的名字 */
  8.     struct device *dev;  /* 设备指针 */
  9.  
  10.     /* driver ops */
  11.     struct snd_soc_dai_driver *driver;  /* 指向dai驱动结构的指针 */
  12.  
  13.     /* DAI runtime info */
  14.     unsigned int capture_active:1;        /* stream is in use */
  15.     unsigned int playback_active:1;        /* stream is in use */
  16.  
  17.     /* DAI DMA data */
  18.     void *playback_dma_data;  /* 用于管理playback dma */
  19.     void *capture_dma_data;  /* 用于管理capture dma */
  20.  
  21.     /* parent platform/codec */
  22.     union {
  23.         struct snd_soc_platform *platform;  /* 如果是cpu dai,指向所绑定的平台 */
  24.         struct snd_soc_codec *codec;  /* 如果是codec dai指向所绑定的codec */
  25.     };
  26.     struct snd_soc_card *card;  /* 指向Machine驱动中的crad实例 */
  27. };


snd_soc_dai_driver:

  1. /*
  2.  * Digital Audio Interface Driver.
  3.  *
  4.  * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
  5.  * operations and capabilities. Codec and platform drivers will register this
  6.  * structure for every DAI they have.
  7.  *
  8.  * This structure covers the clocking, formating and ALSA operations for each
  9.  * interface.
  10.  */
  11. struct snd_soc_dai_driver {
  12.     /* DAI description */
  13.     const char *name;  /* dai驱动名字 */
  14.  
  15.     /* DAI driver callbacks */
  16.     int (*probe)(struct snd_soc_dai *dai);  /* dai驱动的probe函数,由snd_soc_instantiate_card回调 */
  17.     int (*remove)(struct snd_soc_dai *dai);  
  18.     int (*suspend)(struct snd_soc_dai *dai);  /* 电源管理 */
  19.     int (*resume)(struct snd_soc_dai *dai);  
  20.  
  21.     /* ops */
  22.     const struct snd_soc_dai_ops *ops;  /* 指向本dai的snd_soc_dai_ops结构 */
  23.  
  24.     /* DAI capabilities */
  25.     struct snd_soc_pcm_stream capture;  /* 描述capture的能力 */
  26.     struct snd_soc_pcm_stream playback;  /* 描述playback的能力 */
  27. };


snd_soc_dai_ops用于实现该dai的控制盒参数配置:

  1. struct snd_soc_dai_ops {
  2.     /*
  3.      * DAI clocking configuration, all optional.
  4.      * Called by soc_card drivers, normally in their hw_params.
  5.      */
  6.     int (*set_sysclk)(...);
  7.     int (*set_pll)(...);
  8.     int (*set_clkdiv)(...);
  9.     /*
  10.      * DAI format configuration
  11.      * Called by soc_card drivers, normally in their hw_params.
  12.      */
  13.     int (*set_fmt)(...);
  14.     int (*set_tdm_slot)(...);
  15.     int (*set_channel_map)(...);
  16.     int (*set_tristate)(...);
  17.     /*
  18.      * DAI digital mute - optional.
  19.      * Called by soc-core to minimise any pops.
  20.      */
  21.     int (*digital_mute)(...);
  22.     /*
  23.      * ALSA PCM audio operations - all optional.
  24.      * Called by soc-core during audio PCM operations.
  25.      */
  26.     int (*startup)(...);
  27.     void (*shutdown)(...);
  28.     int (*hw_params)(...);
  29.     int (*hw_free)(...);
  30.     int (*prepare)(...);
  31.     int (*trigger)(...);
  32.     /*
  33.      * For hardware based FIFO caused delay reporting.
  34.      * Optional.
  35.      */
  36.     snd_pcm_sframes_t (*delay)(...);
  37. };


3.  Codec的注册
因为Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec和snd_soc_dai的实例,并把它们注册到系统中,注册后的codec和dai才能为Machine驱动所用。以WM8994为例,对应的代码位置:/sound/soc/codecs/wm8994.c,模块的入口函数注册了一个platform driver:

  1. static struct platform_driver wm8994_codec_driver = {
  2.     .driver = {
  3.            .name = "wm8994-codec",
  4.            .owner = THIS_MODULE,
  5.            },
  6.     .probe = wm8994_probe,
  7.     .remove = __devexit_p(wm8994_remove),
  8. };


 
module_platform_driver(wm8994_codec_driver);
有platform driver,必定会有相应的platform device,这个platform device的来源后面再说,显然,platform driver注册后,probe回调将会被调用,这里是wm8994_probe函数:

  1. static int __devinit wm8994_probe(struct platform_device *pdev)
  2. {
  3.     return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
  4.             wm8994_dai, ARRAY_SIZE(wm8994_dai));
  5. }


其中,soc_codec_dev_wm8994和wm8994_dai的定义如下(代码中定义了3个dai,这里只列出{BANNED}中国第一个):

  1. static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
  2.     .probe =    wm8994_codec_probe,
  3.     .remove =    wm8994_codec_remove,
  4.     .suspend =    wm8994_suspend,
  5.     .resume =    wm8994_resume,
  6.     .set_bias_level = wm8994_set_bias_level,
  7.     .reg_cache_size    = WM8994_MAX_REGISTER,
  8.     .volatile_register = wm8994_soc_volatile,
  9. };
  10. static struct snd_soc_dai_driver wm8994_dai[] = {
  11.     {
  12.         .name = "wm8994-aif1",
  13.         .id = 1,
  14.         .playback = {
  15.             .stream_name = "AIF1 Playback",
  16.             .channels_min = 1,
  17.             .channels_max = 2,
  18.             .rates = WM8994_RATES,
  19.             .formats = WM8994_FORMATS,
  20.         },
  21.         .capture = {
  22.             .stream_name = "AIF1 Capture",
  23.             .channels_min = 1,
  24.             .channels_max = 2,
  25.             .rates = WM8994_RATES,
  26.             .formats = WM8994_FORMATS,
  27.          },
  28.         .ops = &wm8994_aif1_dai_ops,
  29.     },
  30.     ......
  31. }


可见,Codec驱动的{BANNED}中国第一个步骤就是定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对Codec进行注册。进入snd_soc_register_codec函数看看:
首先,它申请了一个snd_soc_codec结构的实例:
    codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
确定codec的名字,这个名字很重要,Machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该Codec的!

  1. /* create CODEC component name */
  2.     codec->name = fmt_single_name(dev, &codec->id);


然后初始化它的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例soc_codec_dev_wm8994:

  1.     codec->write = codec_drv->write;
  2.     codec->read = codec_drv->read;
  3.     codec->volatile_register = codec_drv->volatile_register;
  4.     codec->readable_register = codec_drv->readable_register;
  5.     codec->writable_register = codec_drv->writable_register;
  6.     codec->dapm.bias_level = SND_SOC_BIAS_OFF;
  7.     codec->dapm.dev = dev;
  8.     codec->dapm.codec = codec;
  9.     codec->dapm.seq_notifier = codec_drv->seq_notifier;
  10.     codec->dapm.stream_event = codec_drv->stream_event;
  11.     codec->dev = dev;
  12.     codec->driver = codec_drv;
  13.     codec->num_dai = num_dai;


在做了一些寄存器缓存的初始化和配置工作后,通过snd_soc_register_dais函数对本Codec的dai进行注册:
  

  1.  /* register any DAIs */
  2.     if (num_dai) {
  3.         ret = snd_soc_register_dais(dev, dai_drv, num_dai);
  4.         if (ret < 0)
  5.             goto fail;
  6.     }


{BANNED}最佳后,它把codec实例链接到全局链表codec_list中,并且调用snd_soc_instantiate_cards是函数触发Machine驱动进行一次匹配绑定操作:
 

  1.    list_add(&codec->list, &codec_list);
  2.     snd_soc_instantiate_cards();


上面的snd_soc_register_dais函数其实也是和snd_soc_register_codec类似,显示为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化,{BANNED}最佳后把该dai链接到全局链表dai_list中,和Codec一样,{BANNED}最佳后也会调用snd_soc_instantiate_cards函数触发一次匹配绑定的操作。

               图3.1 dai的注册

4.  mfd设备
前面已经提到,codec驱动把自己注册为一个platform driver,那对应的platform device在哪里定义?答案是在以下代码文件中:/drivers/mfd/wm8994-core.c。


阅读(383) | 评论(0) | 转发(0) |
0

上一篇:Linux设备树初始化

下一篇:SDIO总线简介

给主人留下些什么吧!~~