韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之框架(基于优龙FS2410开发板,UDA1341声卡)
一、实验环境
1.1 虚拟机环境
a) Vmware版本:Vmware Workstation 12.5.7
b) Ubuntu版本:9.10
c) 内核版本:2.6.31.14
d) toolchain版本:arm-linux-gcc 4.3.2
1.2 开发板
优龙FS2410开发板,UDA1341声卡
内核版本:3.4.2
二、编写代码
2.1 实现machine驱动的框架(s3c2440_uda1341.c)
(参考sound\soc\samsung\s3c24xx_uda134x.c)
1. 分配注册一个名为soc-audio的平台设备
2. 这个平台设备有一个私有数据 snd_soc_card,里面有一项snd_soc_dai_link,被用来决定ASOC各部分的驱动
static struct snd_soc_ops s3c2440_uda1341_ops = { // .startup = s3c24xx_uda134x_startup, //从内核移植过来后,先暂时屏蔽掉这些函数,等用到的时候再加 // .shutdown = s3c24xx_uda134x_shutdown, // .hw_params = s3c24xx_uda134x_hw_params, }; static struct snd_soc_dai_link s3c2440_uda1341_dai_link = { .name = "100ask_UDA1341", .stream_name = "100ask_UDA1341", .codec_name = "uda1341-codec", //必须和codec驱动的名字相同 .codec_dai_name = "uda1341-iis", //必须和codec_dai驱动的名字相同 .cpu_dai_name = "s3c2440-iis", //必须和cpu_dai驱动的名字相同 .ops = &s3c2440_uda1341_ops, .platform_name = "s3c2440-dma", }; static struct snd_soc_card myalsa_card = { .name = "S3C2440_UDA1341", .owner = THIS_MODULE, .dai_link = &s3c2440_uda1341_dai_link, // snd_soc_dai_link被用来决定ASOC各部分的驱动 .num_links = 1, }; static void asoc_release(struct device * dev) { } static struct platform_device asoc_dev = { .name = "soc-audio", .id = -1, .dev = { .release = asoc_release, }, }; static int s3c2440_uda1341_init(void) { platform_set_drvdata(&asoc_dev, &myalsa_card); //这个平台设备有一个私有数据 snd_soc_card platform_device_register(&asoc_dev); //分配注册一个名为soc-audio的平台设备 return 0; }
2.2 实现codec驱动的框架(uda1341.c)
(参考sound\soc\codecs\uda134x.c)
1. 构造一个snd_soc_dai_driver
static const struct snd_soc_dai_ops uda1341_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, }; static struct snd_soc_dai_driver uda1341_dai = { .name = "uda1341-iis", //必须和machine驱动的s3c2440_uda1341_dai_link.codec_dai_name一致 /* playback capabilities */ //playback和capture的各个属性值会被用于在soc_pcm_open里调用诸如 runtime->hw.rate_min = max(codec_dai_drv->playback.rate_min, cpu_dai_drv->playback.rate_min); .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 = &uda1341_dai_ops, };
2. 构造一个snd_soc_codec_driver
static struct snd_soc_codec_driver soc_codec_dev_uda1341 = { // .probe = uda134x_soc_probe, // 从内核移植过来后,先暂时屏蔽掉这些函数,等用到时候再加 // .remove = uda134x_soc_remove, // .suspend = uda134x_soc_suspend, // .resume = uda134x_soc_resume, // .reg_cache_size = sizeof(uda134x_reg), // .reg_word_size = sizeof(u8), // .reg_cache_default = uda134x_reg, // .reg_cache_step = 1, // .read = uda134x_read_reg_cache, // .write = uda134x_write, // .set_bias_level = uda134x_set_bias_level, };
3. 注册它们(一个小技巧:因为snd_soc_register_codec需要传入device参数,所以无法直接在uda1341_init()里调用snd_soc_register_codec,所以多绕一道弯子,通过注册platform_device、platform_driver,在其probe()函数中调用snd_soc_register_codec)
static struct platform_device uda1341_dev = { .name = "uda1341-codec", //必须和machine驱动的s3c2440_uda1341_dai_link.codec_name一致 .id = -1, .dev = { .release = uda1341_dev_release, }, }; struct platform_driver uda1341_drv = { .probe = uda1341_probe, //在里面会调用snd_soc_register_codec注册codec驱动和codec_dai驱动 .remove = uda1341_remove, .driver = { .name = "uda1341-codec", //必须和uda1341_dev的name一致 } }; static int uda1341_init(void) { platform_device_register(&uda1341_dev); platform_driver_register(&uda1341_drv); return 0; } static void uda1341_exit(void) { platform_device_unregister(&uda1341_dev); platform_driver_unregister(&uda1341_drv); } module_init(uda1341_init); module_exit(uda1341_exit); MODULE_LICENSE("GPL");
2.3 实现cpu_dai驱动的框架(s3c2440_iis.c)
(参考sound\soc\samsung\s3c24xx-i2s.c)
1. 构造一个snd_soc_codec_driver
static const struct snd_soc_dai_ops s3c2440_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_syscl }; #define S3C24XX_I2S_RATES \ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) static struct snd_soc_dai_driver s3c2440_i2s_dai = { //没有name,其实snd_soc_register_dai()里会把s3c2440_iis_dev的name复制给dai->name // .probe = s3c24xx_i2s_probe, //从内核移植过来后,先暂时屏蔽掉这些函数,等用到时候再加
// .suspend = s3c24xx_i2s_suspend, // .resume = s3c24xx_i2s_resum .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 = &s3c2440_i2s_dai_ops, };
2. 注册它
static int s3c2440_iis_probe(struct platform_device *pdev) { return snd_soc_register_dai(&pdev->dev, &s3c2440_i2s_dai); } static int s3c2440_iis_remove(struct platform_device *pdev) { snd_soc_unregister_dai(&pdev->dev); return 0; } static void s3c2440_iis_release(struct device * dev) { } static struct platform_device s3c2440_iis_dev = { .name = "s3c2440-iis", //必须和machine驱动的s3c2440_uda1341_dai_link.cpu_dai_name一致 .id = -1, .dev = { .release = s3c2440_iis_release, }, }; struct platform_driver s3c2440_iis_drv = { .probe = s3c2440_iis_probe, .remove = s3c2440_iis_remove, .driver = { .name = "s3c2440-iis", //必须和s3c2440_iis_dev的name一致 } }; static int s3c2440_iis_init(void) { platform_device_register(&s3c2440_iis_dev); platform_driver_register(&s3c2440_iis_drv); return 0; } static void s3c2440_iis_exit(void) { struct clk *clk; platform_device_unregister(&s3c2440_iis_dev); platform_driver_unregister(&s3c2440_iis_drv); } static void s3c2440_iis_exit(void) { platform_device_unregister(&s3c2440_iis_dev); platform_driver_unregister(&s3c2440_iis_drv); } module_init(s3c2440_iis_init); module_exit(s3c2440_iis_exit); MODULE_LICENSE("GPL");
2.4 实现platform驱动的框架(s3c2440_dma.c)
(参考 sound\soc\samsung\dma.c)
1. 构造一个snd_soc_platform_driver
static struct snd_pcm_ops dma_ops = { // .open = dma_open, //从内核移植过来后,先暂时屏蔽掉这些函数,等用到的时候再加 // .close = dma_close, // .ioctl = snd_pcm_lib_ioctl, // .hw_params = dma_hw_params, // .hw_free = dma_hw_free, // .prepare = dma_prepare, // .trigger = dma_trigger, // .pointer = dma_pointer, // .mmap = dma_mmap, }; static struct snd_soc_platform_driver s3c2440_dma_platform = { .ops = &s3c2440_dma_ops, // .pcm_new = dma_new, //从内核移植过来后,先暂时屏蔽掉这些函数,等用到的时候再加 // .pcm_free = dma_free_dma_buffer };
static int s3c2440_dma_probe(struct platform_device *pdev) { return snd_soc_register_platform(&pdev->dev, &s3c2440_dma_platform); } static int s3c2440_dma_remove(struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); return 0; } static void s3c2440_dma_release(struct device * dev) { } static struct platform_device s3c2440_dma_dev = { .name = "s3c2440-dma", //必须和machine驱动的s3c2440_uda1341_dai_link. platform_name一致 .id = -1, .dev = { .release = s3c2440_dma_release, }, }; struct platform_driver s3c2440_dma_drv = { .probe = s3c2440_dma_probe, .remove = s3c2440_dma_remove, .driver = { .name = "s3c2440-dma", //必须和s3c2440_dma_dev的name一致 } }; static int s3c2440_dma_init(void) { dma_regs = ioremap(DMA2_BASE_ADDR, sizeof(struct s3c_dma_regs)); platform_device_register(&s3c2440_dma_dev); platform_driver_register(&s3c2440_dma_drv); return 0; } static void s3c2440_dma_exit(void) { platform_device_unregister(&s3c2440_dma_dev); platform_driver_unregister(&s3c2440_dma_drv); iounmap(dma_regs); } module_init(s3c2440_dma_init); module_exit(s3c2440_dma_exit); MODULE_LICENSE("GPL");
三、参考资料
1. 韦东山 嵌入式Linux视频教程_3期项目实战之ALSA声卡:第2课第1.1_17节_ALSA声卡08_从零编写之框架
2. DroidPhone 《Linux ALSA 声卡驱动》