声卡驱动原理浅析
:1:聲卡驅動原理淺析
聲卡驅動結構概覽:
聲卡設備模型建立的幾個必要文件是:s3c24xx_uda134x.c,s3c24xx-i2s.c,s3c-dma.c ,uda134x.c,soc-core.c。
s3c24xx-i2s.c :
該文件主要實現了配置cpu上iis接口寄存器的一些操作函數,填充了結構 體 s3c24xx_i2s_dai。並調用函數snd_soc_register_dai(&s3c24xx_i2s_dai)將其掛到鏈
頭dai_list上list_add(&dai->list, &dai_list);
s3c-dma.c :
該文件主要實現了錄音,放音等數據流操作函數。填充了結構體 s3c24xx_soc_platform。並調用函數
snd_soc_register_platform(&s3c24xx_soc_platform);將其掛到鏈表頭
platform_list上,list_add(&platform->list, &platform_list);。 uda134x.c :
該文件主要實現了對編解碼芯片uda1341寄存器的設置,聲音調節,靜音設 置等操 作函數。填充了結構體uda134x_dai。並調用函數
snd_soc_register_dai(&uda134x_dai)將其掛 到鏈表頭dai_list上
list_add(&dai->list, &dai_list);。該文件還實現了一些重要的初
始化,比如 創 建結構體類型為snd_card的card實例,創建pcm實例等。 s3c24xx_uda134x.c :
該文件是設備模型建立要執行的第一個文件。它聯繫了以上三個文 件,導
致了以 上三文件中的初始化函數的調用執行。填充了結構體 s3c24xx_uda134x_ops, s3c24xx_uda134x_dai_link,
snd_soc_s3c24xx_uda134x, s3c24xx_uda134x,
s3c24xx_uda134x_snd_devdata。添加了平臺設備
s3c24xx_uda134x_snd_device到內 核。註冊了與平臺設備
"s3c24xx_uda134x"相匹配的驅動s3c24xx_uda134x_driver。 soc-core.c:
該文件主要實現比以上文件更高一級的通用函數。實現與設備
s3c24xx_uda134x_snd_device相應的驅動。
聲卡驅動設備模型建立
圖:
聲卡設備驅動程序
:
聲卡驅動設備模型的建立應該從文件
sound/soc/s3c24xx/s3c24xx_uda134x.c 開始。這個文件中實現了聲卡uda134x平臺設備驅動。
static struct platform_driver s3c24xx_uda134x_driver = {
.probe = s3c24xx_uda134x_probe, .remove = s3c24xx_uda134x_remove, .driver = {
.name = "s3c24xx_uda134x",
.owner = THIS_MODULE,
},
};
一旦聲卡平臺設備添加到內核與該驅動相匹配,探測函數
s3c24xx_uda134x_probe將被調用。
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
………………………………
//創建平臺設備結構體s3c24xx_uda134x_snd_device
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
………………………………
//結構體s3c24xx_uda134x_snd_devdata是一個很重要的結構,其中包含了很多信//息
platform_set_drvdata(s3c24xx_uda134x_snd_device,
&s3c24xx_uda134x_snd_devdata); //註冊名為"soc-audio"的平臺設備,這將導致與文件sound/soc/soc-core.c中實現
//的平臺設備驅動soc_driver的匹配
ret = platform_device_add(s3c24xx_uda134x_snd_device);
………………………………
}
在文件sound/soc/soc-core.c中:
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
成功匹配之後將調用函數soc_probe(),
static int soc_probe(struct platform_device *pdev) {
ret = snd_soc_register_card(card);
}
static int snd_soc_register_card(struct snd_soc_card *card) {
………………………………
list_add(&card->list, &card_list);
snd_soc_instantiate_cards();
………………………………
}
static void snd_soc_instantiate_cards(void) {
struct snd_soc_card *card;
list_for_each_entry(card, &card_list, list) snd_soc_instantiate_card(card);
}
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
………………………………
//函數cpu_dai->probe(pdev, cpu_dai)就是文件s3c24xx-i2s.c 中實現的 函數
//s3c24xx_i2s_probe(),這個函數主要實現了I2S管腳,時鐘,相關寄存器配置。
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; if (cpu_dai->probe) {
ret = cpu_dai->probe(pdev, cpu_dai);
if (ret < 0)
goto cpu_dai_err;
}
}
//函數 codec_dev->probe(pdev);即是文件uda134x.c中實現的函數uda134x_soc_probe()
//這個函數完成了一些很重要的工作,將在後面分析。 if (codec_dev->probe) {
ret = codec_dev->probe(pdev);
if (ret < 0)
goto cpu_dai_err;
}
………………………………
//下面函數完成了聲卡相關設備的註冊,後面將有分析 ret = snd_card_register(codec->card);
………………………………
}
static int uda134x_soc_probe(struct platform_device *pdev) {
………………………………
//這是一個重要函數,下面將有講解。
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
printk(KERN_ERR "UDA134X: failed to register pcms\n"); goto pcm_err;
}
switch (pd->model) {
case UDA134X_UDA1340:
case UDA134X_UDA1344:
………………………………
case UDA134X_UDA1341:
//將數組uda1341_snd_controls中各結構體元素掛到card->controls鏈表上,這些結構體實
現
//了音頻播放時各個參數的設置。主要的操作函數有三個.info,.get,.put。 ret = snd_soc_add_controls(codec, uda1341_snd_controls, ARRAY_SIZE(uda1341_snd_controls));
break;
case UDA134X_UDA1345:
………………………………
default:
………………………………
}
………………………………
}
int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
{
………………………………
ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card); ………………………………
//PCM實例中包含播放流和錄音流,card->num_links表示pcm實例的個數。
for (i = 0; i < card->num_links; i++) { ret = soc_new_pcm(socdev, &card->dai_link[i], i); ………………………………
}
}
int snd_card_create(int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
//分配一個聲卡設備結構
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); ………………………………
err = snd_ctl_create(card);
………………………………
}
int snd_ctl_create(struct snd_card *card) {
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect, };
/*
在下面函數中將會做這麼幾件事:
dev = kzalloc(sizeof(*dev), GFP_KERNEL); dev->ops = ops;
list_add(&dev->list, &card->devices);
將dev掛到card上,在後面將會有這樣的調用 dev->ops->dev_register(dev),這將導
致註冊一個聲卡控制設備並創建設備節點。 */
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); }
在上面講到的函數 snd_soc_new_pcms中還有如下調用: int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const c
har *xid)
{
………………………………
ret = soc_new_pcm(socdev, &card->dai_link[i], i); ………………………………
}
static int soc_new_pcm(struct snd_soc_device *socdev, struct snd_soc_dai_link *dai_link, int num) {
………………………………
//函數snd_pcm_new::後面講解
ret = snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback
,
………………………………
//將播放流的所有子流的操作函數都設為soc_pcm_ops
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops); //將錄音流的所有子流的操作函數都設為soc_pcm_ops
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops); //下面函數即是函數s3c_dma_new(),在文件s3c-dma.c 中實現,這個函數主要
是為播放流錄
//音流分配緩存區。
ret = platform->pcm_new(codec->card, codec_dai, pcm); }
在上面函數中調用了函數snd_pcm_new()下面將是對這個函數的分析。
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count,
struct snd_pcm ** rpcm)
{
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
………………………………
//創建播放流的子流
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playba
ck_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
//創建錄音流的子流
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture
_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
………………………………
/*
在下面函數中將會做這麼幾件事:
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev->ops = ops;
list_add(&dev->list, &card->devices);
將dev掛到card上,在後面將會有這樣的調用 dev->ops->dev_register(dev),這
將導致註冊一個聲卡錄音流設備和播放流設備並創建設備節點。 */
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
snd_pcm_free(pcm);
return err;
}
………………………………
}
在前面函數snd_soc_instantiate_card()中調用了函數 int snd_card_register(struct snd_card *card)
{
………………………………
/*
調用上面提到的那幾個設備註冊函數,創建設備節點。 list_for_each_entry(dev, &card->devices, list) {
dev->ops->dev_register(dev)
}
*/
if ((err = snd_device_register_all(card)) < 0)
………………………………
}
………………………………
}
在聲卡uda1341的聲卡體系中總共創建了四個設備節點:
"controlC%i", "pcmC%iD%ip","pcmC%iD%ic","timer", 分別代表控制設備,播放流設備,錄音流設備,定時設備。下面討論這幾個設備節點的創建。
控制設備:
在文件control.c中實現函數
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
int err, cardnum;
char name[16];
………………………………
cardnum = card->number;
//下面函數將調用snd_register_device_for_dev()最終創建
//名為"controlC%i"的設備節點,並關聯文件操作函數集snd_ctl_f_ops,其結//構類型為file_operations,其成員函數在文件control.c中實現。 // sprintf(name, "controlC%i", cardnum);
if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -
1,
&snd_ctl_f_ops, card, name)) < 0)
return err;
………………………………
}
播放流設備 和 錄音流設備:
static int snd_pcm_dev_register(struct snd_device *device)
{
………………………………
for (cidx = 0; cidx < 2; cidx++) { //有播放流和錄音流所以是兩次循環
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
//設備節點名為"pcmC%iD%ip"
sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
//設備節點名為"pcmC%iD%ic"
sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
………………………………
//創建設備節點,關聯的文件操作函數集snd_pcm_f_ops[cidx]。該函數集結構
//的結構類型為file_operations。該函數集在文件pcm_native.c中實現。
err = snd_register_device_for_dev(devtype, pcm->card, pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);
………………………………
}
定時設備:
在文件timer.c中
static int __init alsa_timer_init(void)
{
………………………………
//下面函數將調用snd_register_device_for_dev()最終創建
//名為"timer"的設備節點,其關聯的文件操作函數集snd_timer_f_ops。
if ((err = snd_register_device(SNDRV_DEVICE_TYPE_TIMER, NULL,
0,
&snd_timer_f_ops, NULL, "timer")) < 0)
………………………………
}
由上面分析可知,上面四個設備的註冊都將調用同一個函數
snd_register_device_for_dev(type, card, dev, f_ops,
private_data, name,
snd_card_get_device_link(card));
該函數的實現如下:
可以看出,下面函數是將傳入的各參數包裝成結構體snd_minor並根據次
設備號存入數組snd_minors[minor] = preg;然後創建相應設備節點。一個設備
對應一個snd_minor結構。
int snd_register_device_for_dev(int type, struct snd_card *card,
int dev,
const struct file_operations *f_ops, void *private_data,
const char *name, struct device *device) {
……………………
preg->type = type;
preg->card = card ? card->number : -1; preg->device = dev;
preg->f_ops = f_ops;
preg->private_data = private_data; mutex_lock(&sound_mutex);
……………………
minor = snd_kernel_minor(type, card, dev); ……………………
snd_minors[minor] = preg;
preg->dev = device_create(sound_class, device,
MKDEV(major, minor), private_data, "%s", name);
………………………………
}
上面聲卡相關設備都有一個共同的主設備號:
在文件include/sound/core.h中有如下定義
#define CONFIG_SND_MAJOR 116
在文件sound/core/sound.c中又有:
static int major = CONFIG_SND_MAJOR;
主設備號CONFIG_SND_MAJOR的註冊也是在文件sound/core/sound.c
中進行的。
static int __init alsa_sound_init(void)
{
//註冊主設備號為CONFIG_SND_MAJOR的字符設備並關聯一個字符設備操作//函數集snd_fops
if (register_chrdev(major, "alsa", &snd_fops)) {
………………………………
}
………………………………
}
上面提到的字符設備操作函數集只有一個操作函數
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open
};
在這個打開函數中根據打開設備的次設備號在數組snd_minors[]中獲取設備對應的snd_minors結構,並實現了操作函數集的切換。 static int snd_open(struct inode *inode, struct file *file) {
………………………………
mptr = snd_minors[minor];
………………………………
file->f_op = fops_get(mptr->f_ops); ………………………………
}
各操作函數相互調用關係圖