Commit 0b684cc1 authored by Mark Brown's avatar Mark Brown

ASoC: Initial WM8996 headphone impedance measurement support

The WM8996 can measure the impedance of accessories connected to the
headphone output. Implement initial support for this, measuring the
left channel impedance when an accessory is detected and using this
to distinguish between a line load and a headphone load.
Signed-off-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: default avatarLiam Girdwood <lrg@ti.com>
parent 8259df12
...@@ -2350,12 +2350,94 @@ int wm8996_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, ...@@ -2350,12 +2350,94 @@ int wm8996_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
/* Enable interrupts and we're off */ /* Enable interrupts and we're off */
snd_soc_update_bits(codec, WM8996_INTERRUPT_STATUS_2_MASK, 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; return 0;
} }
EXPORT_SYMBOL_GPL(wm8996_detect); 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) static void wm8996_micd(struct snd_soc_codec *codec)
{ {
struct wm8996_priv *wm8996 = snd_soc_codec_get_drvdata(codec); struct wm8996_priv *wm8996 = snd_soc_codec_get_drvdata(codec);
...@@ -2376,28 +2458,36 @@ static void wm8996_micd(struct snd_soc_codec *codec) ...@@ -2376,28 +2458,36 @@ static void wm8996_micd(struct snd_soc_codec *codec)
wm8996->jack_mic = false; wm8996->jack_mic = false;
wm8996->detecting = true; wm8996->detecting = true;
snd_soc_jack_report(wm8996->jack, 0, 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, snd_soc_update_bits(codec, WM8996_MIC_DETECT_1,
WM8996_MICD_RATE_MASK, WM8996_MICD_RATE_MASK,
WM8996_MICD_RATE_MASK); WM8996_MICD_RATE_MASK);
return; return;
} }
/* If the measurement is very high we've got a microphone but /* If the measurement is very high we've got a microphone,
* do a little debounce to account for mechanical issues. * either we just detected one or if we already reported then
* we've got a button release event.
*/ */
if (val & 0x400) { if (val & 0x400) {
dev_dbg(codec->dev, "Microphone detected\n"); if (wm8996->detecting) {
snd_soc_jack_report(wm8996->jack, SND_JACK_HEADSET, dev_dbg(codec->dev, "Microphone detected\n");
SND_JACK_HEADSET | SND_JACK_BTN_0); wm8996->jack_mic = true;
wm8996->jack_mic = true; wm8996_hpdet_start(codec);
wm8996->detecting = false;
/* Increase poll rate to give better responsiveness
/* Increase poll rate to give better responsiveness * for buttons */
* for buttons */ snd_soc_update_bits(codec, WM8996_MIC_DETECT_1,
snd_soc_update_bits(codec, WM8996_MIC_DETECT_1, WM8996_MICD_RATE_MASK,
WM8996_MICD_RATE_MASK, 5 << WM8996_MICD_RATE_SHIFT);
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 we detected a lower impedence during initial startup
...@@ -2429,15 +2519,11 @@ static void wm8996_micd(struct snd_soc_codec *codec) ...@@ -2429,15 +2519,11 @@ static void wm8996_micd(struct snd_soc_codec *codec)
if (val & 0x3fc) { if (val & 0x3fc) {
if (wm8996->jack_mic) { if (wm8996->jack_mic) {
dev_dbg(codec->dev, "Mic button detected\n"); dev_dbg(codec->dev, "Mic button detected\n");
snd_soc_jack_report(wm8996->jack, snd_soc_jack_report(wm8996->jack, SND_JACK_BTN_0,
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_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 /* Increase the detection rate a bit for
* responsiveness. * responsiveness.
...@@ -2445,8 +2531,6 @@ static void wm8996_micd(struct snd_soc_codec *codec) ...@@ -2445,8 +2531,6 @@ static void wm8996_micd(struct snd_soc_codec *codec)
snd_soc_update_bits(codec, WM8996_MIC_DETECT_1, snd_soc_update_bits(codec, WM8996_MIC_DETECT_1,
WM8996_MICD_RATE_MASK, WM8996_MICD_RATE_MASK,
7 << WM8996_MICD_RATE_SHIFT); 7 << WM8996_MICD_RATE_SHIFT);
wm8996->detecting = false;
} }
} }
} }
...@@ -2486,6 +2570,9 @@ static irqreturn_t wm8996_irq(int irq, void *data) ...@@ -2486,6 +2570,9 @@ static irqreturn_t wm8996_irq(int irq, void *data)
if (irq_val & WM8996_MICD_EINT) if (irq_val & WM8996_MICD_EINT)
wm8996_micd(codec); wm8996_micd(codec);
if (irq_val & WM8996_HP_DONE_EINT)
wm8996_hpdet_irq(codec);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment