ASoC: rt5640: Add jack-detect support
authorHans de Goede <hdegoede@redhat.com>
Tue, 8 May 2018 15:35:51 +0000 (17:35 +0200)
committerMark Brown <broonie@kernel.org>
Fri, 11 May 2018 02:23:19 +0000 (11:23 +0900)
Add jack-detect support, loosely based on earlier work on this by:

Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Francisco mendez <francisco.mendez@intel.com>

Note getting the OVCD to work reliable was sort of finicky, so there are
quite a few comments on this to hopefully avoid people breaking it in the
future.

This (and the follow-up button press support) has been tested on the
following devices:

Acer Iconia Tab 8 W1-810
Asus T100CHI
Asus T100TA
Asus T200TA
Axxo WT1011
Chuwi Vi8
Dell Venue 8 Pro 5830
HP Pavilion X2 10-n000nd
HP Stream 7
I.T. Works TW891
Lamina I8270
MSI S100
Peaq C1010
Pipo W4
PoV MobiiTAB-P800W (v2.0)
Toshiba Click Mini L9W-B

BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=196377
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/rt5640.c
sound/soc/codecs/rt5640.h

index d7c7b207f3cc4741390cc3eafc82f6d4cf5197c7..8e306f509601dd5eebea500fcc5204b4e536f392 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/spi/spi.h>
 #include <linux/acpi.h>
 #include <sound/core.h>
+#include <sound/jack.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
@@ -2093,6 +2094,224 @@ int rt5640_sel_asrc_clk_src(struct snd_soc_component *component,
 }
 EXPORT_SYMBOL_GPL(rt5640_sel_asrc_clk_src);
 
