学习笔记 --- LINUX ASoC声卡驱动接口分析

2023-12-12 00:59

本文主要是介绍学习笔记 --- LINUX ASoC声卡驱动接口分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        ASoC(ALSA System on Chip)是ALSA在SoC方面的发展和演变,它在本质上仍然属于ALSA,但是在ALSA架构的基础上对CPU相关的代码和CODEC相关的代码进行了分离。其原因是,采用传统ALSA架构的情况下,同一型号的CODEC工作于不同的CPU时,需要不同的驱动,这不符合代码重用的要求。

ASoC驱动有以下三部分组成:

(1)       CODEC驱动:由内核源代码sound/soc/codecs/uda134x.c实现

(2)       平台驱动:由内核源代码sound/soc/s3c24xx/s3c24xx-i2s.c实现CPU端的DAI驱动,由sound/soc/s3c24xx/s3c24xx_pcm.c实现CPU端的DMA驱动

(3)       板驱动:由内核源代码sound/soc/s3c24xx/s3c24xx_uda134x.c实现,它将第一部分和第二部分进行绑定。

在以上三部分之上的是ASoC核心层,由内核源代码中的sound/soc/soc-core.c实现,查看其源代码发现它完全是一个传统的ALSA驱动。

在以上3部分中,12基本上都可以仍然是通用的驱动了,也就是说,CODEC驱动认为自己可以连接任意CPU,而CPUI2SPCM、或AC’97接口对应的平台驱动则认为自己可以连接任意符合接口类型的CODEC,只有3是不通用的,由特性的电路板上具体的CPUCODEC确定,因此它很像一个插座,上面插上了CODEC和平台这两个插头。

先来分析上层入口:

static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{int ret;printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n");s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;if (s3c24xx_uda134x_l3_pins == NULL) {printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: ""unable to find platform data\n");return -ENODEV;}s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;//设置L3总线的接口都为输出if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,"data") < 0)return -EBUSY;if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,"clk") < 0) {gpio_free(s3c24xx_uda134x_l3_pins->l3_data);return -EBUSY;}if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,"mode") < 0) {gpio_free(s3c24xx_uda134x_l3_pins->l3_data);gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);return -EBUSY;}s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);  //分配一个设备,名字为soc-audio,这个名字会匹配这个设备的驱动if (!s3c24xx_uda134x_snd_device) {printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: ""Unable to register\n");return -ENOMEM;}platform_set_drvdata(s3c24xx_uda134x_snd_device,&s3c24xx_uda134x_snd_devdata);   //设置ASoC驱动所需数据s3c24xx_uda134x_snd_devdata.dev = &s3c24xx_uda134x_snd_device->dev;ret = platform_device_add(s3c24xx_uda134x_snd_device);  //添加一个ASoC设备,将会匹配驱动 soc-audioif (ret) { printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n");platform_device_put(s3c24xx_uda134x_snd_device);}return ret;
}
上面设置好一个设备的数据:s3c24xx_uda134x_snd_devdata

static struct snd_soc_device s3c24xx_uda134x_snd_devdata = {.card = &snd_soc_s3c24xx_uda134x,     //板级= codec(L3) +CPU(IIS,DMA) 操作.codec_dev = &soc_codec_dev_uda134x,     //codec 级.codec_data = &s3c24xx_uda134x,     //codec 实现方法
};

然后添加这个设备,这个设备名:soc-audio,对应内核源代码中的sound/soc/soc-core.c驱动名称:

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

匹配完后,调用驱动中的probe函数:soc_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;/* Bodge while we push things out of socdev */card->socdev = socdev;/* Bodge while we unpick instantiation */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;
}
这个函数开始就获得添加设备的时候传进来的数据: snd_soc_device

然后调用:snd_soc_register_card 函数注册板级,可以知道音频驱动以板级为入口,再ASoC驱动管理CODEC与平台控制。

这个函数会调用snd_soc_instantiate_cards->snd_soc_instantiate_card:

