嵌入式Linux驱动学习【12】—— 声卡

1 硬件

  硬件原理图
在这里插入图片描述
  与mini2440连接示意图
在这里插入图片描述

2 音频基础

(1)采样频率(sample rate):录音设备在一秒钟内对声音信号的采样次数。
(2)通道数(channel):分为单声道mono;立体声stereo。两个声道,说明只有左右两边有声音传过来。
(3)比特率(bitrate):也叫码率。针对编码格式,表示压缩编码后每秒的音频数据量大小。计算公式:比特率 = 采样率 x 采样精度 x 声道数。单位kbps。
(4)量化位数:指每个采样点里传输的数字信号次数,一般有8位、16位、32位等。S3C2440只支持8位和16位。

3 接口

(1)IIS接口
MCLK:主机为编解码芯片提供的系统同步时钟 ( Master/system clock input ) ,在2440中,一般设置为256fs,或者384fs
BCLK:编解码芯片提供的串行时钟信号 ( Audio bit clock output ) ,也就是量化位深,比如I2SIRCK=44.1khz,量化位为32位,则BCLK=44.1khz322
I2SLRCK:采样频率信号,当为低电平时是采样的是左声道信号,为高电平是右声道信号,常见有44.1Khz(1fs)
I2SDI:ADC数据输入
I2SDO:DAC数据输出
(2)IIS时序
LRCK就是采样频率:
  LRCK为低——传输的采样数据是左声道;
  LRCK为高——传输的采样数据是右声道。
在这里插入图片描述
(3)L3接口
L3MODE:0——地址模式;1——数据模式。
L3DATA:地址/数据。
L3CLOCK:时钟。
地址模式:bit7-bit2为地址,bit1-bit0为模式。

bit7-bit2bit1-bit0
000101模式(控制音量、位宽等)

4 驱动分析

4.1 框架

  ASoC(ALSA System on Chip)是 ALSA 在 SoC 方面的发展和演变,它在本质上仍然属于ALSA,但是在 ALSA 架构基础上对 CPU 相关的代码和 Codec 相关的代码进行了分离。其原因是,采用传统 ALSA 架构的情况下,同一型号的 Codec 工作于不同的 CPU 时,需要不同的驱动,这不符合代码重用的要求。
在这里插入图片描述
ASoC 主要由 3 部分组成。
(1)Machine 是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。
(2)Platform 一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。
(3)Codec 字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对内部的寄存器进行控制。

4.2 源码

  分成三个部分:

Machine:
    S3c24xx_uda134x.c (sound\soc\s3c24xx)
    
Platform:
    S3c24xx-i2s.c (sound\soc\s3c24xx)

Codec:
    Uda134x.c (sound\soc\codecs)

4.2.1 结构体

  snd_soc_card代表着Machine驱动,snd_soc_platform则代表着Platform驱动,snd_soc_codec则代表了Codec驱动,而snd_soc_dai_link则负责连接Platform和Codec。linux使用snd_soc_codec进行所有codec设备的抽象,而将codec的驱动抽象为snd_soc_codec_driver结构。

4.2.2 Machine

(1)设备
  /arch/arm/mach-s3c2440/mach-mini2440.c

static struct s3c24xx_uda134x_platform_data mini2440_audio_pins __initdata = {
	.l3_clk = S3C2410_GPB(4),
	.l3_mode = S3C2410_GPB(2),
	.l3_data = S3C2410_GPB(3),
	.model = UDA134X_UDA1341
};

static struct platform_device mini2440_audio __initdata = {
	.name		= "s3c24xx_uda134x",
	.id		= 0,
	.dev		= {
		.platform_data	= &mini2440_audio_pins,
	},
};

static struct platform_device *mini2440_devices[] __initdata = {
	...
	&s3c_device_iis,
	&mini2440_audio,
	...
};

(2)驱动
  初始化,在/sound/soc/s3c24xx/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);
}

  匹配后调用probe。s3c24xx_uda134x_probe首先设置L3相关引脚,然后添加设备platform_device_add(s3c24xx_uda134x_snd_device)。

static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
	...

	s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
	...

	platform_set_drvdata(s3c24xx_uda134x_snd_device,
			     &s3c24xx_uda134x_snd_devdata);
	s3c24xx_uda134x_snd_devdata.dev = &s3c24xx_uda134x_snd_device->dev;
	ret = platform_device_add(s3c24xx_uda134x_snd_device);
	...
}

  添加了名称是"soc-audio"的设备,找到相同名称的driver,在sound/soc/soc-core.c中,snd_soc_init注册驱动。

