Skip to content

Commit 8c4c216

Browse files
Stefan Bindingtiwai
authored andcommitted
ALSA: hda: cs35l41: Add config table to support many laptops without _DSD
This make use of the CS35L41 HDA Property framework, which supports laptops which do not have the _DSD properties in their ACPI. Add configuration table to be able to use a generic function which allows laptops to be supported just by adding an entry into the table. Use configuration table function for existing system 103C89C6. Signed-off-by: Stefan Binding <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent 13d605e commit 8c4c216

File tree

3 files changed

+269
-42
lines changed

3 files changed

+269
-42
lines changed

sound/pci/hda/cs35l41_hda.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
18261826
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
18271827
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
18281828
gpiod_put(cs35l41->reset_gpio);
1829+
gpiod_put(cs35l41->cs_gpio);
18291830
acpi_dev_put(cs35l41->dacpi);
18301831
kfree(cs35l41->acpi_subsystem_id);
18311832

@@ -1853,6 +1854,7 @@ void cs35l41_hda_remove(struct device *dev)
18531854
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
18541855
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
18551856
gpiod_put(cs35l41->reset_gpio);
1857+
gpiod_put(cs35l41->cs_gpio);
18561858
kfree(cs35l41->acpi_subsystem_id);
18571859
}
18581860
EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41);

sound/pci/hda/cs35l41_hda.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ struct cs35l41_amp_efi_data {
3535
} __packed;
3636

3737
enum cs35l41_hda_spk_pos {
38-
CS35l41_LEFT,
39-
CS35l41_RIGHT,
38+
CS35L41_LEFT,
39+
CS35L41_RIGHT,
4040
};
4141

