Alsa音频子系统Codec---al5623.c内核代码框架分析

简介: 驱动代码位于: sound/soc/codec/alc5623.c随便找个linux内核都会有。1、首先进行i2c总线驱动加载在:static int __init alc5623_modinit(void)在该函数中: i2c_add_driver(&alc5623_i2c_driver)...
驱动代码位于: sound/soc/codec/alc5623.c

随便找个linux内核都会有。

1、首先进行i2c总线驱动加载在:

static int __init alc5623_modinit(void)
在该函数中: 
i2c_add_driver(&alc5623_i2c_driver);
alc5623_i2c_driver是一个结构体变量,并且已经被初始化,我们来看看它做了什么?

static struct i2c_driver alc5623_i2c_driver = {
	.driver = {
		.name = "alc562x-codec",  //这是要注册的驱动的名字
		.owner = THIS_MODULE,     //该驱动属于哪个模块
	},
	.probe = alc5623_i2c_probe,   				//驱动执行的probe函数,也就是说,驱动从这个函数开始加载相关的操作,类似第一个步骤里的__init
	.remove =  __devexit_p(alc5623_i2c_remove),	//驱动模块删除函数
	.id_table = alc5623_i2c_table,				//对应的id_table--->其实就是该驱动支持的芯片类型。
};

2、i2c_driver结构体.
//这就是将该设备相关的平台注册到i2c总线上,该结构体变量已经做了初始化的操作.因为音频子系统alsa codec是与平台无关的,故可以这么来实现。

(1)id_table
我们先来看看id_table:
这是一个结构体数组,分别支持的音频芯片是alc5621,alc5622,alc5623---->对应的芯片ID就是0x21、0x22、0x23

