Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: cs35l41: Fix broken shared boost activation

Enabling the active/passive shared boosts requires setting SYNC_EN, but
*not* before receiving the PLL Lock signal.

Due to improper error handling, it was not obvious that waiting for the
completion operation times out and, consequently, the shared boost is
never activated.

Further investigations revealed the signal is triggered while
snd_pcm_start() is executed, right after receiving the
SNDRV_PCM_TRIGGER_START command, which happens long after the
SND_SOC_DAPM_PRE_PMU event handler is invoked as part of
snd_pcm_prepare(). That is where cs35l41_global_enable() is called
from.

Increasing the wait duration doesn't help, as it only causes an
unnecessary delay in the invocation of snd_pcm_start(). Moving the wait
and the subsequent regmap operations to the SNDRV_PCM_TRIGGER_START
callback is not a solution either, since they would be executed in an
IRQ-off atomic context.

Solve the issue by setting the SYNC_EN bit in PWR_CTRL3 register right
after receiving the PLL Lock interrupt.

Additionally, drop the unnecessary writes to PWR_CTRL1 register, part of
the original mdsync_up_seq, which would have toggled GLOBAL_EN with
unwanted consequences on PLL locking behavior.

Fixes: f5030564938b ("ALSA: cs35l41: Add shared boost feature")
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Reviewed-by: David Rhodes <david.rhodes@cirrus.com>
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Link: https://lore.kernel.org/r/20230907171010.1447274-5-cristian.ciocaltea@collabora.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Cristian Ciocaltea and committed by
Mark Brown
77bf613f 5ad668a9

