前段时间把soc的audio模块都bring up起来了领导让整一个audio驱动的文档梳理pcm_open();pcm_read();pcm_write()调用流程,在公司内部作为培训课程讲解,让大家对芯片内部audio的运行机制有个大致的了解。今天有些闲暇时间想来把在公司讲解的过程在博客上用文字复现一遍,为了避免一些问题所以把流程图里以公司开头的前缀给划掉了。
先上pcm_open()总图:最后三个文件分别是soc的i2s驱动文件(platform),codec驱动文件(codec),machine文件。
pcm_write():
pcm_read():
一、应用程序调用tinyalsa库的pcm_open()函数为什么会调用到pcm *pcm_open()?
答:来看看声卡pcm设备节点:
[root@xxxx]/ttt# ls /dev/snd/pcmC* -l
crw-rw---- 1 root audio 116, 24 Jan 1 08:00 /dev/snd/pcmC0D0c
crw-rw---- 1 root audio 116, 48 Jan 1 08:00 /dev/snd/pcmC1D0p
Asoc本质是字符设备驱动程序主设备号是116,次设备号在注册时随机分配,应用层的open函数会调用到字符设备驱动的chrdev_open();看下面的代码段就可以理解了。
char_dev.c
/*
* Called every time a character special file is opened
* 应用层open每一个字符设备都会执行这个函数,也就是这个函数是所以字符设备的入口函数
*/
static int chrdev_open(struct inode *inode, struct file *filp)
{
fops = fops_get(p->ops);//获得file_operations *fops
replace_fops(filp, fops);//替换file_operations
ret = filp->f_op->open(inode, filp);//调用到具体节点对应的open函数。Asoc就是snd_open
}
sound.c:
static const struct file_operations snd_fops =
{//顶层 file_operations。实现转换作用
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
//alsa实际是个字符设备驱动 主设备号是#define CONFIG_SND_MAJOR 116
static int __init alsa_sound_init(void) {
if (register_chrdev(major, "alsa", &snd_fops)) {
pr_err("ALSA core: unable to register native major device number %d\n", major);
return -EIO;
}
}
二、如何调用到snd_pcm_playback_open函数?
答:
驱动在注册声卡的时候会调用snd_register_device()和snd_pcm_dev_register(struct snd_device *device)
/**
* snd_register_device - Register the ALSA device file for the card
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
* @card: the card instance
* @dev: the device index
* @f_ops: the file operations
* @private_data: user pointer for f_ops->open()
* @device: the device to register
*
* Registers an ALSA device file for the given card.
* The operators have to be set in reg parameter.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_register_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data, struct device *device)
{
int minor;
minor = snd_find_free_minor(type, card, dev);//次设备号
device->devt = MKDEV(major, minor);//
snd_minors[minor] = preg;//全局数组
}
const struct file_operations snd_pcm_f_ops[2] = {
{//这个是tinypaly时候调用到的函数入口
.owner = THIS_MODULE,
.write = snd_pcm_write,
.write_iter = snd_pcm_writev,
.open = snd_pcm_playback_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_playback_poll,
.unlocked_ioctl = snd_pcm_playback_ioctl, //触发调用 snd_pcm_hw_params
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
},
{//这个是tinycap的
.owner = THIS_MODULE,
.read = snd_pcm_read,
.read_iter = snd_pcm_readv,
.open = snd_pcm_capture_open,
.release = snd_pcm_release,
.llseek = no_llseek,
.poll = snd_pcm_capture_poll,
.unlocked_ioctl = snd_pcm_capture_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};
static int snd_pcm_dev_register(struct snd_device *device)
{
/* register pcm */
err = snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm, //这里注册进来
&pcm->streams[cidx].dev);
}
三、snd_pcm_attach_substream如何调用到soc_pcm_open()?
答:声卡注册的时候会执行soc_new_pcm()
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {//动态的pcm
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_params = dpcm_fe_dai_hw_params;
rtd->ops.prepare = dpcm_fe_dai_prepare;
rtd->ops.trigger = dpcm_fe_dai_trigger;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_fe_dai_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
} else {//走的是这里
rtd->ops.open = soc_pcm_open;
rtd->ops.hw_params = soc_pcm_hw_params;
rtd->ops.prepare = soc_pcm_prepare;
rtd->ops.trigger = soc_pcm_trigger;
rtd->ops.hw_free = soc_pcm_hw_free;
rtd->ops.close = soc_pcm_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
}
}
四、总结:
通过观察开头的三张时序图,可以发现前面的一系列调用依次是 sound.c;pcm_native.c;pcm_lib.c; soc-pcm.c,这几个文件不管你的soc是mtk还是其它公司都是通用的这部分是alsa的core代码。最终都是通过soc_pcm.c里的soc_pcm_xxx()如soc_pcm_open()触发调用的ASOC的machine,platform,codec三大部分对应结构体里的成员函数,这些成员函数才是正真操作soc和codec(内部或外部)的i2s,dma,中断相关的寄存器实现播放录音功能的。
关于ASOC的整体架构,和声卡注册初始化流程后面领导有要求讲解在说吧。
看看我们公司soc内部codec部分的结构体情况:
palatform部分
static struct snd_soc_dai_ops xxx_i2s_dai_ops = {
.startup = xxx_i2s_startup,
.shutdown = xxx_i2s_shutdown,
.hw_params = xxx_i2s_hw_params,
.prepare = xxx_i2s_prepare,
.trigger = xxx_i2s_trigger,
.set_fmt = xxx_i2s_set_fmt,
.set_tdm_slot = xxx_i2s_set_tdm_slot,
};
///codec部分///
static const struct snd_soc_dai_ops xxxdac_dai_ops = {
.hw_params = xxxdac_hw_params,
.set_fmt = xxxdac_set_dai_fmt,
.startup = xxxdac_startup,
.shutdown = xxxdac_shutdown,
.trigger = xxxdac_trigger,
};
static struct snd_soc_dai_driver xxxdac_dai = {
.name = "xxxdac",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &xxxdac_dai_ops,
};
/machine部分
static struct snd_soc_ops xxx_dac_ops = {
.hw_params = xxx_dac_hw_params,
};
static struct snd_soc_dai_link xxx_dac_dai = {
.name = "xxx-i2s-dac",
.stream_name = "xxx-dac",
.cpu_dai_name = "xxx0000.i2s",
.codec_dai_name = "xxxdac",
.platform_name = "xxx0000.i2s",
.codec_name = "xxxa000.dac",
.ops = &xxx_dac_ops,
.init = xxx_dac_codec_init,
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_IB_IF
| SND_SOC_DAIFMT_CBS_CFS,
};
soc-pcm.c函数集合: