aboutsummaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2011-09-27 17:39:50 +0100
committerMark Brown <broonie@opensource.wolfsonmicro.com>2011-10-11 18:59:01 +0100
commitba896ede9a9a54a9114ee2a4fe534328078c6b02 (patch)
tree887d21c0965bf7af4a5d711ad697aac45c831e18 /sound
parent3a53d827292b657afcb73495cac139371cb157e1 (diff)
ASoC: Implement WM5100 accessory detection support
The WM5100 includes an advanced, low power, accessory detect subsystem capable of detecting both accessory presence and button presses while the device is in an ultra low power mode. Implement initial support for this. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/wm5100.c162
-rw-r--r--sound/soc/codecs/wm5100.h2
2 files changed, 164 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c
index 8d90ba9c1f5..02c011d7512 100644
--- a/sound/soc/codecs/wm5100.c
+++ b/sound/soc/codecs/wm5100.c
@@ -26,6 +26,7 @@
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
+#include <sound/jack.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <sound/wm5100.h>
@@ -68,6 +69,11 @@ struct wm5100_priv {
bool out_ena[2];
+ struct snd_soc_jack *jack;
+ bool jack_detecting;
+ bool jack_mic;
+ int jack_mode;
+
struct wm5100_fll fll[2];
struct wm5100_pdata pdata;
@@ -2113,6 +2119,159 @@ static int wm5100_dig_vu[] = {
WM5100_DAC_DIGITAL_VOLUME_6R,
};
+static void wm5100_set_detect_mode(struct snd_soc_codec *codec, int the_mode)
+{
+ struct wm5100_priv *wm5100 = snd_soc_codec_get_drvdata(codec);
+ struct wm5100_jack_mode *mode = &wm5100->pdata.jack_modes[the_mode];
+
+ BUG_ON(the_mode >= ARRAY_SIZE(wm5100->pdata.jack_modes));
+
+ gpio_set_value_cansleep(wm5100->pdata.hp_pol, mode->hp_pol);
+ snd_soc_update_bits(codec, WM5100_ACCESSORY_DETECT_MODE_1,
+ WM5100_ACCDET_BIAS_SRC_MASK |
+ WM5100_ACCDET_SRC,
+ (mode->bias << WM5100_ACCDET_BIAS_SRC_SHIFT) |
+ mode->micd_src << WM5100_ACCDET_SRC_SHIFT);
+
+ wm5100->jack_mode = the_mode;
+
+ dev_dbg(codec->dev, "Set microphone polarity to %d\n",
+ wm5100->jack_mode);
+}
+
+static void wm5100_micd_irq(struct snd_soc_codec *codec)
+{
+ struct wm5100_priv *wm5100 = snd_soc_codec_get_drvdata(codec);
+ int val;
+
+ val = snd_soc_read(codec, WM5100_MIC_DETECT_3);
+
+ dev_dbg(codec->dev, "Microphone event: %x\n", val);
+
+ if (!(val & WM5100_ACCDET_VALID)) {
+ dev_warn(codec->dev, "Microphone detection state invalid\n");
+ return;
+ }
+
+ /* No accessory, reset everything and report removal */
+ if (!(val & WM5100_ACCDET_STS)) {
+ dev_dbg(codec->dev, "Jack removal detected\n");
+ wm5100->jack_mic = false;
+ wm5100->jack_detecting = true;
+ snd_soc_jack_report(wm5100->jack, 0,
+ SND_JACK_LINEOUT | SND_JACK_HEADSET |
+ SND_JACK_BTN_0);
+
+ snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+ WM5100_ACCDET_RATE_MASK,
+ WM5100_ACCDET_RATE_MASK);
+ return;
+ }
+
+ /* 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) {
+ if (wm5100->jack_detecting) {
+ dev_dbg(codec->dev, "Microphone detected\n");
+ wm5100->jack_mic = true;
+ snd_soc_jack_report(wm5100->jack,
+ SND_JACK_HEADSET,
+ SND_JACK_HEADSET | SND_JACK_BTN_0);
+
+ /* Increase poll rate to give better responsiveness
+ * for buttons */
+ snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+ WM5100_ACCDET_RATE_MASK,
+ 5 << WM5100_ACCDET_RATE_SHIFT);
+ } else {
+ dev_dbg(codec->dev, "Mic button up\n");
+ snd_soc_jack_report(wm5100->jack, 0, SND_JACK_BTN_0);
+ }
+
+ return;
+ }
+
+ /* If we detected a lower impedence during initial startup
+ * then we probably have the wrong polarity, flip it. Don't
+ * do this for the lowest impedences to speed up detection of
+ * plain headphones.
+ */
+ if (wm5100->jack_detecting && (val & 0x3f8)) {
+ wm5100_set_detect_mode(codec, !wm5100->jack_mode);
+
+ return;
+ }
+
+ /* Don't distinguish between buttons, just report any low
+ * impedence as BTN_0.
+ */
+ if (val & 0x3fc) {
+ if (wm5100->jack_mic) {
+ dev_dbg(codec->dev, "Mic button detected\n");
+ snd_soc_jack_report(wm5100->jack, SND_JACK_BTN_0,
+ SND_JACK_BTN_0);
+ } else if (wm5100->jack_detecting) {
+ dev_dbg(codec->dev, "Headphone detected\n");
+ snd_soc_jack_report(wm5100->jack, SND_JACK_HEADPHONE,
+ SND_JACK_HEADPHONE);
+
+ /* Increase the detection rate a bit for
+ * responsiveness.
+ */
+ snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+ WM5100_ACCDET_RATE_MASK,
+ 7 << WM5100_ACCDET_RATE_SHIFT);
+ }
+ }
+}
+
+int wm5100_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack)
+{
+ struct wm5100_priv *wm5100 = snd_soc_codec_get_drvdata(codec);
+
+ if (jack) {
+ wm5100->jack = jack;
+ wm5100->jack_detecting = true;
+
+ wm5100_set_detect_mode(codec, 0);
+
+ /* Slowest detection rate, gives debounce for initial
+ * detection */
+ snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+ WM5100_ACCDET_BIAS_STARTTIME_MASK |
+ WM5100_ACCDET_RATE_MASK,
+ (7 << WM5100_ACCDET_BIAS_STARTTIME_SHIFT) |
+ WM5100_ACCDET_RATE_MASK);
+
+ /* We need the charge pump to power MICBIAS */
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "CP2");
+ snd_soc_dapm_force_enable_pin(&codec->dapm, "SYSCLK");
+ snd_soc_dapm_sync(&codec->dapm);
+
+ /* We start off just enabling microphone detection - even a
+ * plain headphone will trigger detection.
+ */
+ snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+ WM5100_ACCDET_ENA, WM5100_ACCDET_ENA);
+
+ snd_soc_update_bits(codec, WM5100_INTERRUPT_STATUS_3_MASK,
+ WM5100_IM_ACCDET_EINT, 0);
+ } else {
+ snd_soc_update_bits(codec, WM5100_INTERRUPT_STATUS_3_MASK,
+ WM5100_IM_HPDET_EINT |
+ WM5100_IM_ACCDET_EINT,
+ WM5100_IM_HPDET_EINT |
+ WM5100_IM_ACCDET_EINT);
+ snd_soc_update_bits(codec, WM5100_MIC_DETECT_1,
+ WM5100_ACCDET_ENA, 0);
+ wm5100->jack = NULL;
+ }
+
+ return 0;
+}
+
static irqreturn_t wm5100_irq(int irq, void *data)
{
struct snd_soc_codec *codec = data;
@@ -2144,6 +2303,9 @@ static irqreturn_t wm5100_irq(int irq, void *data)
complete(&wm5100->fll[1].lock);
}
+ if (irq_val & WM5100_ACCDET_EINT)
+ wm5100_micd_irq(codec);
+
irq_val = snd_soc_read(codec, WM5100_INTERRUPT_STATUS_4);
if (irq_val < 0) {
dev_err(codec->dev, "Failed to read IRQ status 4: %d\n",
diff --git a/sound/soc/codecs/wm5100.h b/sound/soc/codecs/wm5100.h
index 345b3cffe6f..fa32b124637 100644
--- a/sound/soc/codecs/wm5100.h
+++ b/sound/soc/codecs/wm5100.h
@@ -16,6 +16,8 @@
#include <sound/soc.h>
+int wm5100_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack);
+
#define WM5100_CLK_AIF1 1
#define WM5100_CLK_AIF2 2
#define WM5100_CLK_AIF3 3