+55 -39
+2 -2
include/sound/cs35l41.h
··· 11 11 #define __CS35L41_H 12 12 13 13 #include <linux/regmap.h> 14 - #include <linux/completion.h> 15 14 #include <linux/firmware/cirrus/cs_dsp.h> 16 15 17 16 #define CS35L41_FIRSTREG 0x00000000 ··· 901 902 int cs35l41_init_boost(struct device *dev, struct regmap *regmap, 902 903 struct cs35l41_hw_cfg *hw_cfg); 903 904 bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type); 905 + int cs35l41_mdsync_up(struct regmap *regmap); 904 906 int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type, 905 - int enable, struct completion *pll_lock, bool firmware_running); 907 + int enable, bool firmware_running); 906 908 907 909 #endif /* __CS35L41_H */
+2 -2
sound/pci/hda/cs35l41_hda.c
··· 527 527 528 528 dev_dbg(dev, "Play (Complete)\n"); 529 529 530 - cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL, 530 + cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, 531 531 cs35l41->firmware_running); 532 532 if (cs35l41->firmware_running) { 533 533 regmap_multi_reg_write(reg, cs35l41_hda_unmute_dsp, ··· 546 546 dev_dbg(dev, "Pause (Start)\n"); 547 547 548 548 regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); 549 - cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL, 549 + cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, 550 550 cs35l41->firmware_running); 551 551 } 552 552
+36 -25
sound/soc/codecs/cs35l41-lib.c
··· 1192 1192 } 1193 1193 EXPORT_SYMBOL_GPL(cs35l41_safe_reset); 1194 1194 1195 + /* 1196 + * Enabling the CS35L41_SHD_BOOST_ACTV and CS35L41_SHD_BOOST_PASS shared boosts 1197 + * does also require a call to cs35l41_mdsync_up(), but not before getting the 1198 + * PLL Lock signal. 1199 + * 1200 + * PLL Lock seems to be triggered soon after snd_pcm_start() is executed and 1201 + * SNDRV_PCM_TRIGGER_START command is processed, which happens (long) after the 1202 + * SND_SOC_DAPM_PRE_PMU event handler is invoked as part of snd_pcm_prepare(). 1203 + * 1204 + * This event handler is where cs35l41_global_enable() is normally called from, 1205 + * but waiting for PLL Lock here will time out. Increasing the wait duration 1206 + * will not help, as the only consequence of it would be to add an unnecessary 1207 + * delay in the invocation of snd_pcm_start(). 1208 + * 1209 + * Trying to move the wait in the SNDRV_PCM_TRIGGER_START callback is not a 1210 + * solution either, as the trigger is executed in an IRQ-off atomic context. 1211 + * 1212 + * The current approach is to invoke cs35l41_mdsync_up() right after receiving 1213 + * the PLL Lock interrupt, in the IRQ handler. 1214 + */ 1195 1215 int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type, 1196 - int enable, struct completion *pll_lock, bool firmware_running) 1216 + int enable, bool firmware_running) 1197 1217 { 1198 1218 int ret; 1199 1219 unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status, pup_pdn_mask; ··· 1222 1202 {CS35L41_PWR_CTRL3, 0}, 1223 1203 {CS35L41_GPIO_PAD_CONTROL, 0}, 1224 1204 {CS35L41_PWR_CTRL1, 0, 3000}, 1225 - }; 1226 - struct reg_sequence cs35l41_mdsync_up_seq[] = { 1227 - {CS35L41_PWR_CTRL3, 0}, 1228 - {CS35L41_PWR_CTRL1, 0x00000000, 3000}, 1229 - {CS35L41_PWR_CTRL1, 0x00000001, 3000}, 1230 1205 }; 1231 1206 1232 1207 pup_pdn_mask = enable ? CS35L41_PUP_DONE_MASK : CS35L41_PDN_DONE_MASK; ··· 1256 1241 cs35l41_mdsync_down_seq[0].def = pwr_ctrl3; 1257 1242 cs35l41_mdsync_down_seq[1].def = pad_control; 1258 1243 cs35l41_mdsync_down_seq[2].def = pwr_ctrl1; 1244 + 1259 1245 ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_down_seq, 1260 1246 ARRAY_SIZE(cs35l41_mdsync_down_seq)); 1261 - if (ret || !enable) 1262 - break; 1263 - 1264 - if (!pll_lock) 1265 - return -EINVAL; 1266 - 1267 - ret = wait_for_completion_timeout(pll_lock, msecs_to_jiffies(1000)); 1268 - if (ret == 0) { 1269 - dev_err(dev, "Timed out waiting for pll_lock\n"); 1270 - return -ETIMEDOUT; 1271 - } 1272 - 1273 - regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3); 1274 - pwr_ctrl3 |= CS35L41_SYNC_EN_MASK; 1275 - cs35l41_mdsync_up_seq[0].def = pwr_ctrl3; 1276 - ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq, 1277 - ARRAY_SIZE(cs35l41_mdsync_up_seq)); 1278 - if (ret) 1247 + /* Activation to be completed later via cs35l41_mdsync_up() */ 1248 + if (ret || enable) 1279 1249 return ret; 1280 1250 1281 1251 ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, ··· 1269 1269 if (ret) 1270 1270 dev_err(dev, "Enable(%d) failed: %d\n", enable, ret); 1271 1271 1272 - // Clear PUP/PDN status 1272 + /* Clear PUP/PDN status */ 1273 1273 regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask); 1274 1274 break; 1275 1275 case CS35L41_INT_BOOST: ··· 1350 1350 return ret; 1351 1351 } 1352 1352 EXPORT_SYMBOL_GPL(cs35l41_global_enable); 1353 + 1354 + /* 1355 + * To be called after receiving the IRQ Lock interrupt, in order to complete 1356 + * any shared boost activation initiated by cs35l41_global_enable(). 1357 + */ 1358 + int cs35l41_mdsync_up(struct regmap *regmap) 1359 + { 1360 + return regmap_update_bits(regmap, CS35L41_PWR_CTRL3, 1361 + CS35L41_SYNC_EN_MASK, CS35L41_SYNC_EN_MASK); 1362 + } 1363 + EXPORT_SYMBOL_GPL(cs35l41_mdsync_up); 1353 1364 1354 1365 int cs35l41_gpio_config(struct regmap *regmap, struct cs35l41_hw_cfg *hw_cfg) 1355 1366 {
+15 -9
sound/soc/codecs/cs35l41.c
··· 459 459 460 460 if (status[2] & CS35L41_PLL_LOCK) { 461 461 regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS3, CS35L41_PLL_LOCK); 462 - complete(&cs35l41->pll_lock); 462 + 463 + if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV || 464 + cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS) { 465 + ret = cs35l41_mdsync_up(cs35l41->regmap); 466 + if (ret) 467 + dev_err(cs35l41->dev, "MDSYNC-up failed: %d\n", ret); 468 + else 469 + dev_dbg(cs35l41->dev, "MDSYNC-up done\n"); 470 + 471 + dev_dbg(cs35l41->dev, "PUP-done status: %d\n", 472 + !!(status[0] & CS35L41_PUP_DONE_MASK)); 473 + } 474 + 463 475 ret = IRQ_HANDLED; 464 476 } 465 477 ··· 512 500 ARRAY_SIZE(cs35l41_pup_patch)); 513 501 514 502 ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, 515 - 1, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running); 503 + 1, cs35l41->dsp.cs_dsp.running); 516 504 break; 517 505 case SND_SOC_DAPM_POST_PMD: 518 506 ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, 519 - 0, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running); 507 + 0, cs35l41->dsp.cs_dsp.running); 520 508 521 509 regmap_multi_reg_write_bypassed(cs35l41->regmap, 522 510 cs35l41_pdn_patch, ··· 814 802 static int cs35l41_pcm_startup(struct snd_pcm_substream *substream, 815 803 struct snd_soc_dai *dai) 816 804 { 817 - struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); 818 - 819 - reinit_completion(&cs35l41->pll_lock); 820 - 821 805 if (substream->runtime) 822 806 return snd_pcm_hw_constraint_list(substream->runtime, 0, 823 807 SNDRV_PCM_HW_PARAM_RATE, ··· 1280 1272 cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV) 1281 1273 regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK, 1282 1274 0 << CS35L41_INT3_PLL_LOCK_SHIFT); 1283 - 1284 - init_completion(&cs35l41->pll_lock); 1285 1275 1286 1276 ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL, cs35l41_irq, 1287 1277 IRQF_ONESHOT | IRQF_SHARED | irq_pol,
-1
sound/soc/codecs/cs35l41.h
··· 33 33 int irq; 34 34 /* GPIO for /RST */ 35 35 struct gpio_desc *reset_gpio; 36 - struct completion pll_lock; 37 36 }; 38 37 39 38 int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg);