Unverified Commit bbbc18d8 authored by Charles Keepax's avatar Charles Keepax Committed by Mark Brown
Browse files

ASoC: cs42l43: Allow HP amp to cool off after current limit



Whilst occasional current limiting is fine, constant current limiting
should be avoided. Add a back off system that will disable the
headphone amp, if a lot of current limiting is seen in a short window
of time.

Signed-off-by: default avatarCharles Keepax <ckeepax@opensource.cirrus.com>
Link: https://msgid.link/r/20231211160019.2034442-2-ckeepax@opensource.cirrus.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 28b0b18d
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -506,7 +506,7 @@ static void cs42l43_start_load_detect(struct cs42l43_codec *priv)

	priv->load_detect_running = true;

	if (priv->hp_ena) {
	if (priv->hp_ena && !priv->hp_ilimited) {
		unsigned long time_left;

		reinit_completion(&priv->hp_shutdown);
@@ -571,7 +571,7 @@ static void cs42l43_stop_load_detect(struct cs42l43_codec *priv)
			   CS42L43_ADC1_EN_MASK | CS42L43_ADC2_EN_MASK,
			   priv->adc_ena);

	if (priv->hp_ena) {
	if (priv->hp_ena && !priv->hp_ilimited) {
		unsigned long time_left;

		reinit_completion(&priv->hp_startup);
+85 −3
Original line number Diff line number Diff line
@@ -138,7 +138,87 @@ CS42L43_IRQ_ERROR(spkr_therm_warm)
CS42L43_IRQ_ERROR(spkl_therm_warm)
CS42L43_IRQ_ERROR(spkr_sc_detect)
CS42L43_IRQ_ERROR(spkl_sc_detect)
CS42L43_IRQ_ERROR(hp_ilimit)

void cs42l43_hp_ilimit_clear_work(struct work_struct *work)
{
	struct cs42l43_codec *priv = container_of(work, struct cs42l43_codec,
						  hp_ilimit_clear_work.work);
	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(priv->component);

	snd_soc_dapm_mutex_lock(dapm);

	priv->hp_ilimit_count--;

	if (priv->hp_ilimit_count)
		queue_delayed_work(system_wq, &priv->hp_ilimit_clear_work,
				   msecs_to_jiffies(CS42L43_HP_ILIMIT_DECAY_MS));

	snd_soc_dapm_mutex_unlock(dapm);
}

void cs42l43_hp_ilimit_work(struct work_struct *work)
{
	struct cs42l43_codec *priv = container_of(work, struct cs42l43_codec,
						  hp_ilimit_work);
	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(priv->component);
	struct cs42l43 *cs42l43 = priv->core;

	snd_soc_dapm_mutex_lock(dapm);

	if (priv->hp_ilimit_count < CS42L43_HP_ILIMIT_MAX_COUNT) {
		if (!priv->hp_ilimit_count)
			queue_delayed_work(system_wq, &priv->hp_ilimit_clear_work,
					   msecs_to_jiffies(CS42L43_HP_ILIMIT_DECAY_MS));

		priv->hp_ilimit_count++;
		snd_soc_dapm_mutex_unlock(dapm);
		return;
	}

	dev_err(priv->dev, "Disabling headphone for %dmS, due to frequent current limit\n",
		CS42L43_HP_ILIMIT_BACKOFF_MS);

	priv->hp_ilimited = true;

	// No need to wait for disable, as just disabling for a period of time
	regmap_update_bits(cs42l43->regmap, CS42L43_BLOCK_EN8,
			   CS42L43_HP_EN_MASK, 0);

	snd_soc_dapm_mutex_unlock(dapm);

	msleep(CS42L43_HP_ILIMIT_BACKOFF_MS);

	snd_soc_dapm_mutex_lock(dapm);

	if (priv->hp_ena && !priv->load_detect_running) {
		unsigned long time_left;

		reinit_completion(&priv->hp_startup);

		regmap_update_bits(cs42l43->regmap, CS42L43_BLOCK_EN8,
				   CS42L43_HP_EN_MASK, priv->hp_ena);

		time_left = wait_for_completion_timeout(&priv->hp_startup,
							msecs_to_jiffies(CS42L43_HP_TIMEOUT_MS));
		if (!time_left)
			dev_err(priv->dev, "ilimit HP restore timed out\n");
	}

	priv->hp_ilimited = false;

	snd_soc_dapm_mutex_unlock(dapm);
}

static irqreturn_t cs42l43_hp_ilimit(int irq, void *data)
{
	struct cs42l43_codec *priv = data;

	dev_dbg(priv->dev, "headphone ilimit IRQ\n");

	queue_work(system_long_wq, &priv->hp_ilimit_work);

	return IRQ_HANDLED;
}

#define CS42L43_IRQ_COMPLETE(name) \
static irqreturn_t cs42l43_##name(int irq, void *data) \
@@ -1452,13 +1532,13 @@ static int cs42l43_hp_ev(struct snd_soc_dapm_widget *w,
		if (ret)
			return ret;

		if (!priv->load_detect_running)
		if (!priv->load_detect_running && !priv->hp_ilimited)
			regmap_update_bits(cs42l43->regmap, CS42L43_BLOCK_EN8,
					   mask, val);
		break;
	case SND_SOC_DAPM_POST_PMU:
	case SND_SOC_DAPM_POST_PMD:
		if (priv->load_detect_running)
		if (priv->load_detect_running || priv->hp_ilimited)
			break;

		ret = cs42l43_dapm_wait_completion(&priv->hp_startup,
@@ -2169,7 +2249,9 @@ static int cs42l43_codec_probe(struct platform_device *pdev)
	INIT_DELAYED_WORK(&priv->tip_sense_work, cs42l43_tip_sense_work);
	INIT_DELAYED_WORK(&priv->bias_sense_timeout, cs42l43_bias_sense_timeout);
	INIT_DELAYED_WORK(&priv->button_press_work, cs42l43_button_press_work);
	INIT_DELAYED_WORK(&priv->hp_ilimit_clear_work, cs42l43_hp_ilimit_clear_work);
	INIT_WORK(&priv->button_release_work, cs42l43_button_release_work);
	INIT_WORK(&priv->hp_ilimit_work, cs42l43_hp_ilimit_work);

	pm_runtime_set_autosuspend_delay(priv->dev, 100);
	pm_runtime_use_autosuspend(priv->dev);
+9 −0
Original line number Diff line number Diff line
@@ -28,6 +28,10 @@
#define CS42L43_HP_TIMEOUT_MS		2000
#define CS42L43_LOAD_TIMEOUT_MS		1000

#define CS42L43_HP_ILIMIT_BACKOFF_MS	1000
#define CS42L43_HP_ILIMIT_DECAY_MS	300
#define CS42L43_HP_ILIMIT_MAX_COUNT	4

#define CS42L43_ASP_MAX_CHANNELS	6
#define CS42L43_N_EQ_COEFFS		15

@@ -88,6 +92,11 @@ struct cs42l43_codec {
	bool button_detect_running;
	bool jack_present;
	int jack_override;

	struct work_struct hp_ilimit_work;
	struct delayed_work hp_ilimit_clear_work;
	bool hp_ilimited;
	int hp_ilimit_count;
};

#if IS_REACHABLE(CONFIG_SND_SOC_CS42L43_SDW)