linux/sound/soc/codecs/cs35l56.c

1597 lines
46 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
//
// Driver for Cirrus Logic CS35L56 smart amp
//
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
#include <linux/acpi.h>
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
#include <linux/interrupt.h>
#include <linux/math.h>
#include <linux/module.h>
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/soundwire/sdw.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <sound/cs-amp-lib.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "wm_adsp.h"
#include "cs35l56.h"
static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
static void cs35l56_wait_dsp_ready(struct cs35l56_private *cs35l56)
{
/* Wait for patching to complete */
flush_work(&cs35l56->dsp_work);
}
static int cs35l56_dspwait_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
cs35l56_wait_dsp_ready(cs35l56);
return snd_soc_get_volsw(kcontrol, ucontrol);
}
static int cs35l56_dspwait_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
cs35l56_wait_dsp_ready(cs35l56);
return snd_soc_put_volsw(kcontrol, ucontrol);
}
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
static const unsigned short cs35l56_asp1_mixer_regs[] = {
CS35L56_ASP1TX1_INPUT, CS35L56_ASP1TX2_INPUT,
CS35L56_ASP1TX3_INPUT, CS35L56_ASP1TX4_INPUT,
};
static const char * const cs35l56_asp1_mux_control_names[] = {
"ASP1 TX1 Source", "ASP1 TX2 Source", "ASP1 TX3 Source", "ASP1 TX4 Source"
};
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
static int cs35l56_sync_asp1_mixer_widgets_with_firmware(struct cs35l56_private *cs35l56)
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cs35l56->component);
const char *prefix = cs35l56->component->name_prefix;
char full_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
const char *name;
struct snd_kcontrol *kcontrol;
struct soc_enum *e;
unsigned int val[4];
int i, item, ret;
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
if (cs35l56->asp1_mixer_widgets_initialized)
return 0;
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
/*
* Resume so we can read the registers from silicon if the regmap
* cache has not yet been populated.
*/
ret = pm_runtime_resume_and_get(cs35l56->base.dev);
if (ret < 0)
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
return ret;
/* Wait for firmware download and reboot */
cs35l56_wait_dsp_ready(cs35l56);
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
ret = regmap_bulk_read(cs35l56->base.regmap, CS35L56_ASP1TX1_INPUT,
val, ARRAY_SIZE(val));
pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_put_autosuspend(cs35l56->base.dev);
if (ret) {
dev_err(cs35l56->base.dev, "Failed to read ASP1 mixer regs: %d\n", ret);
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
return ret;
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
}
for (i = 0; i < ARRAY_SIZE(cs35l56_asp1_mux_control_names); ++i) {
name = cs35l56_asp1_mux_control_names[i];
if (prefix) {
snprintf(full_name, sizeof(full_name), "%s %s", prefix, name);
name = full_name;
}
ASoC: soc-card: Fix missing locking in snd_soc_card_get_kcontrol() snd_soc_card_get_kcontrol() must be holding a read lock on card->controls_rwsem while walking the controls list. Compare with snd_ctl_find_numid(). The existing function is renamed snd_soc_card_get_kcontrol_locked() so that it can be called from contexts that are already holding card->controls_rwsem (for example, control get/put functions). There are few direct or indirect callers of snd_soc_card_get_kcontrol(), and most are safe. Three require changes, which have been included in this patch: codecs/cs35l45.c: cs35l45_activate_ctl() is called from a control put() function so is changed to call snd_soc_card_get_kcontrol_locked(). codecs/cs35l56.c: cs35l56_sync_asp1_mixer_widgets_with_firmware() is called from control get()/put() functions so is changed to call snd_soc_card_get_kcontrol_locked(). fsl/fsl_xcvr.c: fsl_xcvr_activate_ctl() is called from three places, one of which already holds card->controls_rwsem: 1. fsl_xcvr_mode_put(), a control put function, which will already be holding card->controls_rwsem. 2. fsl_xcvr_startup(), a DAI startup function. 3. fsl_xcvr_shutdown(), a DAI shutdown function. To fix this, fsl_xcvr_activate_ctl() has been changed to call snd_soc_card_get_kcontrol_locked() so that it is safe to call directly from fsl_xcvr_mode_put(). The fsl_xcvr_startup() and fsl_xcvr_shutdown() functions have been changed to take a read lock on card->controls_rsem() around calls to fsl_xcvr_activate_ctl(). While this is not very elegant, it keeps the change small, to avoid this patch creating a large collateral churn in fsl/fsl_xcvr.c. Analysis of other callers of snd_soc_card_get_kcontrol() is that they do not need any changes, they are not holding card->controls_rwsem when they call snd_soc_card_get_kcontrol(). Direct callers of snd_soc_card_get_kcontrol(): fsl/fsl_spdif.c: fsl_spdif_dai_probe() - DAI probe function fsl/fsl_micfil.c: voice_detected_fn() - IRQ handler Indirect callers via soc_component_notify_control(): codecs/cs42l43: cs42l43_mic_shutter() - IRQ handler codecs/cs42l43: cs42l43_spk_shutter() - IRQ handler codecs/ak4118.c: ak4118_irq_handler() - IRQ handler codecs/wm_adsp.c: wm_adsp_write_ctl() - not currently used Indirect callers via snd_soc_limit_volume(): qcom/sc8280xp.c: sc8280xp_snd_init() - DAIlink init function ti/rx51.c: rx51_aic34_init() - DAI init function I don't have hardware to test the fsl/*, qcom/sc828xp.c, ti/rx51.c and ak4118.c changes. Backport note: The fsl/, qcom/, cs35l45, cs35l56 and cs42l43 callers were added since the Fixes commit so won't all be present on older kernels. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: 209c6cdfd283 ("ASoC: soc-card: move snd_soc_card_get_kcontrol() to soc-card") Link: https://lore.kernel.org/r/20240221123710.690224-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-02-21 12:37:10 +00:00
kcontrol = snd_soc_card_get_kcontrol_locked(dapm->card, name);
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
if (!kcontrol) {
dev_warn(cs35l56->base.dev, "Could not find control %s\n", name);
continue;
}
e = (struct soc_enum *)kcontrol->private_value;
item = snd_soc_enum_val_to_item(e, val[i] & CS35L56_ASP_TXn_SRC_MASK);
snd_soc_dapm_mux_update_power(dapm, kcontrol, item, e, NULL);
}
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
cs35l56->asp1_mixer_widgets_initialized = true;
return 0;
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
}
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
static int cs35l56_dspwait_asp1tx_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
{
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
int index = e->shift_l;
unsigned int addr, val;
int ret;
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
ret = cs35l56_sync_asp1_mixer_widgets_with_firmware(cs35l56);
if (ret)
return ret;
addr = cs35l56_asp1_mixer_regs[index];
ret = regmap_read(cs35l56->base.regmap, addr, &val);
if (ret)
return ret;
val &= CS35L56_ASP_TXn_SRC_MASK;
ucontrol->value.enumerated.item[0] = snd_soc_enum_val_to_item(e, val);
return 0;
}
static int cs35l56_dspwait_asp1tx_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
int item = ucontrol->value.enumerated.item[0];
int index = e->shift_l;
unsigned int addr, val;
bool changed;
int ret;
ret = cs35l56_sync_asp1_mixer_widgets_with_firmware(cs35l56);
if (ret)
return ret;
addr = cs35l56_asp1_mixer_regs[index];
val = snd_soc_enum_item_to_val(e, item);
ret = regmap_update_bits_check(cs35l56->base.regmap, addr,
CS35L56_ASP_TXn_SRC_MASK, val, &changed);
if (ret)
return ret;
if (changed)
snd_soc_dapm_mux_update_power(dapm, kcontrol, item, e, NULL);
return changed;
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
}
static DECLARE_TLV_DB_SCALE(vol_tlv, -10000, 25, 0);
static const struct snd_kcontrol_new cs35l56_controls[] = {
SOC_SINGLE_EXT("Speaker Switch",
CS35L56_MAIN_RENDER_USER_MUTE, 0, 1, 1,
cs35l56_dspwait_get_volsw, cs35l56_dspwait_put_volsw),
SOC_SINGLE_S_EXT_TLV("Speaker Volume",
CS35L56_MAIN_RENDER_USER_VOLUME,
6, -400, 400, 9, 0,
cs35l56_dspwait_get_volsw,
cs35l56_dspwait_put_volsw,
vol_tlv),
SOC_SINGLE_EXT("Posture Number", CS35L56_MAIN_POSTURE_NUMBER,
0, 255, 0,
cs35l56_dspwait_get_volsw, cs35l56_dspwait_put_volsw),
};
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx1_enum,
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SND_SOC_NOPM,
0, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new asp1_tx1_mux =
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SOC_DAPM_ENUM_EXT("ASP1TX1 SRC", cs35l56_asp1tx1_enum,
cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx2_enum,
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SND_SOC_NOPM,
1, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new asp1_tx2_mux =
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SOC_DAPM_ENUM_EXT("ASP1TX2 SRC", cs35l56_asp1tx2_enum,
cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx3_enum,
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SND_SOC_NOPM,
2, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new asp1_tx3_mux =
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SOC_DAPM_ENUM_EXT("ASP1TX3 SRC", cs35l56_asp1tx3_enum,
cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_asp1tx4_enum,
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SND_SOC_NOPM,
3, 0,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new asp1_tx4_mux =
ASoC: cs35l56: Fix for initializing ASP1 mixer registers Defer initializing the state of the ASP1 mixer registers until the firmware has been downloaded and rebooted. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However, if it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. Even if the ASP is used as a firmware-firmware interconnect it is useful to have ALSA controls for the ASP mixer. They are at least useful for debugging. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP mixer registers. This means that we can't assume the default state of these registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. DAPM normally reads the initial state of mux registers during probe() but this would mean blocking probe() for several seconds until the firmware has initialized them. To avoid this, the mixer muxes are set SND_SOC_NOPM to prevent DAPM trying to read the register state. Custom get/set callbacks are implemented for ALSA control access, and these can safely block waiting for the firmware download. After the firmware download has completed, the state of the mux registers is known so a work job is queued to call snd_soc_dapm_mux_update_power() on each of the mux widgets. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-11-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:29 +00:00
SOC_DAPM_ENUM_EXT("ASP1TX4 SRC", cs35l56_asp1tx4_enum,
cs35l56_dspwait_asp1tx_get, cs35l56_dspwait_asp1tx_put);
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx1_enum,
CS35L56_SWIRE_DP3_CH1_INPUT,
0, CS35L56_SWIRETXn_SRC_MASK,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new sdw1_tx1_mux =
SOC_DAPM_ENUM("SDW1TX1 SRC", cs35l56_sdw1tx1_enum);
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx2_enum,
CS35L56_SWIRE_DP3_CH2_INPUT,
0, CS35L56_SWIRETXn_SRC_MASK,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new sdw1_tx2_mux =
SOC_DAPM_ENUM("SDW1TX2 SRC", cs35l56_sdw1tx2_enum);
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx3_enum,
CS35L56_SWIRE_DP3_CH3_INPUT,
0, CS35L56_SWIRETXn_SRC_MASK,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new sdw1_tx3_mux =
SOC_DAPM_ENUM("SDW1TX3 SRC", cs35l56_sdw1tx3_enum);
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l56_sdw1tx4_enum,
CS35L56_SWIRE_DP3_CH4_INPUT,
0, CS35L56_SWIRETXn_SRC_MASK,
cs35l56_tx_input_texts,
cs35l56_tx_input_values);
static const struct snd_kcontrol_new sdw1_tx4_mux =
SOC_DAPM_ENUM("SDW1TX4 SRC", cs35l56_sdw1tx4_enum);
ASoC: cs35l56: Fix to ensure ASP1 registers match cache Add a dummy SUPPLY widget connected to the ASP that forces the chip registers to match the regmap cache when the ASP is powered-up. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However. If it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP registers. This means that we can't assume the default state of the ASP registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. To avoid blocking probe() for several seconds waiting for the firmware, the silicon defaults are assumed. This allows the machine driver to setup the ASP configuration during probe() without being blocked. If the ASP is hooked up and used, the SUPPLY widget ensures that the chip registers match what was configured in the regmap cache. If the machine driver does not hook up the ASP, it is assumed that it won't call any functions to configure the ASP DAI. Therefore the regmap cache will be clean for these registers so a regcache_sync() will not overwrite the chip registers. If the DAI is not hooked up, the dummy SUPPLY widget will not be invoked so it will never force-overwrite the chip registers. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-8-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:26 +00:00
static int cs35l56_asp1_cfg_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
/* Override register values set by firmware boot */
return cs35l56_force_sync_asp1_registers_from_cache(&cs35l56->base);
default:
return 0;
}
}
static int cs35l56_play_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
unsigned int val;
int ret;
dev_dbg(cs35l56->base.dev, "play: %d\n", event);
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
/* Don't wait for ACK, we check in POST_PMU that it completed */
return regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1,
CS35L56_MBOX_CMD_AUDIO_PLAY);
case SND_SOC_DAPM_POST_PMU:
/* Wait for firmware to enter PS0 power state */
ret = regmap_read_poll_timeout(cs35l56->base.regmap,
CS35L56_TRANSDUCER_ACTUAL_PS,
val, (val == CS35L56_PS0),
CS35L56_PS0_POLL_US,
CS35L56_PS0_TIMEOUT_US);
if (ret)
dev_err(cs35l56->base.dev, "PS0 wait failed: %d\n", ret);
return ret;
case SND_SOC_DAPM_POST_PMD:
return cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PAUSE);
default:
return 0;
}
}
static const struct snd_soc_dapm_widget cs35l56_dapm_widgets[] = {
SND_SOC_DAPM_REGULATOR_SUPPLY("VDD_B", 0, 0),
SND_SOC_DAPM_REGULATOR_SUPPLY("VDD_AMP", 0, 0),
ASoC: cs35l56: Fix to ensure ASP1 registers match cache Add a dummy SUPPLY widget connected to the ASP that forces the chip registers to match the regmap cache when the ASP is powered-up. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However. If it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP registers. This means that we can't assume the default state of the ASP registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. To avoid blocking probe() for several seconds waiting for the firmware, the silicon defaults are assumed. This allows the machine driver to setup the ASP configuration during probe() without being blocked. If the ASP is hooked up and used, the SUPPLY widget ensures that the chip registers match what was configured in the regmap cache. If the machine driver does not hook up the ASP, it is assumed that it won't call any functions to configure the ASP DAI. Therefore the regmap cache will be clean for these registers so a regcache_sync() will not overwrite the chip registers. If the DAI is not hooked up, the dummy SUPPLY widget will not be invoked so it will never force-overwrite the chip registers. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-8-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:26 +00:00
SND_SOC_DAPM_SUPPLY("ASP1 CFG", SND_SOC_NOPM, 0, 0, cs35l56_asp1_cfg_event,
SND_SOC_DAPM_PRE_PMU),
SND_SOC_DAPM_SUPPLY("PLAY", SND_SOC_NOPM, 0, 0, cs35l56_play_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_OUT_DRV("AMP", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("SPK"),
SND_SOC_DAPM_PGA_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0, cs35l56_dsp_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_AIF_IN("ASP1RX1", NULL, 0, CS35L56_ASP1_ENABLES1,
CS35L56_ASP_RX1_EN_SHIFT, 0),
SND_SOC_DAPM_AIF_IN("ASP1RX2", NULL, 1, CS35L56_ASP1_ENABLES1,
CS35L56_ASP_RX2_EN_SHIFT, 0),
SND_SOC_DAPM_AIF_OUT("ASP1TX1", NULL, 0, CS35L56_ASP1_ENABLES1,
CS35L56_ASP_TX1_EN_SHIFT, 0),
SND_SOC_DAPM_AIF_OUT("ASP1TX2", NULL, 1, CS35L56_ASP1_ENABLES1,
CS35L56_ASP_TX2_EN_SHIFT, 0),
SND_SOC_DAPM_AIF_OUT("ASP1TX3", NULL, 2, CS35L56_ASP1_ENABLES1,
CS35L56_ASP_TX3_EN_SHIFT, 0),
SND_SOC_DAPM_AIF_OUT("ASP1TX4", NULL, 3, CS35L56_ASP1_ENABLES1,
CS35L56_ASP_TX4_EN_SHIFT, 0),
SND_SOC_DAPM_MUX("ASP1 TX1 Source", SND_SOC_NOPM, 0, 0, &asp1_tx1_mux),
SND_SOC_DAPM_MUX("ASP1 TX2 Source", SND_SOC_NOPM, 0, 0, &asp1_tx2_mux),
SND_SOC_DAPM_MUX("ASP1 TX3 Source", SND_SOC_NOPM, 0, 0, &asp1_tx3_mux),
SND_SOC_DAPM_MUX("ASP1 TX4 Source", SND_SOC_NOPM, 0, 0, &asp1_tx4_mux),
SND_SOC_DAPM_MUX("SDW1 TX1 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx1_mux),
SND_SOC_DAPM_MUX("SDW1 TX2 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx2_mux),
SND_SOC_DAPM_MUX("SDW1 TX3 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx3_mux),
SND_SOC_DAPM_MUX("SDW1 TX4 Source", SND_SOC_NOPM, 0, 0, &sdw1_tx4_mux),
SND_SOC_DAPM_SIGGEN("VMON ADC"),
SND_SOC_DAPM_SIGGEN("IMON ADC"),
SND_SOC_DAPM_SIGGEN("ERRVOL ADC"),
SND_SOC_DAPM_SIGGEN("CLASSH ADC"),
SND_SOC_DAPM_SIGGEN("VDDBMON ADC"),
SND_SOC_DAPM_SIGGEN("VBSTMON ADC"),
SND_SOC_DAPM_SIGGEN("TEMPMON ADC"),
};
#define CS35L56_SRC_ROUTE(name) \
{ name" Source", "ASP1RX1", "ASP1RX1" }, \
{ name" Source", "ASP1RX2", "ASP1RX2" }, \
{ name" Source", "VMON", "VMON ADC" }, \
{ name" Source", "IMON", "IMON ADC" }, \
{ name" Source", "ERRVOL", "ERRVOL ADC" }, \
{ name" Source", "CLASSH", "CLASSH ADC" }, \
{ name" Source", "VDDBMON", "VDDBMON ADC" }, \
{ name" Source", "VBSTMON", "VBSTMON ADC" }, \
{ name" Source", "DSP1TX1", "DSP1" }, \
{ name" Source", "DSP1TX2", "DSP1" }, \
{ name" Source", "DSP1TX3", "DSP1" }, \
{ name" Source", "DSP1TX4", "DSP1" }, \
{ name" Source", "DSP1TX5", "DSP1" }, \
{ name" Source", "DSP1TX6", "DSP1" }, \
{ name" Source", "DSP1TX7", "DSP1" }, \
{ name" Source", "DSP1TX8", "DSP1" }, \
{ name" Source", "TEMPMON", "TEMPMON ADC" }, \
{ name" Source", "INTERPOLATOR", "AMP" }, \
{ name" Source", "SDW1RX1", "SDW1 Playback" }, \
{ name" Source", "SDW1RX2", "SDW1 Playback" },
static const struct snd_soc_dapm_route cs35l56_audio_map[] = {
{ "AMP", NULL, "VDD_B" },
{ "AMP", NULL, "VDD_AMP" },
ASoC: cs35l56: Fix to ensure ASP1 registers match cache Add a dummy SUPPLY widget connected to the ASP that forces the chip registers to match the regmap cache when the ASP is powered-up. On a SoundWire system the ASP is free for use as a chip-to-chip interconnect. This can be either for the firmware on multiple CS35L56 to share reference audio; or as a bridge to another device. If it is a firmware interconnect it is owned by the firmware and the Linux driver should avoid writing the registers. However. If it is a bridge then Linux may take over and handle it as a normal codec-to-codec link. CS35L56 is designed for SDCA and a generic SDCA driver would know nothing about these chip-specific registers. So if the ASP is being used on a SoundWire system the firmware sets up the ASP registers. This means that we can't assume the default state of the ASP registers. But we don't know the initial state that the firmware set them to until after the firmware has been downloaded and booted, which can take several seconds when downloading multiple amps. To avoid blocking probe() for several seconds waiting for the firmware, the silicon defaults are assumed. This allows the machine driver to setup the ASP configuration during probe() without being blocked. If the ASP is hooked up and used, the SUPPLY widget ensures that the chip registers match what was configured in the regmap cache. If the machine driver does not hook up the ASP, it is assumed that it won't call any functions to configure the ASP DAI. Therefore the regmap cache will be clean for these registers so a regcache_sync() will not overwrite the chip registers. If the DAI is not hooked up, the dummy SUPPLY widget will not be invoked so it will never force-overwrite the chip registers. Backport note: This won't apply cleanly to kernels older than v6.6. Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Fixes: e49611252900 ("ASoC: cs35l56: Add driver for Cirrus Logic CS35L56") Link: https://msgid.link/r/20240129162737.497-8-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2024-01-29 16:27:26 +00:00
{ "ASP1 Playback", NULL, "ASP1 CFG" },
{ "ASP1 Capture", NULL, "ASP1 CFG" },
{ "ASP1 Playback", NULL, "PLAY" },
{ "SDW1 Playback", NULL, "PLAY" },
{ "ASP1RX1", NULL, "ASP1 Playback" },
{ "ASP1RX2", NULL, "ASP1 Playback" },
{ "DSP1", NULL, "ASP1RX1" },
{ "DSP1", NULL, "ASP1RX2" },
{ "DSP1", NULL, "SDW1 Playback" },
{ "AMP", NULL, "DSP1" },
{ "SPK", NULL, "AMP" },
CS35L56_SRC_ROUTE("ASP1 TX1")
CS35L56_SRC_ROUTE("ASP1 TX2")
CS35L56_SRC_ROUTE("ASP1 TX3")
CS35L56_SRC_ROUTE("ASP1 TX4")
{ "ASP1TX1", NULL, "ASP1 TX1 Source" },
{ "ASP1TX2", NULL, "ASP1 TX2 Source" },
{ "ASP1TX3", NULL, "ASP1 TX3 Source" },
{ "ASP1TX4", NULL, "ASP1 TX4 Source" },
{ "ASP1 Capture", NULL, "ASP1TX1" },
{ "ASP1 Capture", NULL, "ASP1TX2" },
{ "ASP1 Capture", NULL, "ASP1TX3" },
{ "ASP1 Capture", NULL, "ASP1TX4" },
CS35L56_SRC_ROUTE("SDW1 TX1")
CS35L56_SRC_ROUTE("SDW1 TX2")
CS35L56_SRC_ROUTE("SDW1 TX3")
CS35L56_SRC_ROUTE("SDW1 TX4")
{ "SDW1 Capture", NULL, "SDW1 TX1 Source" },
{ "SDW1 Capture", NULL, "SDW1 TX2 Source" },
{ "SDW1 Capture", NULL, "SDW1 TX3 Source" },
{ "SDW1 Capture", NULL, "SDW1 TX4 Source" },
};
static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
dev_dbg(cs35l56->base.dev, "%s: %d\n", __func__, event);
return wm_adsp_event(w, kcontrol, event);
}
static int cs35l56_asp_dai_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(codec_dai->component);
unsigned int val;
dev_dbg(cs35l56->base.dev, "%s: %#x\n", __func__, fmt);
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_CBC_CFC:
break;
default:
dev_err(cs35l56->base.dev, "Unsupported clock source mode\n");
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
val = CS35L56_ASP_FMT_DSP_A << CS35L56_ASP_FMT_SHIFT;
cs35l56->tdm_mode = true;
break;
case SND_SOC_DAIFMT_I2S:
val = CS35L56_ASP_FMT_I2S << CS35L56_ASP_FMT_SHIFT;
cs35l56->tdm_mode = false;
break;
default:
dev_err(cs35l56->base.dev, "Unsupported DAI format\n");
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_IF:
val |= CS35L56_ASP_FSYNC_INV_MASK;
break;
case SND_SOC_DAIFMT_IB_NF:
val |= CS35L56_ASP_BCLK_INV_MASK;
break;
case SND_SOC_DAIFMT_IB_IF:
val |= CS35L56_ASP_BCLK_INV_MASK | CS35L56_ASP_FSYNC_INV_MASK;
break;
case SND_SOC_DAIFMT_NB_NF:
break;
default:
dev_err(cs35l56->base.dev, "Invalid clock invert\n");
return -EINVAL;
}
regmap_update_bits(cs35l56->base.regmap,
CS35L56_ASP1_CONTROL2,
CS35L56_ASP_FMT_MASK |
CS35L56_ASP_BCLK_INV_MASK | CS35L56_ASP_FSYNC_INV_MASK,
val);
/* Hi-Z DOUT in unused slots and when all TX are disabled */
regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL3,
CS35L56_ASP1_DOUT_HIZ_CTRL_MASK,
CS35L56_ASP_UNUSED_HIZ_OFF_HIZ);
return 0;
}
static unsigned int cs35l56_make_tdm_config_word(unsigned int reg_val, unsigned long mask)
{
unsigned int channel_shift;
int bit_num;
/* Enable consecutive TX1..TXn for each of the slots set in mask */
channel_shift = 0;
for_each_set_bit(bit_num, &mask, 32) {
reg_val &= ~(0x3f << channel_shift);
reg_val |= bit_num << channel_shift;
channel_shift += 8;
}
return reg_val;
}
static int cs35l56_asp_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int slot_width)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
if ((slots == 0) || (slot_width == 0)) {
dev_dbg(cs35l56->base.dev, "tdm config cleared\n");
cs35l56->asp_slot_width = 0;
cs35l56->asp_slot_count = 0;
return 0;
}
if (slot_width > (CS35L56_ASP_RX_WIDTH_MASK >> CS35L56_ASP_RX_WIDTH_SHIFT)) {
dev_err(cs35l56->base.dev, "tdm invalid slot width %d\n", slot_width);
return -EINVAL;
}
/* More than 32 slots would give an unsupportable BCLK frequency */
if (slots > 32) {
dev_err(cs35l56->base.dev, "tdm invalid slot count %d\n", slots);
return -EINVAL;
}
cs35l56->asp_slot_width = (u8)slot_width;
cs35l56->asp_slot_count = (u8)slots;
// Note: rx/tx is from point of view of the CPU end
if (tx_mask == 0)
tx_mask = 0x3; // ASPRX1/RX2 in slots 0 and 1
if (rx_mask == 0)
rx_mask = 0xf; // ASPTX1..TX4 in slots 0..3
/* Default unused slots to 63 */
regmap_write(cs35l56->base.regmap, CS35L56_ASP1_FRAME_CONTROL1,
cs35l56_make_tdm_config_word(0x3f3f3f3f, rx_mask));
regmap_write(cs35l56->base.regmap, CS35L56_ASP1_FRAME_CONTROL5,
cs35l56_make_tdm_config_word(0x3f3f3f, tx_mask));
dev_dbg(cs35l56->base.dev, "tdm slot width: %u count: %u tx_mask: %#x rx_mask: %#x\n",
cs35l56->asp_slot_width, cs35l56->asp_slot_count, tx_mask, rx_mask);
return 0;
}
static int cs35l56_asp_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
unsigned int rate = params_rate(params);
u8 asp_width, asp_wl;
asp_wl = params_width(params);
if (cs35l56->asp_slot_width)
asp_width = cs35l56->asp_slot_width;
else
asp_width = asp_wl;
dev_dbg(cs35l56->base.dev, "%s: wl=%d, width=%d, rate=%d",
__func__, asp_wl, asp_width, rate);
if (!cs35l56->sysclk_set) {
unsigned int slots = cs35l56->asp_slot_count;
unsigned int bclk_freq;
int freq_id;
if (slots == 0) {
slots = params_channels(params);
/* I2S always has an even number of slots */
if (!cs35l56->tdm_mode)
slots = round_up(slots, 2);
}
bclk_freq = asp_width * slots * rate;
freq_id = cs35l56_get_bclk_freq_id(bclk_freq);
if (freq_id < 0) {
dev_err(cs35l56->base.dev, "%s: Invalid BCLK %u\n", __func__, bclk_freq);
return -EINVAL;
}
regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL1,
CS35L56_ASP_BCLK_FREQ_MASK,
freq_id << CS35L56_ASP_BCLK_FREQ_SHIFT);
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL2,
CS35L56_ASP_RX_WIDTH_MASK, asp_width <<
CS35L56_ASP_RX_WIDTH_SHIFT);
regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_DATA_CONTROL5,
CS35L56_ASP_RX_WL_MASK, asp_wl);
} else {
regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL2,
CS35L56_ASP_TX_WIDTH_MASK, asp_width <<
CS35L56_ASP_TX_WIDTH_SHIFT);
regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_DATA_CONTROL1,
CS35L56_ASP_TX_WL_MASK, asp_wl);
}
return 0;
}
static int cs35l56_asp_dai_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
int freq_id;
if (freq == 0) {
cs35l56->sysclk_set = false;
return 0;
}
freq_id = cs35l56_get_bclk_freq_id(freq);
if (freq_id < 0)
return freq_id;
regmap_update_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL1,
CS35L56_ASP_BCLK_FREQ_MASK,
freq_id << CS35L56_ASP_BCLK_FREQ_SHIFT);
cs35l56->sysclk_set = true;
return 0;
}
static const struct snd_soc_dai_ops cs35l56_ops = {
.set_fmt = cs35l56_asp_dai_set_fmt,
.set_tdm_slot = cs35l56_asp_dai_set_tdm_slot,
.hw_params = cs35l56_asp_dai_hw_params,
.set_sysclk = cs35l56_asp_dai_set_sysclk,
};
static void cs35l56_sdw_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
snd_soc_dai_set_dma_data(dai, substream, NULL);
}
static int cs35l56_sdw_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int slot_width)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
/* rx/tx are from point of view of the CPU end so opposite to our rx/tx */
cs35l56->rx_mask = tx_mask;
cs35l56->tx_mask = rx_mask;
return 0;
}
static int cs35l56_sdw_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
struct sdw_stream_config sconfig;
struct sdw_port_config pconfig;
int ret;
dev_dbg(cs35l56->base.dev, "%s: rate %d\n", __func__, params_rate(params));
if (!cs35l56->base.init_done)
return -ENODEV;
if (!sdw_stream)
return -EINVAL;
memset(&sconfig, 0, sizeof(sconfig));
memset(&pconfig, 0, sizeof(pconfig));
sconfig.frame_rate = params_rate(params);
sconfig.bps = snd_pcm_format_width(params_format(params));
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
sconfig.direction = SDW_DATA_DIR_RX;
pconfig.num = CS35L56_SDW1_PLAYBACK_PORT;
pconfig.ch_mask = cs35l56->rx_mask;
} else {
sconfig.direction = SDW_DATA_DIR_TX;
pconfig.num = CS35L56_SDW1_CAPTURE_PORT;
pconfig.ch_mask = cs35l56->tx_mask;
}
if (pconfig.ch_mask == 0) {
sconfig.ch_count = params_channels(params);
pconfig.ch_mask = GENMASK(sconfig.ch_count - 1, 0);
} else {
sconfig.ch_count = hweight32(pconfig.ch_mask);
}
ret = sdw_stream_add_slave(cs35l56->sdw_peripheral, &sconfig, &pconfig,
1, sdw_stream);
if (ret) {
dev_err(dai->dev, "Failed to add sdw stream: %d\n", ret);
return ret;
}
return 0;
}
static int cs35l56_sdw_dai_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(dai->component);
struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
if (!cs35l56->sdw_peripheral)
return -EINVAL;
sdw_stream_remove_slave(cs35l56->sdw_peripheral, sdw_stream);
return 0;
}
static int cs35l56_sdw_dai_set_stream(struct snd_soc_dai *dai,
void *sdw_stream, int direction)
{
snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
return 0;
}
static const struct snd_soc_dai_ops cs35l56_sdw_dai_ops = {
.set_tdm_slot = cs35l56_sdw_dai_set_tdm_slot,
.shutdown = cs35l56_sdw_dai_shutdown,
.hw_params = cs35l56_sdw_dai_hw_params,
.hw_free = cs35l56_sdw_dai_hw_free,
.set_stream = cs35l56_sdw_dai_set_stream,
};
static struct snd_soc_dai_driver cs35l56_dai[] = {
{
.name = "cs35l56-asp1",
.id = 0,
.playback = {
.stream_name = "ASP1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = CS35L56_RATES,
.formats = CS35L56_RX_FORMATS,
},
.capture = {
.stream_name = "ASP1 Capture",
.channels_min = 1,
.channels_max = 4,
.rates = CS35L56_RATES,
.formats = CS35L56_TX_FORMATS,
},
.ops = &cs35l56_ops,
.symmetric_rate = 1,
.symmetric_sample_bits = 1,
},
{
.name = "cs35l56-sdw1",
.id = 1,
.playback = {
.stream_name = "SDW1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = CS35L56_RATES,
.formats = CS35L56_RX_FORMATS,
},
.capture = {
.stream_name = "SDW1 Capture",
.channels_min = 1,
.channels_max = 4,
.rates = CS35L56_RATES,
.formats = CS35L56_TX_FORMATS,
},
.symmetric_rate = 1,
.ops = &cs35l56_sdw_dai_ops,
}
};
static int cs35l56_write_cal(struct cs35l56_private *cs35l56)
{
int ret;
if (cs35l56->base.secured || !cs35l56->base.cal_data_valid)
return -ENODATA;
ret = wm_adsp_run(&cs35l56->dsp);
if (ret)
return ret;
ret = cs_amp_write_cal_coeffs(&cs35l56->dsp.cs_dsp,
&cs35l56_calibration_controls,
&cs35l56->base.cal_data);
wm_adsp_stop(&cs35l56->dsp);
if (ret == 0)
dev_info(cs35l56->base.dev, "Calibration applied\n");
return ret;
}
static void cs35l56_reinit_patch(struct cs35l56_private *cs35l56)
{
int ret;
/* Use wm_adsp to load and apply the firmware patch and coefficient files */
ret = wm_adsp_power_up(&cs35l56->dsp, true);
if (ret) {
dev_dbg(cs35l56->base.dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
return;
}
cs35l56_write_cal(cs35l56);
/* Always REINIT after applying patch or coefficients */
cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
}
static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing)
{
int ret;
/*
* Disable SoundWire interrupts to prevent race with IRQ work.
* Setting sdw_irq_no_unmask prevents the handler re-enabling
* the SoundWire interrupt.
*/
if (cs35l56->sdw_peripheral) {
cs35l56->sdw_irq_no_unmask = true;
flush_work(&cs35l56->sdw_irq_work);
sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0);
sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1);
sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF);
flush_work(&cs35l56->sdw_irq_work);
}
ret = cs35l56_firmware_shutdown(&cs35l56->base);
if (ret)
goto err;
/*
* Use wm_adsp to load and apply the firmware patch and coefficient files,
* but only if firmware is missing. If firmware is already patched just
* power-up wm_adsp without downloading firmware.
*/
ret = wm_adsp_power_up(&cs35l56->dsp, !!firmware_missing);
if (ret) {
dev_dbg(cs35l56->base.dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret);
goto err;
}
mutex_lock(&cs35l56->base.irq_lock);
reinit_completion(&cs35l56->init_completion);
cs35l56->soft_resetting = true;
cs35l56_system_reset(&cs35l56->base, !!cs35l56->sdw_peripheral);
if (cs35l56->sdw_peripheral) {
/*
* The system-reset causes the CS35L56 to detach from the bus.
* Wait for the manager to re-enumerate the CS35L56 and
* cs35l56_init() to run again.
*/
if (!wait_for_completion_timeout(&cs35l56->init_completion,
msecs_to_jiffies(5000))) {
dev_err(cs35l56->base.dev, "%s: init_completion timed out (SDW)\n",
__func__);
goto err_unlock;
}
} else if (cs35l56_init(cs35l56)) {
goto err_unlock;
}
regmap_clear_bits(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS,
CS35L56_FIRMWARE_MISSING);
cs35l56->base.fw_patched = true;
if (cs35l56_write_cal(cs35l56) == 0)
cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT);
err_unlock:
mutex_unlock(&cs35l56->base.irq_lock);
err:
/* Re-enable SoundWire interrupts */
if (cs35l56->sdw_peripheral) {
cs35l56->sdw_irq_no_unmask = false;
sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1,
CS35L56_SDW_INT_MASK_CODEC_IRQ);
}
}
static void cs35l56_dsp_work(struct work_struct *work)
{
struct cs35l56_private *cs35l56 = container_of(work,
struct cs35l56_private,
dsp_work);
unsigned int firmware_version;
bool firmware_missing;
int ret;
if (!cs35l56->base.init_done)
return;
pm_runtime_get_sync(cs35l56->base.dev);
ret = cs35l56_read_prot_status(&cs35l56->base, &firmware_missing, &firmware_version);
if (ret)
goto err;
/* Populate fw file qualifier with the revision and security state */
kfree(cs35l56->dsp.fwf_name);
if (firmware_missing) {
cs35l56->dsp.fwf_name = kasprintf(GFP_KERNEL, "%02x-dsp1", cs35l56->base.rev);
} else {
/* Firmware files must match the running firmware version */
cs35l56->dsp.fwf_name = kasprintf(GFP_KERNEL,
"%02x%s-%06x-dsp1",
cs35l56->base.rev,
cs35l56->base.secured ? "-s" : "",
firmware_version);
}
if (!cs35l56->dsp.fwf_name)
goto err;
dev_dbg(cs35l56->base.dev, "DSP fwf name: '%s' system name: '%s'\n",
cs35l56->dsp.fwf_name, cs35l56->dsp.system_name);
/*
* The firmware cannot be patched if it is already running from
* patch RAM. In this case the firmware files are versioned to
* match the running firmware version and will only contain
* tunings. We do not need to shutdown the firmware to apply
* tunings so can use the lower cost reinit sequence instead.
*/
if (!firmware_missing)
cs35l56_reinit_patch(cs35l56);
else
cs35l56_patch(cs35l56, firmware_missing);
err:
pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_put_autosuspend(cs35l56->base.dev);
}
static int cs35l56_component_probe(struct snd_soc_component *component)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
struct dentry *debugfs_root = component->debugfs_root;
unsigned short vendor, device;
BUILD_BUG_ON(ARRAY_SIZE(cs35l56_tx_input_texts) != ARRAY_SIZE(cs35l56_tx_input_values));
if (!cs35l56->dsp.system_name &&
(snd_soc_card_get_pci_ssid(component->card, &vendor, &device) == 0)) {
/* Append a speaker qualifier if there is a speaker ID */
if (cs35l56->speaker_id >= 0) {
cs35l56->dsp.system_name = devm_kasprintf(cs35l56->base.dev,
GFP_KERNEL,
"%04x%04x-spkid%d",
vendor, device,
cs35l56->speaker_id);
} else {
cs35l56->dsp.system_name = devm_kasprintf(cs35l56->base.dev,
GFP_KERNEL,
"%04x%04x",
vendor, device);
}
if (!cs35l56->dsp.system_name)
return -ENOMEM;
}
if (!wait_for_completion_timeout(&cs35l56->init_completion,
msecs_to_jiffies(5000))) {
dev_err(cs35l56->base.dev, "%s: init_completion timed out\n", __func__);
return -ENODEV;
}
cs35l56->dsp.part = kasprintf(GFP_KERNEL, "cs35l%02x", cs35l56->base.type);
if (!cs35l56->dsp.part)
return -ENOMEM;
cs35l56->component = component;
wm_adsp2_component_probe(&cs35l56->dsp, component);
debugfs_create_bool("init_done", 0444, debugfs_root, &cs35l56->base.init_done);
debugfs_create_bool("can_hibernate", 0444, debugfs_root, &cs35l56->base.can_hibernate);
debugfs_create_bool("fw_patched", 0444, debugfs_root, &cs35l56->base.fw_patched);
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
/*
* The widgets for the ASP1TX mixer can't be initialized
* until the firmware has been downloaded and rebooted.
*/
regcache_drop_region(cs35l56->base.regmap, CS35L56_ASP1TX1_INPUT, CS35L56_ASP1TX4_INPUT);
cs35l56->asp1_mixer_widgets_initialized = false;
queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
return 0;
}
static void cs35l56_component_remove(struct snd_soc_component *component)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
cancel_work_sync(&cs35l56->dsp_work);
if (cs35l56->dsp.cs_dsp.booted)
wm_adsp_power_down(&cs35l56->dsp);
wm_adsp2_component_remove(&cs35l56->dsp, component);
kfree(cs35l56->dsp.part);
cs35l56->dsp.part = NULL;
kfree(cs35l56->dsp.fwf_name);
cs35l56->dsp.fwf_name = NULL;
cs35l56->component = NULL;
}
static int cs35l56_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct cs35l56_private *cs35l56 = snd_soc_component_get_drvdata(component);
switch (level) {
case SND_SOC_BIAS_STANDBY:
/*
* Wait for patching to complete when transitioning from
* BIAS_OFF to BIAS_STANDBY
*/
if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
cs35l56_wait_dsp_ready(cs35l56);
break;
default:
break;
}
return 0;
}
static const struct snd_soc_component_driver soc_component_dev_cs35l56 = {
.probe = cs35l56_component_probe,
.remove = cs35l56_component_remove,
.dapm_widgets = cs35l56_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(cs35l56_dapm_widgets),
.dapm_routes = cs35l56_audio_map,
.num_dapm_routes = ARRAY_SIZE(cs35l56_audio_map),
.controls = cs35l56_controls,
.num_controls = ARRAY_SIZE(cs35l56_controls),
.set_bias_level = cs35l56_set_bias_level,
.suspend_bias_off = 1, /* see cs35l56_system_resume() */
};
static int __maybe_unused cs35l56_runtime_suspend_i2c_spi(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
return cs35l56_runtime_suspend_common(&cs35l56->base);
}
static int __maybe_unused cs35l56_runtime_resume_i2c_spi(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
return cs35l56_runtime_resume_common(&cs35l56->base, false);
}
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
int cs35l56_system_suspend(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
dev_dbg(dev, "system_suspend\n");
ASoC: cs35l56: Fix deadlock in ASP1 mixer register initialization 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: 07f7d6e7a124 ("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>
2024-02-08 12:37:42 +00:00
if (cs35l56->component)
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
flush_work(&cs35l56->dsp_work);
/*
* The interrupt line is normally shared, but after we start suspending
* we can't check if our device is the source of an interrupt, and can't
* clear it. Prevent this race by temporarily disabling the parent irq
* until we reach _no_irq.
*/
if (cs35l56->base.irq)
disable_irq(cs35l56->base.irq);
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
return pm_runtime_force_suspend(dev);
}
EXPORT_SYMBOL_GPL(cs35l56_system_suspend);
int cs35l56_system_suspend_late(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
dev_dbg(dev, "system_suspend_late\n");
/*
* Assert RESET before removing supplies.
* RESET is usually shared by all amps so it must not be asserted until
* all driver instances have done their suspend() stage.
*/
if (cs35l56->base.reset_gpio) {
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
cs35l56_wait_min_reset_pulse();
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
}
regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
return 0;
}
EXPORT_SYMBOL_GPL(cs35l56_system_suspend_late);
int cs35l56_system_suspend_no_irq(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
dev_dbg(dev, "system_suspend_no_irq\n");
/* Handlers are now disabled so the parent IRQ can safely be re-enabled. */
if (cs35l56->base.irq)
enable_irq(cs35l56->base.irq);
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
return 0;
}
EXPORT_SYMBOL_GPL(cs35l56_system_suspend_no_irq);
int cs35l56_system_resume_no_irq(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
dev_dbg(dev, "system_resume_no_irq\n");
/*
* WAKE interrupts unmask if the CS35L56 hibernates, which can cause
* spurious interrupts, and the interrupt line is normally shared.
* We can't check if our device is the source of an interrupt, and can't
* clear it, until it has fully resumed. Prevent this race by temporarily
* disabling the parent irq until we complete resume().
*/
if (cs35l56->base.irq)
disable_irq(cs35l56->base.irq);
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
return 0;
}
EXPORT_SYMBOL_GPL(cs35l56_system_resume_no_irq);
int cs35l56_system_resume_early(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "system_resume_early\n");
/* Ensure a spec-compliant RESET pulse. */
if (cs35l56->base.reset_gpio) {
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
cs35l56_wait_min_reset_pulse();
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
}
/* Enable supplies before releasing RESET. */
ret = regulator_bulk_enable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
if (ret) {
dev_err(dev, "system_resume_early failed to enable supplies: %d\n", ret);
return ret;
}
/* Release shared RESET before drivers start resume(). */
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
return 0;
}
EXPORT_SYMBOL_GPL(cs35l56_system_resume_early);
int cs35l56_system_resume(struct device *dev)
{
struct cs35l56_private *cs35l56 = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "system_resume\n");
/*
* We might have done a hard reset or the CS35L56 was power-cycled
* so wait for control port to be ready.
*/
cs35l56_wait_control_port_ready();
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
/* Undo pm_runtime_force_suspend() before re-enabling the irq */
ret = pm_runtime_force_resume(dev);
if (cs35l56->base.irq)
enable_irq(cs35l56->base.irq);
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
if (ret)
return ret;
/* Firmware won't have been loaded if the component hasn't probed */
if (!cs35l56->component)
return 0;
ret = cs35l56_is_fw_reload_needed(&cs35l56->base);
dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret);
if (ret < 1)
return ret;
cs35l56->base.fw_patched = false;
wm_adsp_power_down(&cs35l56->dsp);
queue_work(cs35l56->dsp_wq, &cs35l56->dsp_work);
/*
* suspend_bias_off ensures we are now in BIAS_OFF so there will be
* a BIAS_OFF->BIAS_STANDBY transition to complete dsp patching.
*/
return 0;
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
}
EXPORT_SYMBOL_GPL(cs35l56_system_resume);
static int cs35l56_dsp_init(struct cs35l56_private *cs35l56)
{
struct wm_adsp *dsp;
int ret;
cs35l56->dsp_wq = create_singlethread_workqueue("cs35l56-dsp");
if (!cs35l56->dsp_wq)
return -ENOMEM;
INIT_WORK(&cs35l56->dsp_work, cs35l56_dsp_work);
dsp = &cs35l56->dsp;
cs35l56_init_cs_dsp(&cs35l56->base, &dsp->cs_dsp);
/*
* dsp->part is filled in later as it is based on the DEVID. In a
* SoundWire system that cannot be read until enumeration has occurred
* and the device has attached.
*/
dsp->fw = 12;
dsp->wmfw_optional = true;
dev_dbg(cs35l56->base.dev, "DSP system name: '%s'\n", dsp->system_name);
ret = wm_halo_init(dsp);
if (ret != 0) {
dev_err(cs35l56->base.dev, "wm_halo_init failed\n");
return ret;
}
return 0;
}
static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56)
{
struct device *dev = cs35l56->base.dev;
const char *prop;
int ret;
ret = device_property_read_string(dev, "cirrus,firmware-uid", &prop);
/* If bad sw node property, return 0 and fallback to legacy firmware path */
if (ret < 0)
return 0;
/* Append a speaker qualifier if there is a speaker ID */
if (cs35l56->speaker_id >= 0)
cs35l56->dsp.system_name = devm_kasprintf(dev, GFP_KERNEL, "%s-spkid%d",
prop, cs35l56->speaker_id);
else
cs35l56->dsp.system_name = devm_kstrdup(dev, prop, GFP_KERNEL);
if (cs35l56->dsp.system_name == NULL)
return -ENOMEM;
dev_dbg(dev, "Firmware UID: %s\n", cs35l56->dsp.system_name);
return 0;
}
/*
* Some SoundWire laptops have a spk-id-gpios property but it points to
* the wrong ACPI Device node so can't be used to get the GPIO. Try to
* find the SDCA node containing the GpioIo resource and add a GPIO
* mapping to it.
*/
static const struct acpi_gpio_params cs35l56_af01_first_gpio = { 0, 0, false };
static const struct acpi_gpio_mapping cs35l56_af01_spkid_gpios_mapping[] = {
{ "spk-id-gpios", &cs35l56_af01_first_gpio, 1 },
{ }
};
static void cs35l56_acpi_dev_release_driver_gpios(void *adev)
{
acpi_dev_remove_driver_gpios(adev);
}
static int cs35l56_try_get_broken_sdca_spkid_gpio(struct cs35l56_private *cs35l56)
{
struct fwnode_handle *af01_fwnode;
const union acpi_object *obj;
struct gpio_desc *desc;
int ret;
/* Find the SDCA node containing the GpioIo */
af01_fwnode = device_get_named_child_node(cs35l56->base.dev, "AF01");
if (!af01_fwnode) {
dev_dbg(cs35l56->base.dev, "No AF01 node\n");
return -ENOENT;
}
ret = acpi_dev_get_property(ACPI_COMPANION(cs35l56->base.dev),
"spk-id-gpios", ACPI_TYPE_PACKAGE, &obj);
if (ret) {
dev_dbg(cs35l56->base.dev, "Could not get spk-id-gpios package: %d\n", ret);
return -ENOENT;
}
/* The broken properties we can handle are a 4-element package (one GPIO) */
if (obj->package.count != 4) {
dev_warn(cs35l56->base.dev, "Unexpected spk-id element count %d\n",
obj->package.count);
return -ENOENT;
}
/* Add a GPIO mapping if it doesn't already have one */
if (!fwnode_property_present(af01_fwnode, "spk-id-gpios")) {
struct acpi_device *adev = to_acpi_device_node(af01_fwnode);
/*
* Can't use devm_acpi_dev_add_driver_gpios() because the
* mapping isn't being added to the node pointed to by
* ACPI_COMPANION().
*/
ret = acpi_dev_add_driver_gpios(adev, cs35l56_af01_spkid_gpios_mapping);
if (ret) {
return dev_err_probe(cs35l56->base.dev, ret,
"Failed to add gpio mapping to AF01\n");
}
ret = devm_add_action_or_reset(cs35l56->base.dev,
cs35l56_acpi_dev_release_driver_gpios,
adev);
if (ret)
return ret;
dev_dbg(cs35l56->base.dev, "Added spk-id-gpios mapping to AF01\n");
}
desc = fwnode_gpiod_get_index(af01_fwnode, "spk-id", 0, GPIOD_IN, NULL);
if (IS_ERR(desc)) {
ret = PTR_ERR(desc);
return dev_err_probe(cs35l56->base.dev, ret, "Get GPIO from AF01 failed\n");
}
ret = gpiod_get_value_cansleep(desc);
gpiod_put(desc);
if (ret < 0) {
dev_err_probe(cs35l56->base.dev, ret, "Error reading spk-id GPIO\n");
return ret;
}
dev_info(cs35l56->base.dev, "Got spk-id from AF01\n");
return ret;
}
int cs35l56_common_probe(struct cs35l56_private *cs35l56)
{
int ret;
init_completion(&cs35l56->init_completion);
mutex_init(&cs35l56->base.irq_lock);
cs35l56->base.cal_index = -1;
cs35l56->speaker_id = -ENOENT;
dev_set_drvdata(cs35l56->base.dev, cs35l56);
cs35l56_fill_supply_names(cs35l56->supplies);
ret = devm_regulator_bulk_get(cs35l56->base.dev, ARRAY_SIZE(cs35l56->supplies),
cs35l56->supplies);
if (ret != 0)
return dev_err_probe(cs35l56->base.dev, ret, "Failed to request supplies\n");
/* Reset could be controlled by the BIOS or shared by multiple amps */
cs35l56->base.reset_gpio = devm_gpiod_get_optional(cs35l56->base.dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(cs35l56->base.reset_gpio)) {
ret = PTR_ERR(cs35l56->base.reset_gpio);
/*
* If RESET is shared the first amp to probe will grab the reset
* line and reset all the amps
*/
if (ret != -EBUSY)
return dev_err_probe(cs35l56->base.dev, ret, "Failed to get reset GPIO\n");
dev_info(cs35l56->base.dev, "Reset GPIO busy, assume shared reset\n");
cs35l56->base.reset_gpio = NULL;
}
ret = regulator_bulk_enable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
if (ret != 0)
return dev_err_probe(cs35l56->base.dev, ret, "Failed to enable supplies\n");
if (cs35l56->base.reset_gpio) {
/* ACPI can override GPIOD_OUT_LOW flag so force it to start low */
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
cs35l56_wait_min_reset_pulse();
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1);
}
ret = cs35l56_get_speaker_id(&cs35l56->base);
if (ACPI_COMPANION(cs35l56->base.dev) && cs35l56->sdw_peripheral && (ret == -ENOENT))
ret = cs35l56_try_get_broken_sdca_spkid_gpio(cs35l56);
if ((ret < 0) && (ret != -ENOENT))
goto err;
cs35l56->speaker_id = ret;
ret = cs35l56_get_firmware_uid(cs35l56);
if (ret != 0)
goto err;
ret = cs35l56_dsp_init(cs35l56);
if (ret < 0) {
dev_err_probe(cs35l56->base.dev, ret, "DSP init failed\n");
goto err;
}
ret = devm_snd_soc_register_component(cs35l56->base.dev,
&soc_component_dev_cs35l56,
cs35l56_dai, ARRAY_SIZE(cs35l56_dai));
if (ret < 0) {
dev_err_probe(cs35l56->base.dev, ret, "Register codec failed\n");
goto err;
}
return 0;
err:
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
return ret;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_common_probe, SND_SOC_CS35L56_CORE);
int cs35l56_init(struct cs35l56_private *cs35l56)
{
int ret;
/*
* Check whether the actions associated with soft reset or one time
* init need to be performed.
*/
if (cs35l56->soft_resetting)
goto post_soft_reset;
if (cs35l56->base.init_done)
return 0;
pm_runtime_set_autosuspend_delay(cs35l56->base.dev, 100);
pm_runtime_use_autosuspend(cs35l56->base.dev);
pm_runtime_set_active(cs35l56->base.dev);
pm_runtime_enable(cs35l56->base.dev);
ret = cs35l56_hw_init(&cs35l56->base);
if (ret < 0)
return ret;
ret = cs35l56_set_patch(&cs35l56->base);
if (ret)
return ret;
ret = cs35l56_get_calibration(&cs35l56->base);
if (ret)
return ret;
if (!cs35l56->base.reset_gpio) {
dev_dbg(cs35l56->base.dev, "No reset gpio: using soft reset\n");
cs35l56->soft_resetting = true;
cs35l56_system_reset(&cs35l56->base, !!cs35l56->sdw_peripheral);
if (cs35l56->sdw_peripheral) {
/* Keep alive while we wait for re-enumeration */
pm_runtime_get_noresume(cs35l56->base.dev);
return 0;
}
}
post_soft_reset:
if (cs35l56->soft_resetting) {
cs35l56->soft_resetting = false;
/* Done re-enumerating after one-time init so release the keep-alive */
if (cs35l56->sdw_peripheral && !cs35l56->base.init_done)
pm_runtime_put_noidle(cs35l56->base.dev);
regcache_mark_dirty(cs35l56->base.regmap);
ret = cs35l56_wait_for_firmware_boot(&cs35l56->base);
if (ret)
return ret;
dev_dbg(cs35l56->base.dev, "Firmware rebooted after soft reset\n");
}
/* Disable auto-hibernate so that runtime_pm has control */
ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE);
if (ret)
return ret;
/* Registers could be dirty after soft reset or SoundWire enumeration */
regcache_sync(cs35l56->base.regmap);
/* Set ASP1 DOUT to high-impedance when it is not transmitting audio data. */
ret = regmap_set_bits(cs35l56->base.regmap, CS35L56_ASP1_CONTROL3,
CS35L56_ASP1_DOUT_HIZ_CTRL_MASK);
if (ret)
return dev_err_probe(cs35l56->base.dev, ret, "Failed to write ASP1_CONTROL3\n");
cs35l56->base.init_done = true;
complete(&cs35l56->init_completion);
return 0;
}
EXPORT_SYMBOL_NS_GPL(cs35l56_init, SND_SOC_CS35L56_CORE);
void cs35l56_remove(struct cs35l56_private *cs35l56)
{
cs35l56->base.init_done = false;
/*
* WAKE IRQs unmask if CS35L56 hibernates so free the handler to
* prevent it racing with remove().
*/
if (cs35l56->base.irq)
devm_free_irq(cs35l56->base.dev, cs35l56->base.irq, &cs35l56->base);
flush_workqueue(cs35l56->dsp_wq);
destroy_workqueue(cs35l56->dsp_wq);
pm_runtime_dont_use_autosuspend(cs35l56->base.dev);
pm_runtime_suspend(cs35l56->base.dev);
pm_runtime_disable(cs35l56->base.dev);
regcache_cache_only(cs35l56->base.regmap, true);
gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0);
regulator_bulk_disable(ARRAY_SIZE(cs35l56->supplies), cs35l56->supplies);
}
EXPORT_SYMBOL_NS_GPL(cs35l56_remove, SND_SOC_CS35L56_CORE);
#if IS_ENABLED(CONFIG_SND_SOC_CS35L56_I2C) || IS_ENABLED(CONFIG_SND_SOC_CS35L56_SPI)
EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = {
SET_RUNTIME_PM_OPS(cs35l56_runtime_suspend_i2c_spi, cs35l56_runtime_resume_i2c_spi, NULL)
ASoC: cs35l56: Add basic system suspend handling This adds the main handling for system suspend but does not handle re-patching the firmware after system resume. This is a multi-stage suspend and resume because if there is a RESET line it is almost certain that it will be shared by all the amps. So every amp must have done its suspend before we can assert RESET. Likewise we must de-assert RESET before the amps can resume. It's preferable to assert RESET before we turning off regulators, and while they power up. The actual suspend and resume is done by using the pair pm_runtime_force_suspend() and pm_runtime_force_resume() to re-use our runtime suspend/resume sequences. pm_runtime_force_suspend() will disable our pm_runtime. If we were runtime-resumed it calls our runtime_suspend(). pm_runtime_force_resume() re-enables pm_runtime and if we were originally runtime-resumed before the pm_runtime_force_suspend() it calls our runtime_resume(). Otherwise it leaves us runtime-suspended. The general process is therefore: suspend() -> finish dsp_work and then run our runtime_suspend suspend_late() -> assert RESET and turn off supplies resume_early() -> enable supplies and de-assert RESET resume() -> pm_runtime_force_resume() In addition, to prevent the IRQ handler running in the period between pm_runtime_force_suspend() and pm_runtime_force_resume() the parent IRQ is temporarily disabled: - from suspend until suspend_noirq - from resume_noirq until resume Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com> Link: https://lore.kernel.org/r/20230411152528.329803-6-rf@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-04-11 16:25:27 +01:00
SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend, cs35l56_system_resume)
LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early)
NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_no_irq, cs35l56_system_resume_no_irq)
};
#endif
MODULE_DESCRIPTION("ASoC CS35L56 driver");
MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED);
MODULE_IMPORT_NS(SND_SOC_CS_AMP_LIB);
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
MODULE_AUTHOR("Simon Trimmer <simont@opensource.cirrus.com>");
MODULE_LICENSE("GPL");