static const struct i2c_device_id alc5623_i2c_table[] = {
	{"alc5621", 0x21},
	{"alc5622", 0x22},
	{"alc5623", 0x23},
	{}
};
(2)alc5623_i2c_remove
接下来看看该结构体中__devexit_p(alc5623_i2c_remove)的alc5623_i2c_remove函数:
这里实现很简单:
static int alc5623_i2c_remove(struct i2c_client *client)
{	
	//通过客户端传回来的结构体指针变量--->获取i2c客户端的数据,因为这个驱动是i2c驱动。
	struct alc5623_priv *alc5623 = i2c_get_clientdata(client);
	//注销codec平台相关的设备
	snd_soc_unregister_codec(&client->dev);
	//释放内存
	kfree(alc5623);
	return 0;
}
(3)alc5623_i2c_probe
来看看我们驱动在probe函数的实现。
以下就是probe函数,作为一个支持i2c协议的芯片平台
static int alc5623_i2c_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
	//定义一个获取芯片数据的指针
	struct alc5623_platform_data *pdata;
	//
	struct alc5623_priv *alc5623;
	int ret, vid1, vid2;
	//通过i2c总线获取芯片供应商的第一个ID--->下面就是这个宏的相关定义
	//#define ALC5623_VENDOR_ID1			0x7C
	vid1 = i2c_smbus_read_word_data(client, ALC5623_VENDOR_ID1);
	//出错处理
	if (vid1 < 0) {
		dev_err(&client->dev, "failed to read I2C\n");
		return -EIO;
	}
	//将ID的高低八位互换再组合
	vid1 = ((vid1 & 0xff) << 8) | (vid1 >> 8);
	//通过i2c总线获取芯片供应商的第二个ID--->下面就是这个宏的相关定义---->通过芯片手册查阅到
	//#define ALC5623_VENDOR_ID2			0x7E
	vid2 = i2c_smbus_read_byte_data(client, ALC5623_VENDOR_ID2);
	if (vid2 < 0) {
		dev_err(&client->dev, "failed to read I2C\n");
		return -EIO;
	}
	//出错处理
	if ((vid1 != 0x10ec) || (vid2 != id->driver_data)) {
		dev_err(&client->dev, "unknown or wrong codec\n");
		dev_err(&client->dev, "Expected %x:%lx, got %x:%x\n",
				0x10ec, id->driver_data,
				vid1, vid2);
		return -ENODEV;
	}
	//通过以上的操作组合我们可以得知我们需要操作的芯片是alc56xxx
	dev_dbg(&client->dev, "Found codec id : alc56%02x\n", vid2);
	//开始对这个设备分配内核空间,根据GFP_KERNEL来分配
	alc5623 = kzalloc(sizeof(struct alc5623_priv), GFP_KERNEL);
	//如果分配为空,返回错误码
	if (alc5623 == NULL)
		return -ENOMEM;
	//获取数据
	pdata = client->dev.platform_data;
	if (pdata) {
		alc5623->add_ctrl = pdata->add_ctrl;
		alc5623->jack_det_ctrl = pdata->jack_det_ctrl;
	}
	//这里就获取到了我们要操作的芯片的id
	alc5623->id = vid2;
	
	//根据id号会给alc5623_dai.name赋值,dai就是一些音频组建相关的一些操作
	switch (alc5623->id) {
	case 0x21:
		alc5623_dai.name = "alc5621-hifi";
		break;
	case 0x22:
		alc5623_dai.name = "alc5622-hifi";
		break;
	case 0x23:
		alc5623_dai.name = "alc5623-hifi";
		break;
	default:
		kfree(alc5623);
		return -EINVAL;
	}
	//设置客户端数据,设置对应的芯片设备
	i2c_set_clientdata(client, alc5623);
	//设置控制数据
	alc5623->control_data = client;
	//设置控制类型
	alc5623->control_type = SND_SOC_I2C;
	mutex_init(&alc5623->mutex);
	//将设备注册到codec平台
	//到这里,芯片平台的注册工作就完成了,我们可以根据相关注册的soc_codec_device_alc5623和alc5623_dai(数字音频接口)提供的函数通过上层的系统调用来操作音频设备
	//接下来看第三部分,我们来分析&soc_codec_device_alc5623, &alc5623_dai这两个究竟做了什么操作。
	ret =  snd_soc_register_codec(&client->dev,
		&soc_codec_device_alc5623, &alc5623_dai, 1);
	if (ret != 0) {
		dev_err(&client->dev, "Failed to register codec: %d\n", ret);
		kfree(alc5623);
	}
	return ret;
}
3、soc_codec_device_alc5623和alc5623_dai
(1)alc5623_dai

我们先来看看数字音频接口做了什么?
以下具体的就是就是音频接口的操作,根据PCM音频和playback来进行设置
同时,也加载了alc5623_dai_ops这个操作集合

static struct snd_soc_dai_driver alc5623_dai = {
	.name = "alc5623-hifi",
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rate_min =	8000,
		.rate_max =	48000,
		.rates = SNDRV_PCM_RATE_8000_48000,
		.formats = ALC5623_FORMATS,},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rate_min =	8000,
		.rate_max =	48000,
		.rates = SNDRV_PCM_RATE_8000_48000,
		.formats = ALC5623_FORMATS,},

	.ops = &alc5623_dai_ops,
};
(1)alc5623_dai_ops操作集合的实现
以下这些得找具体的调音频的工程师才知道怎么设置相应的操作,具体我也不是特别懂,有兴趣可以看看函数的实现,对着数据手册的寄存器一个个看
static struct snd_soc_dai_ops alc5623_dai_ops = {
		.hw_params = alc5623_pcm_hw_params,
		.digital_mute = alc5623_mute,
		.set_fmt = alc5623_set_dai_fmt,
		.set_sysclk = alc5623_set_dai_sysclk,
		.set_pll = alc5623_set_dai_pll,
};
(2)soc_codec_device_alc5623
static struct snd_soc_codec_driver soc_codec_device_alc5623 = {
	.probe = alc5623_probe,
	.remove = alc5623_remove,
	.suspend = alc5623_suspend,   //  1 
	.resume = alc5623_resume,     //  2  1和2是电源管理的回调函数,分别在声卡加载和卸载的时候被调用
	.set_bias_level = alc5623_set_bias_level, //设置偏置电压配置函数
	.reg_cache_size = ALC5623_VENDOR_ID2+2,
	.reg_word_size = sizeof(u16),
	.reg_cache_step = 2,
};
<1> alc5623_probe函数分析

