1 Linux ALSA声卡驱动
众所周知,android是基于linux的。讲android的audio的系统,就不得不从linux的声卡驱动说起。为了更好的支持嵌入式CPU,linux在标准的ALSA驱动上建立了ASoC(ALSA System on Chip)。下面我们就从ASoC说起。
ASoC的驱动代码位于sound\soc\目录下。ASoC音频系统可以被划分为Machine、Platform、Codec三大部分。Codec驱动通常都在sound\soc\codecs\目录下。Machine、Platform的驱动通常在sound\soc\于CPU相关的目录下,例如,飞思卡尔的Machine、Platform的驱动就在sound\soc\imx\目录下,全志的就在sound\soc\sunxi\目录下。
.Codec驱动主要是针对音频CODEC的驱动,主要是进行AD、DA转换,对音频通路的控制,音量控制、EQ控制等等。
.Platform驱动主要是针对CPU端的驱动,主要包括DMA的设置,数据音频接口的配置,时钟频率、数据格式等等。
.Machine驱动主要是针对设备的,实现Codec和Platform耦合。
1.1 Machine驱动
不同的设备硬件形态是各式各样的,不同的CPU、不同的CODEC芯片。Machine驱动就是负责将不同的Platform驱动和Codec驱动关联起来,形成一个完整的音频驱动。没有Machine驱动,Platform驱动和Codec驱动是无法独立工作的。下面就来看看Machine驱动是如何实现的。
首先来看看两个重要的结构:
struct snd_soc_dai_link {
const char *name;
const char *stream_name;
const char *codec_name;
const struct device_node *codec_of_node;
const char *platform_name;
const struct device_node *platform_of_node;
const char *cpu_dai_name;
const struct device_node *cpu_dai_of_node;
const char *codec_dai_name;
unsigned int dai_fmt;
unsigned int ignore_suspend:1;
unsigned int symmetric_rates:1;
unsigned int ignore_pmdown_time:1;
int (*init)(struct snd_soc_pcm_runtime *rtd);
struct snd_soc_ops *ops;
};
在这个结构里面我们重点需要关注下面几个成员
codec_name :codec的名称,系统将根据这个名字匹配相应的CODEC驱动
codec_dai_name :codec的数字音频接口(DAI)的名称,系统根据这个匹配codec_dai驱动。
platform_name : Platform的名称,用来匹配Platform驱动的。
cpu_dai_name :Platform的数字音频接口(DAI)的名称,系统根据这个匹配Platform_dai驱动。(codec_dai和Platform_dai将在codec和Platform驱动里面说明)。
ops 回调函数指针的结构,这这里可以定义对设备的硬件设置的一些代码,比较常用的是ops.hw_params实现设备级硬件的一些配置。
struct snd_soc_card {
const char *name;
const char *long_name;
const char *driver_name;
struct device *dev;
struct snd_card *snd_card;
struct module *owner;
struct list_head list;
struct mutex mutex;
struct mutex dapm_mutex;
bool instantiated;
int (*probe)(struct snd_soc_card *card);
int (*late_probe)(struct snd_soc_card *card);
int (*remove)(struct snd_soc_card *card);
/* the pre and post PM functions are used to do any PM work before and
* after the codec and DAI's do any PM work. */
int (*suspend_pre)(struct snd_soc_card *card);
int (*suspend_post)(struct snd_soc_card *card);
int (*resume_pre)(struct snd_soc_card *card);
int (*resume_post)(struct snd_soc_card *card);
/* callbacks */
int (*set_bias_level)(struct snd_soc_card *,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level);
int (*set_bias_level_post)(struct snd_soc_card *,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level);
long pmdown_time;
/* CPU <--> Codec DAI links */
struct snd_soc_dai_link *dai_link;
int num_links;
struct snd_soc_pcm_runtime *rtd;
int num_rtd;
/* optional codec specific configuration */
struct snd_soc_codec_conf *codec_conf;
int num_configs;
/*
* optional auxiliary devices such as amplifiers or codecs with DAI
* link unused
*/
struct snd_soc_aux_dev *aux_dev;
int num_aux_devs;
struct snd_soc_pcm_runtime *rtd_aux;
int num_aux_rtd;
const struct snd_kcontrol_new *controls;
int num_controls;
/*
* Card-specific routes and widgets.
*/
const struct snd_soc_dapm_widget *dapm_widgets;
int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
int num_dapm_routes;
bool fully_routed;
struct work_struct deferred_resume_work;
/* lists of probed devices belonging to this card */
struct list_head codec_dev_list;
struct list_head platform_dev_list;
struct list_head dai_dev_list;
struct list_head widgets;
struct list_head paths;
struct list_head dapm_list;
struct list_head dapm_dirty;
/* Generic DAPM context for the card */
struct snd_soc_dapm_context dapm;
struct snd_soc_dapm_stats dapm_stats;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_card_root;
struct dentry *debugfs_pop_time;
#endif
u32 pop_time;
void *drvdata;
};
这个结构看起来非常复杂,实际上我们只要关注下面几个成员就好了
name :为我们的声卡定义一个名字
dai_link :前面介绍的snd_soc_dai_link结构的实例的地址。可以是一个snd_soc_dai_link的变量的地址,也可以是snd_soc_dai_link数组的首地址的指针。
num_links : dai_link 的数量,例如我们定义了一个结构变量
struct snd_soc_dai_link a;
.dai_link = &a ;
那么num_links就是1。
如果定义了一个数值
struct snd_soc_dai_link a[5]={......};
.dai_link = a ;
那么num_links就是5。
下面看看怎么注册一个Machine驱动
首先定义前面介绍的结构
static struct snd_soc_dai_link a= {
.name
= "audiocodec",
.stream_name
= "CODEC",
.cpu_dai_name
= "codec",
.codec_dai_name = "sndcodec",
.platform_name
= "cpu-codec-audio",
.codec_name
= "pcm-codec",
//.ops = &sndpcm_ops,
};
如果需要进行设备的一些硬件设置,可以定义.ops = &sndpcm_ops,如下:
static struct snd_soc_ops sndpcm_ops = {
.hw_params = sndpcm_hw_params,
};
sndpcm_hw_params进行一些硬件的设置。
static struct snd_soc_card card= {
.name
= "audiocodec",
.owner
= THIS_MODULE,
.dai_link
= &a,
.num_links
= 1,
};
Machine驱动是一个Platform Device,所以我们要先定义一个Platform Device,这里就是实现一个标准的Platform Device,就不详细说明了。
在Platform Device的probe函数中注册 Machine驱动。有两种写法
1 struct snd_soc_card *mycard = &card;
mycard->dev = &pdev->dev;
ret = snd_soc_register_card(mycard);
2 static struct platform_device * Machine;
Machine = platform_device_alloc("soc-audio", -1);//注意名称一定为soc-audio
platform_set_drvdata(Machine , &card);//一定要把snd_soc_card 保存到platform_device结构的dev.drvdata字段中
platform_device_add(Machine );
1.2 Platform驱动
Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,简单的说就是对音频DMA的设置。dai_driver则主要完成cpu一侧的dai的参数配置,也就是对cpu端音频控制器的寄存器的设置,例如时钟频率、采样率、数据格式等等的设置。
1.2.1 snd_soc_platform_driver的注册
先介绍两个数据结构
struct snd_soc_platform_driver {
int (*probe)(struct snd_soc_platform *);
int (*remove)(struct snd_soc_platform *);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* pcm creation and destruction */
int (*pcm_new)(struct snd_soc_pcm_runtime *);
void (*pcm_free)(struct snd_pcm *);
/* Default control and setup, added after probe() is run */
const struct snd_kcontrol_new *controls;
int num_controls;
const struct snd_soc_dapm_widget *dapm_widgets;
int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
int num_dapm_routes;
/*
* For platform caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
/* platform stream ops */
struct snd_pcm_ops *ops;
/* platform stream completion event */
int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);
/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;
/* platform IO - used for platform DAPM */
unsigned int (*read)(struct snd_soc_platform *, unsigned int);
int (*write)(struct snd_soc_platform *, unsigned int, unsigned int);
};
这个结构通常我们需要关注下面的几个成员
.pcm_new :函数指针,在驱动创建的时候由系统回调。
.pcm_free:函数指针,在驱动销毁的时候由系统回调。
.ops :snd_pcm_ops结构的指针,定义了一系列回调函数。
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream);
int (*close)(struct snd_pcm_substream *substream);
int (*ioctl)(struct snd_pcm_substream * substream, unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*copy)(struct snd_pcm_substream *substream, int channel,snd_pcm_uframes_t pos, void __user *buf, snd_pcm_uframes_t count);
int (*silence)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
struct page *(*page)(struct snd_pcm_substream *substream, unsigned long offset);
int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
int (*ack)(struct snd_pcm_substream *substream);
};
.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 : 建立内存映射。
介绍完数据结构,下面介绍如何注册snd_soc_platform_driver。首先定义一个snd_soc_platform_driver
struct snd_soc_platform_driver soc_platform = {
.ops
= &ops,
.pcm_new
= xxx_pcm_new,
.pcm_free
= xxx_pcm_free,
};
snd_soc_platform_driver也是一个platform driver ,所以首先要定义一个platform driver ,这里要注意的是我们定义的这个platform driver的name一定要和前面snd_soc_dai_link 结构中定义的platform_name相同,这样我们定义的snd_soc_platform_driver才会被关联。
然后再这个驱动的probe函数中,调用snd_soc_register_platform(&pdev->dev, &soc_platform );就完成了snd_soc_platform_driver的注册。
1.2.1 snd_soc_dai_driver驱动的注册
struct snd_soc_dai_driver {
/* DAI description */
const char *name;
unsigned int id;
int ac97_control;
/* DAI driver callbacks */
int (*probe)(struct snd_soc_dai *dai);
int (*remove)(struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
/* ops */
const struct snd_soc_dai_ops *ops;
/* DAI capabilities */
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
unsigned int symmetric_rates:1;
/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;
};
主要的成员如下:
.probe :回调函数,分别在声卡加载时被调用;
.remove :回调函数,分别在声卡卸载时被调用;
.suspend .resume: 分别在休眠唤醒的时候被调用
.ops :指向snd_soc_dai_ops结构,用于配置和控制该dai;
.playback: snd_soc_pcm_stream结构,用于说明播放时支持的声道数,码率,数据格式等能力;
.capture : snd_soc_pcm_stream结构,用于说明录音时支持的声道数,码率,数据格式等能力;
snd_soc_dai_driver中的ops字段介绍,这个字段是一个
struct snd_soc_dai_ops {
/*
* DAI clocking configuration, all optional.
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_sysclk)(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
/*
* DAI format configuration
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
int (*set_tdm_slot)(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int slot_width);
int (*set_channel_map)(struct snd_soc_dai *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot);
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
/*
* DAI digital mute - optional.
* Called by soc-core to minimise any pops.
*/
int (*digital_mute)(struct snd_soc_dai *dai, int mute);
/*
* ALSA PCM audio operations - all optional.
* Called by soc-core during audio PCM operations.
*/
int (*startup)(struct snd_pcm_substream *,
struct snd_soc_dai *);
void (*shutdown)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*hw_params)(struct snd_pcm_substream *,
struct snd_pcm_hw_params *, struct snd_soc_dai *);
int (*hw_free)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*prepare)(struct snd_pcm_substream *,
struct snd_soc_dai *);
int (*trigger)(struct snd_pcm_substream *, int,
struct snd_soc_dai *);
/*
* For hardware based FIFO caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
};的结构体。
.set_sysclk : 设置dai的主时钟;
.set_pll : 设置PLL参数;
.set_clkdiv : 设置分频系数;
.set_fmt :设置dai的数据格式;
.set_tdm_slot : 如果dai支持时分复用,用于设置时分复用的slot;
.set_channel_map :声道的时分复用映射设置;
.set_tristate :设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;
.sunxi_i2s_hw_params:设置硬件的相关参数。
.startup :打开设备,设备开始工作的时候回调。
.shutdown:关闭设备前调用。
.trigger: DAM开始时传输,结束传输,暂停传世,恢复传输的时候被回调。
首先要定义一个结构体
static struct snd_soc_dai_driver pcm_dai = {
.playback
= {
.channels_min = 1,
.channels_max = 2,
.rates = SUNXI_PCM_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
},
.capture
= {
.channels_min = 1,
.channels_max = 2,
.rates = SUNXI_PCM_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
},
.ops
= &dai_ops,
};
同样的snd_soc_dai_driver也是一个platform driver ,所以首先要定义一个platform driver ,这里要注意的是我们定义的这个platform driver的name一定要和前面snd_soc_dai_link 结构中定义的cpu_dai_name相同,这样我们定义的snd_soc_dai_driver才会被关联。
然后再这个驱动的probe函数中,调用snd_soc_register_dai(&pdev->dev, &pcm_dai );就完成了snd_soc_dai_driver的注册。
1.3 Codec驱动
还是先介绍下相关的数据结构。
struct snd_soc_codec_driver {
/* driver ops */
int (*probe)(struct snd_soc_codec *);
int (*remove)(struct snd_soc_codec *);
int (*suspend)(struct snd_soc_codec *);
int (*resume)(struct snd_soc_codec *);
/* Default control and setup, added after probe() is run */
const struct snd_kcontrol_new *controls;
int num_controls;
const struct snd_soc_dapm_widget *dapm_widgets;
int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
int num_dapm_routes;
/* codec wide operations */
int (*set_sysclk)(struct snd_soc_codec *codec,
int clk_id, int source, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out);
/* codec IO */
unsigned int (*read)(struct snd_soc_codec *, unsigned int);
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
int (*display_register)(struct snd_soc_codec *, char *,
size_t, unsigned int);
int (*volatile_register)(struct snd_soc_codec *, unsigned int);
int (*readable_register)(struct snd_soc_codec *, unsigned int);
int (*writable_register)(struct snd_soc_codec *, unsigned int);
unsigned int reg_cache_size;
short reg_cache_step;
short reg_word_size;
const void *reg_cache_default;
short reg_access_size;
const struct snd_soc_reg_access *reg_access_default;
enum snd_soc_compress_type compress_type;
/* codec bias level */
int (*set_bias_level)(struct snd_soc_codec *,
enum snd_soc_bias_level level);
bool idle_bias_off;
void (*seq_notifier)(struct snd_soc_dapm_context *,
enum snd_soc_dapm_type, int);
/* codec stream completion event */
int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);
bool ignore_pmdown_time; /* Doesn't benefit from pmdown delay */
/* probe ordering - for components with runtime dependencies */
int probe_order;
int remove_order;
};
.probe : codec 的probe函数,由snd_soc_instantiate_card回调
.remove:驱动卸载的时候调用
.suspend .resume :电源管理,休眠唤醒的时候调用
.controls :codec控制接口的指针,例如控制音量的调节、通道的选择等等
.num_controls:codec控制接口的个数。也就是snd_kcontrol_new 的数量。
.set_sysclk :设置时钟函数指针
.set_pll :设置锁相环的函数指针
.set_bias_level : 设置偏置电压。
.read :读codec寄存器的函数
.write:些codec寄存器的函数
另外一个重要的结构
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface;
/* interface identifier */
unsigned int device;
/* device/client number */
unsigned int subdevice;
/* subdevice (substream) number */
const unsigned char *name;
/* ASCII name of item */
unsigned int index;
/* index of item */
unsigned int access;
/* access rights */
unsigned int count;
/* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
这个结构是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 :函数指正,设置相关的寄存器。
在include/sound/soc.h文件中定义了一些宏,来实现snd_kcontrol_new 的定义,有兴趣的话可以自己看看。
下面来介绍如何注册一个codec驱动
首先需要实现相关的数据结构
const struct snd_kcontrol_new codec_controls[] = {......};
struct snd_soc_dai_driver sndcodec_dai ={
.name = "sndcodec",//注意这里的name一定要和machine驱动中的snd_soc_dai_link结构的codec_dai_name 相同,这样才能匹配上。
......
};
struct snd_soc_codec_driver soc_codec_dev_sndpcm = {...};
同样的codec驱动也是一个platform driver ,所以首先要定义一个platform driver ,这里要注意的是我们定义的这个platform driver的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_register_codec的最后一个参数是snd_soc_dai_driver 的个数,我们只定义了一个所以就是1,如果是一个snd_soc_dai_driver 的数组,那么这个参数就是数组元素的个数。
讲到这里,我们的驱动都已经注册好了,ALSA已经可以正常工作了,但是如果这个时候播放一段音乐,我们是听不到声音的。为什么呢?因为我们还没有添加control呢,所以实际上codec还没有工作呢!下面介绍下添加control的方法。
第一个办法是,直接在snd_soc_codec_driver 的结构中添加两个字段
.controls = codec_controls,
.num_controls = ARRAY_SIZE(codec_controls),
这样调用snd_soc_register_codec 的时候control就添加了。
第二个方法是在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;
}
这样整个ALSA驱动就已经可以正常工作了。