ASOC驱动框架分析
ASOC(ALSA system on chip)。是由wolfson公司做的一个针对嵌入式移动设备的声音驱动的架构。支持三种不同的CODEC接口:AC97(Intel),I2S(Philips),PCM接口。ASOC在实现上区分了具体的平台和CODEC,从而使得同一个CODEC芯片可以在不同的体系结构中使用。而相关的与体系统结构相关的部分主要实现DMA的传输和控制管理。
所谓ALSA,(Advansed Linux Sound Architecture,高级Linux声音架构),是Linux 2.6 版本内核的声音子系统。
2.4版本的声音子系统叫OSS(Open Sound System,开放声音系统),现以去除,不过为了兼容,ALSA会提供OSS的接口(设备节点)。
本驱动框架分析基于mini2440开发板,其中配备了片上系统s3c2440,声卡Uda1341。
分析包括的文件有:
arch\arm\mach-s3c2440\Mach-mini2440.c (平台设备)
sound\soc\s3c24xx\S3c24xx_uda134x.c (平台驱动)
sound\soc\Soc-core.c (ALSA主模块)
sound\soc\codecs\Uda134x.c (声音控制)
sound\soc\s3c24xx\S3c24xx-i2s.c (i2s初始化)
sound\soc\s3c24xx\S3c24xx-pcm.c (音频码流传输)
arch\arm\mach-s3c2440\Mach-mini2440.c
文件设置了各种各样的mini2440平台设备,当我们需要注册平台驱动的时候,会去比对该文件中设备的名称跟我们自己驱动的设备名称,发现同名设备则调用驱动内的probe函数。
static struct platform_device s3c24xx_uda134x = {
.name = "s3c24xx_uda134x",
.dev = {
.platform_data = &s3c24xx_uda134x_data,
}
};
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,
};
sound\soc\s3c24xx\S3c24xx_uda134x.c
设置了跟Mach-mini2440.c中同名的驱动"s3c24xx_uda134x"
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,
},
};
发现同名设备后调用probe函数
probe函数内部创建了名为“soc-audio”的声卡设备,然后用s3c24xx_uda134x_snd_devdata设置设备信息
s3c24xx_uda134x_probe(struct platform_device *pdev)
s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,"data") //初始化l3所用到的gpio
s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,"clk")
s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,"mode")
s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1); //创建声卡dev(名字为soc-audio)
platform_set_drvdata(s3c24xx_uda134x_snd_device,&s3c24xx_uda134x_snd_devdata);
platform_device_add(s3c24xx_uda134x_snd_device);
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,
};
其中的snd_soc_s3c24xx_uda134x联系起声卡驱动各个模块
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.platform = &s3c24xx_soc_platform, //S3c24xx-pcm.c,向用户空间提供pcm操作函数
.dai_link = &s3c24xx_uda134x_dai_link, //关联codec(uda134x)跟cpu(s3c24xx)
.num_links = 1,
};
dai(digital audio interface,数字音频接口),dai_link包含了两个dai接口:uda134x_dai 跟 s3c24xx_i2s_dai
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_dai = &uda134x_dai, //Uda134x.c,创建声卡设备
.cpu_dai = &s3c24xx_i2s_dai, //S3c24xx-i2s.c,初始化i2s
.ops = &s3c24xx_uda134x_ops,
};
sound\soc\Soc-core.c
开始创建声卡驱动,作为总线设备驱动框架下的一员,在发现同名设备“soc-audio”后,soc_driver中的probe函数会被调用
Soc-core.c //声卡drv
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
在probe函数内部会把新增的声卡snd_soc_s3c24xx_uda134x加入card链表card_list,然后对声卡链表的每个节点,进行platform(码流传输模块),dai_list(数字音频接口)比对,如果发现相同,则调用各自的probe函数(这里card没有probe函数)
soc_probe(struct platform_device *pdev)
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_card *card = socdev->card;
ret = snd_soc_register_card(card);
list_add(&card->list, &card_list);
snd_soc_instantiate_cards();
list_for_each_entry(card, &card_list, list)
snd_soc_instantiate_card(card);
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
for (i = 0; i < card->num_links; i++) {
if (!card->dai_link[i].codec_dai->ops)
card->dai_link[i].codec_dai->ops = &null_dai_ops;
}
card->probe(pdev);
cpu_dai->probe(pdev, cpu_dai); //s3c24xx_i2s_probe
codec_dev->probe(pdev); //uda134x_soc_probe
platform->probe(pdev);
另外,Soc-core.c中也向外提供函数,如:
snd_soc_init_card(struct snd_soc_device *socdev)
snd_card_register(codec->card); //位于sound\core\init.c
device_create(sound_class, card->dev,MKDEV(0, 0), card,"card%i", card->number); //创建设备节点
可以注意到上方的函数中,probe有四个,除了已经注册的一个card之外,还有另外三个
以下三个文件就是这三个probe所在的变量的本体
Uda134x.c
提供音量控制等的声音操作函数,并且向pcm提供声音操作函数
uda134x_init(void)
snd_soc_register_dai(&uda134x_dai); /*Soc-core.c提供该函数*/
list_add(&dai->list, &dai_list); //uda134x_dai加入dai_list
snd_soc_instantiate_cards();
list_for_each_entry(card, &card_list, list)
snd_soc_instantiate_card(card);
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
for (i = 0; i < card->num_links; i++) {
if (!card->dai_link[i].codec_dai->ops)
card->dai_link[i].codec_dai->ops = &null_dai_ops;
}
card->probe(pdev);
cpu_dai->probe(pdev, cpu_dai); //s3c24xx_i2s_probe
codec_dev->probe(pdev); //uda134x_soc_probe
platform->probe(pdev);
struct snd_soc_dai uda134x_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,
};
static struct snd_soc_dai_ops uda134x_dai_ops = {
.startup = uda134x_startup,
.shutdown = uda134x_shutdown,
.hw_params = uda134x_hw_params,
.digital_mute = uda134x_mute,
.set_sysclk = uda134x_set_dai_sysclk,
.set_fmt = uda134x_set_dai_fmt,
};
另外就是该文件提供的probe函数是最终创建出声卡实例,使声卡能够运行于操作系统之上
uda134x_soc_probe(struct platform_device *pdev)
snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); //创建声卡pcm实例
soc_new_pcm(socdev, &card->dai_link[i], i) //S3c-core.c提供的实例创建函数
snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback,capture, &pcm);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops); //设置操作函数
ret = platform->pcm_new(codec->card, codec_dai, pcm); //platform = s3c24xx_soc_platform, pcm_new = s3c24xx_pcm_new,
s3c24xx_pcm_new(struct snd_card *card,struct snd_soc_dai *dai, struct snd_pcm *pcm)
s3c24xx_pcm_preallocate_dma_buffer(pcm,SNDRV_PCM_STREAM_PLAYBACK); //分配DMA
snd_soc_add_controls(codec, uda1341_snd_controls,ARRAY_SIZE(uda1341_snd_controls)); //将声卡与混频控制单元关联,控制音量
snd_soc_init_card(socdev); //Soc-core.c提供,提交声卡(创建设备节点)
struct snd_soc_codec_device soc_codec_dev_uda134x = {
.probe = uda134x_soc_probe,
.remove = uda134x_soc_remove,
.suspend = uda134x_soc_suspend,
.resume = uda134x_soc_resume,
};
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 const struct snd_kcontrol_new uda1340_snd_controls[] = { //声音控制
SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
};
S3c24xx-i2s.c
进行soc的i2s端口的初始化,向pcm提供i2s操作接口,在probe函数内初始化i2s用到的GPIO
s3c24xx_i2s_exit(void)
snd_soc_register_dai(&s3c24xx_i2s_dai); /*Soc-core.c提供该函数*/
list_add(&dai->list, &dai_list);
snd_soc_instantiate_cards();
list_for_each_entry(card, &card_list, list)
snd_soc_instantiate_card(card);
list_for_each_entry(platform, &platform_list, list)
if (card->platform == platform) {
found = 1;
break;
}
list_for_each_entry(dai, &dai_list, list)
if (card->dai_link[i].cpu_dai == dai) {
found = 1;
break;
}
for (i = 0; i < card->num_links; i++) {
if (!card->dai_link[i].codec_dai->ops)
card->dai_link[i].codec_dai->ops = &null_dai_ops;
}
card->probe(pdev);
cpu_dai->probe(pdev, cpu_dai); //s3c24xx_i2s_probe
codec_dev->probe(pdev); //uda134x_soc_probe
platform->probe(pdev);
struct snd_soc_dai s3c24xx_i2s_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,
};
static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
.trigger = s3c24xx_i2s_trigger,
.hw_params = s3c24xx_i2s_hw_params,
.set_fmt = s3c24xx_i2s_set_fmt,
.set_clkdiv = s3c24xx_i2s_set_clkdiv,
.set_sysclk = s3c24xx_i2s_set_sysclk,
};
s3c24xx_i2s_probe(struct platform_device *pdev,struct snd_soc_dai *dai)
/* Configure the I2S pins in correct mode */
s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
s3c24xx_snd_txctrl(0);
s3c24xx_snd_rxctrl(0);
S3c24xx-pcm.c
向设备节点提供pcm操作函数ioctl等,后面会被封装成设备节点的fops,没有probe函数
struct snd_soc_platform s3c24xx_soc_platform = {
.name = "s3c24xx-audio",
.pcm_ops = &s3c24xx_pcm_ops,
.pcm_new = s3c24xx_pcm_new,
.pcm_free = s3c24xx_pcm_free_dma_buffers,
};
static struct snd_pcm_ops s3c24xx_pcm_ops = {
.open = s3c24xx_pcm_open,
.close = s3c24xx_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = s3c24xx_pcm_hw_params,
.hw_free = s3c24xx_pcm_hw_free,
.prepare = s3c24xx_pcm_prepare,
.trigger = s3c24xx_pcm_trigger,
.pointer = s3c24xx_pcm_pointer,
.mmap = s3c24xx_pcm_mmap,
};
总结: