声明:本篇中可能存在大量错误,欢迎指正。
前言
在高通平台上,常见的扬声器解决方案主要有:
- WCD -- SWR --> WSA --> Speaker(例如WCD9340 + WSA8815)
- WCD -- I2S --> 3rd-party Amplifier --> Speaker(例如WCD9340 + TFA9874)
大多数OEM机器采用三方功放,部分厂商采用高通的WSA方案,高通工程机绝大部分采用WCD + WSA方案。
本文讨论方案2,也就是WCD + 三方功放方案。
基础知识
常见英文简写/名词说明:
- AP: Application Processor,应用处理器,这里指处理器
- WCD: 高通的Audio CodeC
- I2C: 也常写作I²C/IIC, 只需要两根线就可以双向传输数据
- I2S: 集成电路内置音频总线,传输音频数据
- SWR: SoundWire,MIPI 2014年定义的音频接口
- SLIMBus: MIPI 2007年定义的音频接口
- DSDT: Differentiated System Description Table,ACPI表中的一个,用以描述平台的外围设备和系统硬件
- ASL: ACPI源语言
- Microsoft asl: 微软的ASL编译器
- iasl: Intel的ASL语言编译器
- AML: ACPI计算机语言,即ASL编译后产物
- dsl: iasl反编译AML之后产出的反编译源代码文件
- ACDB: Audio Calibration Database 高通平台音频配置
- amp: 下文对Amplifier即功放芯片的简称
- LA: Linux Android平台
- WP: WIndows平台
硬件相关:
- WCD通过SPI, SLIMBus 与AP进行通信。
- 功放芯片通常通过I2C与AP进行通信,即我们可以通过i2c传输命令配置和控制功放芯片。
- AP通过I2S向功放芯片传输音频数据。
- I2S一般连接四根线,即BCK, WCK, DATAO, DATAI。
- 在Linux内核或者安卓内核源码中可以找到平台GPIO的功能配置等信息,例如SM8150。
- 高通的I2S不只一组,其命名编号通常为Primary,Secondary,Tertiary,Quaternary或其简写。
- 在Linux内核或者安卓内核中的设备树源码中可以寻找到I2C相关的属性,例如SM8150
软件相关:
高通Windows平台已经提供了大部分音频相关的DSDT配置和驱动,即:
- 基础的DSDT
- 音频相关驱动以及inf中的默认配置
- 默认的ACDB
我们能够在Windows Update,或者WOA-Project的Qualcomm-Reference-Drivers上下载到的参考平台驱动和固件,其中对应关系:
移动PC平台 | 移动平台 |
---|---|
SD850 | SD845 |
7c Gen1/Gen2 | SD720 |
7c Gen3 | SD778 |
SD835 | SD835 |
对于
- 如何从firmware cab中解包出ACPI表
- 如何反编译DSDT
- 如何反编译dtb
- 如何解决回编译.dsl中遇到的error
- 如何反编译AeoB
- 如何回编译AeoBSL
均不在本文探讨范围之内。
所以,在阅读下述内容时,假设你已经拥有DSDT表和Audio相关驱动,并且说实在的,这是最常见的情况。
配置DSDT表
对于使用三方功放的平台,我们需要在DSDT中定义:
- 功放设备,其中包含了功放的硬件连接的资源描述,主要为I2C和Gpio中断(如果你的驱动不处理中断,可以不定义)
- 功放设备的电源管理配置,其中包含功放的Reset脚以及电源配置(如果你的功放在dts中未定义电源,此处也不需要定义)
- I2S Gpio功能配置
- I2C Master设备定义
- I2C Master设备资源定义
下面我们以一加7T Pro为例,具体说说如何定义。
定义功放设备:
要在DSDT中定义我们的功放设备,首先我们需要了解其基本的连接属性,即挂在哪个i2c master下面,中断gpio是多少,reset gpio是多少,reset gpio是高电平使能还是低电平使能等等,这些通常在设备树中有所定义。
一:在设备树中寻找属性:
在安卓里面,打开Device Info HW
软件,点击右上角设置
,打开显示i2c地址
和使用Root
在首页,我们可以看到设备的音频那一栏写着诸如tfa98xx (i2c 0-0034)
、tas2557 (i2c 1-004c)
、cs35l41 (i2c 0-0040)
之类的字样,此时我们打开反编译之后的dts,搜索相关字段,例如我在一加7T Pro的dts中搜索tfaxx,可以找到相关节点:
i2c@890000 {
......
tfa98xx_right@34 {
compatible = "nxp,tfa98xx";
reg = <0x34>;
reset-gpio = <0x38 0x25 0x00>;
status = "ok";
phandle = <0x761>;
};
tfa98xx_left@35 {
compatible = "nxp,tfa98xx";
reg = <0x35>;
reset-gpio = <0x38 0x64 0x00>;
status = "ok";
phandle = <0x762>;
};
......
};
一加7T Pro拥有双扬声器,故此处定义了左右两个tfa98xx设备。观察上下文即可得出tfa98xx设备位于i2c@890000
下,reset 脚分别为gpio37
(hex: 0x25)和gpio100
(hex: 0x64),并且这两个slave设备在i2c上地址/ID分别为0x34
和0x35
。
二:使用asl创建设备:
新建文本文件cust_spkr.asl并粘贴如以下内容:
Device (SPK1)
{
Name (_HID, "PLHD0001")
Name (_UID, 0)
Alias(\_SB.PSUB, _SUB)
Name (_DEP, Package()
{
\_SB.GIO0,
\_SB.I2CX
})
Method (_CRS, 0x0, NotSerialized)
{
Name (RBUF, ResourceTemplate()
{
I2CSerialBus(0xAA, , 400000, AddressingMode7Bit, "\\_SB.I2CX",,,,)
})
Return (RBUF)
}
}
粘贴进去之后,我们需要修改几个地方,
Device (SPK1)
中的1修改为实际数值,例如有两个amp,则第二个设备名应该为SPK2
,以此类推。- 将
Name (_HID, "PLHD0001")
将此处的PLHD0001
改成你的设备的名称,例如对于一加7TPro, 此处为GTFA9874
,请记住你定义的_HID,下文驱动中还需要用到。 Name (_UID, 0)
中的0修改为实际数值,例如有两个amp,则第二个设备的_UID应该为1
,以此类推。\_SB.I2CX
中的X修改为实际数值,例如本文中amp挂在i2c@890000
下,通过观察xxx-qupv3.dtsi源码可知其属于qupv3_se4_i2c,故此处的X
为5
(se号+1)。I2CSerialBus(0xAA, , 400000, AddressingMode7Bit, "\\_SB.I2CX",,,,)
中的0xAA
修改为实际值,例如本文中第一个tfa98xx地址为0x34
,则此处修改为0x34
,400000
为频率,通常为400khz
或者100khz,"\\_SB.I2CX"
中的X
需要修改为实际值,参考上条。
自此,我们已经在asl中完成了amp设备的定义。接下来,我们需要在DSDT.dsl中添加Include("cust_spkr.asl")
将我们的文件包含在DSDT.dsl中。
需要注意的是,这条Include语句必须位于 Scope (\_SB){ ... }
内。
定义I2C Master设备:
上文中我们使用了\_SB.I2CX
这个设备,但是大部分情况下这个设备都是没有被定义的,我们需要手动在dsdt中添加这个设备。
打开的DSDT.dsl,搜索Device (I2C
,此时应当有较多和候选结果,如图:
此时我们将Device (I2CX) {
到对应的 }
复制并粘贴一份,依照我们的需求做相应修改:
- 修改粘贴后的文本中的
I2CX
的X
为我们需要的se号+1。由于上文我们已经在dts中找到了i2c对应的se号,此处不在赘述。在一加7T Pro例子中,我们得到的是5
。注意,如果这里的X
是两位数,你应当写作ICX
,例如IC17
。 - 修改
_UID
为实际值。此处与上文X
的值保持相同,在一加7T Pro例子中,此处是5
。 - 修改
Memory32Fixed
中的Address Base为实际值。在一加7T Pro的例子中,我们找到的i2c@890000
里面的890000
即为此处的值,注意此数字为16精致数字,我们补上0x后,此处填写0x890000
。 - 修改
Interrupt
里面的中断号。在上文中提到的xxxx-quiv3.dtsi中有此中断号的定义,需要注意的是,对于GIC_SPI,此处的值需要+32,即605 + 32= 637
。
配置I2CMaster设备的硬件资源
在DSDT.dsl中,我们再次搜索\\_SB.I2C
,我们会找到一个类似如下结构的地方(注意,此处我将缩进和可省略代码整理了一下以便能够放在这里):
将你的搜索结果中的这一个Package{...}
复制并粘贴这个Package{...}
下方,然后修改这里的一些字符串和Gpio配置为我们需要的实际值:
- 修改此处的
"\\_SB.I2CX"
为实际值,此处X
应当与你在上方所得的X
值保持一致。 - 修改上下两方的
QUP_Y、wrap_Y
和sZ_clk
中的Y
与Z
为实际值。你仍然需要参考安卓xxxx-qupv3.dts中的一个宏来确定这里的值,例如此处的GCC_QUPV3_WRAP0_S4_CLK
中的WARP0
中的0即为Y的值,S4
中的4
为此处的Z
。 - 修改TLMM Gpio的值为正确的值,你可以在安卓内核源码中的xxxx-pinctrl.dtsi和linux内核源码中找到对应的gpio以及当其配置为qup时对应的func编号,在本文一加7T Pro的例子中,i2c_se4在pinctrl.dtsi中对应的gpio编号为51和52,gpio51、gpio52在内核源码中qup4功能对应的func为1。注意,PINGROUP(...)从左往右数依次为:(PIN编号,块,func1,func2,func3 ..... )。
- 此处TLMMGPIO内的Package释义如下:{PIN编号,电平,func,方向,上下拉,驱动电流强度},具体使用参考DSDT.dsl内其他TLMMGPIO Package在DSTATE0和DSTATE3的区别即可知。
配置Amp设备的硬件资源
通过上述操作,我们已经成功定义了I2C从设备和I2C主设备,并且掌握了如何在Package中定义一个gpio状态。
如果我们此时进入系统并安装上I2C驱动,应当会在设备管理器中出现一个未知设备,且通过查看其详细信息可知此设备为SPK1。
假如此时使用SPBTool能够获取到Amp的回传数据,则此条目可忽略。当然,为了追求更高效率的电源管理,还是建议配置。
通常参考设备的dsdt的中PEP会保留OPMD方法,我们可以尝试在DSDT.dsl中搜索此OPMD,如果没有搜索结果,你可以新建一个文件,并且将他Include到你的DSDT中,文件内容大致如下:
Scope(\_SB_.PEP0){
Method(OPMD){ Return(OPCC) }
Name(OPCC, Package () {
Package(){
"DEVICE",
"\\_SB.SPK1",
Package(){
"DSTATE", 0,
// Set your Reset gpio here, IC should be enabled in this DSTATE.
// Package(){"TLMMGPIO",Package(){123, 1, 0, 1, 0, 0}},
// Delay in ms if you need it, in most time not needed.
//Package() { "DELAY", Package() {2} },
}
Package(){"DSTATE", 3,
// Set your Reset gpio here, IC should be shutdown in this DSTATE.
// Package(){"TLMMGPIO",Package(){123, 0, 0, 0, 0, 0}},
// Delay in ms if you need it, in most time not needed.
//Package() { "DELAY", Package() {2} },
}
}
}
}
根据上文中所述的TLMMGPIO Package配置方法,并且按照你从dts中获得的Reset Gpio编号,你可以很轻松的按照这里的提示填写对应的数值。
在本文一加7T Pro的例子中,Reset Gpio编号为gpio37,我们将其TLMMGPIO的package前面的//删掉以取消注释,并将pin编号(第一个数字)改为37,由于此例中配置Reset脚为高电平时IC关闭(注意,通常情况下是Reset高电平时IC正常工作,此例是特殊),故将第二个数据改为0,在D3状态下,则将第二个数字改为1,第四个数字改为1。
添加I2S配置
恭喜你,已经成功完成了大部分配置了。现在我们即将完成最后一步,配置I2S所需的硬件资源。
相信根据前面的操作,你现在能过熟练使用TLMMGPIO这个Package来配置gpio状态了。
一:在设备树中寻找属性:
要配置正确的I2S,我们首先需要在源码中dts中,或者反编译后的dts中寻找到codec对应的节点,此处为quat
,故使用了Quaternary
组的I2S,通过观察可知mi2s_quat
在active时137、138
均启用,但是由于缺乏原理图DATA线无法确定是使用SD0、SD1、SD2还是SD3(详见pinctrl dts里面的描述),可以先考虑将几个gpio都写进去,日后再acdb中进行尝试。
当然如果你有原理图,直接搜索amp的名称,观察其BCK、WCK、DO、DI走线对应的gpio编号即可。
二:在DSDT中的合适位置添加资源:
在DSDT.dsl中搜索\\_SB.ADSP.SLM1.ADCM.AUDD
,找到对应的资源配置Package。
在此Package中寻找正确的Component ID,在本例中,Quaternary
对应Component ID
为0xD。
你可以通过查阅auddev_ext.inf
中的COMPONENT_GPIOUID
与其对应的GroupID_DeviceID
来确定其对应关系,具体方法在之后说到ACDB配置时会详细阐述。
通常的对应关系为:
RX I2S ID | Component ID |
---|---|
Quaternary | 0xD |
Tertiary | 0xB |
Secondary | 0x9 |
Primary | 0x7 |
右侧图示为整理并添加完I2S相关的GPIO之后的Component 0xD,之前已经介绍过TLMMGPIO Package内部各个参数的含义,故不在赘述。
请务必将dts和源码中定义的对应的I2S的GPIO添加到这里,并且指定正确的Function,否则声音信号无法传输到Amp。
自此,我们已经完成了对DSDT方面的修改,下面我们介绍驱动所需要的修改,以及如何书写一个驱动控制Amplifier。
配置ACDB
WP平台的ACDB与LA平台的ACDB并不兼容,即便是同样的SOC。
旧平台的ACDB通常可以在QACT v7.4中进行编辑。
你可以在这里下载QACT。
这是QACT 7.4正常打开时候的精神面貌,点击Open ACDB From Disk
, 再选择解压后的qcacsp_xxxx.cab
包里面的workspaceFile.qwsp
。如果没问题的话你会看到这个页面:
选择上方的Tools -> Device Designer 进入设备设计器中。
打开qcaudminiport.inf,搜索TopologySpeaker, 找到
HKR,QCAUD\TopologySpeaker\Device0\,DeviceID,0x00010001,0x000000FF
这里写的Device ID即为扬声器设备的Device ID,同时也就串联起了之前我们提到的auddev_ext
里面的Device ID,一个device中包含了其使用的I2S,以及Data Line是SD0还是SD1、SD2、SD3。Device 存储在ACDB中,通过QACT的Device Designer中我们可以修改原来设备的属性或者添加删除设备。
推荐尽量不要修改qcaudminiport.inf中的Device ID,通过默认配置的方式达到我们的目的,这些DeviceID和ComponentID之类的如果一处配置出错都会导致驱动异常(表现为音量条自动调到最大,且无法控制)