|
@@ -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",
|