-
Notifications
You must be signed in to change notification settings - Fork 0
解决无外放声音的问题
xuwd1 edited this page Jul 20, 2023
·
9 revisions
- 通过I2C连接到PCH的I2C_3上(alderlake的I2C控制器是集成在PCH的lpss中的,由
intel_lpss_pci
驱动负责),地址分别为0x40
和0x41
- 具有三个引脚
- 其中第一个引脚是复位脚,两个cs35l41共同连接到一个PCH的GPIO上
- 第二个引脚是中断脚,两个cs35l41共同连接到APIC上
- 第三个引脚的功能在其驱动
cs35l41_hda.c
中称为VSPK_SWITCH
,有时也被混用为spkid
,应当与放大器的编号和片选有关
- cs35l41首先需要被正常例化成i2c设备(或者spi设备,比如SteamDeck上的cs35l41即是通过spi连接的),这个工作是由内核中的
serial-multi-instantiate
驱动负责的. - 在i2c/spi设备例化后,
cs35l41_hda
驱动需要能正确的初始化该放大器,具体地,其需要从ACPI表中的CSC3551设备的_DSD
方法读出一系列设置,随后对其进行配置和复位 - 在播放开始前,需要加载正确的固件(是的,这破烂东西居然有固件!),固件的加载是通过
cs35l41_hda
驱动向hda
驱动挂上playback hook
,并随后由hda
驱动在初始化时执行这个钩子函数实现的
- 在旧版本的内核中,
serial-multi-instantiate
驱动在例化cs35l41设备时总是假设其中断脚是连接到PCH的GPIO上的,然而联想工程师认为连接到APIC上是一个更妙的选择,因此cs35l41对应的i2c/spi设备从一开始就无法例化 - 自然地,
cs35l41_hda
根本不会进行probing. 但即使修改了serial-multi-instantiate
驱动,由于联想没有在ACPI表中实现CSC3551设备的_DSD
方法,cs35l41_hda
驱动也无法完成配置 - 最后,即使改正了前面两个问题,
hda
驱动也不会加载cs35l41的固件,这是因为hda
驱动中没有增加对应y9000x 2022的quirk.
希望解决该机器上的外放问题,唯一可行的办法只有对内核进行修改,并运行自制内核。内核补丁分为两部分:
请注意这一部分内核补丁已经被接受,不久后应当会出现在内核主线代码中
.../platform/x86/serial-multi-instantiate.c | 21 +++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c
index f3dcbdd72fec..2c2abf69f049 100644
--- a/drivers/platform/x86/serial-multi-instantiate.c
+++ b/drivers/platform/x86/serial-multi-instantiate.c
@@ -21,6 +21,7 @@
#define IRQ_RESOURCE_NONE 0
#define IRQ_RESOURCE_GPIO 1
#define IRQ_RESOURCE_APIC 2
+#define IRQ_RESOURCE_AUTO 3
enum smi_bus_type {
SMI_I2C,
@@ -52,6 +53,18 @@ static int smi_get_irq(struct platform_device *pdev, struct acpi_device *adev,
int ret;
switch (inst->flags & IRQ_RESOURCE_TYPE) {
+ case IRQ_RESOURCE_AUTO:
+ ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx);
+ if (ret > 0) {
+ dev_dbg(&pdev->dev, "Using gpio irq\n");
+ break;
+ }
+ ret = platform_get_irq(pdev, inst->irq_idx);
+ if (ret > 0) {
+ dev_dbg(&pdev->dev, "Using platform irq\n");
+ break;
+ }
+ break;
case IRQ_RESOURCE_GPIO:
ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx);
break;
@@ -307,10 +320,10 @@ static const struct smi_node int3515_data = {
static const struct smi_node cs35l41_hda = {
.instances = {
- { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 },
- { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 },
- { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 },
- { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 },
+ { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },
+ { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },
+ { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },
+ { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },
{}
},
.bus_type = SMI_AUTO_DETECT,
请注意Cirrus Logics的内核维护者拒绝了这个patch的合并请求. 其称几周内会有一个更干净的解决方法出现在内核代码中,我们可以期待其工作. 希望届时y9000x的声音外放问题可以通过更新内核彻底解决。
sound/pci/hda/cs35l41_hda.c | 160 ++++++++++++++++++++++++++++++++++++
1 file changed, 160 insertions(+)
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index ce5faa620517..d957458dd4e6 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -1211,6 +1211,159 @@ static int cs35l41_get_speaker_id(struct device *dev, int amp_index,
return speaker_id;
}
+#define CS35L41_FIXUP_CFG_MAX_DEVICES 4
+
+struct cs35l41_fixup_cfg {
+ unsigned short vender;
+ unsigned short device;
+ unsigned int num_device; /* The num of cs35l41 instances */
+ /* cs35l41 instance ids, can be i2c index or spi index */
+ int ids[CS35L41_FIXUP_CFG_MAX_DEVICES];
+ unsigned int reset_gpio_idx[CS35L41_FIXUP_CFG_MAX_DEVICES];
+ enum gpiod_flags reset_gpio_flags[CS35L41_FIXUP_CFG_MAX_DEVICES];
+ int spkid_gpio_idx[CS35L41_FIXUP_CFG_MAX_DEVICES];
+ unsigned int spk_pos[CS35L41_FIXUP_CFG_MAX_DEVICES];
+ enum cs35l41_hda_gpio_function gpio1_func[CS35L41_FIXUP_CFG_MAX_DEVICES];
+ enum cs35l41_hda_gpio_function gpio2_func[CS35L41_FIXUP_CFG_MAX_DEVICES];
+ enum cs35l41_boost_type bst_type[CS35L41_FIXUP_CFG_MAX_DEVICES];
+};
+
+static const struct cs35l41_fixup_cfg cs35l41_fixup_cfgtbl[] = {
+ { // Lenovo Legion Slim 7i 16IAH7
+ .vender = 0x17aa,
+ .device = 0x386e,
+ .num_device = 2,
+ .ids = {0x40, 0x41},
+ .reset_gpio_idx = {0, 0},
+ .reset_gpio_flags = {GPIOD_OUT_LOW, GPIOD_OUT_LOW},
+ .spkid_gpio_idx = {1, 1},
+ .spk_pos = {0, 1},
+ .gpio1_func = {CS35l41_VSPK_SWITCH, CS35l41_VSPK_SWITCH},
+ .gpio2_func = {CS35L41_INTERRUPT, CS35L41_NOT_USED},
+ .bst_type = {CS35L41_EXT_BOOST, CS35L41_EXT_BOOST}
+ },
+ { // Lenovo Legion Slim 7i 16IAH7 type2
+ .vender = 0x17aa,
+ .device = 0x3803,
+ .num_device = 2,
+ .ids = {0x40, 0x41},
+ .reset_gpio_idx = {0, 0},
+ .reset_gpio_flags = {GPIOD_OUT_LOW, GPIOD_OUT_LOW},
+ .spkid_gpio_idx = {1, 1},
+ .spk_pos = {0, 1},
+ .gpio1_func = {CS35l41_VSPK_SWITCH, CS35l41_VSPK_SWITCH},
+ .gpio2_func = {CS35L41_INTERRUPT, CS35L41_NOT_USED},
+ .bst_type = {CS35L41_EXT_BOOST, CS35L41_EXT_BOOST}
+ },
+ { // Lenovo Legion 7i 16IAX7
+ .vender = 0x17aa,
+ .device = 0x3874,
+ .num_device = 2,
+ .ids = {0x40, 0x41},
+ .reset_gpio_idx = {0, 0},
+ .reset_gpio_flags = {GPIOD_OUT_LOW, GPIOD_OUT_LOW},
+ .spkid_gpio_idx = {1, 1},
+ .spk_pos = {0, 1},
+ .gpio1_func = {CS35l41_VSPK_SWITCH, CS35l41_VSPK_SWITCH},
+ .gpio2_func = {CS35L41_INTERRUPT, CS35L41_INTERRUPT},
+ .bst_type = {CS35L41_EXT_BOOST, CS35L41_EXT_BOOST}
+ },
+ { // Lenovo Legion 7i 16IAX7 type 2
+ .vender = 0x17aa,
+ .device = 0x386f,
+ .num_device = 2,
+ .ids = {0x40, 0x41},
+ .reset_gpio_idx = {0, 0},
+ .reset_gpio_flags = {GPIOD_OUT_LOW, GPIOD_OUT_LOW},
+ .spkid_gpio_idx = {1, 1},
+ .spk_pos = {0, 1},
+ .gpio1_func = {CS35l41_VSPK_SWITCH, CS35l41_VSPK_SWITCH},
+ .gpio2_func = {CS35L41_INTERRUPT, CS35L41_INTERRUPT},
+ .bst_type = {CS35L41_EXT_BOOST, CS35L41_EXT_BOOST}
+ },
+ { // Lenovo Legion Slim 7 16ARHA7
+ .vender = 0x17aa,
+ .device = 0x3877,
+ .num_device = 2,
+ .ids = {0x40, 0x41},
+ .reset_gpio_idx = {0, 0},
+ .reset_gpio_flags = {GPIOD_OUT_LOW, GPIOD_OUT_LOW},
+ .spkid_gpio_idx = {1, 1},
+ .spk_pos = {0, 1},
+ .gpio1_func = {CS35l41_VSPK_SWITCH, CS35l41_VSPK_SWITCH},
+ .gpio2_func = {CS35L41_INTERRUPT, CS35L41_INTERRUPT},
+ .bst_type = {CS35L41_EXT_BOOST, CS35L41_EXT_BOOST}
+ },
+ {} // terminator
+};
+
+static inline int cs35l41_fixup_get_index(const struct cs35l41_fixup_cfg *fixup, int cs35l41_addr)
+{
+ int i;
+
+ for (i = 0; i < fixup->num_device; i++) {
+ if (fixup->ids[i] == cs35l41_addr)
+ return i;
+ }
+ return -ENODEV;
+}
+
+static int apply_cs35l41_fixup_cfg(struct cs35l41_hda *cs35l41,
+ struct device *physdev,
+ int cs35l41_addr,
+ const struct cs35l41_fixup_cfg *fixup_tbl)
+{
+ const char *ssid;
+ unsigned int vendid;
+ unsigned int devid;
+ const struct cs35l41_fixup_cfg *cur_fixup;
+ struct cs35l41_hw_cfg *hw_cfg;
+ int cs35l41_index;
+ int ret;
+ int i;
+
+ ssid = cs35l41->acpi_subsystem_id;
+ ret = sscanf(ssid, "%04x%04x", &vendid, &devid);
+ if (ret != 2)
+ return -EINVAL;
+
+ hw_cfg = &cs35l41->hw_cfg;
+ for (cur_fixup = fixup_tbl; cur_fixup->vender; cur_fixup++) {
+ if (cur_fixup->vender == vendid && cur_fixup->device == devid) {
+ cs35l41_index = cs35l41_fixup_get_index(cur_fixup, cs35l41_addr);
+ if (cs35l41_index == -ENODEV)
+ return -ENODEV;
+ cs35l41->index = cs35l41_index;
+ cs35l41->reset_gpio = gpiod_get_index(
+ physdev,
+ NULL,
+ cur_fixup->reset_gpio_idx[cs35l41_index],
+ cur_fixup->reset_gpio_flags[cs35l41_index]
+ );
+ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev,
+ cs35l41_index,
+ cur_fixup->num_device,
+ cur_fixup->spkid_gpio_idx[cs35l41_index]
+ );
+ hw_cfg->spk_pos = cur_fixup->spk_pos[cs35l41_index];
+ cs35l41->channel_index = 0;
+ for (i = 0; i < cs35l41->index; i++)
+ if (cur_fixup->spk_pos[i] == hw_cfg->spk_pos)
+ cs35l41->channel_index++;
+
+ hw_cfg->gpio1.func = cur_fixup->gpio1_func[cs35l41_index];
+ hw_cfg->gpio1.valid = true;
+ hw_cfg->gpio2.func = cur_fixup->gpio2_func[cs35l41_index];
+ hw_cfg->gpio2.valid = true;
+ hw_cfg->bst_type = cur_fixup->bst_type[cs35l41_index];
+ dev_dbg(physdev, "Fixup applied.\n");
+ break;
+ }
+ }
+ return 0;
+
+}
+
/*
* Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work.
* And devices created by serial-multi-instantiate don't have their device struct
@@ -1221,6 +1374,7 @@ static int cs35l41_get_speaker_id(struct device *dev, int amp_index,
static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
const char *hid)
{
+ int ret;
struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
/* check I2C address to assign the index */
@@ -1243,7 +1397,13 @@ static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physd
/*
* Note: CLSA010(0/1) are special cases which use a slightly different design.
* All other HIDs e.g. CSC3551 require valid ACPI _DSD properties to be supported.
+ * However many OEMs hardcoded the configurations into their proprietary software
+ * thus leaving our Linux installation with no speaker sound at all while we see
+ * no hope those OEMs would fix it. So we apply a ssid specific fixup to fix it.
*/
+ if (apply_cs35l41_fixup_cfg(cs35l41, physdev, id, cs35l41_fixup_cfgtbl) == 0)
+ return 0;
+
dev_err(cs35l41->dev, "Error: ACPI _DSD Properties are missing for HID %s.\n", hid);
hw_cfg->valid = false;
hw_cfg->gpio1.valid = false;
sound/pci/hda/patch_realtek.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index e2f8b608de82..cc10bb8b75d1 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -9831,6 +9831,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x31af, "ThinkCentre Station", ALC623_FIXUP_LENOVO_THINKSTATION_P340),
SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3803, "Legion Slim 7i 16IAH7", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS),
SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7),
SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS),
@@ -9846,6 +9847,10 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6),
SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x386e, "Legion Slim 7i 16IAH7", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x17aa, 0x3877, "Legion 7 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC),
SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
至此,y9000x的扬声器应当可以正常发出声音了,一些值得注意的点如下:
- 如patch中的代码所示,这些patch同样可以用来修复r9000x 2022以及y9000k 2022的外放问题
- 打上patch后你可能会发现扬声器的声音比较小,这是因为cs35l41的扬声器驱动方式分为外部驱动和内部驱动两种,patch中给出的是使用外部驱动,实际上就是不对发向扬声器的声音做任何进一步的放大处理,而这就会使得声音比较小. 目前本人没有能力解决cs35l41内部驱动的问题,这是因为cs35l41的内部驱动模式同样需要一系列正确的配置才可以工作,若配置不当,Cirrus Logics的人声称可能会对扬声器造成不可逆的损害,而本人没有能力获得正确的内部驱动配置。