韦东山嵌入式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 
};
2. 注册它
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 声卡驱动》

posted @ 2020-02-18 15:39  normalmanzhao2003  阅读(495)  评论(0编辑  收藏  举报
levels of contents