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。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
2. asoc中对codec的数据抽象
描述codec的最主要的几个数据结构分别是: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:
[html]view plaincopy
/*socaudiocodecdevice*/
structsnd_soc_codec{
constchar*name;/*codec的名字*/
structdevice*dev;/*指向codec设备的指针*/
conststructsnd_soc_codec_driver*driver;/*指向该codec的驱动的指针*/
structsnd_soc_card*card;/*指向machine驱动的card实例*/
intnum_dai;/*该codec数字接口的个数,目前越来越多的codec带有多个i2s或者是pcm接口*/
int(*volatile_register)(...);/*用于判定某一寄存器是否是volatile*/
int(*readable_register)(...);/*用于判定某一寄存器是否可读*/
int(*writable_register)(...);/*用于判定某一寄存器是否可写*/
/*runtime*/
......
/*codecio*/
void*control_data;/*该指针指向的结构用于对codec的控制,通常和read,write字段联合使用*/
enumsnd_soc_control_typecontrol_type;/*可以是snd_soc_spi,snd_soc_i2c,snd_soc_regmap中的一种*/
unsignedint(*read)(structsnd_soc_codec*,unsignedint);/*读取codec寄存器的函数*/
int(*write)(structsnd_soc_codec*,unsignedint,unsignedint);/*写入codec寄存器的函数*/
/*dapm*/
structsnd_soc_dapm_contextdapm;/*用于dapm控件*/
};
snd_soc_codec_driver:
[html]view plaincopy
/*codecdriver*/
structsnd_soc_codec_driver{
/*driverops*/
int(*probe)(structsnd_soc_codec*);/*codec驱动的probe函数,由snd_soc_instantiate_card回调*/
int(*remove)(structsnd_soc_codec*);
int(*suspend)(structsnd_soc_codec*);/*电源管理*/
int(*resume)(structsnd_soc_codec*);/*电源管理*/
/*defaultcontrolandsetup,addedafterprobe()isrun*/
conststructsnd_kcontrol_new*controls;/*音频控件指针*/
conststructsnd_soc_dapm_widget*dapm_widgets;/*dapm部件指针*/
conststructsnd_soc_dapm_route*dapm_routes;/*dapm路由指针*/
/*codecwideoperations*/
int(*set_sysclk)(...);/*时钟配置函数*/
int(*set_pll)(...);/*锁相环配置函数*/
/*codecio*/
unsignedint(*read)(...);/*读取codec寄存器函数*/
int(*write)(...);/*写入codec寄存器函数*/
int(*volatile_register)(...);/*用于判定某一寄存器是否是volatile*/
int(*readable_register)(...);/*用于判定某一寄存器是否可读*/
int(*writable_register)(...);/*用于判定某一寄存器是否可写*/
/*codecbiaslevel*/
int(*set_bias_level)(...);/*偏置电压配置函数*/
};
snd_soc_dai:
[html]view plaincopy
/*
*digitalaudiointerfaceruntimedata.
*
*holdsruntimedataforadai.
*/
structsnd_soc_dai{
constchar*name;/*dai的名字*/
structdevice*dev;/*设备指针*/
/*driverops*/
structsnd_soc_dai_driver*driver;/*指向dai驱动结构的指针*/
/*dairuntimeinfo*/
unsignedintcapture_active:1;/*streamisinuse*/
unsignedintplayback_active:1;/*streamisinuse*/
/*daidmadata*/
void*playback_dma_data;/*用于管理playbackdma*/
void*capture_dma_data;/*用于管理capturedma*/
/*parentplatform/codec*/
union{
structsnd_soc_platform*platform;/*如果是cpudai,指向所绑定的平台*/
structsnd_soc_codec*codec;/*如果是codecdai指向所绑定的codec*/
};
structsnd_soc_card*card;/*指向machine驱动中的crad实例*/
};
snd_soc_dai_driver:
[html]view plaincopy
/*
*digitalaudiointerfacedriver.
*
*describesthedigitalaudiointerfaceintermsofitsalsa,daiandac97
*operationsandcapabilities.codecandplatformdriverswillregisterthis
*structureforeverydaitheyhave.
*
*thisstructurecoverstheclocking,formatingandalsaoperationsforeach
*interface.
*/
structsnd_soc_dai_driver{
/*daidescription*/
constchar*name;/*dai驱动名字*/
/*daidrivercallbacks*/
int(*probe)(structsnd_soc_dai*dai);/*dai驱动的probe函数,由snd_soc_instantiate_card回调*/
int(*remove)(structsnd_soc_dai*dai);
int(*suspend)(structsnd_soc_dai*dai);/*电源管理*/
int(*resume)(structsnd_soc_dai*dai);
/*ops*/
conststructsnd_soc_dai_ops*ops;/*指向本dai的snd_soc_dai_ops结构*/
/*daicapabilities*/
structsnd_soc_pcm_streamcapture;/*描述capture的能力*/
structsnd_soc_pcm_streamplayback;/*描述playback的能力*/
};
snd_soc_dai_ops用于实现该dai的控制盒参数配置:
[html]view plaincopy
structsnd_soc_dai_ops{
/*
*daiclockingconfiguration,alloptional.
*calledbysoc_carddrivers,normallyintheirhw_params.
*/
int(*set_sysclk)(...);
int(*set_pll)(...);
int(*set_clkdiv)(...);
/*
*daiformatconfiguration
*calledbysoc_carddrivers,normallyintheirhw_params.
*/
int(*set_fmt)(...);
int(*set_tdm_slot)(...);
int(*set_channel_map)(...);
int(*set_tristate)(...);
/*
*daidigitalmute-optional.
*calledbysoc-coretominimiseanypops.
*/
int(*digital_mute)(...);
/*
*alsapcmaudiooperations-alloptional.
*calledbysoc-coreduringaudiopcmoperations.
*/
int(*startup)(...);
void(*shutdown)(...);
int(*hw_params)(...);
int(*hw_free)(...);
int(*prepare)(...);
int(*trigger)(...);
/*
*forhardwarebasedfifocauseddelayreporting.
*optional.
*/
snd_pcm_sframes_t(*delay)(...);
};
3. codec的注册
因为codec驱动的代码要做到平台无关性,要使得machine驱动能够使用该codec,codec驱动的首要任务就是确定snd_soc_codec和snd_soc_dai的实例,并把它们注册到系统中,注册后的codec和dai才能为machine驱动所用。以wm8994为例,对应的代码位置:/sound/soc/codecs/wm8994.c,模块的入口函数注册了一个platform driver:
[html]view plaincopy
staticstructplatform_driverwm8994_codec_driver={
.driver={
.name=wm8994-codec,
.owner=this_module,
},
.probe=wm8994_probe,
.remove=__devexit_p(wm8994_remove),
};
module_platform_driver(wm8994_codec_driver);
有platform driver,必定会有相应的platform device,这个platform device的来源后面再说,显然,platform driver注册后,probe回调将会被调用,这里是wm8994_probe函数:
[html]view plaincopy
staticint__devinitwm8994_probe(structplatform_device*pdev)
{
returnsnd_soc_register_codec(&pdev->dev,&soc_codec_dev_wm8994,
wm8994_dai,array_size(wm8994_dai));
}
其中,soc_codec_dev_wm8994和wm8994_dai的定义如下(代码中定义了3个dai,这里只列出第一个):
[html]view plaincopy
staticstructsnd_soc_codec_driversoc_codec_dev_wm8994={
.probe=wm8994_codec_probe,
.remove=wm8994_codec_remove,
.suspend=wm8994_suspend,
.resume=wm8994_resume,
.set_bias_level=wm8994_set_bias_level,
.reg_cache_size=wm8994_max_register,
.volatile_register=wm8994_soc_volatile,
};
[html]view plaincopy
staticstructsnd_soc_dai_driverwm8994_dai[]={
{
.name=wm8994-aif1,
.id=1,
.playback={
.stream_name=aif1playback,
.channels_min=1,
.channels_max=2,
.rates=wm8994_rates,
.formats=wm8994_formats,
},
.capture={
.stream_name=aif1capture,
.channels_min=1,
.channels_max=2,
.rates=wm8994_rates,
.formats=wm8994_formats,
},
.ops=&wm8994_aif1_dai_ops,
},
......
}
可见,codec驱动的第一个步骤就是定义snd_soc_codec_driver和snd_soc_dai_driver的实例,然后调用snd_soc_register_codec函数对codec进行注册。进入snd_soc_register_codec函数看看:
首先,它申请了一个snd_soc_codec结构的实例:
[html]view plaincopy
codec=kzalloc(sizeof(structsnd_soc_codec),gfp_kernel);
确定codec的名字,这个名字很重要,machine驱动定义的snd_soc_dai_link中会指定每个link的codec和dai的名字,进行匹配绑定时就是通过和这里的名字比较,从而找到该codec的!
[html]view plaincopy
/*createcodeccomponentname*/
codec->name=fmt_single_name(dev,&codec->id);
然后初始化它的各个字段,多数字段的值来自上面定义的snd_soc_codec_driver的实例soc_codec_dev_wm8994:
[html]view plaincopy
codec->write=codec_drv->write;
codec->read=codec_drv->read;
codec->volatile_register=codec_drv->volatile_register;
codec->readable_register=codec_drv->readable_register;
codec->writable_register=codec_drv->writable_register;
codec->dapm.bias_level=snd_soc_bias_off;
codec->dapm.dev=dev;
codec->dapm.codec=codec;
codec->dapm.seq_notifier=codec_drv->seq_notifier;
codec->dapm.stream_event=codec_drv->stream_event;
codec->dev=dev;
codec->driver=codec_drv;
codec->num_dai=num_dai;
在做了一些寄存器缓存的初始化和配置工作后,通过snd_soc_register_dais函数对本codec的dai进行注册:
[html]view plaincopy
/*registeranydais*/
if(num_dai){
ret=snd_soc_register_dais(dev,dai_drv,num_dai);
if(retlist,&codec_list);
snd_soc_instantiate_cards();
上面的snd_soc_register_dais函数其实也是和snd_soc_register_codec类似,显示为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化,最后把该dai链接到全局链表dai_list中,和codec一样,最后也会调用snd_soc_instantiate_cards函数触发一次匹配绑定的操作。
图3.1 dai的注册
关于snd_soc_instantiate_cards函数,请参阅另一篇博文:linux音频驱动之六:asoc架构中的machine。
4. mfd设备
前面已经提到,codec驱动把自己注册为一个platform driver,那对应的platform device在哪里定义?答案是在以下代码文件中:/drivers/mfd/wm8994-core.c。
wm8994本身具备多种功能,除了codec外,它还有作为ldo和gpio使用,这几种功能共享一些io和中断资源,linux为这种设备提供了一套标准的实现方法:mfd设备。其基本思想是为这些功能的公共部分实现一个父设备,以便共享某些系统资源和功能,然后每个子功能实现为它的子设备,这样既共享了资源和代码,又能实现合理的设备层次结构,主要利用到的api就是:mfd_add_devices(),mfd_remove_devices(),mfd_cell_enable(),mfd_cell_disable(),mfd_clone_cell()。
回到wm8994-core.c中,因为wm8994使用i2c进行内部寄存器的存取,它首先注册了一个i2c驱动:
[html]view plaincopy
staticstructi2c_driverwm8994_i2c_driver={
.driver={
.name=wm8994,
.owner=this_module,
.pm=&wm8994_pm_ops,
.of_match_table=wm8994_of_match,
},
.probe=wm8994_i2c_probe,
.remove=wm8994_i2c_remove,
.id_table=wm8994_i2c_id,
};
staticint__initwm8994_i2c_init(void)
{
intret;
ret=i2c_add_driver(&wm8994_i2c_driver);
if(ret!=0)
pr_err(failedtoregisterwm8994i2cdriver:%d,ret);
returnret;
}
module_init(wm8994_i2c_init);
进入wm8994_i2c_probe()函数,它先申请了一个wm8994结构的变量,该变量被作为这个i2c设备的driver_data使用,上面已经讲过,codec作为它的子设备,将会取出并使用这个driver_data。接下来,本函数利用regmap_init_i2c()初始化并获得一个regmap结构,该结构主要用于后续基于regmap机制的寄存器i/o,关于regmap我们留在后面再讲。最后,通过wm8994_device_init()来添加mfd子设备:
[html]view plaincopy
staticintwm8994_i2c_probe(structi2c_client*i2c,
conststructi2c_device_id*id)
{
structwm8994*wm8994;
intret;
wm8994=devm_kzalloc(&i2c->dev,sizeof(structwm8994),gfp_kernel);
i2c_set_clientdata(i2c,wm8994);
wm8994->dev=&i2c->dev;
wm8994->irq=i2c->irq;
wm8994->type=id->driver_data;
wm8994->regmap=regmap_init_i2c(i2c,&wm8994_base_regmap_config);
returnwm8994_device_init(wm8994,i2c->irq);
}
继续进入wm8994_device_init()函数,它首先为两个ldo添加mfd子设备:
[html]view plaincopy
/*addtheon-chipregulatorsfirstforbootstrapping*/
ret=mfd_add_devices(wm8994->dev,-1,
wm8994_regulator_devs,
array_size(wm8994_regulator_devs),
null,0);
因为wm1811,wm8994,wm8958三个芯片功能类似,因此这三个芯片都使用了wm8994的代码,所以wm8994_device_init()接下来根据不同的芯片型号做了一些初始化动作,这部分的代码就不贴了。接着,从platform_data中获得部分配置信息:
[html]view plaincopy
if(pdata){
wm8994->irq_base=pdata->irq_base;
wm8994->gpio_base=pdata->gpio_base;
/*gpioconfigurationisonlyappliedifit'snon-zero*/
......
}
最后,初始化irq,然后添加codec子设备和gpio子设备:
[html]view plaincopy
wm8994_irq_init(wm8994);
ret=mfd_add_devices(wm8994->dev,-1,
wm8994_devs,array_size(wm8994_devs),
null,0);
经过以上这些处理后,作为父设备的i2c设备已经准备就绪,它的下面挂着4个子设备:ldo-0,ldo-1,codec,gpio。其中,codec子设备的加入,它将会和前面所讲codec的platform driver匹配,触发probe回调完成下面所说的codec驱动的初始化工作。
5. codec初始化
machine驱动的初始化,codec和dai的注册,都会调用snd_soc_instantiate_cards()进行一次声卡和codec,dai,platform的匹配绑定过程,这里所说的绑定,正如machine驱动一文中所描述,就是通过3个全局链表,按名字进行匹配,把匹配的codec,dai和platform实例赋值给声卡每对dai的snd_soc_pcm_runtime变量中。一旦绑定成功,将会使得codec和dai驱动的probe回调被调用,codec的初始化工作就在该回调中完成。对于wm8994,该回调就是wm8994_codec_probe函数:
图5.1 wm8994_codec_probe
取出父设备的driver_data,其实就是上一节的wm8994结构变量,取出其中的regmap字段,复制到codec的control_data字段中;
申请一个wm8994_priv私有数据结构,并把它设为codec设备的driver_data;
通过snd_soc_codec_set_cache_io初始化regmap io,完成这一步后,就可以使用api:snd_soc_read(),snd_soc_write()对codec的寄存器进行读写了;
把父设备的driver_data(struct wm8994)和platform_data保存到私有结构wm8994_priv中;
因为要同时支持3个芯片型号,这里要根据芯片的型号做一些特定的初始化工作;
申请必要的几个中断;
设置合适的偏置电平;
通过snd_soc_update_bits修改某些寄存器;
根据父设备的platform_data,完成特定于平台的初始化配置;
添加必要的control,dapm部件进而dapm路由信息;
至此,codec驱动的初始化完成。
5. regmap-io
我们知道,要想对codec进行控制,通常都是通过读写它的内部寄存器完成的,读写的接口通常是i2c或者是spi接口,不过每个codec芯片寄存器的比特位组成都有所不同,寄存器地址的比特位也有所不同。例如wm8753的寄存器地址是7bits,数据是9bits,wm8993的寄存器地址是8bits,数据也是16bits,而wm8994的寄存器地址是16bits,数据也是16bits。在kernel3.1版本,内核引入了一套regmap机制和相关的api,这样就可以用统一的操作来实现对这些多样的寄存器的控制。regmap使用起来也相对简单:
为codec定义一个regmap_config结构实例,指定codec寄存器的地址和数据位等信息;
根据codec的控制总线类型,调用以下其中一个函数,得到一个指向regmap结构的指针:
struct regmap *regmap_init_i2c(struct i2c_client *i2c,const struct regmap_config *config);
struct regmap *regmap_init_spi(struct spi_device *dev,const struct regmap_config *config);
把获得的regmap结构指针赋值给codec->control_data;
调用soc-io的api:snd_soc_codec_set_cache_io使得soc-io和regmap进行关联;
完成以上步骤后,codec驱动就可以使用诸如snd_soc_read、snd_soc_write、snd_soc_update_bits等api对codec的寄存器进行读写了。
荣耀9最新消息:华为荣耀9要玩大招,全面屏?这下必买!
基于虚拟仪器技术实现信号采集系统的设计
中软国际与浪潮达成战略合作,共拓企业ERP市场
全球服务机器人需求剧增,前景可观
自动驾驶领域中的CMS系统应用探讨
Linux ALSA声卡驱动之一:ASoC架构中的Codec
机器人板块为何关注点提升了
如何降低数据中心能耗?
智能化薄膜表面缺陷检测设备的原理及优势
什么是频谱分析仪?适用于需要射频测试的应用
Galaxy S20 Ultra的新更新给用户带来了麻烦
探讨国外品牌车联网的发展趋势
台积电或为苹果AR眼镜研发Micro LED屏幕
华为Mate30Pro后置三摄主要参数曝光 主摄4000万像素支持120度广角拍摄
移动通信技术如何向后5G和6G天线系统技术演进和创新
连续血糖监测+可穿戴让糖尿病的日常管理变得更容易
消弧线圈柜出现故障应如何处理
多元互联!绿米Aqara推Matter协议,促进AIot行业绿色健康发展
格力色界手机5天销量暴增1100倍,明年还要骁龙820手机!
受到iPhone7发布影响 苹果iPhone本年度第一季营收同比下降23%