+static void rt5640_enable_micbias1_for_ovcd(struct snd_soc_component *component)
+{
+       struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+
+       snd_soc_dapm_mutex_lock(dapm);
+       snd_soc_dapm_force_enable_pin_unlocked(dapm, "LDO2");
+       snd_soc_dapm_force_enable_pin_unlocked(dapm, "MICBIAS1");
+       /* OVCD is unreliable when used with RCCLK as sysclk-source */
+       snd_soc_dapm_force_enable_pin_unlocked(dapm, "Platform Clock");
+       snd_soc_dapm_sync_unlocked(dapm);
+       snd_soc_dapm_mutex_unlock(dapm);
+}
+
+static void rt5640_disable_micbias1_for_ovcd(struct snd_soc_component *component)
+{
+       struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+
+       snd_soc_dapm_mutex_lock(dapm);
+       snd_soc_dapm_disable_pin_unlocked(dapm, "Platform Clock");
+       snd_soc_dapm_disable_pin_unlocked(dapm, "MICBIAS1");
+       snd_soc_dapm_disable_pin_unlocked(dapm, "LDO2");
+       snd_soc_dapm_sync_unlocked(dapm);
+       snd_soc_dapm_mutex_unlock(dapm);
+}
+
+static void rt5640_clear_micbias1_ovcd(struct snd_soc_component *component)
+{
+       snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
+               RT5640_MB1_OC_STATUS, 0);
+}
+
+static bool rt5640_micbias1_ovcd(struct snd_soc_component *component)
+{
+       int val;
+
+       val = snd_soc_component_read32(component, RT5640_IRQ_CTRL2);
+       dev_dbg(component->dev, "irq ctrl2 %#04x\n", val);
+
+       return (val & RT5640_MB1_OC_STATUS);
+}
+
+static bool rt5640_jack_inserted(struct snd_soc_component *component)
+{
+       struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);
+       int val;
+
+       val = snd_soc_component_read32(component, RT5640_INT_IRQ_ST);
+       dev_dbg(component->dev, "irq status %#04x\n", val);
+
+       if (rt5640->jd_inverted)
+               return !(val & RT5640_JD_STATUS);
+       else
+               return (val & RT5640_JD_STATUS);
+}
+
+/* Jack detect timings */
+#define JACK_SETTLE_TIME       100 /* milli seconds */
+#define JACK_DETECT_COUNT      5
+#define JACK_DETECT_MAXCOUNT   20  /* Aprox. 2 seconds worth of tries */
+
+static int rt5640_detect_headset(struct snd_soc_component *component)
+{
+       int i, headset_count = 0, headphone_count = 0;
+
+       /*
+        * We get the insertion event before the jack is fully inserted at which
+        * point the second ring on a TRRS connector may short the 2nd ring and
+        * sleeve contacts, also the overcurrent detection is not entirely
+        * reliable. So we try several times with a wait in between until we
+        * detect the same type JACK_DETECT_COUNT times in a row.
+        */
+       for (i = 0; i < JACK_DETECT_MAXCOUNT; i++) {
+               /* Clear any previous over-current status flag */
+               rt5640_clear_micbias1_ovcd(component);
+
+               msleep(JACK_SETTLE_TIME);
+
+               /* Check the jack is still connected before checking ovcd */
+               if (!rt5640_jack_inserted(component))
+                       return 0;
+
+               if (rt5640_micbias1_ovcd(component)) {
+                       /*
+                        * Over current detected, there is a short between the
+                        * 2nd ring contact and the ground, so a TRS connector
+                        * without a mic contact and thus plain headphones.
+                        */
+                       dev_dbg(component->dev, "jack mic-gnd shorted\n");
+                       headset_count = 0;
+                       headphone_count++;
+                       if (headphone_count == JACK_DETECT_COUNT)
+                               return SND_JACK_HEADPHONE;
+               } else {
+                       dev_dbg(component->dev, "jack mic-gnd open\n");
+                       headphone_count = 0;
+                       headset_count++;
+                       if (headset_count == JACK_DETECT_COUNT)
+                               return SND_JACK_HEADSET;
+               }
+       }
+
+       dev_err(component->dev, "Error detecting headset vs headphones, bad contact?, assuming headphones\n");
+       return SND_JACK_HEADPHONE;
+}
+
+static void rt5640_jack_work(struct work_struct *work)
+{
+       struct rt5640_priv *rt5640 =
+               container_of(work, struct rt5640_priv, jack_work);
+       struct snd_soc_component *component = rt5640->component;
+       int status;
+
+       if (!rt5640_jack_inserted(component)) {
+               /* Jack removed, or spurious IRQ? */
+               if (rt5640->jack->status & SND_JACK_HEADPHONE) {
+                       snd_soc_jack_report(rt5640->jack, 0, SND_JACK_HEADSET);
+                       dev_dbg(component->dev, "jack unplugged\n");
+               }
+       } else if (!(rt5640->jack->status & SND_JACK_HEADPHONE)) {
+               /* Jack inserted */
+               rt5640_enable_micbias1_for_ovcd(component);
+               status = rt5640_detect_headset(component);
+               rt5640_disable_micbias1_for_ovcd(component);
+
+               dev_dbg(component->dev, "detect status %#02x\n", status);
+               snd_soc_jack_report(rt5640->jack, status, SND_JACK_HEADSET);
+       }
+}
+
+static irqreturn_t rt5640_irq(int irq, void *data)
+{
+       struct rt5640_priv *rt5640 = data;
+
+       if (rt5640->jack)
+               queue_work(system_long_wq, &rt5640->jack_work);
+
+       return IRQ_HANDLED;
+}
+
+static void rt5640_cancel_work(void *data)
+{
+       struct rt5640_priv *rt5640 = data;
+
+       cancel_work_sync(&rt5640->jack_work);
+}
+
+static void rt5640_enable_jack_detect(struct snd_soc_component *component,
+                                     struct snd_soc_jack *jack)
+{
+       struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);
+
+       /* Select JD-source */
+       snd_soc_component_update_bits(component, RT5640_JD_CTRL,
+               RT5640_JD_MASK, rt5640->jd_src);
+
+       /* Selecting GPIO01 as an interrupt */
+       snd_soc_component_update_bits(component, RT5640_GPIO_CTRL1,
+               RT5640_GP1_PIN_MASK, RT5640_GP1_PIN_IRQ);
+
+       /* Set GPIO1 output */
+       snd_soc_component_update_bits(component, RT5640_GPIO_CTRL3,
+               RT5640_GP1_PF_MASK, RT5640_GP1_PF_OUT);
+
+       /* Enabling jd2 in general control 1 */
+       snd_soc_component_write(component, RT5640_DUMMY1, 0x3f41);
+
+       /* Enabling jd2 in general control 2 */
+       snd_soc_component_write(component, RT5640_DUMMY2, 0x4001);
+
+       snd_soc_component_write(component, RT5640_PR_BASE + RT5640_BIAS_CUR4,
+               0xa800 | rt5640->ovcd_sf);
+
+       snd_soc_component_update_bits(component, RT5640_MICBIAS,
+               RT5640_MIC1_OVTH_MASK | RT5640_MIC1_OVCD_MASK,
+               rt5640->ovcd_th | RT5640_MIC1_OVCD_EN);
+
+       /*
+        * The over-current-detect is only reliable in detecting the absence
+        * of over-current, when the mic-contact in the jack is short-circuited,
+        * the hardware periodically retries if it can apply the bias-current
+        * leading to the ovcd status flip-flopping 1-0-1 with it being 0 about
+        * 10% of the time, as we poll the ovcd status bit we might hit that
+        * 10%, so we enable sticky mode and when checking OVCD we clear the
+        * status, msleep() a bit and then check to get a reliable reading.
+        */
+       snd_soc_component_update_bits(component, RT5640_IRQ_CTRL2,
+               RT5640_MB1_OC_STKY_MASK, RT5640_MB1_OC_STKY_EN);
+
+       snd_soc_component_write(component, RT5640_IRQ_CTRL1,
+                               RT5640_IRQ_JD_NOR);
+
+       rt5640->jack = jack;
+       enable_irq(rt5640->irq);
+       /* sync initial jack state */
+       queue_work(system_long_wq, &rt5640->jack_work);
+}
+
+static void rt5640_disable_jack_detect(struct snd_soc_component *component)
+{
+       struct rt5640_priv *rt5640 = snd_soc_component_get_drvdata(component);
+
+       disable_irq(rt5640->irq);
+       rt5640_cancel_work(rt5640);
+
+       rt5640->jack = NULL;
+}
+
+static int rt5640_set_jack(struct snd_soc_component *component,
+                          struct snd_soc_jack *jack, void *data)
+{
+       if (jack)
+               rt5640_enable_jack_detect(component, jack);
+       else
+               rt5640_disable_jack_detect(component);
+
+       return 0;
+}
+
 static int rt5640_probe(struct snd_soc_component *component)
 {
        struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
@@ -2176,6 +2395,53 @@ static int rt5640_probe(struct snd_soc_component *component)
        if (dmic_en)
                rt5640_dmic_enable(component, dmic1_data_pin, dmic2_data_pin);
 
+       if (device_property_read_u32(component->dev,
+                                    "realtek,jack-detect-source", &val) == 0) {
+               if (val <= RT5640_JD_SRC_GPIO4)
+                       rt5640->jd_src = val << RT5640_JD_SFT;
+               else
+                       dev_warn(component->dev, "Warning: Invalid jack-detect-source value: %d, leaving jack-detect disabled\n",
+                                val);
+       }
+
+       if (!device_property_read_bool(component->dev, "realtek,jack-detect-not-inverted"))
+               rt5640->jd_inverted = true;
+
+       /*
+        * Testing on various boards has shown that good defaults for the OVCD
+        * threshold and scale-factor are 2000µA and 0.75. For an effective
+        * limit of 1500µA, this seems to be more reliable then 1500µA and 1.0.
+        */
+       rt5640->ovcd_th = RT5640_MIC1_OVTH_2000UA;
+       rt5640->ovcd_sf = RT5640_MIC_OVCD_SF_0P75;
+
+       if (device_property_read_u32(component->dev,
+                       "realtek,over-current-threshold-microamp", &val) == 0) {
+               switch (val) {
+               case 600:
+                       rt5640->ovcd_th = RT5640_MIC1_OVTH_600UA;
+                       break;
+               case 1500:
+                       rt5640->ovcd_th = RT5640_MIC1_OVTH_1500UA;
+                       break;
+               case 2000:
+                       rt5640->ovcd_th = RT5640_MIC1_OVTH_2000UA;
+                       break;
+               default:
+                       dev_warn(component->dev, "Warning: Invalid over-current-threshold-microamp value: %d, defaulting to 2000uA\n",
+                                val);
+               }
+       }
+
+       if (device_property_read_u32(component->dev,
+                       "realtek,over-current-scale-factor", &val) == 0) {
+               if (val <= RT5640_OVCD_SF_1P5)
+                       rt5640->ovcd_sf = val << RT5640_MIC_OVCD_SF_SFT;
+               else
+                       dev_warn(component->dev, "Warning: Invalid over-current-scale-factor value: %d, defaulting to 0.75\n",
+                                val);
+       }
+
        return 0;
 }
 