static void snd_soc_instantiate_card(struct snd_soc_card *card)
{struct platform_device *pdev = container_of(card->dev,struct platform_device,dev);struct snd_soc_codec_device *codec_dev = card->socdev->codec_dev;struct snd_soc_platform *platform;struct snd_soc_dai *dai;int i, found, ret, ac97;if (card->instantiated)return;found = 0;list_for_each_entry(platform, &platform_list, list)if (card->platform == platform) {found = 1;break;}if (!found) {dev_dbg(card->dev, "Platform %s not registered\n",card->platform->name);return;}ac97 = 0;for (i = 0; i < card->num_links; i++) {found = 0;list_for_each_entry(dai, &dai_list, list)if (card->dai_link[i].cpu_dai == dai) {found = 1;break;}if (!found) {dev_dbg(card->dev, "DAI %s not registered\n",card->dai_link[i].cpu_dai->name);return;}if (card->dai_link[i].cpu_dai->ac97_control)ac97 = 1;}for (i = 0; i < card->num_links; i++) {if (!card->dai_link[i].codec_dai->ops)card->dai_link[i].codec_dai->ops = &null_dai_ops;}/* If we have AC97 in the system then don't wait for the* codec.  This will need revisiting if we have to handle* systems with mixed AC97 and non-AC97 parts.  Only check for* DAIs currently; we can't do this per link since some AC97* codecs have non-AC97 DAIs.*/if (!ac97)for (i = 0; i < card->num_links; i++) {found = 0;list_for_each_entry(dai, &dai_list, list)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;}}/* Note that we do not current check for codec components */dev_dbg(card->dev, "All components present, instantiating\n");/* Found everything, bring it up */if (card->probe) {ret = card->probe(pdev);    // 1 执行板级安装if (ret < 0)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); // 2 执行CPU dai安装if (ret < 0)goto cpu_dai_err;}}if (codec_dev->probe) {ret = codec_dev->probe(pdev);      // 3 执行codec级安装if (ret < 0)goto cpu_dai_err;}if (platform->probe) {ret = platform->probe(pdev);    // 4 执行平台级安装 if (ret < 0)goto platform_err;}/* DAPM stream work */INIT_DELAYED_WORK(&card->delayed_work, close_delayed_work);
#ifdef CONFIG_PM/* deferred resume work */INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endifcard->instantiated = 1;return;platform_err:if (codec_dev->remove)codec_dev->remove(pdev);cpu_dai_err:for (i--; i >= 0; i--) {struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;if (cpu_dai->remove)cpu_dai->remove(pdev, cpu_dai);}if (card->remove)card->remove(pdev);
}
这个函数调用各个部分的probe函数执行。到这里就交给ASoC核心处理了。这些probe处理,都是通过之前添加设备的时候传进来的 snd_soc_device数据:

/* SoC Device - the audio subsystem */
struct snd_soc_device {struct device *dev;struct snd_soc_card *card;struct snd_soc_codec_device *codec_dev;void *codec_data;
};
所以我们的驱动工作就主要是完成这个数据结构。

这个结构体是对ASoC设备的整体封装,包括了:

1 板级用的snd_soc_card

CODEC用的snd_soc_codec_device

3 codec_data(平台给CODEC用的数据)

看下具体设置:

1 snd_coc_card

static struct snd_soc_card snd_soc_s3c24xx_uda134x = {.name = "S3C24XX_UDA134X",.platform = &s3c24xx_soc_platform,  //CPU的DMA操作音频数据在IIS的传送.dai_link = &s3c24xx_uda134x_dai_link,  //CPU与CODEC DAI集合.num_links = 1,
};

struct snd_soc_platform s3c24xx_soc_platform = {.name		= "s3c24xx-audio",.pcm_ops 	= &s3c24xx_pcm_ops, //DMA操作.pcm_new	= s3c24xx_pcm_new, //分配DMA内存.pcm_free	= s3c24xx_pcm_free_dma_buffers, //释放DMA内存
};

