Commit 8210804b authored by Hans de Goede's avatar Hans de Goede Committed by Mark Brown

ASoC: rt5640: Add jack-detect support

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=196377Signed-off-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent fb509fa9
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <linux/spi/spi.h> #include <linux/spi/spi.h>
#include <linux/acpi.h> #include <linux/acpi.h>
#include <sound/core.h> #include <sound/core.h>
#include <sound/jack.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>
...@@ -2093,6 +2094,224 @@ int rt5640_sel_asrc_clk_src(struct snd_soc_component *component, ...@@ -2093,6 +2094,224 @@ int rt5640_sel_asrc_clk_src(struct snd_soc_component *component,
} }
EXPORT_SYMBOL_GPL(rt5640_sel_asrc_clk_src); 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) static int rt5640_probe(struct snd_soc_component *component)
{ {
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(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) ...@@ -2176,6 +2395,53 @@ static int rt5640_probe(struct snd_soc_component *component)
if (dmic_en) if (dmic_en)
rt5640_dmic_enable(component, dmic1_data_pin, dmic2_data_pin); 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; return 0;
} }
...@@ -2276,6 +2542,7 @@ static const struct snd_soc_component_driver soc_component_dev_rt5640 = { ...@@ -2276,6 +2542,7 @@ static const struct snd_soc_component_driver soc_component_dev_rt5640 = {
.suspend = rt5640_suspend, .suspend = rt5640_suspend,
.resume = rt5640_resume, .resume = rt5640_resume,
.set_bias_level = rt5640_set_bias_level, .set_bias_level = rt5640_set_bias_level,
.set_jack = rt5640_set_jack,
.controls = rt5640_snd_controls, .controls = rt5640_snd_controls,
.num_controls = ARRAY_SIZE(rt5640_snd_controls), .num_controls = ARRAY_SIZE(rt5640_snd_controls),
.dapm_widgets = rt5640_dapm_widgets, .dapm_widgets = rt5640_dapm_widgets,
...@@ -2409,6 +2676,25 @@ static int rt5640_i2c_probe(struct i2c_client *i2c, ...@@ -2409,6 +2676,25 @@ static int rt5640_i2c_probe(struct i2c_client *i2c,
RT5640_MCLK_DET, RT5640_MCLK_DET); RT5640_MCLK_DET, RT5640_MCLK_DET);
rt5640->hp_mute = 1; 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, return devm_snd_soc_register_component(&i2c->dev,
&soc_component_dev_rt5640, &soc_component_dev_rt5640,
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
#define _RT5640_H #define _RT5640_H
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/workqueue.h>
#include <dt-bindings/sound/rt5640.h>
/* Info */ /* Info */
#define RT5640_RESET 0x00 #define RT5640_RESET 0x00
...@@ -145,6 +147,7 @@ ...@@ -145,6 +147,7 @@
/* Index of Codec Private Register definition */ /* Index of Codec Private Register definition */
#define RT5640_BIAS_CUR4 0x15
#define RT5640_CHPUMP_INT_REG1 0x24 #define RT5640_CHPUMP_INT_REG1 0x24
#define RT5640_MAMP_INT_REG2 0x37 #define RT5640_MAMP_INT_REG2 0x37
#define RT5640_3D_SPK 0x63 #define RT5640_3D_SPK 0x63
...@@ -1606,10 +1609,17 @@ ...@@ -1606,10 +1609,17 @@
#define RT5640_MB2_OC_P_SFT 6 #define RT5640_MB2_OC_P_SFT 6
#define RT5640_MB2_OC_P_NOR (0x0 << 6) #define RT5640_MB2_OC_P_NOR (0x0 << 6)
#define RT5640_MB2_OC_P_INV (0x1 << 6) #define RT5640_MB2_OC_P_INV (0x1 << 6)
#define RT5640_MB1_OC_CLR (0x1 << 3) #define RT5640_MB1_OC_STATUS (0x1 << 3)
#define RT5640_MB1_OC_CLR_SFT 3 #define RT5640_MB1_OC_STATUS_SFT 3
#define RT5640_MB2_OC_CLR (0x1 << 2) #define RT5640_MB2_OC_STATUS (0x1 << 2)
#define RT5640_MB2_OC_CLR_SFT 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) */ /* GPIO Control 1 (0xc0) */
#define RT5640_GP1_PIN_MASK (0x1 << 15) #define RT5640_GP1_PIN_MASK (0x1 << 15)
...@@ -1977,6 +1987,15 @@ ...@@ -1977,6 +1987,15 @@
#define RT5640_MCLK_DET (0x1 << 11) #define RT5640_MCLK_DET (0x1 << 11)
/* Codec Private Register definition */ /* 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) */ /* 3D Speaker Control (0x63) */
#define RT5640_3D_SPK_MASK (0x1 << 15) #define RT5640_3D_SPK_MASK (0x1 << 15)
#define RT5640_3D_SPK_SFT 15 #define RT5640_3D_SPK_SFT 15
...@@ -2106,6 +2125,7 @@ struct rt5640_priv { ...@@ -2106,6 +2125,7 @@ struct rt5640_priv {
struct clk *mclk; struct clk *mclk;
int ldo1_en; /* GPIO for LDO1_EN */ int ldo1_en; /* GPIO for LDO1_EN */
int irq;
int sysclk; int sysclk;
int sysclk_src; int sysclk_src;
int lrck[RT5640_AIFS]; int lrck[RT5640_AIFS];
...@@ -2118,6 +2138,14 @@ struct rt5640_priv { ...@@ -2118,6 +2138,14 @@ struct rt5640_priv {
bool hp_mute; bool hp_mute;
bool asrc_en; 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, int rt5640_dmic_enable(struct snd_soc_component *component,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment