Skip to content

Commit 4b5d89a

Browse files
rfvirgilgregkh
authored andcommitted
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization
[ Upstream commit c14f09f ] Rewrite the handling of ASP1 TX mixer mux initialization to prevent a deadlock during component_remove(). The firmware can overwrite the ASP1 TX mixer registers with system-specific settings. This is mainly for hardware that uses the ASP as a chip-to-chip link controlled by the firmware. Because of this the driver cannot know the starting state of the ASP1 mixer muxes until the firmware has been downloaded and rebooted. The original workaround for this was to queue a work function from the dsp_work() job. This work then read the register values (populating the regmap cache the first time around) and then called snd_soc_dapm_mux_update_power(). The problem with this is that it was ultimately triggered by cs35l56_component_probe() queueing dsp_work, which meant that it would be running in parallel with the rest of the ASoC component and card initialization. To prevent accessing DAPM before it was fully initialized the work function took the card mutex. But this would deadlock if cs35l56_component_remove() was called before the work job had completed, because ASoC calls component_remove() with the card mutex held. This new version removes the work function. Instead the regmap cache and DAPM mux widgets are initialized the first time any of the associated ALSA controls is read or written. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: 07f7d6e ("ASoC: cs35l56: Fix for initializing ASP1 mixer registers") Link: https://lore.kernel.org/r/20240208123742.1278104-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 9f05fe5 commit 4b5d89a

2 files changed

Lines changed: 73 additions & 82 deletions

File tree

sound/soc/codecs/cs35l56.c

Lines changed: 72 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -68,63 +68,7 @@ static const char * const cs35l56_asp1_mux_control_names[] = {
6868
"ASP1 TX1 Source", "ASP1 TX2 Source", "ASP1 TX3 Source", "ASP1 TX4 Source"
6969
};
7070