static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.owner		= THIS_MODULE,
		.pm		= &soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};

static int __init snd_soc_init(void)
{
	...
	return platform_driver_register(&soc_driver);
}

  与驱动匹配后调用probe。

static int soc_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
	struct snd_soc_card *card = socdev->card;

	card->socdev = socdev;

	card->dev = &pdev->dev;
	ret = snd_soc_register_card(card);
	if (ret != 0) {
		dev_err(&pdev->dev, "Failed to register card\n");
		return ret;
	}

	return 0;
}

  platform_get_drvdata与s3c24xx_uda134x_probe中的platform_set_drvdata相对应,即得到s3c24xx_uda134x_snd_devdata。
  相关结构体。


static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
	.name = "UDA134X",
	.stream_name = "UDA134X",
	.codec_dai = &uda134x_dai,
	.cpu_dai = &s3c24xx_i2s_dai,
	.ops = &s3c24xx_uda134x_ops,
};
//联系起声卡驱动各个模块
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
	.name = "S3C24XX_UDA134X",
	.platform = &s3c24xx_soc_platform,
	.dai_link = &s3c24xx_uda134x_dai_link,
	.num_links = 1,
};
//对整个声卡结构体的封装
static struct snd_soc_device s3c24xx_uda134x_snd_devdata = {
	.card = &snd_soc_s3c24xx_uda134x,
	.codec_dev = &soc_codec_dev_uda134x,
	.codec_data = &s3c24xx_uda134x,
};

  添加"soc-audio"设备,soc_probe–>snd_soc_register_card。

static int snd_soc_register_card(struct snd_soc_card *card)
{
	...
	list_add(&card->list, &card_list);//添加snd_soc_s3c24xx_uda134x
	snd_soc_instantiate_cards();
	...
}

  继续

static void snd_soc_instantiate_cards(void)
{
	struct snd_soc_card *card;
	list_for_each_entry(card, &card_list, list)
		snd_soc_instantiate_card(card);
}

  遍历card_list,调用snd_soc_instantiate_card。

static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
	
	list_for_each_entry(platform, &platform_list, list)//从platform_list中找与card->platform相同的
		if (card->platform == platform) {
			found = 1;
			break;
		}
	...
	
	
	if (!ac97)
		for (i = 0; i < card->num_links; i++) {
			found = 0;
			list_for_each_entry(dai, &dai_list, list)//从dai_list中找与card->dai_link[i].codec_dai相同的
				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;
			}
		}

	
	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);//即s3c24xx_i2s_probe
			if (ret < 0)
				goto cpu_dai_err;
		}
	}

	if (codec_dev->probe) {
		ret = codec_dev->probe(pdev);//即uda134x_soc_probe
		if (ret < 0)
			goto cpu_dai_err;
	}

	if (platform->probe) {
		ret = platform->probe(pdev); //没有
		if (ret < 0)
			goto platform_err;
	}

	...
}

  在probe函数内部会把新增的声卡snd_soc_s3c24xx_uda134x加入card链表card_list,然后对声卡链表的每个节点,进行platform(码流传输模块),dai_list(数字音频接口)比对,如果发现相同,则调用各自的probe函数(这里card、platform没有probe函数)。

4.2.3 platform

  上面调用cpu_dai->probe(pdev, cpu_dai),.cpu_dai = &s3c24xx_i2s_dai。playback用于播放,capture用于录音,最大/最少所支持的通道channels_max和channels_min,采样频率rates,支持的格式formats。

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 int __init s3c24xx_i2s_init(void)
{
	return snd_soc_register_dai(&s3c24xx_i2s_dai);
}

  在s3c24xx_i2s_probe中初始化了i2s接口。

4.2.4 codec

  在snd_soc_instantiate_card函数中调用codec_dev->probe(pdev),即uda134x_soc_probe

uda134x_soc_probe --->
	snd_soc_new_pcms ---> //注册pcm
		snd_card_create --->
			snd_ctl_create   //用户空间开辟了一个控制音频的接口
		soc_new_pcm      //生成音频控制接口,录音/播音

5 测试

(1)内核配置

-> Device Drivers
  -> Sound card support
    -> Advanced Linux Sound Architecture
        -> ALSA for Soc audio support
        <*> Soc Audio for the Samsung S3C24XX chips
        <*> Soc I2S Audio support UDA134X wired to a S3C24XX

(2)播放
用madplay播放测试。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值