//这部分我们不详细进行分析,拿出最主要的东西来讲,比如后面的snd_soc_add_codec_controls,主要说说它的作用
static int alc5623_probe(struct snd_soc_codec *codec)
{
	struct alc5623_priv *alc5623 = snd_soc_codec_get_drvdata(codec); //获取数据
	struct snd_soc_dapm_context *dapm = &codec->dapm;                //dapm相当于音频的一个组件
	int ret;
	
	ret = snd_soc_codec_set_cache_io(codec, 8, 16, alc5623->control_type);
	if (ret < 0) {
		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
		return ret;
	}

	alc5623_reset(codec);  //复位芯片
	alc5623_fill_cache(codec);

	/* power on device */
	alc5623_set_bias_level(codec, SND_SOC_BIAS_STANDBY);

	if (alc5623->add_ctrl) {
		snd_soc_write(codec, ALC5623_ADD_CTRL_REG,
				alc5623->add_ctrl);
	}

	if (alc5623->jack_det_ctrl) {
		snd_soc_write(codec, ALC5623_JACK_DET_CTRL,
				alc5623->jack_det_ctrl);
	}
	//根据芯片的id执行对应的snd_soc_add_codec_controls,这个controls就是给上层提供相应的操作接口
	//比如调节音量,耳机模式等等。
	//这里是alc5621,我们找到了这个controls的集合
	switch (alc5623->id) {
	case 0x21:
		snd_soc_add_codec_controls(codec, alc5621_vol_snd_controls,
			ARRAY_SIZE(alc5621_vol_snd_controls));
		break;
	case 0x22:
		snd_soc_add_codec_controls(codec, alc5622_vol_snd_controls,
			ARRAY_SIZE(alc5622_vol_snd_controls));
		break;
	case 0x23:
		snd_soc_add_codec_controls(codec, alc5623_vol_snd_controls,
			ARRAY_SIZE(alc5623_vol_snd_controls));
		break;
	default:
		return -EINVAL;
	}

	snd_soc_add_codec_controls(codec, alc5623_snd_controls,
			ARRAY_SIZE(alc5623_snd_controls));

	snd_soc_dapm_new_controls(dapm, alc5623_dapm_widgets,
					ARRAY_SIZE(alc5623_dapm_widgets));

	/* set up audio path interconnects */
	snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));

	switch (alc5623->id) {
	case 0x21:
	case 0x22:
		snd_soc_dapm_new_controls(dapm, alc5623_dapm_amp_widgets,
					ARRAY_SIZE(alc5623_dapm_amp_widgets));
		snd_soc_dapm_add_routes(dapm, intercon_amp_spk,
					ARRAY_SIZE(intercon_amp_spk));
		break;
	case 0x23:
		snd_soc_dapm_add_routes(dapm, intercon_spk,
					ARRAY_SIZE(intercon_spk));
		break;
	default:
		return -EINVAL;
	}

	return ret;
}
上面看到了snd_soc_add_codec_controls这个函数,具体做了什么事情,就是将要给上层控制的命令写到这个结构体数组里
上面发相应的命令,比如Speaker Playback Volume,后面再加相应的参数就可以实现音量的控制,其它也是类似的,可以参考手册去添加。

