ASoC: es8316: Add jack-detect support
authorHans de Goede <hdegoede@redhat.com>
Thu, 3 Jan 2019 13:45:26 +0000 (14:45 +0100)
committerMark Brown <broonie@kernel.org>
Fri, 4 Jan 2019 15:09:48 +0000 (15:09 +0000)
Adding jack-detect support may seem weird for a codec with only
a single output, but it is necessary. The ES8316 appnote showing
the intended usage uses a jack-receptacle which physically disconnects
the speakers from the output when a jack is plugged in.

But all 3 devices using the es8316 which I have (2 Cherry Trail
devices and one Bay Trail CR device), use an analog mux to disconnect
the speakers, driven by a GPIO. In order to enable/disable the speakers
at the right time, we need jack-detect.

The same goes for the microphone where we must correctly set the mux
for the single ADC to either the internal or the headset microphone.

All devices I have support the es8316's builtin jack-detect functionality.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/codecs/es8316.c
sound/soc/codecs/es8316.h

index e97d12d578b00c1a287209e24fed9d29688d777e..26413851e43464b2aa812b0a769009c2cdc5efe2 100644 (file)
 #include <linux/delay.h>
 #include <linux/i2c.h>
 #include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
 #include <linux/regmap.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
 #include <sound/soc-dapm.h>
 #include <sound/tlv.h>
+#include <sound/jack.h>
 #include "es8316.h"
 
 /* In slave mode at single speed, the codec is documented as accepting 5
@@ -33,6 +35,11 @@ static const unsigned int supported_mclk_lrck_ratios[] = {
 };
 
 struct es8316_priv {
+       struct mutex lock;
+       struct regmap *regmap;
+       struct snd_soc_component *component;
+       struct snd_soc_jack *jack;
+       int irq;
        unsigned int sysclk;
        unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS];
        struct snd_pcm_hw_constraint_list sysclk_constraints;
@@ -529,8 +536,162 @@ static struct snd_soc_dai_driver es8316_dai = {
        .symmetric_rates = 1,
 };
 
+static void es8316_enable_micbias_for_mic_gnd_short_detect(
+       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, "Bias");
+       snd_soc_dapm_force_enable_pin_unlocked(dapm, "Analog power");
+       snd_soc_dapm_force_enable_pin_unlocked(dapm, "Mic Bias");
+       snd_soc_dapm_sync_unlocked(dapm);
+       snd_soc_dapm_mutex_unlock(dapm);
+
+       msleep(20);
+}
+
+static void es8316_disable_micbias_for_mic_gnd_short_detect(
+       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, "Mic Bias");
+       snd_soc_dapm_disable_pin_unlocked(dapm, "Analog power");
+       snd_soc_dapm_disable_pin_unlocked(dapm, "Bias");
+       snd_soc_dapm_sync_unlocked(dapm);
+       snd_soc_dapm_mutex_unlock(dapm);
+}
+
+static irqreturn_t es8316_irq(int irq, void *data)
+{
+       struct es8316_priv *es8316 = data;
+       struct snd_soc_component *comp = es8316->component;
+       unsigned int flags;
+
+       mutex_lock(&es8316->lock);
+
+       regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags);
+       if (flags == 0x00)
+               goto out; /* Powered-down / reset */
+
+       /* Catch spurious IRQ before set_jack is called */
+       if (!es8316->jack)
+               goto out;
+
+       dev_dbg(comp->dev, "gpio flags %#04x\n", flags);
+       if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) {
+               /* Jack removed, or spurious IRQ? */
+               if (es8316->jack->status & SND_JACK_MICROPHONE)
+                       es8316_disable_micbias_for_mic_gnd_short_detect(comp);
+
+               if (es8316->jack->status & SND_JACK_HEADPHONE) {
+                       snd_soc_jack_report(es8316->jack, 0,
+                                           SND_JACK_HEADSET | SND_JACK_BTN_0);
+                       dev_dbg(comp->dev, "jack unplugged\n");
+               }
+       } else if (!(es8316->jack->status & SND_JACK_HEADPHONE)) {
+               /* Jack inserted, determine type */
+               es8316_enable_micbias_for_mic_gnd_short_detect(comp);
+               regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags);
+               dev_dbg(comp->dev, "gpio flags %#04x\n", flags);
+               if (flags & ES8316_GPIO_FLAG_HP_NOT_INSERTED) {
+                       /* Jack unplugged underneath us */
+                       es8316_disable_micbias_for_mic_gnd_short_detect(comp);
+               } else if (flags & ES8316_GPIO_FLAG_GM_NOT_SHORTED) {
+                       /* Open, headset */
+                       snd_soc_jack_report(es8316->jack,
+                                           SND_JACK_HEADSET,
+                                           SND_JACK_HEADSET);
+                       /* Keep mic-gnd-short detection on for button press */
+               } else {
+                       /* Shorted, headphones */
+                       snd_soc_jack_report(es8316->jack,
+                                           SND_JACK_HEADPHONE,
+                                           SND_JACK_HEADSET);
+                       /* No longer need mic-gnd-short detection */
+                       es8316_disable_micbias_for_mic_gnd_short_detect(comp);
+               }
+       } else if (es8316->jack->status & SND_JACK_MICROPHONE) {
+               /* Interrupt while jack inserted, report button state */
+               if (flags & ES8316_GPIO_FLAG_GM_NOT_SHORTED) {
+                       /* Open, button release */
+                       snd_soc_jack_report(es8316->jack, 0, SND_JACK_BTN_0);
+               } else {
+                       /* Short, button press */
+                       snd_soc_jack_report(es8316->jack,
+                                           SND_JACK_BTN_0,
+                                           SND_JACK_BTN_0);
+               }
+       }
+
+out:
+       mutex_unlock(&es8316->lock);
+       return IRQ_HANDLED;
+}
+
+static void es8316_enable_jack_detect(struct snd_soc_component *component,
+                                     struct snd_soc_jack *jack)
+{
+       struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);
+
+       mutex_lock(&es8316->lock);
+
+       es8316->jack = jack;
+
+       if (es8316->jack->status & SND_JACK_MICROPHONE)
+               es8316_enable_micbias_for_mic_gnd_short_detect(component);
+
+       snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE,
+                                     ES8316_GPIO_ENABLE_INTERRUPT,
+                                     ES8316_GPIO_ENABLE_INTERRUPT);
+
+       mutex_unlock(&es8316->lock);
+
+       /* Enable irq and sync initial jack state */
+       enable_irq(es8316->irq);
+       es8316_irq(es8316->irq, es8316);
+}
+
+static void es8316_disable_jack_detect(struct snd_soc_component *component)
+{
+       struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);
+
+       disable_irq(es8316->irq);
+
+       mutex_lock(&es8316->lock);
+
+       snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE,
+                                     ES8316_GPIO_ENABLE_INTERRUPT, 0);
+
+       if (es8316->jack->status & SND_JACK_MICROPHONE) {
+               es8316_disable_micbias_for_mic_gnd_short_detect(component);
+               snd_soc_jack_report(es8316->jack, 0, SND_JACK_BTN_0);
+       }
+
+       es8316->jack = NULL;
+
+       mutex_unlock(&es8316->lock);
+}
+
+static int es8316_set_jack(struct snd_soc_component *component,
+                          struct snd_soc_jack *jack, void *data)
+{
+       if (jack)
+               es8316_enable_jack_detect(component, jack);
+       else
+               es8316_disable_jack_detect(component);
+
+       return 0;
+}
+
 static int es8316_probe(struct snd_soc_component *component)
 {
+       struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component);
+
+       es8316->component = component;
+
        /* Reset codec and enable current state machine */
        snd_soc_component_write(component, ES8316_RESET, 0x3f);
        usleep_range(5000, 5500);