71-
static int cs35l56_dspwait_asp1tx_get(struct snd_kcontrol *kcontrol,
72-
struct snd_ctl_elem_value *ucontrol)
73-
{
74-
struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
75-
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
76-
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
77-
int index = e->shift_l;
78-
unsigned int addr, val;
79-
int ret;
80-
81-
/* Wait for mux to be initialized */
82-
cs35l56_wait_dsp_ready(cs35l56);
83-
flush_work(&cs35l56->mux_init_work);
84-
85-
addr = cs35l56_asp1_mixer_regs[index];
86-
ret = regmap_read(cs35l56->base.regmap, addr, &val);
87-
if (ret)
88-
return ret;
89-
90-
val &= CS35L56_ASP_TXn_SRC_MASK;
91-
ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(e, val);
92-
93-
return 0;
94-
}
95-
96-
static int cs35l56_dspwait_asp1tx_put(struct snd_kcontrol *kcontrol,
97-
struct snd_ctl_elem_value *ucontrol)
98-
{
99-
struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
100-
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
101-
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
102-
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
103-
int item = ucontrol->value.enumerated.item[0];
104-
int index = e->shift_l;
105-
unsigned int addr, val;
106-
bool changed;
107-
int ret;
108-
109-
/* Wait for mux to be initialized */
110-
cs35l56_wait_dsp_ready(cs35l56);
111-
flush_work(&cs35l56->mux_init_work);
112-
113-
addr = cs35l56_asp1_mixer_regs[index];
114-
val = snd_soc_enum_item_to_val(e, item);
115-
116-
ret = regmap_update_bits_check(cs35l56->base.regmap, addr,
117-
CS35L56_ASP_TXn_SRC_MASK, val, &changed);
118-
if (!ret)
119-
return ret;
120-
121-
if (changed)
122-
snd_soc_dapm_mux_update_power(dapm, kcontrol, item, e, NULL);
123-
124-
return changed;
125-
}
126-
127-
static void cs35l56_mark_asp1_mixer_widgets_dirty(struct cs35l56_private *cs35l56)
71+
static int cs35l56_sync_asp1_mixer_widgets_with_firmware(struct cs35l56_private *cs35l56)
12872
{
12973
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l56->component);
13074
const char *prefix = cs35l56->component->name_prefix;
@@ -135,13 +79,19 @@ static void cs35l56_mark_asp1_mixer_widgets_dirty(struct cs35l56_private *cs35l5
13579
unsigned int val[4];
13680
int i, item, ret;
13781

82+
if (cs35l56->asp1_mixer_widgets_initialized)
83+
return 0;
84+
13885
/*
13986
* Resume so we can read the registers from silicon if the regmap
14087
* cache has not yet been populated.
14188
*/
14289
ret = pm_runtime_resume_and_get(cs35l56->base.dev);
14390
if (ret < 0)
144-
return;
91+
return ret;
92+
93+
/* Wait for firmware download and reboot */
94+
cs35l56_wait_dsp_ready(cs35l56);
14595

14696
ret = regmap_bulk_read(cs35l56->base.regmap, CS35L56_ASP1TX1_INPUT,
14797
val, ARRAY_SIZE(val));
@@ -151,12 +101,9 @@ static void cs35l56_mark_asp1_mixer_widgets_dirty(struct cs35l56_private *cs35l5
151101

152102
if (ret) {
153103
dev_err(cs35l56->base.dev, "Failed to read ASP1 mixer regs: %d\n", ret);
154-
return;
104+
return ret;
155105
}
156106

157-
snd_soc_card_mutex_lock(dapm->card);
158-
WARN_ON(!dapm->card->instantiated);
159-
160107
for (i = 0; i < ARRAY_SIZE(cs35l56_asp1_mux_control_names); ++i) {
161108
name = cs35l56_asp1_mux_control_names[i];
162109

@@ -176,16 +123,65 @@ static void cs35l56_mark_asp1_mixer_widgets_dirty(struct cs35l56_private *cs35l5
176123
snd_soc_dapm_mux_update_power(dapm, kcontrol, item, e, NULL);
177124
}
178125

179-
snd_soc_card_mutex_unlock(dapm->card);
126+
cs35l56->asp1_mixer_widgets_initialized = true;
127+
128+
return 0;
180129
}
181130

182-
static void cs35l56_mux_init_work(struct work_struct *work)
131+
static int cs35l56_dspwait_asp1tx_get(struct snd_kcontrol *kcontrol,
132+
struct snd_ctl_elem_value *ucontrol)
183133
{
184-
struct cs35l56_private *cs35l56 = container_of(work,
185-
struct cs35l56_private,
186-
mux_init_work);
134+
struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
135+
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
136+
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
137+
int index = e->shift_l;
138+
unsigned int addr, val;
139+
int ret;
187140

188-
cs35l56_mark_asp1_mixer_widgets_dirty(cs35l56);
141+
ret = cs35l56_sync_asp1_mixer_widgets_with_firmware(cs35l56);
142+
if (ret)
143+
return ret;
144+
145+
addr = cs35l56_asp1_mixer_regs[index];
146+
ret = regmap_read(cs35l56->base.regmap, addr, &val);
147+
if (ret)
148+
return ret;
149+
150+
val &= CS35L56_ASP_TXn_SRC_MASK;
151+
ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(e, val);
152+
153+
return 0;
154+
}
155+
156+
static int cs35l56_dspwait_asp1tx_put(struct snd_kcontrol *kcontrol,
157+
struct snd_ctl_elem_value *ucontrol)
158+
{
159+
struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
160+
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
161+
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
162+
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
163+
int item = ucontrol->value.enumerated.item[0];
164+
int index = e->shift_l;
165+
unsigned int addr, val;
166+
bool changed;
167+
int ret;
168+
169+
ret = cs35l56_sync_asp1_mixer_widgets_with_firmware(cs35l56);
170+
if (ret)
171+
return ret;
172+
173+
addr = cs35l56_asp1_mixer_regs[index];
174+
val = snd_soc_enum_item_to_val(e, item);
175+
176+
ret = regmap_update_bits_check(cs35l56->base.regmap, addr,
177+
CS35L56_ASP_TXn_SRC_MASK, val, &changed);
178+
if (!ret)
179+
return ret;
180+
181+
if (changed)
182+
snd_soc_dapm_mux_update_power(dapm, kcontrol, item, e, NULL);
183+
184+
return changed;
189185
}
190186

191187
static DECLARE_TLV_DB_SCALE(vol_tlv, -10000, 25, 0);
@@ -909,14 +905,6 @@ static void cs35l56_dsp_work(struct work_struct *work)
909905
else
910906
cs35l56_patch(cs35l56);
911907

912-
913-
/*
914-
* Set starting value of ASP1 mux widgets. Updating a mux takes
915-
* the DAPM mutex. Post this to a separate job so that DAPM
916-
* power-up can wait for dsp_work to complete without deadlocking
917-
* on the DAPM mutex.
918-
*/
919-
queue_work(cs35l56->dsp_wq, &cs35l56->mux_init_work);
920908
err:
921909
pm_runtime_mark_last_busy(cs35l56->base.dev);
922910
pm_runtime_put_autosuspend(cs35l56->base.dev);
@@ -953,6 +941,13 @@ static int cs35l56_component_probe(struct snd_soc_component *component)
953941
debugfs_create_bool("can_hibernate", 0444, debugfs_root, &cs35l56->base.can_hibernate);
954942
debugfs_create_bool("fw_patched", 0444, debugfs_root, &cs35l56->base.fw_patched);
955943

944+
/*
945+
* The widgets for the ASP1TX mixer can't be initialized
946+
* until the firmware has been downloaded and rebooted.
947+
*/
948+
regcache_drop_region(cs35l56->base.regmap, CS35L56_ASP1TX1_INPUT, CS35L56_ASP1TX4_INPUT);
949+
cs35l56->asp1_mixer_widgets_initialized = false;
950+
956951
queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
957952

958953
return 0;
@@ -963,7 +958,6 @@ static void cs35l56_component_remove(struct snd_soc_component *component)
963958
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
964959

965960
cancel_work_sync(&cs35l56->dsp_work);
966-
cancel_work_sync(&cs35l56->mux_init_work);
967961

968962
if (cs35l56->dsp.cs_dsp.booted)
969963
wm_adsp_power_down(&cs35l56->dsp);
@@ -1034,10 +1028,8 @@ int cs35l56_system_suspend(struct device *dev)
10341028

10351029
dev_dbg(dev, "system_suspend\n");
10361030

1037-
if (cs35l56->component) {
1031+
if (cs35l56->component)
10381032
flush_work(&cs35l56->dsp_work);
1039-
cancel_work_sync(&cs35l56->mux_init_work);
1040-
}
10411033

10421034
/*
10431035
* The interrupt line is normally shared, but after we start suspending
@@ -1188,7 +1180,6 @@ static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
11881180
return -ENOMEM;
11891181

11901182
INIT_WORK(&cs35l56->dsp_work, cs35l56_dsp_work);
1191-
INIT_WORK(&cs35l56->mux_init_work, cs35l56_mux_init_work);
11921183

11931184
dsp = &cs35l56->dsp;
11941185
cs35l56_init_cs_dsp(&cs35l56->base, &dsp->cs_dsp);

sound/soc/codecs/cs35l56.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ struct cs35l56_private {
3434
struct wm_adsp dsp; /* must be first member */
3535
struct cs35l56_base base;
3636
struct work_struct dsp_work;
37-
struct work_struct mux_init_work;
3837
struct workqueue_struct *dsp_wq;
3938
struct snd_soc_component *component;
4039
struct regulator_bulk_data supplies[CS35L56_NUM_BULK_SUPPLIES];
@@ -51,6 +50,7 @@ struct cs35l56_private {
5150
u8 asp_slot_count;
5251
bool tdm_mode;
5352
bool sysclk_set;
53+
bool asp1_mixer_widgets_initialized;
5454
u8 old_sdw_clock_scale;
5555
};
5656

0 commit comments

Comments
 (0)