ASoC: cs42l43: Allow HP amp to cool off after current limit
authorCharles Keepax <ckeepax@opensource.cirrus.com>
Mon, 11 Dec 2023 16:00:19 +0000 (16:00 +0000)
committerMark Brown <broonie@kernel.org>
Mon, 11 Dec 2023 17:26:01 +0000 (17:26 +0000)
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: Charles Keepax <ckeepax@opensource.cirrus.com>
Link: https://msgid.link/r/20231211160019.2034442-2-ckeepax@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/cs42l43-jack.c
sound/soc/codecs/cs42l43.c
sound/soc/codecs/cs42l43.h

index 73454de068cf8c9d5575ed1e5e62733a63eefe68..929497edb83dc4edbbeb69bef78d072c4ff888a3 100644 (file)
@@ -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);
index 5c98343ebf71b47966996a26110a8101d256f65c..d2412dab35996a5c900e3dc93f393d144837f791 100644 (file)
@@ -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);
index bf4f728eea3e03da1dbb27960b8dd06c1089e575..125e36861d5d5b891664428152b935b764c3d61d 100644 (file)
 #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)