/*
	static const struct snd_kcontrol_new alc5621_vol_snd_controls[] = {
	SOC_DOUBLE_TLV("Speaker Playback Volume",
			ALC5623_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv),
	SOC_DOUBLE("Speaker Playback Switch",
			ALC5623_SPK_OUT_VOL, 15, 7, 1, 1),
	SOC_DOUBLE_TLV("Headphone Playback Volume",
			ALC5623_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv),
	SOC_DOUBLE("Headphone Playback Switch",
			ALC5623_HP_OUT_VOL, 15, 7, 1, 1),
	};
*/
而snd_soc_dapm_new_controls这个函数又提供什么功能呢?它其实是一个动态音频电源管理,主要是描述dapm的信息,同时也处理一些dapm的事件。
相关的结构体也是和上面的类似的,大家可以对着数据手册去参考一下。


至此,整个alc5623的芯片的框架就已经剖析完毕。


那么,我们如何来测试这个音频是否可以用呢?在linux终端提供了amixer和aplay这样的命令可以去操作,当然也可以去用alsa库,调函数写一个C程序去测试。
这里我们主要来说说amixer和aplay的使用。到开发板的linux终端。


1、用以下命令查看amixer支持哪些命令:
amixer --help


2、查看驱动里面已经提供了多少接口可以去操作:
amixer contents
例如:
显示:numid=5,iface=MIXER,name='Line In Volume'


3、获取当前参数的值---->Speaker Playback Volume这个就是刚刚我们看到的controls组件提供的控制参数
amixer cget  numid=1,iface=MIXER,name='Speaker Playback Volume'
numid=1,iface=MIXER,name='Speaker Playback Volume'
  ; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
  : values=20,20
  | dBscale-min=-46.50dB,step=1.50dB,mute=0
  
从这上面得知的结果是声音的值最小为0,最大为31


4、设置某个参数
比如就设置上面音量的大小:
amixer cset  numid=1,iface=MIXER,name='Speaker Playback Volume'  25(这个值就是我们设置调节音量的值)


alc5623.c和alc相关的数据手册网上有,搜下来对着框架分析就可以看懂了。





目录
相关文章
|
10月前
|
编解码 Linux
Linux MIPI DSI驱动调试笔记-设备树DCS格式序列之配置LCD初始化代码(二)
Linux MIPI DSI驱动调试笔记-设备树DCS格式序列之配置LCD初始化代码(二)
1017 0
|
Ubuntu Linux Windows
Linux下音频开发: 读取声卡PCM数据保存到文件(alsa-lib库)
Linux下音频开发: 读取声卡PCM数据保存到文件(alsa-lib库)
1273 0
Linux下音频开发: 读取声卡PCM数据保存到文件(alsa-lib库)
|
2月前
|
Perl
【ZYNQ】SPI 简介及 EMIO 模拟 SPI 驱动示例
【ZYNQ】SPI 简介及 EMIO 模拟 SPI 驱动示例
262 0
|
Linux API vr&ar
让终端支持播放mp3,移植mp3解码库libmad和madplay到嵌入式linux
让终端支持播放mp3,移植mp3解码库libmad和madplay到嵌入式linux
|
IDE 编译器 开发工具
【NXP】LPC55S69-RT-Thread Micropython移植日志
【NXP】LPC55S69-RT-Thread Micropython移植日志
146 0
|
Linux API 开发者
Linux驱动分析之RTC框架
当Linux内核启动时,它会从RTC中读取时间与日期,作为基准值。然后通过软件来维护系统时间和日期。Linux系统中提供了RTC核心层,对于驱动开发者而言,操作起来就变得很简单了。我们来看看整体框架。
SPI设备标准驱动源码分析(linux kernel 5.18)
SPI设备标准驱动源码分析(linux kernel 5.18)
SPI设备标准驱动源码分析(linux kernel 5.18)
|
Linux 内存技术
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)下
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)下
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)下
|
编解码 Linux API
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)上
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)上
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)上
|
编解码 Linux 索引
Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)下
Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)下
Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)下