diff mbox series

[1/2] ALSA: hda: cs35l41: Add fixups for machines without a valid ACPI _DSD

Message ID SY4P282MB183539E5A91B351249A2F8F1E037A@SY4P282MB1835.AUSP282.PROD.OUTLOOK.COM (mailing list archive)
State New, archived
Headers show
Series Fix CSC3551 speaker sound problem for machines without a valid ACPI _DSD | expand

Commit Message

David Xu July 13, 2023, 4:29 p.m. UTC
As the comments added in commit 4d4c4bff4f8ed79d95e05 ("ALSA: hda:
cs35l41: Clarify support for CSC3551 without _DSD Properties"), CSC3551
requires a valid _DSD to work and the current implementation just
fails when no _DSD can be found for CSC3551. However it is a fact
that many OEMs hardcoded the configurations needed by CSC3551 into their
proprietary software for various 2022 and later laptop models,
and this makes the Linux installations on these models cannot make
any speaker sound. Meanwhile, at this point of time, we see no hope
that these OEMs would ever fix this problem via a BIOS update. So
to fix this bothering problem it might be worthwhile to add some
model-specific fixups to apply some proper configurations
to the cs35l41.

To address the above problem, a configuration fixup function
apply_cs35l41_fixup_cfg that would be called in cs35l41_no_acpi_dsd,
along with a fixup table cs35l41_fixup_cfgtbl which is a array of
fixup entry struct cs35l41_fixup_cfg are introduced. Each fixup entry
records the ACPI _SUB(vender and device ID) to be matched, and a
configuration to be applied to each of the cs35l41 device in CSC3551.

More specifically for the design of this fixup mechanism,
the maximum number of cs35l41 configurations inside a fixup entry
is defined as a macro CS35L41_FIXUP_CFG_MAX_DEVICES, and the actual
number of cs35l41 devices in a CSC3551 system is recorded in the
num_device field in the fixup entry. The apply_cs35l41_fixup_cfg
function is responsible for finding and applying a fixup for a
specific model according to the model's ACPI _SUB. If no valid fixup
can be applied, the fixup function fails and returns to the normal
cs35l41_no_acpi_dsd execution flow.

This patch now contains only several fixups for three Lenovo laptop
models, namely 16IAH7, 16IAX7, and 16ARHA7 and this fixup mechanism
has been verified to work on 16IAH7. As far as is known, several other
laptop models from ASUS and HP also suffer from this no valid _DSD
problem and could have it addressed with this fixup mechanism when
proper fixup entries are inserted.

Signed-off-by: David Xu <xuwd1@hotmail.com>
---
 sound/pci/hda/cs35l41_hda.c | 160 ++++++++++++++++++++++++++++++++++++
 1 file changed, 160 insertions(+)
diff mbox series

Patch

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;