/* Enable interrupts and we're off */
snd_soc_update_bits(codec, WM8996_INTERRUPT_STATUS_2_MASK,
- WM8996_IM_MICD_EINT, 0);
+ WM8996_IM_MICD_EINT | WM8996_HP_DONE_EINT, 0);
return 0;
}
EXPORT_SYMBOL_GPL(wm8996_detect);
+static void wm8996_hpdet_irq(struct snd_soc_codec *codec)
+{
+ struct wm8996_priv *wm8996 = snd_soc_codec_get_drvdata(codec);
+ int val, reg, report;
+
+ /* Assume headphone in error conditions; we need to report
+ * something or we stall our state machine.
+ */
+ report = SND_JACK_HEADPHONE;
+
+ reg = snd_soc_read(codec, WM8996_HEADPHONE_DETECT_2);
+ if (reg < 0) {
+ dev_err(codec->dev, "Failed to read HPDET status\n");
+ goto out;
+ }
+
+ if (!(reg & WM8996_HP_DONE)) {
+ dev_err(codec->dev, "Got HPDET IRQ but HPDET is busy\n");
+ goto out;
+ }
+
+ val = reg & WM8996_HP_LVL_MASK;
+
+ dev_dbg(codec->dev, "HPDET measured %d ohms\n", val);
+
+ /* If we've got high enough impedence then report as line,
+ * otherwise assume headphone.
+ */
+ if (val >= 126)
+ report = SND_JACK_LINEOUT;
+ else
+ report = SND_JACK_HEADPHONE;
+
+out:
+ if (wm8996->jack_mic)
+ report |= SND_JACK_MICROPHONE;
+
+ snd_soc_jack_report(wm8996->jack, report,
+ SND_JACK_LINEOUT | SND_JACK_HEADSET);
+
+ wm8996->detecting = false;
+
+ /* If the output isn't running re-clamp it */
+ if (!(snd_soc_read(codec, WM8996_POWER_MANAGEMENT_1) &
+ (WM8996_HPOUT1L_ENA | WM8996_HPOUT1R_RMV_SHORT)))
+ snd_soc_update_bits(codec, WM8996_ANALOGUE_HP_1,
+ WM8996_HPOUT1L_RMV_SHORT |
+ WM8996_HPOUT1R_RMV_SHORT, 0);
+
+ /* Go back to looking at the microphone */
+ snd_soc_update_bits(codec, WM8996_ACCESSORY_DETECT_MODE_1,
+ WM8996_JD_MODE_MASK, 0);
+ snd_soc_update_bits(codec, WM8996_MIC_DETECT_1, WM8996_MICD_ENA,
+ WM8996_MICD_ENA);
+
+ snd_soc_dapm_disable_pin(&codec->dapm, "Bandgap");
+ snd_soc_dapm_sync(&codec->dapm);
+}
+
+static void wm8996_hpdet_start(struct snd_soc_codec *codec)
+{
+ /* Unclamp the output, we can't measure while we're shorting it */
+ snd_soc_update_bits(codec, WM8996_ANALOGUE_HP_1,
+ WM8996_HPOUT1L_RMV_SHORT |
+ WM8996_HPOUT1R_RMV_SHORT,
+ WM8996_HPOUT1L_RMV_SHORT |
+ WM8996_HPOUT1R_RMV_SHORT);
+
+ /* We need bandgap for HPDET */
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "Bandgap");
+ snd_soc_dapm_sync(&codec->dapm);
+
+ /* Go into headphone detect left mode */
+ snd_soc_update_bits(codec, WM8996_MIC_DETECT_1, WM8996_MICD_ENA, 0);
+ snd_soc_update_bits(codec, WM8996_ACCESSORY_DETECT_MODE_1,
+ WM8996_JD_MODE_MASK, 1);
+
+ /* Trigger a measurement */
+ snd_soc_update_bits(codec, WM8996_HEADPHONE_DETECT_1,
+ WM8996_HP_POLL, WM8996_HP_POLL);
+}
+
static void wm8996_micd(struct snd_soc_codec *codec)
{
struct wm8996_priv *wm8996 = snd_soc_codec_get_drvdata(codec);
wm8996->jack_mic = false;
wm8996->detecting = true;
snd_soc_jack_report(wm8996->jack, 0,
- SND_JACK_HEADSET | SND_JACK_BTN_0);
+ SND_JACK_LINEOUT | SND_JACK_HEADSET |
+ SND_JACK_BTN_0);
+
snd_soc_update_bits(codec, WM8996_MIC_DETECT_1,
WM8996_MICD_RATE_MASK,
WM8996_MICD_RATE_MASK);
return;
}
- /* If the measurement is very high we've got a microphone but
- * do a little debounce to account for mechanical issues.
+ /* If the measurement is very high we've got a microphone,
+ * either we just detected one or if we already reported then
+ * we've got a button release event.
*/
if (val & 0x400) {
- dev_dbg(codec->dev, "Microphone detected\n");
- snd_soc_jack_report(wm8996->jack, SND_JACK_HEADSET,
- SND_JACK_HEADSET | SND_JACK_BTN_0);
- wm8996->jack_mic = true;
- wm8996->detecting = false;
-
- /* Increase poll rate to give better responsiveness
- * for buttons */
- snd_soc_update_bits(codec, WM8996_MIC_DETECT_1,
- WM8996_MICD_RATE_MASK,
- 5 << WM8996_MICD_RATE_SHIFT);
+ if (wm8996->detecting) {
+ dev_dbg(codec->dev, "Microphone detected\n");
+ wm8996->jack_mic = true;
+ wm8996_hpdet_start(codec);
+
+ /* Increase poll rate to give better responsiveness
+ * for buttons */
+ snd_soc_update_bits(codec, WM8996_MIC_DETECT_1,
+ WM8996_MICD_RATE_MASK,
+ 5 << WM8996_MICD_RATE_SHIFT);
+ } else {
+ dev_dbg(codec->dev, "Mic button up\n");
+ snd_soc_jack_report(wm8996->jack, 0, SND_JACK_BTN_0);
+ }
+
+ return;
}
/* If we detected a lower impedence during initial startup
if (val & 0x3fc) {
if (wm8996->jack_mic) {
dev_dbg(codec->dev, "Mic button detected\n");
- snd_soc_jack_report(wm8996->jack,
- SND_JACK_HEADSET | SND_JACK_BTN_0,
- SND_JACK_HEADSET | SND_JACK_BTN_0);
- } else {
- dev_dbg(codec->dev, "Headphone detected\n");
- snd_soc_jack_report(wm8996->jack,
- SND_JACK_HEADPHONE,
- SND_JACK_HEADSET |
+ snd_soc_jack_report(wm8996->jack, SND_JACK_BTN_0,
SND_JACK_BTN_0);
+ } else if (wm8996->detecting) {
+ dev_dbg(codec->dev, "Headphone detected\n");
+ wm8996_hpdet_start(codec);
/* Increase the detection rate a bit for
* responsiveness.
snd_soc_update_bits(codec, WM8996_MIC_DETECT_1,
WM8996_MICD_RATE_MASK,
7 << WM8996_MICD_RATE_SHIFT);
-
- wm8996->detecting = false;
}
}
}
if (irq_val & WM8996_MICD_EINT)
wm8996_micd(codec);
+ if (irq_val & WM8996_HP_DONE_EINT)
+ wm8996_hpdet_irq(codec);
+
return IRQ_HANDLED;
}