Commit dd235eea authored by Mark Brown's avatar Mark Brown Committed by Chanwoo Choi

extcon: arizona: Support HPDET based accessory identification

The accessory detection functionality in Arizona devices is flexible and
supports several system designs in addition to the default one implemented
by the existing driver. One such design uses the HPDET feature to determine
what kind of accessory is present by comparing measurements taken with the
two headphone grounds available on the device, implement that if selected
by platform data.
Signed-off-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: default avatarChanwoo Choi <cw00.choi@samsung.com>
Signed-off-by: default avatarMyungjoo Ham <myungjoo.ham@samsung.com>
parent 9b1270c7
...@@ -55,6 +55,9 @@ struct arizona_extcon_info { ...@@ -55,6 +55,9 @@ struct arizona_extcon_info {
bool hpdet_active; bool hpdet_active;
int num_hpdet_res;
unsigned int hpdet_res[2];
bool mic; bool mic;
bool detecting; bool detecting;
int jack_flips; int jack_flips;
...@@ -65,8 +68,8 @@ struct arizona_extcon_info { ...@@ -65,8 +68,8 @@ struct arizona_extcon_info {
}; };
static const struct arizona_micd_config micd_default_modes[] = { static const struct arizona_micd_config micd_default_modes[] = {
{ ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
{ 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
{ ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
}; };
static struct { static struct {
...@@ -118,10 +121,6 @@ static void arizona_start_mic(struct arizona_extcon_info *info) ...@@ -118,10 +121,6 @@ static void arizona_start_mic(struct arizona_extcon_info *info)
bool change; bool change;
int ret; int ret;
info->detecting = true;
info->mic = false;
info->jack_flips = 0;
/* Microphone detection can't use idle mode */ /* Microphone detection can't use idle mode */
pm_runtime_get(info->dev); pm_runtime_get(info->dev);
...@@ -311,12 +310,80 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) ...@@ -311,12 +310,80 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info)
return val; return val;
} }
static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading)
{
struct arizona *arizona = info->arizona;
int ret;
/*
* If we're using HPDET for accessory identification we need
* to take multiple measurements, step through them in sequence.
*/
if (arizona->pdata.hpdet_acc_id) {
info->hpdet_res[info->num_hpdet_res++] = *reading;
/*
* If the impedence is too high don't measure the
* second ground.
*/
if (info->num_hpdet_res == 1 && *reading >= 45) {
dev_dbg(arizona->dev, "Skipping ground flip\n");
info->hpdet_res[info->num_hpdet_res++] = *reading;
}
if (info->num_hpdet_res == 1) {
dev_dbg(arizona->dev, "Flipping ground\n");
regmap_update_bits(arizona->regmap,
ARIZONA_ACCESSORY_DETECT_MODE_1,
ARIZONA_ACCDET_SRC,
~info->micd_modes[0].src);
regmap_update_bits(arizona->regmap,
ARIZONA_HEADPHONE_DETECT_1,
ARIZONA_HP_POLL, 0);
regmap_update_bits(arizona->regmap,
ARIZONA_HEADPHONE_DETECT_1,
ARIZONA_HP_POLL, ARIZONA_HP_POLL);
return -EAGAIN;
}
/* OK, got both. Now, compare... */
dev_dbg(arizona->dev, "HPDET measured %d %d\n",
info->hpdet_res[0], info->hpdet_res[1]);
if (info->hpdet_res[0] > info->hpdet_res[1] * 2) {
dev_dbg(arizona->dev, "Detected mic\n");
info->mic = true;
ret = extcon_set_cable_state_(&info->edev,
ARIZONA_CABLE_MICROPHONE,
true);
if (ret != 0) {
dev_err(arizona->dev,
"Failed to report mic: %d\n", ret);
}
/* Take the headphone impedance for the main report */
*reading = info->hpdet_res[1];
} else {
dev_dbg(arizona->dev, "Detected headphone\n");
}
/* Make sure everything is reset back to the real polarity */
regmap_update_bits(arizona->regmap,
ARIZONA_ACCESSORY_DETECT_MODE_1,
ARIZONA_ACCDET_SRC,
info->micd_modes[0].src);
}
return 0;
}
static irqreturn_t arizona_hpdet_irq(int irq, void *data) static irqreturn_t arizona_hpdet_irq(int irq, void *data)
{ {
struct arizona_extcon_info *info = data; struct arizona_extcon_info *info = data;
struct arizona *arizona = info->arizona; struct arizona *arizona = info->arizona;
int report = ARIZONA_CABLE_HEADPHONE; int report = ARIZONA_CABLE_HEADPHONE;
int ret; int ret, reading;
mutex_lock(&info->lock); mutex_lock(&info->lock);
...@@ -344,14 +411,23 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) ...@@ -344,14 +411,23 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
} else if (ret < 0) { } else if (ret < 0) {
goto done; goto done;
} }
reading = ret;
/* Reset back to starting range */ /* Reset back to starting range */
regmap_update_bits(arizona->regmap, regmap_update_bits(arizona->regmap,
ARIZONA_HEADPHONE_DETECT_1, ARIZONA_HEADPHONE_DETECT_1,
ARIZONA_HP_IMPEDANCE_RANGE_MASK, 0); ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL,
0);
ret = arizona_hpdet_do_id(info, &reading);
if (ret == -EAGAIN) {
goto out;
} else if (ret < 0) {
goto done;
}
/* Report high impedence cables as line outputs */ /* Report high impedence cables as line outputs */
if (ret >= 5000) if (reading >= 5000)
report = ARIZONA_CABLE_LINEOUT; report = ARIZONA_CABLE_LINEOUT;
else else
report = ARIZONA_CABLE_HEADPHONE; report = ARIZONA_CABLE_HEADPHONE;
...@@ -370,8 +446,6 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) ...@@ -370,8 +446,6 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret);
done: done:
regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1,
ARIZONA_HP_POLL, 0);
/* Revert back to MICDET mode */ /* Revert back to MICDET mode */
regmap_update_bits(arizona->regmap, regmap_update_bits(arizona->regmap,
...@@ -451,8 +525,58 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info) ...@@ -451,8 +525,58 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info)
info->hpdet_active = false; info->hpdet_active = false;
} }
static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
{
struct arizona *arizona = info->arizona;
int ret;
dev_dbg(arizona->dev, "Starting identification via HPDET\n");
/* Make sure we keep the device enabled during the measurement */
pm_runtime_get(info->dev);
info->hpdet_active = true;
ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000);
if (ret != 0)
dev_warn(arizona->dev, "Failed to do magic: %d\n", ret);
ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000);
if (ret != 0)
dev_warn(arizona->dev, "Failed to do magic: %d\n", ret);
ret = regmap_update_bits(arizona->regmap,
ARIZONA_ACCESSORY_DETECT_MODE_1,
ARIZONA_ACCDET_SRC | ARIZONA_ACCDET_MODE_MASK,
info->micd_modes[0].src |
ARIZONA_ACCDET_MODE_HPL);
if (ret != 0) {
dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret);
goto err;
} }
ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1,
ARIZONA_HP_POLL, ARIZONA_HP_POLL);
if (ret != 0) {
dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n",
ret);
goto err;
}
return;
err:
regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1,
ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
/* Just report headphone */
ret = extcon_update_state(&info->edev,
1 << ARIZONA_CABLE_HEADPHONE,
1 << ARIZONA_CABLE_HEADPHONE);
if (ret != 0)
dev_err(arizona->dev, "Failed to report headphone: %d\n", ret);
info->hpdet_active = false; info->hpdet_active = false;
} }
...@@ -612,12 +736,24 @@ static irqreturn_t arizona_jackdet(int irq, void *data) ...@@ -612,12 +736,24 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
dev_err(arizona->dev, "Mechanical report failed: %d\n", dev_err(arizona->dev, "Mechanical report failed: %d\n",
ret); ret);
arizona_start_mic(info); if (!arizona->pdata.hpdet_acc_id) {
info->detecting = true;
info->mic = false;
info->jack_flips = 0;
arizona_start_mic(info);
} else {
arizona_start_hpdet_acc_id(info);
}
} else { } else {
dev_dbg(arizona->dev, "Detected jack removal\n"); dev_dbg(arizona->dev, "Detected jack removal\n");
arizona_stop_mic(info); arizona_stop_mic(info);
info->num_hpdet_res = 0;
for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++)
info->hpdet_res[i] = 0;
info->mic = false;
for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
input_report_key(info->input, input_report_key(info->input,
...@@ -665,7 +801,6 @@ static int arizona_extcon_probe(struct platform_device *pdev) ...@@ -665,7 +801,6 @@ static int arizona_extcon_probe(struct platform_device *pdev)
mutex_init(&info->lock); mutex_init(&info->lock);
info->arizona = arizona; info->arizona = arizona;
info->dev = &pdev->dev; info->dev = &pdev->dev;
info->detecting = true;
platform_set_drvdata(pdev, info); platform_set_drvdata(pdev, info);
switch (arizona->type) { switch (arizona->type) {
......
...@@ -99,6 +99,9 @@ struct arizona_pdata { ...@@ -99,6 +99,9 @@ struct arizona_pdata {
/** GPIO5 is used for jack detection */ /** GPIO5 is used for jack detection */
bool jd_gpio5; bool jd_gpio5;
/** Use the headphone detect circuit to identify the accessory */
bool hpdet_acc_id;
/** GPIO for mic detection polarity */ /** GPIO for mic detection polarity */
int micd_pol_gpio; int micd_pol_gpio;
......
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