extcon: arizona: Fix some issues when HPDET IRQ fires after the jack has been unplugged

[ Upstream commit c309a3e8793f7e01c4a4ec7960658380572cb576 ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Hans de Goede 2021-03-07 16:17:56 +01:00 committed by Greg Kroah-Hartman
parent b10f94efbd
commit ae066d1687

View file

@ -602,7 +602,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
struct arizona *arizona = info->arizona; struct arizona *arizona = info->arizona;
int id_gpio = arizona->pdata.hpdet_id_gpio; int id_gpio = arizona->pdata.hpdet_id_gpio;
unsigned int report = EXTCON_JACK_HEADPHONE; unsigned int report = EXTCON_JACK_HEADPHONE;
int ret, reading; int ret, reading, state;
bool mic = false; bool mic = false;
mutex_lock(&info->lock); mutex_lock(&info->lock);
@ -615,12 +615,11 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
} }
/* If the cable was removed while measuring ignore the result */ /* If the cable was removed while measuring ignore the result */
ret = extcon_get_state(info->edev, EXTCON_MECHANICAL); state = extcon_get_state(info->edev, EXTCON_MECHANICAL);
if (ret < 0) { if (state < 0) {
dev_err(arizona->dev, "Failed to check cable state: %d\n", dev_err(arizona->dev, "Failed to check cable state: %d\n", state);
ret);
goto out; goto out;
} else if (!ret) { } else if (!state) {
dev_dbg(arizona->dev, "Ignoring HPDET for removed cable\n"); dev_dbg(arizona->dev, "Ignoring HPDET for removed cable\n");
goto done; goto done;
} }
@ -673,7 +672,7 @@ done:
ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
/* If we have a mic then reenable MICDET */ /* If we have a mic then reenable MICDET */
if (mic || info->mic) if (state && (mic || info->mic))
arizona_start_mic(info); arizona_start_mic(info);
if (info->hpdet_active) { if (info->hpdet_active) {
@ -681,7 +680,9 @@ done:
info->hpdet_active = false; 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: out:
mutex_unlock(&info->lock); mutex_unlock(&info->lock);