#define ARIZONA_HPDET_MAX 10000
#define HPDET_DEBOUNCE 500
+#define DEFAULT_MICD_TIMEOUT 2000
struct arizona_extcon_info {
struct device *dev;
const struct arizona_micd_range *micd_ranges;
int num_micd_ranges;
+ int micd_timeout;
+
bool micd_reva;
bool micd_clamp;
struct delayed_work hpdet_work;
+ struct delayed_work micd_detect_work;
+ struct delayed_work micd_timeout_work;
bool hpdet_active;
bool hpdet_done;
return val;
}
-static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading)
+static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading,
+ bool *mic)
{
struct arizona *arizona = info->arizona;
int id_gpio = arizona->pdata.hpdet_id_gpio;
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, ARIZONA_HP_POLL);
- return -EAGAIN;
- }
-
/* Only check the mic directly if we didn't already ID it */
- if (id_gpio && info->num_hpdet_res == 2 &&
- !((info->hpdet_res[0] > info->hpdet_res[1] * 2))) {
+ if (id_gpio && info->num_hpdet_res == 1) {
dev_dbg(arizona->dev, "Measuring mic\n");
regmap_update_bits(arizona->regmap,
}
/* OK, got both. Now, compare... */
- dev_dbg(arizona->dev, "HPDET measured %d %d %d\n",
- info->hpdet_res[0], info->hpdet_res[1],
- info->hpdet_res[2]);
-
+ dev_dbg(arizona->dev, "HPDET measured %d %d\n",
+ info->hpdet_res[0], info->hpdet_res[1]);
/* Take the headphone impedance for the main report */
*reading = info->hpdet_res[0];
}
/*
- * Either the two grounds measure differently or we
- * measure the mic as high impedance.
+ * If we measure the mic as
*/
- if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) ||
- (id_gpio && info->hpdet_res[2] > 1257)) {
+ if (!id_gpio || info->hpdet_res[1] > 50) {
dev_dbg(arizona->dev, "Detected mic\n");
- info->mic = true;
+ *mic = true;
info->detecting = true;
} else {
dev_dbg(arizona->dev, "Detected headphone\n");
int id_gpio = arizona->pdata.hpdet_id_gpio;
int report = ARIZONA_CABLE_HEADPHONE;
int ret, reading;
+ bool mic = false;
mutex_lock(&info->lock);
ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL,
0);
- ret = arizona_hpdet_do_id(info, &reading);
+ ret = arizona_hpdet_do_id(info, &reading, &mic);
if (ret == -EAGAIN) {
goto out;
} else if (ret < 0) {
ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
/* If we have a mic then reenable MICDET */
- if (info->mic)
+ if (mic || info->mic)
arizona_start_mic(info);
if (info->hpdet_active) {
static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
{
struct arizona *arizona = info->arizona;
+ int hp_reading = 32;
+ bool mic;
int ret;
dev_dbg(arizona->dev, "Starting identification via HPDET\n");
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;
+ if (arizona->pdata.hpdet_acc_id_line) {
+ 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;
+ }
+ } else {
+ arizona_hpdet_do_id(info, &hp_reading, &mic);
}
return;
info->hpdet_active = false;
}
-static irqreturn_t arizona_micdet(int irq, void *data)
+static void arizona_micd_timeout_work(struct work_struct *work)
{
- struct arizona_extcon_info *info = data;
+ struct arizona_extcon_info *info = container_of(work,
+ struct arizona_extcon_info,
+ micd_timeout_work.work);
+
+ mutex_lock(&info->lock);
+
+ dev_dbg(info->arizona->dev, "MICD timed out, reporting HP\n");
+ arizona_identify_headphone(info);
+
+ info->detecting = false;
+
+ arizona_stop_mic(info);
+
+ mutex_unlock(&info->lock);
+}
+
+static void arizona_micd_detect(struct work_struct *work)
+{
+ struct arizona_extcon_info *info = container_of(work,
+ struct arizona_extcon_info,
+ micd_detect_work.work);
struct arizona *arizona = info->arizona;
unsigned int val = 0, lvl;
int ret, i, key;
+ cancel_delayed_work_sync(&info->micd_timeout_work);
+
mutex_lock(&info->lock);
for (i = 0; i < 10 && !(val & 0x7fc); i++) {
if (ret != 0) {
dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
mutex_unlock(&info->lock);
- return IRQ_NONE;
+ return;
}
dev_dbg(arizona->dev, "MICDET: %x\n", val);
if (!(val & ARIZONA_MICD_VALID)) {
dev_warn(arizona->dev, "Microphone detection state invalid\n");
mutex_unlock(&info->lock);
- return IRQ_NONE;
+ return;
}
}
if (i == 10 && !(val & 0x7fc)) {
dev_err(arizona->dev, "Failed to get valid MICDET value\n");
mutex_unlock(&info->lock);
- return IRQ_NONE;
+ return;
}
/* Due to jack detect this should never happen */
lvl = val & ARIZONA_MICD_LVL_MASK;
lvl >>= ARIZONA_MICD_LVL_SHIFT;
+ for (i = 0; i < info->num_micd_ranges; i++)
+ input_report_key(info->input,
+ info->micd_ranges[i].key, 0);
+
WARN_ON(!lvl);
WARN_ON(ffs(lvl) - 1 >= info->num_micd_ranges);
if (lvl && ffs(lvl) - 1 < info->num_micd_ranges) {
}
handled:
+ if (info->detecting)
+ schedule_delayed_work(&info->micd_timeout_work,
+ msecs_to_jiffies(info->micd_timeout));
+
pm_runtime_mark_last_busy(info->dev);
mutex_unlock(&info->lock);
+}
+
+static irqreturn_t arizona_micdet(int irq, void *data)
+{
+ struct arizona_extcon_info *info = data;
+ struct arizona *arizona = info->arizona;
+ int debounce = arizona->pdata.micd_detect_debounce;
+
+ cancel_delayed_work_sync(&info->micd_detect_work);
+ cancel_delayed_work_sync(&info->micd_timeout_work);
+
+ mutex_lock(&info->lock);
+ if (!info->detecting)
+ debounce = 0;
+ mutex_unlock(&info->lock);
+
+ if (debounce)
+ schedule_delayed_work(&info->micd_detect_work,
+ msecs_to_jiffies(debounce));
+ else
+ arizona_micd_detect(&info->micd_detect_work.work);
return IRQ_HANDLED;
}
struct arizona_extcon_info *info = data;
struct arizona *arizona = info->arizona;
unsigned int val, present, mask;
- bool cancelled;
+ bool cancelled_hp, cancelled_mic;
int ret, i;
- cancelled = cancel_delayed_work_sync(&info->hpdet_work);
+ cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work);
+ cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work);
pm_runtime_get_sync(info->dev);
val &= mask;
if (val == info->last_jackdet) {
dev_dbg(arizona->dev, "Suppressing duplicate JACKDET\n");
- if (cancelled)
+ if (cancelled_hp)
schedule_delayed_work(&info->hpdet_work,
msecs_to_jiffies(HPDET_DEBOUNCE));
+ if (cancelled_mic)
+ schedule_delayed_work(&info->micd_timeout_work,
+ msecs_to_jiffies(info->micd_timeout));
+
goto out;
}
info->last_jackdet = val;
ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB);
}
+ if (arizona->pdata.micd_timeout)
+ info->micd_timeout = arizona->pdata.micd_timeout;
+ else
+ info->micd_timeout = DEFAULT_MICD_TIMEOUT;
+
/* Clear trig_sts to make sure DCVDD is not forced up */
regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG,
ARIZONA_MICD_CLAMP_FALL_TRIG_STS |
info->dev = &pdev->dev;
info->last_jackdet = ~(ARIZONA_MICD_CLAMP_STS | ARIZONA_JD1_STS);
INIT_DELAYED_WORK(&info->hpdet_work, arizona_hpdet_work);
+ INIT_DELAYED_WORK(&info->micd_detect_work, arizona_micd_detect);
+ INIT_DELAYED_WORK(&info->micd_timeout_work, arizona_micd_timeout_work);
platform_set_drvdata(pdev, info);
switch (arizona->type) {