@@ -2276,6 +2542,7 @@ static const struct snd_soc_component_driver soc_component_dev_rt5640 = {
        .suspend                = rt5640_suspend,
        .resume                 = rt5640_resume,
        .set_bias_level         = rt5640_set_bias_level,
+       .set_jack               = rt5640_set_jack,
        .controls               = rt5640_snd_controls,
        .num_controls           = ARRAY_SIZE(rt5640_snd_controls),
        .dapm_widgets           = rt5640_dapm_widgets,
@@ -2409,6 +2676,25 @@ static int rt5640_i2c_probe(struct i2c_client *i2c,
                                RT5640_MCLK_DET, RT5640_MCLK_DET);
 
        rt5640->hp_mute = 1;
+       rt5640->irq = i2c->irq;
+       INIT_WORK(&rt5640->jack_work, rt5640_jack_work);
+
+       /* Make sure work is stopped on probe-error / remove */
+       ret = devm_add_action_or_reset(&i2c->dev, rt5640_cancel_work, rt5640);
+       if (ret)
+               return ret;
+
+       ret = devm_request_irq(&i2c->dev, rt5640->irq, rt5640_irq,
+                              IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+                              | IRQF_ONESHOT, "rt5640", rt5640);
+       if (ret == 0) {
+               /* Gets re-enabled by rt5640_set_jack() */
+               disable_irq(rt5640->irq);
+       } else {
+               dev_warn(&i2c->dev, "Failed to reguest IRQ %d: %d\n",
+                        rt5640->irq, ret);
+               rt5640->irq = -ENXIO;
+       }
 
        return devm_snd_soc_register_component(&i2c->dev,
                                      &soc_component_dev_rt5640,
index 2db6586f5ab45c8bb7fa0284e714a7ab1a719876..9d08471734b34e05433785b4eeceaf90b8c49b54 100644 (file)
@@ -13,6 +13,8 @@
 #define _RT5640_H
 
 #include <linux/clk.h>
+#include <linux/workqueue.h>
+#include <dt-bindings/sound/rt5640.h>
 
 /* Info */
 #define RT5640_RESET                           0x00
 
 
 /* Index of Codec Private Register definition */
+#define RT5640_BIAS_CUR4                       0x15
 #define RT5640_CHPUMP_INT_REG1                 0x24
 #define RT5640_MAMP_INT_REG2                   0x37
 #define RT5640_3D_SPK                          0x63
 #define RT5640_MB2_OC_P_SFT                    6
 #define RT5640_MB2_OC_P_NOR                    (0x0 << 6)
 #define RT5640_MB2_OC_P_INV                    (0x1 << 6)
-#define RT5640_MB1_OC_CLR                      (0x1 << 3)
-#define RT5640_MB1_OC_CLR_SFT                  3
-#define RT5640_MB2_OC_CLR                      (0x1 << 2)
-#define RT5640_MB2_OC_CLR_SFT                  2
+#define RT5640_MB1_OC_STATUS                   (0x1 << 3)
+#define RT5640_MB1_OC_STATUS_SFT               3
+#define RT5640_MB2_OC_STATUS                   (0x1 << 2)
+#define RT5640_MB2_OC_STATUS_SFT               2
+
+/* GPIO and Internal Status (0xbf) */
+#define RT5640_GPIO1_STATUS                    (0x1 << 8)
+#define RT5640_GPIO2_STATUS                    (0x1 << 7)
+#define RT5640_JD_STATUS                       (0x1 << 4)
+#define RT5640_OVT_STATUS                      (0x1 << 3)
+#define RT5640_CLS_D_OVCD_STATUS               (0x1 << 0)
 
 /* GPIO Control 1 (0xc0) */
 #define RT5640_GP1_PIN_MASK                    (0x1 << 15)
 #define RT5640_MCLK_DET                                (0x1 << 11)
 
 /* Codec Private Register definition */
+
+/* MIC Over current threshold scale factor (0x15) */
+#define RT5640_MIC_OVCD_SF_MASK                        (0x3 << 8)
+#define RT5640_MIC_OVCD_SF_SFT                 8
+#define RT5640_MIC_OVCD_SF_0P5                 (0x0 << 8)
+#define RT5640_MIC_OVCD_SF_0P75                        (0x1 << 8)
+#define RT5640_MIC_OVCD_SF_1P0                 (0x2 << 8)
+#define RT5640_MIC_OVCD_SF_1P5                 (0x3 << 8)
+
 /* 3D Speaker Control (0x63) */
 #define RT5640_3D_SPK_MASK                     (0x1 << 15)
 #define RT5640_3D_SPK_SFT                      15
@@ -2106,6 +2125,7 @@ struct rt5640_priv {
        struct clk *mclk;
 
        int ldo1_en; /* GPIO for LDO1_EN */
+       int irq;
        int sysclk;
        int sysclk_src;
        int lrck[RT5640_AIFS];
@@ -2118,6 +2138,14 @@ struct rt5640_priv {
 
        bool hp_mute;
        bool asrc_en;
+
+       /* Jack detect data */
+       struct work_struct jack_work;
+       struct snd_soc_jack *jack;
+       unsigned int jd_src;
+       bool jd_inverted;
+       unsigned int ovcd_th;
+       unsigned int ovcd_sf;
 };
 
 int rt5640_dmic_enable(struct snd_soc_component *component,