#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
 };
 
 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;
        .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);
 
 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,
        .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);
 
        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,