static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {.name = "UDA134X",.stream_name = "UDA134X",.codec_dai = &uda134x_dai,      //DODEC DAI ---- uda134x 的L3总线控制操作(决定L3发什么).cpu_dai = &s3c24xx_i2s_dai,   //CPU DAI -----CPU 的IIS总线配置.ops = &s3c24xx_uda134x_ops, 
};

1.1 DODEC DAI  :

struct snd_soc_dai uda134x_dai = {.name = "UDA134X",/* 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 = &uda134x_dai_ops,
};

1.1.1 对于DODEC  L3的操作集uda134x_dai_ops:

static struct snd_soc_dai_ops uda134x_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,
};

1.2 CPU DAI  :

struct snd_soc_dai s3c24xx_i2s_dai = {.name = "s3c24xx-i2s",.id = 0,.probe = s3c24xx_i2s_probe, //完成CPU上I2S接口的初始化.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,
};

1.2.1 对于CPU IIS 的操作集s3c24xx_i2s_dai_ops:

static struct snd_soc_dai_ops s3c24xx_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_sysclk,
};

snd_soc_codec_device
struct snd_soc_codec_device soc_codec_dev_uda134x = {.probe =        uda134x_soc_probe,  //初始化复位uda1341,装载指令操作结构uda1341_snd_controls.remove =       uda134x_soc_remove,.suspend =      uda134x_soc_suspend,.resume =       uda134x_soc_resume,
};
codec_data
static struct uda134x_platform_data s3c24xx_uda134x = {.l3 = {.setdat = setdat, //设置数据线电平.setclk = setclk, //设置CLK电平.setmode = setmode, //设置MODE电平.data_hold = 1,.data_setup = 1,.clock_high = 1,.mode_hold = 1,.mode = 1,.mode_setup = 1,},
};
这个结构体设置L3总线的发送数据方法。

综合以上,我们的工作就是完成snd_soc_device这个结构体,然后注册一个名为soc-audio的设备。内核一般都已经实现好了声卡驱动和平台驱动,我们只要修改声卡的控制口L3就可以了。

————————————————————————————————————————————————————————————————————————————


测试:
1. 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-uda1341.c
-> Device Drivers
  -> Sound
    -> Advanced Linux Sound Architecture
      -> Advanced Linux Sound Architecture
        -> System on Chip audio support
        <*> I2S of the Samsung S3C24XX chips


2. make uImage
   使用新内核启动


3. ls -l /dev/dsp /dev/mixer
4. 播放:
   在WINDOWS PC里找一个wav文件,放到开发板根文件系统里
   cat Windows.wav > /dev/dsp
5. 录音:
   cat /dev/dsp > sound.bin  
   然后对着麦克风说话
   ctrl+c退出
   cat sound.bin > /dev/dsp  // 就可以听到录下的声音


使用madplay测试声卡:
1. 解压:
tar xzf libid3tag-0.15.1b.tar.gz  // 库
tar xzf libmad-0.15.1b.tar.gz     // 库
tar xzf madplay-0.15.2b.tar.gz    // APP


2. 编译 libid3tag-0.15.1b
mkdir tmp
cd libid3tag-0.15.1b
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
make
make install


3. 编译 libmad-0.15.1b
cd libmad-0.15.1b
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
make
make install


4. 编译madplay
cd madplay-0.15.2b/
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp LDFLAGS="-L/work/drivers_and_test/21th_sound/app/tmp/lib" CFLAGS="-I /work/drivers_and_test/21th_sound/app/tmp/include"
make
make install


5. 把tmp/bin/*  tmp/lib/*so* 复制到根文件系统:


6. 把一个mp3文件复制到根文件系统


7. madplay --tty-control /1.mp3 
   播放过程中不断按小键盘的减号("-")会降低音量
             不断按小键盘的加号("+")会降低音量







这篇关于学习笔记 --- LINUX ASoC声卡驱动接口分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/482694

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss