本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记
一.uda134x平台设备的注册
static struct s3c24xx_uda134x_platform_data s3c24xx_uda134x_data = {
.l3_clk = S3C2410_GPB(4),
.l3_data = S3C2410_GPB(3),
.l3_mode = S3C2410_GPB(2),
.model = UDA134X_UDA1341,
};
static struct platform_device s3c24xx_uda134x = {
.name = "s3c24xx_uda134x",
.dev = {
.platform_data = &s3c24xx_uda134x_data,
}
};
- 设备名称:s3c24xx_uda134x
- 设备的platform_data的:s3c24xx_uda134x_data
L3控制引脚:
L3CLOCK: GPB4
L3DATA: GPB3
L3MODE: GPB2
uda134x设备在mini2440_machine_init函数中调用,mini2440_machine_init函数在内核起来时调用。
mini2440_machine_init函数中初始化大部分硬件设备,包括uda134x设备。
static void __init mini2440_machine_init(void)
{
#if defined (LCD_WIDTH)
s3c24xx_fb_set_platdata(&mini2440_fb_info);
#endif
s3c_i2c0_set_platdata(NULL);
s3c2410_gpio_cfgpin(S3C2410_GPC(0), S3C2410_GPC0_LEND);
s3c_device_nand.dev.platform_data = &friendly_arm_nand_info;
s3c_device_sdi.dev.platform_data = &mini2440_mmc_cfg;
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
s3c_pm_init();
}
- 设置地址:/sys/devices/platform/s3c24xx_uda134x.0
二. uda134x平台driver的注册
- 搜索"s3c24xx_uda134x"字符串,对应的平台driver在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,
},
};
static int __init s3c24xx_uda134x_init(void)
{
return platform_driver_register(&s3c24xx_uda134x_driver);
}
module_init(s3c24xx_uda134x_init);
- 注册时间:内核起来时注册该platform driver。
- probe函数为s3c24xx_uda134x_probe,注册platform driver或者platform device时自动匹配,匹配上了调用probe函数。
三. uda134x platform driver的probe函数
- 申请 L3CLOCK,L3DATA,L3MODE三个引脚,设置输出模式,并拉低。
if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data, "data") < 0)
return -EBUSY;
if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk, "clk") < 0)
{
gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
return -EBUSY;
}
if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode, "mode") < 0)
{
gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
return -EBUSY;
}
- 申请注册了一个soc-audio的平台设备,设备的注册的地址是:/sys/devices/platform/soc-audio
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
ret = platform_device_add(s3c24xx_uda134x_snd_device);
- 设置drvdata,把s3c24xx_uda134x_snd_devdata这个全集变量设置给它。
platform_set_drvdata(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x_snd_devdata);
四. soc-audio设备的drvdata
看一下s3c24xx_uda134x_snd_devdata这个全局变量,这个变量十分复杂。
static struct snd_soc_device s3c24xx_uda134x_snd_devdata =
{
.card = &snd_soc_s3c24xx_uda134x,
.codec_dev = &soc_codec_dev_uda134x,
.codec_data = &s3c24xx_uda134x,
};
把里面的展开,得到下面的结构体:
static struct snd_soc_device s3c24xx_uda134x_snd_devdata =
{
.card =
{
.name = "S3C24XX_UDA134X",
.platform =
{
.name = "s3c24xx-audio",
.pcm_ops = &s3c24xx_pcm_ops,
.pcm_new = s3c24xx_pcm_new,
.pcm_free = s3c24xx_pcm_free_dma_buffers,
};
.dai_link =
{
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_dai =
{
.name = "UDA134X",
/* 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,
},
.cpu_dai =
{
.name = "s3c24xx-i2s",
.id = 0,
.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,
},
.ops = &s3c24xx_uda134x_ops,
};
.num_links = 1,
};
.codec_dev =
{
.probe = uda134x_soc_probe,
.remove = uda134x_soc_remove,
.suspend = uda134x_soc_suspend,
.resume = uda134x_soc_resume,
},
.codec_data =
{
.l3 = {
.setdat = setdat,
.setclk = setclk,
.setmode = setmode,
.data_hold = 1,
.data_setup = 1,
.clock_high = 1,
.mode_hold = 1,
.mode = 1,
.mode_setup = 1,
},
},
};
https://blog.csdn.net/gqb_driver/article/details/8551551
参考其他人的博客
codec_dai:
name:“UDA134X”
作用:对声卡芯片设置波特率,数据传输模式等硬件设置。
playback(播放):
stream_name = “Playback”,
channels_min = 1,
channels_max = 2,
rates = UDA134X_RATES,采样频率为8KHz ~ 48KHz
formats = UDA134X_FORMATS =SNDRV_PCM_FMTBIT_S8,SNDRV_PCM_FMTBIT_S16_LE,SNDRV_PCM_FMTBIT_S18_3LE,SNDRV_PCM_FMTBIT_S20_3LE
数据可以是8位,16位,18位,20位,但是都是先发高位数据。
录音:
录音上面的参数设置和播放是一样的。
cpu_dai:
name = “s3c24xx-i2s”,
作用:I2S的硬件设置
playback(播放):
channels_min = 2,
channels_max = 2,
rates = S3C24XX_I2S_RATES, 采用频率是8KHz ~ 96KHz
formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,只支持8位和16位的数据。
capture(录制):
录制的设置也是和上面是一样的。
可见,采样频率CPU支持的种类比UDA134X芯片多,数据格式UDA134X芯片支持的多。
s3c24xx_uda134x_ops操作集:
调用结构体uda134x_dai和结构体s3c24xx_i2s_dai中的硬件操作函数对声卡uda1341,和CPU上IIS接口进行初始化设置。
platform:
name = “s3c24xx-audio”
作用:使用DMA在内存和I2S的缓存中搬运数据。
五. 匹配soc-audio设备的driver
搜索"soc-audio",在soc-core.c文件中注册了这个driver。也是在内核起来的时候注册的。
static int __init snd_soc_init(void)
{
return platform_driver_register(&soc_driver);
}
六. soc-audio driver的probe函数soc_probe
ret = snd_soc_register_card(card);
注册soc card,也就是snd_soc_s3c24xx_uda134x。
将snd_soc_s3c24xx_uda134x添加到card_list这个链表。
list_add(&card->list, &card_list);
初始化所有的cards。取出card_list的每一次card,调用snd_soc_instantiate_card函数。
snd_soc_instantiate_cards();
list_for_each_entry(card, &card_list, list)
snd_soc_instantiate_card(card);
七. snd_soc_instantiate_card函数分析
- 取出platform_list的每一个platform,看有没有注册过s3c24xx-audio这个platform,没有的话直接返回。
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform)
{
found = 1;
break;
}
if (!found)
{
dev_dbg(card->dev, "Platform %s not registered\n", card->platform->name);
return;
}
- 判断有没有注册上面的s3c24xx_i2s_dai这个cpu_dai,没有的话直接返回。
ac97 = 0;
for (i = 0; i < card->num_links; i++)
{
found = 0;
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai)
{
found = 1;
break;
}
if (!found)
{
dev_dbg(card->dev, "DAI %s not registered\n",
card->dai_link[i].cpu_dai->name);
return;
}
if (card->dai_link[i].cpu_dai->ac97_control)
ac97 = 1;
}
- 判断有没有注册上面的uda134x_dai这个codec_dai,没有的话直接返回。
for (i = 0; i < card->num_links; i++)
{
found = 0;
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].codec_dai == dai)
{
found = 1;
break;
}
if (!found)
{
dev_dbg(card->dev, "DAI %s not registered\n",
card->dai_link[i].codec_dai->name);
return;
}
}
- 通过上面的几步检查,我们要的platform, cpu_dai,codec_dai都已经准备好了。
- 执行card->probe,但是我们这里card->probe = NULL,就不执行了。
if (card->probe) {
ret = card->probe(pdev);
if (ret < 0)
return;
}
- 执行s3c24xx_i2s_dai的probe函数。
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;
}
}
这个函数里面做的操作:
获取iis时钟,并使能iis时钟;
配置IIS引脚;
I2S enable;
-
执行soc_codec_dev_uda134x的probe函数,就是uda134x_soc_probe函数。
-
执行s3c24xx_soc_platform的probe函数,但是没有probe函数。
if (platform->probe) {
ret = platform->probe(pdev);
if (ret < 0)
goto platform_err;
}
八. uda134x_soc_probe函数分析
- 申请snd_soc_s3c24xx_uda134x的codec内存,snd_soc_codec类型,初始化赋值。
socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (socdev->card->codec == NULL)
return ret;
codec = socdev->card->codec;
uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
if (uda134x == NULL)
goto priv_err;
codec->private_data = uda134x;
codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg),
GFP_KERNEL);
if (codec->reg_cache == NULL)
goto reg_err;
mutex_init(&codec->mutex);
codec->reg_cache_size = sizeof(uda134x_reg);
codec->reg_cache_step = 1;
codec->name = "UDA134X";
codec->owner = THIS_MODULE;
codec->dai = &uda134x_dai;
codec->num_dai = 1;
codec->read = uda134x_read_reg_cache;
codec->write = uda134x_write;
#ifdef POWER_OFF_ON_STANDBY
codec->set_bias_level = uda134x_set_bias_level;
#endif
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
codec->control_data = codec_setup_data;
codec.name = “UDA134X”
codec.dai = &uda134x_dai
codec.read = uda134x_read_reg_cache
codec.write = uda134x_write
codec.control_data = &s3c24xx_uda134x
codec.private_data = uda134x_priv
codec.socdev =s3c24xx_uda134x_snd_devdata
现在整理一下各个数据结构之间的关系,如下图所示:
- 调用snd_soc_new_pcms函数
调用snd_card_create函数创建一个声卡变量struct snd_card *card。
card->number赋值
card->module赋值
card->devices链表初始化
card->controls链表初始化
card->files_list链表初始化
card->number = idx;
card->module = module;
INIT_LIST_HEAD(&card->devices);
init_rwsem(&card->controls_rwsem);
rwlock_init(&card->ctl_files_rwlock);
INIT_LIST_HEAD(&card->controls);
INIT_LIST_HEAD(&card->ctl_files);
spin_lock_init(&card->files_lock);
INIT_LIST_HEAD(&card->files_list);
init_waitqueue_head(&card->shutdown_sleep);
调用snd_ctl_create函数创建一个conctrol组件,并初始化,然后插入card->devices链表
int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops)
{
struct snd_device *dev;
if (snd_BUG_ON(!card || !device_data || !ops))
return -ENXIO;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
snd_printk(KERN_ERR "Cannot allocate device\n");
return -ENOMEM;
}
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
list_add(&dev->list, &card->devices); /* add to the head of list */
return 0;
}
将创建的声卡赋值给codec->card,如下图所示:
调用soc_new_pcm函数创建pcm部件
for (i = 0; i < card->num_links; i++)
{
ret = soc_new_pcm(socdev, &card->dai_link[i], i);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm %s\n",
card->dai_link[i].stream_name);
mutex_unlock(&codec->mutex);
return ret;
}
}
card->num_links = 1,只创建了1个pcm部件
申请结构体snd_soc_pcm_runtime *rtd,并赋值
rtd->dai = s3c24xx_uda134x_dai_link
rtd->socdev = s3c24xx_uda134x_snd_devdata
调用snd_pcm_new函数创建一个pcm实例
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count,
struct snd_pcm ** rpcm)
{
struct snd_pcm *pcm;
int err;
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 (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (pcm == NULL) {
snd_printk(KERN_ERR "Cannot allocate PCM\n");
return -ENOMEM;
}
pcm->card = card;
pcm->device = device;
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_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;
}
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
分配一个snd_pcm结构体
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
pcm实例和声卡关联
pcm->card = card;
然后分别创建一个playback stream和capture stream。
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_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;
}
分配一个pcm设备snd_device,放入声卡的devices链表中。
snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
pcm指针保存在s3c24xx_uda134x_dai_link.pcm中。
dai_link->pcm = pcm;
上面申请的rtd指针保存在pcm->private_data中。
pcm->private_data = rtd;
给snd_pcm.streams[i].substream[i].ops赋值,mmap,pointer,ioctl等几个函数使用s3c24xx_pcm_ops的,其他的使用soc_pcm_ops自己的。soc_pcm_ops如下:
soc_pcm_ops.open = soc_pcm_open,
soc_pcm_ops.close = soc_codec_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.mmap = platform->pcm_ops->mmap;
soc_pcm_ops.pointer = platform->pcm_ops->pointer;
soc_pcm_ops.ioctl = platform->pcm_ops->ioctl;
soc_pcm_ops.copy = platform->pcm_ops->copy;
soc_pcm_ops.silence = platform->pcm_ops->silence;
soc_pcm_ops.ack = platform->pcm_ops->ack;
soc_pcm_ops.page = platform->pcm_ops->page;
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
给snd_pcm.streams[i].substream[i]申请DMA内存
ret = platform->pcm_new(codec->card, codec_dai, pcm);
就是调用的是s3c24xx_pcm_new函数,申请的DMA内存的物理地址保存在snd_pcm.streams[i].substream[i].dma_buffer.addr中,虚拟地址保存在snd_pcm.streams[i].substream[i].dma_buffer.area中,内存大小是128Kb。
把DMA内存释放的接口告诉pcm。
pcm->private_free = platform->pcm_free;
platform->pcm_free = s3c24xx_pcm_free_dma_buffers,这个接口释放DMA内存。
上面几个数据结构之间的关联如下图所示:
- 调用snd_soc_add_controls接口增加Control接口
Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。
本文使用的芯片是uda1341,所有的Control接口都定义在了uda1341_snd_controls数组中。
数据中的每个元素SOC_SINGLE和SOC_ENUM这种宏定义。
我们选取一个音量控制的Control接口,把他展开,得到:
uda1341_snd_controls[0] =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Volume",
.info = snd_soc_info_volsw,
.get = snd_soc_get_volsw,
.put = snd_soc_put_volsw,
.private_value =
{
soc_mixer_control.reg = UDA134X_DATA000,
soc_mixer_control.shift = 0,
soc_mixer_control.rshift = 0,
soc_mixer_control.max = 0x3F,
soc_mixer_control.invert = 1,
},
}
uda1341_snd_controls这个数组中一共定义了20个Control接口,调用snd_soc_add_controls注册,实际上是注册到snd_card的controls链表中。
进入snd_soc_add_controls函数分析。
int snd_soc_add_controls(struct snd_soc_codec *codec,const struct snd_kcontrol_new *controls, int num_controls)
{
struct snd_card *card = codec->card;
int err, i;
for (i = 0; i < num_controls; i++) {
const struct snd_kcontrol_new *control = &controls[i];
err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL));
if (err < 0) {
dev_err(codec->dev, "%s: Failed to add %s\n",
codec->name, control->name);
return err;
}
}
return 0;
}
调用snd_ctl_add函数使用for循环全部添加。snd_ctl_add函数参数2传入snd_kcontrol结构体数据。
看一下snd_soc_cnew函数。
snd_soc_cnew
memcpy(&template, _template, sizeof(template));
snd_ctl_new1(&template, data);
snd_ctl_new(&kctl, access)
主要是把上面的uda1341_snd_controls[0]拷贝,然后把里面的数据保存到snd_kcontrol这个结构体,然后申请内存,保存snd_kcontrol这个结构体,同时返回申请到的内存的指针。返回给snd_ctl_add函数的参数2。
进入snd_ctl_add函数
snd_ctl_add
snd_ctl_find_id(card, &id)
snd_ctl_find_hole(card, kcontrol->count)
list_add_tail(&kcontrol->list, &card->controls);
这个函数里面首先检查这个Control接口是否已经存在了,然后看看还能不能注册,最后是把Control接口添加到snd_card的controls链表中。
数据结构如下所示:
- 调用snd_soc_init_card函数注册声卡
主要是调用了snd_card_register函数注册声卡。
snd_card_register(codec->card);
snd_device_register_all(card)
list_for_each_entry(dev, &card->devices, list) {
if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
if ((err = dev->ops->dev_register(dev)) < 0)
return err;
dev->state = SNDRV_DEV_REGISTERED;
}
}
取出card->devices的每一个设备,调用它的dev_register函数,传入的参数是snd_device类型。
前面主要注册了两个设备,一个是SNDRV_DEV_PCM类型的snd_device, 一个是SNDRV_DEV_CONTROL
类型的的snd_device。
它们的dev_register函数分别是:snd_pcm_dev_register和 snd_ctl_dev_register。
进入snd_pcm_dev_register函数分析。
snd_pcm_dev_register
err = snd_pcm_add(pcm);
for (cidx = 0; cidx < 2; cidx++)
{
switch (cidx)
{
case SNDRV_PCM_STREAM_PLAYBACK:
sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
snd_register_device_for_dev(devtype, pcm->card, pcm->device, &snd_pcm_f_ops[cidx], pcm, str, dev);
device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name)
}
将pcm放入链表snd_pcm_devices中。
调用device_create创建pcmC0D0p,pcmC0D0c两个pcm设备。
分别创建snd_minor类型的变量preg,初始化赋值后存入snd_minors[256]中。
进入snd_ctl_dev_register函数分析。
static int snd_ctl_dev_register(struct snd_device *device)
snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, &snd_ctl_f_ops, card, name)
snd_register_device_for_dev(type, card, dev, f_ops, private_data, name,snd_card_get_device_link(card)
device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name)
最终调用device_create函数注册的Ctrl设备。
同时分配了一个snd_minor类型的变量preg。
preg->type = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
preg->card = 0;
preg->device = 1;
preg->f_ops = &snd_pcm_f_ops;
preg->private_data = g_snd_pcm;
把preg保存在snd_minors[256]中。
九. 总结
看到这里,在梳理一下前面的数据结构之间的关系,如下图所示: