smt-august-1-u2w
汇报分享
ASoC学习与upstream
ASoC–ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有一些局限性:
Codec驱动与SoC CPU的底层耦合过于紧密,这种不理想会导致代码的重复.
例如,仅是wm8731的驱动,当时Linux中有分别针对4个平台的驱动代码。
音频事件没有标准的方法来通知用户,例如耳机、麦克风的插拔和检测,这些事件在移动设备中是非常普通的,而且通常都需要特定于机器的代码进行重新对音频路劲进行配置。
当进行播放或录音时,驱动会让整个codec处于上电状态,这对于PC没问题,但对于移动设备来说,这意味着浪费大量的电量。同时也不支持通过改变过取样频率和偏置电流来达到省电的目的。
ASoC就是在这样的背景下诞生的,为了支持移动设备中的音频,ASoC将Codec驱动与SoC CPU解耦,通过标准ALSA接口向上提供统一的音频服务。这样,用户只需要安装ASoC驱动,就可以使用各种Codec,而不需要针对不同SoC平台编写不同驱动。同时,ASoC还提供了标准的音频事件通知机制,使得用户可以方便地进行音频配置。
ASoC的分层
想使用ASoC驱动框架编写驱动需要了解三个层次:
- Platform: 这一层是平台层,所有与平台(SoC)相关的代码都属于这一层。比如DAI,PCM等。
- Codec: 这一层是声卡层,一般来说,声卡厂家会提供该层的驱动。比如wm8731.
- Machine: 这一层属于胶水层,起到连接Platform和Codec的作用。比如指定CPU的DAI是哪一个,指定Codec的是哪一个,然后打包成一个声卡(Card)设备在系统中体现出来。
现代ASoC驱动的编写
DAI
无论什么时候,一个未被支持的设备想支持ASoC,首先需要编写DAI。
因为DAI是一个声卡设备的核心,是底层接口。
它决定了SoC和Codec之间的通信协议, 是最底层的支撑。
想实现DAI,就要了解dai_ops这个数据结构,这是一个操作接口,提供给上层。
static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { |
- probe: dai注册成功后会调用,负责一些初始化, 在k1中是设置了dma的参数和复位了i2s
- startup: 打开声卡设备时的调用,主要是电源/时钟等使能
- hw_params: 根据采样率,通道数等配置DAI硬件
- set_sysclk: 设置sysclk
- set_fmt: 配置数据格式
- trigger: 控制音频流的启动和停止, 暂停和恢复等
PCM
在原先,DMAengine还不够完善,一般是需要自己编写PCM的部分,这里的PCM不是指硬件PCM(Pulse-Code Modulation),而是说linux里面的数据结构如何传输。
或者有特殊需求的话,也可以自己编写。
在k1的ASoC驱动中,PCM的部分是自己实现的,因为涉及到了Capture无法单独启动和hdmi音频的问题。
后面经过调查,Capture是可以单独启动,并且hdmi音频基本能够不依赖自己写的PCM去实现。
所以现代ASoC的PCM一般都是通过devm_snd_dmaengine_pcm_register
去使用dmaengine提供的api传输,这套api内部会自动获取dts node的dma节点并申请dma通道去自动完成传输流程,省去很多的研发时间。
Codec
驱动由厂商提供,只需要开启config和编写dts node即可。不做过多介绍。
Card
上面介绍过Machine层就是一个胶水层,它将Platform和Codec连接起来,形成一个完整的声卡设备。
基本是一个通用操作,所以已经有现成的驱动实现了:simple-card.
想要使用该驱动,只需要将config开启,然后参考dt-binding去编写dts node即可。
这里给出一个例子:
sound_card: snd-card@1 { |
其中,每一个dai-link就是一个声卡设备,在dai-link中指定通信协议,frame-master和bitclock-master分别指定了音频数据和时钟的来源。Link0_cpu就是指定cpu的通信接口是哪一个。 link0_codec就是指定codec设备是哪一个,形成一个绑定关系。
I2C scl clock upstream
在upstream的i2c中,scl时钟的频率依赖ILCR寄存器的默认值去实现。
硬件的默认 ILCR 值 (SLV=0x156, FLV=0x5d) 会导致 SCL 频率低于预期。
例如,在默认的 31.5 MHz 输入时钟下,这些默认设置会导致SCL 频率:
- 在目标频率为 100 kHz 时约为 93 kHz(标准模式
- 在目标频率为 400 kHz 时约为 338 kHz(快速模式)。
这些频率低于 100 kHz/400 kHz 的标称速度。
为了解决这个问题,不应该依赖默认值或者在设备树中写死ilcr的值。
应该动态获取dts node中的freq去调整ilcr的值。
V0 (PLCT内部版本)
该版本直接获取dts node中的freq保存,创建了一个函数去通过freq设置ilcr的值。
经过Yixun Lan的建议,应引入ccf框架。
V1
引入ccf框架,将ilcr寄存器注册为scl clock,在i2c驱动中内部托管。
实现round, recalc, set_rate函数。
经过Yaozi的建议,取消goto并且使用devm_add_action_or_reset函数简化代码。
如原先是:
ret = register_xxx() |
现在可以使用:
ret = register_xxx() |
V2
添加了一些函数检查。
当dts获取不到合法freq时,默认给一个freq而不是报错退出。
SSPA I2S_BCLK父时钟upstream修复
在upstream clock驱动中,完全按照了spacemit public document去实现clk.
但是公开文档并不是最新的,导致出现偏差。
在SSPA的CLOCK_RESET寄存器中,BIT6:4(FNCLKSEL)是用来选择SSPA的父时钟的。
但是这里有一个特殊点,就是当FNCLKSEL=7时,为I2S_BCLK作为SSPA的父时钟.
此时BIT3必须为1,否则无效。其他情况下BIT3的值无关紧要。
upstream clock中并没有关注到bit3这一点,所以:
V1
在set_parent的时候,重新定义了set_parent_sspa这一个ops.
该ops在index为7的时候设置BIT3为1.
该版本并不完美,代码复用效率不够高。
V2
在Yaozi的建议下,跟Jin确认了bit3为1时,并不会对其他的父时钟产生影响。
所以引入virtual(dummy) clock去做父时钟。
这样在级联的影响下,bit3和fcnsel都会选择到正确的值。
代码差异如下:
-static const struct clk_parent_data sspa_parents[] = { |
V3
在V2中,我在.h文件中添加CLK_SSPAx_I2S_BCLK的定义是直接在中间插入。
经过Yaozi和Rob的提醒,这会导致ABI breaking.
所以在V3中修复了这点。
讨论链接:https://lore.kernel.org/all/20250723044128.GA1207874-robh@kernel.org/
P1 MTP掉码问题解决
P1寄存器的值来自于内部的MTP模块(eeprom),但由于未知原因,mtp中的数据会出现掉码的问题(码值不对)。
但是现在这批有问题的P1已经贴上了,所以寻找不拆机不飞线的刷写补救方法:
进入刷机模式通过fastboot命令发送bin文件数据并刷写,基础程序是rock提供的baremetal。
day 1
- 熟悉pmic读出,擦除,写入流程
- 成功实现读出,写入函数
- 发现无法进行全擦除操作,只可以实现页擦除
- 由于可以读取,擦出,所以没有怀疑是测试模式没进入的原因
day 2
- 发现读出数据是中间对称的,查看手册并且与F部确认发现是无法进入测试模式
- 现在无法全擦的原因也找到了,也是因为测试模式没进入成功
- 示波器抓波形发现是发送测试模式seq code的时候被i2c hardware controller自己挂了个stop(进入测试模式的seq中不可以出现stop)
- 随后想到先用0x41(pmic slave addr)作为slave addr,后面的seq code作为数据发送,但是中间必须涉及到restart信号,此时seq又变成slave addr了还是会加入stop.
- 经过讨论研究决定使用sw-i2c去进入test mode
day 3
- 开发sw-i2c, 成功进入测试模式,成功全擦除,正常读写,正常写入
- 收到新需求,检测PMIC的版本是否是B,如果是B直接进行刷写
- 修改代码格式,规范,提交到rock维护的baremetal上游
k3 pinctrl
接到k3 pinctrl驱动编写的任务,首先我先做了如下几件事情:
- 查看k1和k3的pinmux寄存器bit定义,发现完全一致,代码基本可复用
- 查看k1和k3的pinmux base addr,发现完全一致
- 查看k1和k3的引脚定义,发现并不相同,此时需要自己写引脚资料描述了
经过如上调研,我直接拿来了upstream的驱动进行修改,根据Kevin的建议保留pinctrl-k1.c的命名。
并且在Kconfig里面加入K3的支持描述。
在进行引脚资源描述的时候,由于寄存器偏移中间会有中断,所以引脚资源描述并没有根据数据手册上面的comment作为索引,而是保持了顺序, 否则会出现内存资源浪费的情况. 这种情况在k1比较严重,
在k3上面比较简单,仅在130的时候断了8个字节。
由于在引脚资源描述的时候按照了递增顺序而不是手册顺序,所以需要有一个pin_to_offset的函数根据pin计算reg offset值。
为了保持和k1原驱动一致,在匹配data的结构体中增加了指针函数去根据k1和k3指向不同的pin_to_offset.
diff:
diff --git a/drivers/pinctrl/spacemit/Kconfig b/drivers/pinctrl/spacemit/Kconfig |
ebpf学习
eBPF(Extended Berkeley Packet Filter)是一个Linux内核中的强大技术,它允许用户空间程序在不修改内核源代码或加载内核模块的情况下,在内核中运行特权程序。
比如可以在不修改内核的情况下为内核增加新的功能。
比如可以在任何一个内核代码上附加一个ebpf程序来调试或监控。
对于高性能监控或跟踪场景,ftrace不够高效,可能会出现卡顿,比如双DIE互联场景,此时使用ebpf来进行性能监控和测试更佳合适。
对于安全,网络性能的监控,ebpf更加合适。
我学习了如下:
- 如何使用python bcc创建ebpf程序
- 如何使用libbpf创建ebpf程序
- awesome ebpf
JATG & gdb使用
如题。