4242
enum cs35l41_hda_gpio_function {
@@ -50,6 +50,7 @@ struct cs35l41_hda {
5050
struct device *dev;
5151
struct regmap *regmap;
5252
struct gpio_desc *reset_gpio;
53+
struct gpio_desc *cs_gpio;
5354
struct cs35l41_hw_cfg hw_cfg;
5455
struct hda_codec *codec;
5556

sound/pci/hda/cs35l41_hda_property.c

Lines changed: 264 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,271 @@
66
//
77
// Author: Stefan Binding <[email protected]>
88

9+
#include <linux/acpi.h>
910
#include <linux/gpio/consumer.h>
1011
#include <linux/string.h>
1112
#include "cs35l41_hda_property.h"
13+
#include <linux/spi/spi.h>
14+
15+
#define MAX_AMPS 4
16+
17+
struct cs35l41_config {
18+
const char *ssid;
19+
enum {
20+
SPI,
21+
I2C
22+
} bus;
23+
int num_amps;
24+
enum {
25+
INTERNAL,
26+
EXTERNAL
27+
} boost_type;
28+
u8 channel[MAX_AMPS];
29+
int reset_gpio_index; /* -1 if no reset gpio */
30+
int spkid_gpio_index; /* -1 if no spkid gpio */
31+
int cs_gpio_index; /* -1 if no cs gpio, or cs-gpios already exists, max num amps == 2 */
32+
int boost_ind_nanohenry; /* Required if boost_type == Internal */
33+
int boost_peak_milliamp; /* Required if boost_type == Internal */
34+
int boost_cap_microfarad; /* Required if boost_type == Internal */
35+
};
36+
37+
static const struct cs35l41_config cs35l41_config_table[] = {
38+
/*
39+
* Device 103C89C6 does have _DSD, however it is setup to use the wrong boost type.
40+
* We can override the _DSD to correct the boost type here.
41+
* Since this laptop has valid ACPI, we do not need to handle cs-gpios, since that already exists
42+
* in the ACPI. The Reset GPIO is also valid, so we can use the Reset defined in _DSD.
43+
*/
44+
{ "103C89C6", SPI, 2, INTERNAL, { CS35L41_RIGHT, CS35L41_LEFT, 0, 0 }, -1, -1, -1, 1000, 4500, 24 },
45+
{}
46+
};
47+
48+
static int cs35l41_add_gpios(struct cs35l41_hda *cs35l41, struct device *physdev, int reset_gpio,
49+
int spkid_gpio, int cs_gpio_index, int num_amps)
50+
{
51+
struct acpi_gpio_mapping *gpio_mapping;
52+
struct acpi_gpio_params *reset_gpio_params;
53+
struct acpi_gpio_params *spkid_gpio_params;
54+
struct acpi_gpio_params *cs_gpio_params;
55+
unsigned int num_entries = 0;
56+
unsigned int reset_index, spkid_index, csgpio_index;
57+
int i;
58+
59+
/*
60+
* GPIO Mapping only needs to be done once, since it would be available for subsequent amps
61+
*/
62+
if (cs35l41->dacpi->driver_gpios)
63+
return 0;
64+
65+
if (reset_gpio >= 0) {
66+
reset_index = num_entries;
67+
num_entries++;
68+
}
69+
70+
if (spkid_gpio >= 0) {
71+
spkid_index = num_entries;
72+
num_entries++;
73+
}
74+
75+
if ((cs_gpio_index >= 0) && (num_amps == 2)) {
76+
csgpio_index = num_entries;
77+
num_entries++;
78+
}
79+
80+
if (!num_entries)
81+
return 0;
82+
83+
/* must include termination entry */
84+
num_entries++;
85+
86+
gpio_mapping = devm_kcalloc(physdev, num_entries, sizeof(struct acpi_gpio_mapping),
87+
GFP_KERNEL);
88+
89+
if (!gpio_mapping)
90+
goto err;
91+
92+
if (reset_gpio >= 0) {
93+
gpio_mapping[reset_index].name = "reset-gpios";
94+
reset_gpio_params = devm_kcalloc(physdev, num_amps, sizeof(struct acpi_gpio_params),
95+
GFP_KERNEL);
96+
if (!reset_gpio_params)
97+
goto err;
98+
99+
for (i = 0; i < num_amps; i++)
100+
reset_gpio_params[i].crs_entry_index = reset_gpio;
101+
102+
gpio_mapping[reset_index].data = reset_gpio_params;
103+
gpio_mapping[reset_index].size = num_amps;
104+
}
105+
106+
if (spkid_gpio >= 0) {
107+
gpio_mapping[spkid_index].name = "spk-id-gpios";
108+
spkid_gpio_params = devm_kcalloc(physdev, num_amps, sizeof(struct acpi_gpio_params),
109+
GFP_KERNEL);
110+
if (!spkid_gpio_params)
111+
goto err;
112+
113+
for (i = 0; i < num_amps; i++)
114+
spkid_gpio_params[i].crs_entry_index = spkid_gpio;
115+
116+
gpio_mapping[spkid_index].data = spkid_gpio_params;
117+
gpio_mapping[spkid_index].size = num_amps;
118+
}
119+
120+
if ((cs_gpio_index >= 0) && (num_amps == 2)) {
121+
gpio_mapping[csgpio_index].name = "cs-gpios";
122+
/* only one GPIO CS is supported without using _DSD, obtained using index 0 */
123+
cs_gpio_params = devm_kzalloc(physdev, sizeof(struct acpi_gpio_params), GFP_KERNEL);
124+
if (!cs_gpio_params)
125+
goto err;
126+
127+
cs_gpio_params->crs_entry_index = cs_gpio_index;
128+
129+
gpio_mapping[csgpio_index].data = cs_gpio_params;
130+
gpio_mapping[csgpio_index].size = 1;
131+
}
132+
133+
return devm_acpi_dev_add_driver_gpios(physdev, gpio_mapping);
134+
err:
135+
devm_kfree(physdev, gpio_mapping);
136+
devm_kfree(physdev, reset_gpio_params);
137+
devm_kfree(physdev, spkid_gpio_params);
138+
devm_kfree(physdev, cs_gpio_params);
139+
return -ENOMEM;
140+
}
141+
142+
static int generic_dsd_config(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
143+
const char *hid)
144+
{
145+
struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
146+
const struct cs35l41_config *cfg;
147+
struct gpio_desc *cs_gpiod;
148+
struct spi_device *spi;
149+
bool dsd_found;
150+
int ret;
151+
152+
for (cfg = cs35l41_config_table; cfg->ssid; cfg++) {
153+
if (!strcasecmp(cfg->ssid, cs35l41->acpi_subsystem_id))
154+
break;
155+
}
156+
157+
if (!cfg->ssid)
158+
return -ENOENT;
159+
160+
if (!cs35l41->dacpi || cs35l41->dacpi != ACPI_COMPANION(physdev)) {
161+
dev_err(cs35l41->dev, "ACPI Device does not match, cannot override _DSD.\n");
162+
return -ENODEV;
163+
}
164+
165+
dev_info(cs35l41->dev, "Adding DSD properties for %s\n", cs35l41->acpi_subsystem_id);
166+
167+
dsd_found = acpi_dev_has_props(cs35l41->dacpi);
168+
169+
if (!dsd_found) {
170+
ret = cs35l41_add_gpios(cs35l41, physdev, cfg->reset_gpio_index,
171+
cfg->spkid_gpio_index, cfg->cs_gpio_index,
172+
cfg->num_amps);
173+
if (ret) {
174+
dev_err(cs35l41->dev, "Error adding GPIO mapping: %d\n", ret);
175+
return ret;
176+
}
177+
} else if (cfg->reset_gpio_index >= 0 || cfg->spkid_gpio_index >= 0) {
178+
dev_warn(cs35l41->dev, "Cannot add Reset/Speaker ID/SPI CS GPIO Mapping, "
179+
"_DSD already exists.\n");
180+
}
181+
182+
if (cfg->bus == SPI) {
183+
cs35l41->index = id;
184+
/*
185+
* Manually set the Chip Select for the second amp <cs_gpio_index> in the node.
186+
* This is only supported for systems with 2 amps, since we cannot expand the
187+
* default number of chip selects without using cs-gpios
188+
* The CS GPIO must be set high prior to communicating with the first amp (which
189+
* uses a native chip select), to ensure the second amp does not clash with the
190+
* first.
191+
*/
192+
if (cfg->cs_gpio_index >= 0) {
193+
spi = to_spi_device(cs35l41->dev);
194+
195+
if (cfg->num_amps != 2) {
196+
dev_warn(cs35l41->dev,
197+
"Cannot update SPI CS, Number of Amps (%d) != 2\n",
198+
cfg->num_amps);
199+
} else if (dsd_found) {
200+
dev_warn(cs35l41->dev,
201+
"Cannot update SPI CS, _DSD already exists.\n");
202+
} else {
203+
/*
204+
* This is obtained using driver_gpios, since only one GPIO for CS
205+
* exists, this can be obtained using index 0.
206+
*/
207+
cs_gpiod = gpiod_get_index(physdev, "cs", 0, GPIOD_OUT_LOW);
208+
if (IS_ERR(cs_gpiod)) {
209+
dev_err(cs35l41->dev,
210+
"Unable to get Chip Select GPIO descriptor\n");
211+
return PTR_ERR(cs_gpiod);
212+
}
213+
if (id == 1) {
214+
spi_set_csgpiod(spi, 0, cs_gpiod);
215+
cs35l41->cs_gpio = cs_gpiod;
216+
} else {
217+
gpiod_set_value_cansleep(cs_gpiod, true);
218+
gpiod_put(cs_gpiod);
219+
}
220+
spi_setup(spi);
221+
}
222+
}
223+
} else {
224+
if (cfg->num_amps > 2)
225+
/*
226+
* i2c addresses for 3/4 amps are used in order: 0x40, 0x41, 0x42, 0x43,
227+
* subtracting 0x40 would give zero-based index
228+
*/
229+
cs35l41->index = id - 0x40;
230+
else
231+
/* i2c addr 0x40 for first amp (always), 0x41/0x42 for 2nd amp */
232+
cs35l41->index = id == 0x40 ? 0 : 1;
233+
}
234+
235+
if (cfg->num_amps == 3)
236+
/* 3 amps means a center channel, so no duplicate channels */
237+
cs35l41->channel_index = 0;
238+
else
239+
/*
240+
* if 4 amps, there are duplicate channels, so they need different indexes
241+
* if 2 amps, no duplicate channels, channel_index would be 0
242+
*/
243+
cs35l41->channel_index = cs35l41->index / 2;
244+
245+
cs35l41->reset_gpio = fwnode_gpiod_get_index(acpi_fwnode_handle(cs35l41->dacpi), "reset",
246+
cs35l41->index, GPIOD_OUT_LOW,
247+
"cs35l41-reset");
248+
cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, cs35l41->index, cfg->num_amps, -1);
249+
250+
hw_cfg->spk_pos = cfg->channel[cs35l41->index];
251+
252+
if (cfg->boost_type == INTERNAL) {
253+
hw_cfg->bst_type = CS35L41_INT_BOOST;
254+
hw_cfg->bst_ind = cfg->boost_ind_nanohenry;
255+
hw_cfg->bst_ipk = cfg->boost_peak_milliamp;
256+
hw_cfg->bst_cap = cfg->boost_cap_microfarad;
257+
hw_cfg->gpio1.func = CS35L41_NOT_USED;
258+
hw_cfg->gpio1.valid = true;
259+
} else {
260+
hw_cfg->bst_type = CS35L41_EXT_BOOST;
261+
hw_cfg->bst_ind = -1;
262+
hw_cfg->bst_ipk = -1;
263+
hw_cfg->bst_cap = -1;
264+
hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH;
265+
hw_cfg->gpio1.valid = true;
266+
}
267+
268+
hw_cfg->gpio2.func = CS35L41_INTERRUPT;
269+
hw_cfg->gpio2.valid = true;
270+
hw_cfg->valid = true;
271+
272+
return 0;
273+
}
12274

13275
/*
14276
* Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work.
@@ -43,44 +305,6 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy
43305
return 0;
44306
}
45307

46-
/*
47-
* Device 103C89C6 does have _DSD, however it is setup to use the wrong boost type.
48-
* We can override the _DSD to correct the boost type here.
49-
* Since this laptop has valid ACPI, we do not need to handle cs-gpios, since that already exists
50-
* in the ACPI.
51-
*/
52-
static int hp_vision_acpi_fix(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
53-
const char *hid)
54-
{
55-
struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
56-
57-
dev_info(cs35l41->dev, "Adding DSD properties for %s\n", cs35l41->acpi_subsystem_id);
58-
59-
cs35l41->index = id;
60-
cs35l41->channel_index = 0;
61-
62-
/*
63-
* This system has _DSD, it just contains an error, so we can still get the reset using
64-
* the "reset" label.
65-
*/
66-
cs35l41->reset_gpio = fwnode_gpiod_get_index(acpi_fwnode_handle(cs35l41->dacpi), "reset",
67-
cs35l41->index, GPIOD_OUT_LOW,
68-
"cs35l41-reset");
69-
cs35l41->speaker_id = -ENOENT;
70-
hw_cfg->spk_pos = cs35l41->index ? 0 : 1; // right:left
71-
hw_cfg->gpio1.func = CS35L41_NOT_USED;
72-
hw_cfg->gpio1.valid = true;
73-
hw_cfg->gpio2.func = CS35L41_INTERRUPT;
74-
hw_cfg->gpio2.valid = true;
75-
hw_cfg->bst_type = CS35L41_INT_BOOST;
76-
hw_cfg->bst_ind = 1000;
77-
hw_cfg->bst_ipk = 4500;
78-
hw_cfg->bst_cap = 24;
79-
hw_cfg->valid = true;
80-
81-
return 0;
82-
}
83-
84308
struct cs35l41_prop_model {
85309
const char *hid;
86310
const char *ssid;
@@ -91,7 +315,7 @@ struct cs35l41_prop_model {
91315
static const struct cs35l41_prop_model cs35l41_prop_model_table[] = {
92316
{ "CLSA0100", NULL, lenovo_legion_no_acpi },
93317
{ "CLSA0101", NULL, lenovo_legion_no_acpi },
94-
{ "CSC3551", "103C89C6", hp_vision_acpi_fix },
318+
{ "CSC3551", "103C89C6", generic_dsd_config },
95319
{}
96320
};
97321

@@ -104,7 +328,7 @@ int cs35l41_add_dsd_properties(struct cs35l41_hda *cs35l41, struct device *physd
104328
if (!strcmp(model->hid, hid) &&
105329
(!model->ssid ||
106330
(cs35l41->acpi_subsystem_id &&
107-
!strcmp(model->ssid, cs35l41->acpi_subsystem_id))))
331+
!strcasecmp(model->ssid, cs35l41->acpi_subsystem_id))))
108332
return model->add_prop(cs35l41, physdev, id, hid);
109333
}
110334

0 commit comments

Comments
 (0)