ASoC: es8316: Add jack-detect support
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>
This commit is contained in:
parent
e1de3d237b
commit
8222576610
@ -15,12 +15,14 @@
|
|||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/mod_devicetable.h>
|
#include <linux/mod_devicetable.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <sound/pcm.h>
|
#include <sound/pcm.h>
|
||||||
#include <sound/pcm_params.h>
|
#include <sound/pcm_params.h>
|
||||||
#include <sound/soc.h>
|
#include <sound/soc.h>
|
||||||
#include <sound/soc-dapm.h>
|
#include <sound/soc-dapm.h>
|
||||||
#include <sound/tlv.h>
|
#include <sound/tlv.h>
|
||||||
|
#include <sound/jack.h>
|
||||||
#include "es8316.h"
|
#include "es8316.h"
|
||||||
|
|
||||||
/* In slave mode at single speed, the codec is documented as accepting 5
|
/* 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 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 sysclk;
|
||||||
unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS];
|
unsigned int allowed_rates[NR_SUPPORTED_MCLK_LRCK_RATIOS];
|
||||||
struct snd_pcm_hw_constraint_list sysclk_constraints;
|
struct snd_pcm_hw_constraint_list sysclk_constraints;
|
||||||
@ -529,8 +536,162 @@ static struct snd_soc_dai_driver es8316_dai = {
|
|||||||
.symmetric_rates = 1,
|
.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)
|
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 */
|
/* Reset codec and enable current state machine */
|
||||||
snd_soc_component_write(component, ES8316_RESET, 0x3f);
|
snd_soc_component_write(component, ES8316_RESET, 0x3f);
|
||||||
usleep_range(5000, 5500);
|
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 = {
|
static const struct snd_soc_component_driver soc_component_dev_es8316 = {
|
||||||
.probe = es8316_probe,
|
.probe = es8316_probe,
|
||||||
|
.set_jack = es8316_set_jack,
|
||||||
.controls = es8316_snd_controls,
|
.controls = es8316_snd_controls,
|
||||||
.num_controls = ARRAY_SIZE(es8316_snd_controls),
|
.num_controls = ARRAY_SIZE(es8316_snd_controls),
|
||||||
.dapm_widgets = es8316_dapm_widgets,
|
.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,
|
.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 = {
|
static const struct regmap_config es8316_regmap = {
|
||||||
.reg_bits = 8,
|
.reg_bits = 8,
|
||||||
.val_bits = 8,
|
.val_bits = 8,
|
||||||
.max_register = 0x53,
|
.max_register = 0x53,
|
||||||
|
.volatile_table = &es8316_volatile_table,
|
||||||
.cache_type = REGCACHE_RBTREE,
|
.cache_type = REGCACHE_RBTREE,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int es8316_i2c_probe(struct i2c_client *i2c_client,
|
static int es8316_i2c_probe(struct i2c_client *i2c_client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
|
struct device *dev = &i2c_client->dev;
|
||||||
struct es8316_priv *es8316;
|
struct es8316_priv *es8316;
|
||||||
struct regmap *regmap;
|
int ret;
|
||||||
|
|
||||||
es8316 = devm_kzalloc(&i2c_client->dev, sizeof(struct es8316_priv),
|
es8316 = devm_kzalloc(&i2c_client->dev, sizeof(struct es8316_priv),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
@ -586,9 +759,23 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client,
|
|||||||
|
|
||||||
i2c_set_clientdata(i2c_client, es8316);
|
i2c_set_clientdata(i2c_client, es8316);
|
||||||
|
|
||||||
regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
|
es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap);
|
||||||
if (IS_ERR(regmap))
|
if (IS_ERR(es8316->regmap))
|
||||||
return PTR_ERR(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,
|
return devm_snd_soc_register_component(&i2c_client->dev,
|
||||||
&soc_component_dev_es8316,
|
&soc_component_dev_es8316,
|
||||||
|
@ -126,4 +126,11 @@
|
|||||||
#define ES8316_SERDATA2_LEN_16 0x0c
|
#define ES8316_SERDATA2_LEN_16 0x0c
|
||||||
#define ES8316_SERDATA2_LEN_32 0x10
|
#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
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user