extcon: arizona: Fix some issues when HPDET IRQ fires after the jack has been unplugged
authorHans de Goede <hdegoede@redhat.com>
Sun, 7 Mar 2021 15:17:56 +0000 (16:17 +0100)
committerLee Jones <lee.jones@linaro.org>
Thu, 18 Mar 2021 11:46:15 +0000 (11:46 +0000)
When the jack is partially inserted and then removed again it may be
removed while the hpdet code is running. In this case the following
may happen:

1. The "JACKDET rise" or ""JACKDET fall" IRQ triggers
2. arizona_jackdet runs and takes info->lock
3. The "HPDET" IRQ triggers
4. arizona_hpdet_irq runs, blocks on info->lock
5. arizona_jackdet calls arizona_stop_mic() and clears info->hpdet_done
6. arizona_jackdet releases info->lock
7. arizona_hpdet_irq now can continue running and:
7.1 Calls arizona_start_mic() (if a mic was detected)
7.2 sets info->hpdet_done

Step 7 is undesirable / a bug:
7.1 causes the device to stay in a high power-state (with MICVDD enabled)
7.2 causes hpdet to not run on the next jack insertion, which in turn
    causes the EXTCON_JACK_HEADPHONE state to never get set

This fixes both issues by skipping these 2 steps when arizona_hpdet_irq
runs after the jack has been unplugged.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Acked-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Tested-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Acked-by: Chanwoo Choi <cw00.choi@samsung.com>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
drivers/extcon/extcon-arizona.c

index aae82db..f7ef247 100644 (file)
@@ -601,7 +601,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
        struct arizona *arizona = info->arizona;
        int id_gpio = arizona->pdata.hpdet_id_gpio;
        unsigned int report = EXTCON_JACK_HEADPHONE;
-       int ret, reading;
+       int ret, reading, state;
        bool mic = false;
 
        mutex_lock(&info->lock);
@@ -614,12 +614,11 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
        }
 
        /* If the cable was removed while measuring ignore the result */
-       ret = extcon_get_state(info->edev, EXTCON_MECHANICAL);
-       if (ret < 0) {
-               dev_err(arizona->dev, "Failed to check cable state: %d\n",
-                       ret);
+       state = extcon_get_state(info->edev, EXTCON_MECHANICAL);
+       if (state < 0) {
+               dev_err(arizona->dev, "Failed to check cable state: %d\n", state);
                goto out;
-       } else if (!ret) {
+       } else if (!state) {
                dev_dbg(arizona->dev, "Ignoring HPDET for removed cable\n");
                goto done;
        }
@@ -667,7 +666,7 @@ done:
                gpio_set_value_cansleep(id_gpio, 0);
 
        /* If we have a mic then reenable MICDET */
-       if (mic || info->mic)
+       if (state && (mic || info->mic))
                arizona_start_mic(info);
 
        if (info->hpdet_active) {
@@ -675,7 +674,9 @@ done:
                info->hpdet_active = false;
        }
 
-       info->hpdet_done = true;
+       /* Do not set hp_det done when the cable has been unplugged */
+       if (state)
+               info->hpdet_done = true;
 
 out:
        mutex_unlock(&info->lock);