高通Windows平台音频驱动适配

来自Uotan Wiki · 刷机百科
Sunflower2333讨论 | 贡献2024年2月18日 (日) 02:17的版本 (添加了基础dsdt配置方法)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

声明:本篇中可能存在大量错误,欢迎指正。

前言

在高通平台上,常见的扬声器解决方案主要有:

  1. WCD -- SWR --> WSA --> Speaker(例如WCD9340 + WSA8815)
  2. 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即功放芯片的简称


硬件相关:

  • 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配置和驱动,即:

  1. 基础的DSDT
  2. 音频相关驱动以及inf中的默认配置
  3. 默认的ACDB

我们能够在Windows Update,或者WOA-ProjectQualcomm-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分别为0x340x35


二:使用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)

        }

    }

粘贴进去之后,我们需要修改几个地方,

  1. Device (SPK1) 中的1修改为实际数值,例如有两个amp,则第二个设备名应该为SPK2,以此类推。
  2. Name (_HID, "PLHD0001")将此处的PLHD0001改成你的设备的名称,例如对于一加7TPro, 此处为GTFA9874,请记住你定义的_HID,下文驱动中还需要用到。
  3. Name (_UID, 0) 中的0修改为实际数值,例如有两个amp,则第二个设备的_UID应该为1,以此类推。
  4. \_SB.I2CX 中的X修改为实际数值,例如本文中amp挂在i2c@890000下,通过观察xxx-qupv3.dtsi源码可知其属于qupv3_se4_i2c,故此处的X5(se号+1)。
  5. I2CSerialBus(0xAA, , 400000, AddressingMode7Bit, "\\_SB.I2CX",,,,) 中的0xAA修改为实际值,例如本文中第一个tfa98xx地址为0x34,则此处修改为0x34400000为频率,通常为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) { 到对应的 } 复制并粘贴一份,依照我们的需求做相应修改:

  1. 修改粘贴后的文本中的I2CXX为我们需要的se号+1。由于上文我们已经在dts中找到了i2c对应的se号,此处不在赘述。在一加7T Pro例子中,我们得到的是5 。注意,如果这里的X是两位数,你应当写作ICX,例如IC17
  2. 修改_UID为实际值。此处与上文X的值保持相同,在一加7T Pro例子中,此处是5
  3. 修改Memory32Fixed中的Address Base为实际值。在一加7T Pro的例子中,我们找到的i2c@890000 里面的890000即为此处的值,注意此数字为16精致数字,我们补上0x后,此处填写0x890000
  4. 修改Interrupt里面的中断号。在上文中提到的xxxx-quiv3.dtsi中有此中断号的定义,需要注意的是,对于GIC_SPI,此处的值需要+32,即 605 + 32= 637


配置I2CMaster设备的硬件资源

在DSDT.dsl中,我们再次搜索\\_SB.I2C,我们会找到一个类似如下结构的地方(注意,此处我将缩进和可省略代码整理了一下以便能够放在这里):










将你的搜索结果中的这一个Package{...}复制并粘贴这个Package{...}下方,然后修改这里的一些字符串和Gpio配置为我们需要的实际值:

  1. 修改此处的"\\_SB.I2CX"为实际值,此处X应当与你在上方所得的X值保持一致。
  2. 修改上下两方的QUP_Y、wrap_YsZ_clk中的YZ为实际值。你仍然需要参考安卓xxxx-qupv3.dts中的一个宏来确定这里的值,例如此处的GCC_QUPV3_WRAP0_S4_CLK中的WARP0中的0即为Y的值,S4中的4为此处的Z
  3. 修改TLMM Gpio的值为正确的值,你可以在安卓内核源码中的xxxx-pinctrl.dtsilinux内核源码中找到对应的gpio以及当其配置为qup时对应的func编号,在本文一加7T Pro的例子中,i2c_se4在pinctrl.dtsi中对应的gpio编号为51和52,gpio51、gpio52在内核源码中qup4功能对应的func为1。注意,PINGROUP(...)从左往右数依次为:(PIN编号,块,func1,func2,func3 ..... )。
  4. 此处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均启用,但是由于缺乏原理图无法确定使用SD0、SD1、SD2还是SD3,可以先考虑将几个gpio都写进去,日后再acdb中进行尝试。

当然如果你有原理图,直接搜索amp的名称,观察其BCK、WCK、DO、DI走线即可。

二:在DSDT中的合适位置添加资源:

在DSDT.dsl中搜索\\_SB.ADSP.SLM1.ADCM.AUDD,找到对应的资源配置Package,找到Quaternary对应Component ID,这通常为0xD,你可以通过查阅auddev_ext.inf中的COMPONENT_GPIOUID与其对应的GroupID_DeviceID来确定,具体方法在之后说到ACDB配置时会详细阐述。


未完待续.