在前面两篇文章中,我们分别讲了嵌入式Linux系统声卡注册的过程和调用的过程:
https://blog.csdn.net/qq_37659294/article/details/104748747
https://blog.csdn.net/qq_37659294/article/details/104802868
讲了那么多,我们最终的目的无非就是想写一个声卡驱动,然后给上层的APP使用而已,在之前的文章中可以看出内核里面关于声卡这部分是非常复杂的,但实际上我们写驱动的时候,只需要实现和硬件相关的那几个结构体,如cpu_dai等。然后借助内核的ASOC框架把我们的驱动注册进去而已。下面是我们编写的驱动:
①我们构造一个snd_soc_card结构体myalsa_card,它的dai_link指定了要使用那些cpu_dai、codec_dai...然后我们模仿内核自带的声卡驱动,调用platform_set_drvdata把myalsa_card保存在platform_device中(《ASOC注册过程》machine部分的第①点有介绍)。
②构造一个名为"soc-audio"的平台设备,然后注册它,因为内核中有同名的平台驱动,所以调用了相应的probe函数,即我们在《ASOC注册过程》machine部分的第①点有介绍到的soc_probe函数。这个函数就会根据我们构造的myalsa_card结构体里的信息,完成我们声卡的所有注册任务。
至此,我们的machine部分的驱动就编写完成了。
#include
#include
#include
#include
/* 参考sound\soc\samsung\s3c24xx_uda134x.c
*/
/*
* 1. 分配注册一个名为soc-audio的平台设备
* 2. 这个平台设备有一个私有数据 snd_soc_card
* snd_soc_card里有一项snd_soc_dai_link
* snd_soc_dai_link被用来决定ASOC各部分的驱动
*/
static struct snd_soc_ops s3c2440_uda1341_ops = {
//.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_dai_name = "uda1341-iis",
.cpu_dai_name = "s3c2440-iis",
.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,
.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);
platform_device_register(&asoc_dev);
return 0;
}
static void s3c2440_uda1341_exit(void)
{
platform_device_unregister(&asoc_dev);
}
module_init(s3c2440_uda1341_init);
module_exit(s3c2440_uda1341_exit);
MODULE_LICENSE("GPL");
1.cpu_dai
①构造一个snd_soc_dai_driver结构体变量s3c2440_i2s_dai,s3c2440_i2s_dai里面的函数是我们需要自己实现的关于硬件的操作函数。
static int s3c2440_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
/* 根据params设置IIS控制器 */
/* 配置GPIO用于IIS */
...
return 0;
}
static int s3c2440_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
/* 硬件相关的操作 */
}
static const struct snd_soc_dai_ops s3c2440_i2s_dai_ops = {
.hw_params = s3c2440_i2s_hw_params,
.trigger = s3c2440_i2s_trigger,
};
#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 = {
.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,
};
②分别构造一个平台设备和平台驱动(它们的名字必须相同,且必须和machine部分的dai_lnk指定的名字相同),然后就调用它们的probe函数,
static struct platform_device s3c2440_iis_dev = {
.name = "s3c2440-iis",
.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",
}
};
③在这个probe函数里调用snd_soc_register_dai(&pdev->dev, &s3c2440_i2s_dai);将第①步构造的结构体变量放入链表dai_list,并把它命名为第②步提到的平台设备的名字。machine部分就是根据dai_link指定的名字,在dai_list中把s3c2440_i2s_dai找出来。
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;
}
至此,cpu_dai部分的驱动框架编写也已经完成,具体的硬件操作函数需要根据不同的硬件来编写。(dma、codec_dai和这个类似,下面将不再赘述,直接贴出代码示意)
2.dma
/* 参考 sound\soc\samsung\dma.c
*/
static struct snd_pcm_ops s3c2440_dma_ops = {
/* 需要我们自己编写的,关于硬件的函数 */
.open = s3c2440_dma_open,
.close = s3c2440_dma_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = s3c2440_dma_hw_params,
.prepare = s3c2440_dma_prepare,
.trigger = s3c2440_dma_trigger,
.pointer = s3c2440_dma_pointer,
};
static struct snd_soc_platform_driver s3c2440_dma_platform = {
/* 需要我们自己编写的,关于硬件的函数 */
.ops = &s3c2440_dma_ops,
.pcm_new = dma_new,
.pcm_free = dma_free_dma_buffers,
};
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)
{
return snd_soc_unregister_platform(&pdev->dev);
}
static void s3c2440_dma_release(struct device * dev)
{
}
static struct platform_device s3c2440_dma_dev = {
.name = "s3c2440-dma",
.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", //必须和dai_link里面的platform_name相同
}
};
static int s3c2440_dma_init(void)
{
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);
}
module_init(s3c2440_dma_init);
module_exit(s3c2440_dma_exit);
/* 参考 sound\soc\codecs\uda134x.c
*/
/* 1. 构造一个snd_soc_dai_driver
* 2. 构造一个snd_soc_codec_driver
* 3. 注册它们
*/
static struct snd_soc_codec_driver soc_codec_dev_uda1341 = {
/* 硬件相关的函数 */
.probe = uda1341_soc_probe,
.reg_cache_size = sizeof(uda1341_reg),
.reg_word_size = sizeof(u8),
.reg_cache_default = uda1341_reg,
.reg_cache_step = 1,
.read = uda1341_read_reg_cache,
.write = uda1341_write_reg, /* 写寄存器 */
};
static const struct snd_soc_dai_ops uda1341_dai_ops = {
/* 硬件相关的操作 */
.hw_params = uda1341_hw_params,
};
static struct snd_soc_dai_driver uda1341_dai = {
.name = "uda1341-iis", //必须和dai_link里面的codec_dai_name相同
/* 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 = &uda1341_dai_ops,
};
/* 通过注册平台设备、平台驱动来实现对snd_soc_register_codec的调用
*
*/
static void uda1341_dev_release(struct device * dev)
{
}
static int uda1341_probe(struct platform_device *pdev)
{
return snd_soc_register_codec(&pdev->dev,
&soc_codec_dev_uda1341, &uda1341_dai, 1);
}
static int uda1341_remove(struct platform_device *pdev)
{
return snd_soc_unregister_codec(&pdev->dev);
}
static struct platform_device uda1341_dev = {
.name = "uda1341-codec", //必须和dai_link里面的codec_name相同
.id = -1,
.dev = {
.release = uda1341_dev_release,
},
};
struct platform_driver uda1341_drv = {
.probe = uda1341_probe,
.remove = uda1341_remove,
.driver = {
.name = "uda1341-codec",
}
};
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);