@@ -555,6 +716,7 @@ static int es8316_probe(struct snd_soc_component *component)
 
 static const struct snd_soc_component_driver soc_component_dev_es8316 = {
        .probe                  = es8316_probe,
+       .set_jack               = es8316_set_jack,
        .controls               = es8316_snd_controls,
        .num_controls           = ARRAY_SIZE(es8316_snd_controls),
        .dapm_widgets           = es8316_dapm_widgets,
@@ -566,18 +728,29 @@ static const struct snd_soc_component_driver soc_component_dev_es8316 = {
        .non_legacy_dai_naming  = 1,
 };
 
+static const struct regmap_range es8316_volatile_ranges[] = {
+       regmap_reg_range(ES8316_GPIO_FLAG, ES8316_GPIO_FLAG),
+};
+
+static const struct regmap_access_table es8316_volatile_table = {
+       .yes_ranges     = es8316_volatile_ranges,
+       .n_yes_ranges   = ARRAY_SIZE(es8316_volatile_ranges),
+};
+
 static const struct regmap_config es8316_regmap = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = 0x53,
+       .volatile_table = &es8316_volatile_table,
        .cache_type = REGCACHE_RBTREE,
 };
 
 static int es8316_i2c_probe(struct i2c_client *i2c_client,
                            const struct i2c_device_id *id)
 {
+       struct device *dev = &i2c_client->dev;
        struct es8316_priv *es8316;
-       struct regmap *regmap;
+       int ret;
 
        es8316 = devm_kzalloc(&i2c_client->dev, sizeof(struct es8316_priv),
                              GFP_KERNEL);
@@ -586,9 +759,23 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client,
 
        i2c_set_clientdata(i2c_client, es8316);
 
-       regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
-       if (IS_ERR(regmap))
-               return PTR_ERR(regmap);
+       es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
+       if (IS_ERR(es8316->regmap))
+               return PTR_ERR(es8316->regmap);
+
+       es8316->irq = i2c_client->irq;
+       mutex_init(&es8316->lock);
+
+       ret = devm_request_threaded_irq(dev, es8316->irq, NULL, es8316_irq,
+                                       IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
+                                       "es8316", es8316);
+       if (ret == 0) {
+               /* Gets re-enabled by es8316_set_jack() */
+               disable_irq(es8316->irq);
+       } else {
+               dev_warn(dev, "Failed to get IRQ %d: %d\n", es8316->irq, ret);
+               es8316->irq = -ENXIO;
+       }
 
        return devm_snd_soc_register_component(&i2c_client->dev,
                                      &soc_component_dev_es8316,
index 6bcdd63ea45948d7d08ed4583917e757a0f05dbc..439a0130cbb7d51a5f227e24a3d4586ac78f89e0 100644 (file)
 #define ES8316_SERDATA2_LEN_16         0x0c
 #define ES8316_SERDATA2_LEN_32         0x10
 
+/* ES8316_GPIO_DEBOUNCE        */
+#define ES8316_GPIO_ENABLE_INTERRUPT           0x02
+
+/* ES8316_GPIO_FLAG */
+#define ES8316_GPIO_FLAG_GM_NOT_SHORTED                0x02
+#define ES8316_GPIO_FLAG_HP_NOT_INSERTED       0x04
+
 #endif