|
@@ -33,12 +33,17 @@
|
|
|
#include <linux/mfd/arizona/pdata.h>
|
|
|
#include <linux/mfd/arizona/registers.h>
|
|
|
|
|
|
-#define ARIZONA_NUM_BUTTONS 6
|
|
|
+#define ARIZONA_MAX_MICD_RANGE 8
|
|
|
|
|
|
#define ARIZONA_ACCDET_MODE_MIC 0
|
|
|
#define ARIZONA_ACCDET_MODE_HPL 1
|
|
|
#define ARIZONA_ACCDET_MODE_HPR 2
|
|
|
|
|
|
+#define ARIZONA_HPDET_MAX 10000
|
|
|
+
|
|
|
+#define HPDET_DEBOUNCE 500
|
|
|
+#define DEFAULT_MICD_TIMEOUT 2000
|
|
|
+
|
|
|
struct arizona_extcon_info {
|
|
|
struct device *dev;
|
|
|
struct arizona *arizona;
|
|
@@ -46,17 +51,27 @@ struct arizona_extcon_info {
|
|
|
struct regulator *micvdd;
|
|
|
struct input_dev *input;
|
|
|
|
|
|
+ u16 last_jackdet;
|
|
|
+
|
|
|
int micd_mode;
|
|
|
const struct arizona_micd_config *micd_modes;
|
|
|
int micd_num_modes;
|
|
|
|
|
|
+ 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;
|
|
|
+ bool hpdet_retried;
|
|
|
|
|
|
int num_hpdet_res;
|
|
|
unsigned int hpdet_res[3];
|
|
@@ -71,20 +86,25 @@ struct arizona_extcon_info {
|
|
|
};
|
|
|
|
|
|
static const struct arizona_micd_config micd_default_modes[] = {
|
|
|
- { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
|
|
|
{ ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
|
|
|
+ { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
|
|
|
};
|
|
|
|
|
|
-static struct {
|
|
|
- u16 status;
|
|
|
- int report;
|
|
|
-} arizona_lvl_to_key[ARIZONA_NUM_BUTTONS] = {
|
|
|
- { 0x1, BTN_0 },
|
|
|
- { 0x2, BTN_1 },
|
|
|
- { 0x4, BTN_2 },
|
|
|
- { 0x8, BTN_3 },
|
|
|
- { 0x10, BTN_4 },
|
|
|
- { 0x20, BTN_5 },
|
|
|
+static const struct arizona_micd_range micd_default_ranges[] = {
|
|
|
+ { .max = 11, .key = BTN_0 },
|
|
|
+ { .max = 28, .key = BTN_1 },
|
|
|
+ { .max = 54, .key = BTN_2 },
|
|
|
+ { .max = 100, .key = BTN_3 },
|
|
|
+ { .max = 186, .key = BTN_4 },
|
|
|
+ { .max = 430, .key = BTN_5 },
|
|
|
+};
|
|
|
+
|
|
|
+static const int arizona_micd_levels[] = {
|
|
|
+ 3, 6, 8, 11, 13, 16, 18, 21, 23, 26, 28, 31, 34, 36, 39, 41, 44, 46,
|
|
|
+ 49, 52, 54, 57, 60, 62, 65, 67, 70, 73, 75, 78, 81, 83, 89, 94, 100,
|
|
|
+ 105, 111, 116, 122, 127, 139, 150, 161, 173, 186, 196, 209, 220, 245,
|
|
|
+ 270, 295, 321, 348, 375, 402, 430, 489, 550, 614, 681, 752, 903, 1071,
|
|
|
+ 1257,
|
|
|
};
|
|
|
|
|
|
#define ARIZONA_CABLE_MECHANICAL 0
|
|
@@ -100,10 +120,63 @@ static const char *arizona_cable[] = {
|
|
|
NULL,
|
|
|
};
|
|
|
|
|
|
+static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info);
|
|
|
+
|
|
|
+static void arizona_extcon_do_magic(struct arizona_extcon_info *info,
|
|
|
+ unsigned int magic)
|
|
|
+{
|
|
|
+ struct arizona *arizona = info->arizona;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ mutex_lock(&arizona->dapm->card->dapm_mutex);
|
|
|
+
|
|
|
+ arizona->hpdet_magic = magic;
|
|
|
+
|
|
|
+ /* Keep the HP output stages disabled while doing the magic */
|
|
|
+ if (magic) {
|
|
|
+ ret = regmap_update_bits(arizona->regmap,
|
|
|
+ ARIZONA_OUTPUT_ENABLES_1,
|
|
|
+ ARIZONA_OUT1L_ENA |
|
|
|
+ ARIZONA_OUT1R_ENA, 0);
|
|
|
+ if (ret != 0)
|
|
|
+ dev_warn(arizona->dev,
|
|
|
+ "Failed to disable headphone outputs: %d\n",
|
|
|
+ ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000,
|
|
|
+ magic);
|
|
|
+ if (ret != 0)
|
|
|
+ dev_warn(arizona->dev, "Failed to do magic: %d\n",
|
|
|
+ ret);
|
|
|
+
|
|
|
+ ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000,
|
|
|
+ magic);
|
|
|
+ if (ret != 0)
|
|
|
+ dev_warn(arizona->dev, "Failed to do magic: %d\n",
|
|
|
+ ret);
|
|
|
+
|
|
|
+ /* Restore the desired state while not doing the magic */
|
|
|
+ if (!magic) {
|
|
|
+ ret = regmap_update_bits(arizona->regmap,
|
|
|
+ ARIZONA_OUTPUT_ENABLES_1,
|
|
|
+ ARIZONA_OUT1L_ENA |
|
|
|
+ ARIZONA_OUT1R_ENA, arizona->hp_ena);
|
|
|
+ if (ret != 0)
|
|
|
+ dev_warn(arizona->dev,
|
|
|
+ "Failed to restore headphone outputs: %d\n",
|
|
|
+ ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&arizona->dapm->card->dapm_mutex);
|
|
|
+}
|
|
|
+
|
|
|
static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode)
|
|
|
{
|
|
|
struct arizona *arizona = info->arizona;
|
|
|
|
|
|
+ mode %= info->micd_num_modes;
|
|
|
+
|
|
|
if (arizona->pdata.micd_pol_gpio > 0)
|
|
|
gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio,
|
|
|
info->micd_modes[mode].gpio);
|
|
@@ -330,7 +403,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info)
|
|
|
/* If we go out of range report top of range */
|
|
|
if (val < 100 || val > 0x3fb) {
|
|
|
dev_dbg(arizona->dev, "Measurement out of range\n");
|
|
|
- return 10000;
|
|
|
+ return ARIZONA_HPDET_MAX;
|
|
|
}
|
|
|
|
|
|
dev_dbg(arizona->dev, "HPDET read %d in range %d\n",
|
|
@@ -391,7 +464,8 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info)
|
|
|
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;
|
|
@@ -403,32 +477,8 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading)
|
|
|
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,
|
|
@@ -447,22 +497,28 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading)
|
|
|
}
|
|
|
|
|
|
/* 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];
|
|
|
|
|
|
+ /* Sometimes we get false readings due to slow insert */
|
|
|
+ if (*reading >= ARIZONA_HPDET_MAX && !info->hpdet_retried) {
|
|
|
+ dev_dbg(arizona->dev, "Retrying high impedance\n");
|
|
|
+ info->num_hpdet_res = 0;
|
|
|
+ info->hpdet_retried = true;
|
|
|
+ arizona_start_hpdet_acc_id(info);
|
|
|
+ pm_runtime_put(info->dev);
|
|
|
+ return -EAGAIN;
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
- * 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] > 10)) {
|
|
|
+ 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");
|
|
@@ -484,8 +540,8 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
|
|
|
struct arizona *arizona = info->arizona;
|
|
|
int id_gpio = arizona->pdata.hpdet_id_gpio;
|
|
|
int report = ARIZONA_CABLE_HEADPHONE;
|
|
|
- unsigned int val;
|
|
|
int ret, reading;
|
|
|
+ bool mic = false;
|
|
|
|
|
|
mutex_lock(&info->lock);
|
|
|
|
|
@@ -521,7 +577,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
|
|
|
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) {
|
|
@@ -539,28 +595,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
|
|
|
dev_err(arizona->dev, "Failed to report HP/line: %d\n",
|
|
|
ret);
|
|
|
|
|
|
- mutex_lock(&arizona->dapm->card->dapm_mutex);
|
|
|
-
|
|
|
- ret = regmap_read(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, &val);
|
|
|
- if (ret != 0) {
|
|
|
- dev_err(arizona->dev, "Failed to read output enables: %d\n",
|
|
|
- ret);
|
|
|
- val = 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (!(val & (ARIZONA_OUT1L_ENA | ARIZONA_OUT1R_ENA))) {
|
|
|
- ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0);
|
|
|
- if (ret != 0)
|
|
|
- dev_warn(arizona->dev, "Failed to undo magic: %d\n",
|
|
|
- ret);
|
|
|
-
|
|
|
- ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0);
|
|
|
- if (ret != 0)
|
|
|
- dev_warn(arizona->dev, "Failed to undo magic: %d\n",
|
|
|
- ret);
|
|
|
- }
|
|
|
-
|
|
|
- mutex_unlock(&arizona->dapm->card->dapm_mutex);
|
|
|
+ arizona_extcon_do_magic(info, 0);
|
|
|
|
|
|
done:
|
|
|
if (id_gpio)
|
|
@@ -572,7 +607,7 @@ done:
|
|
|
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) {
|
|
@@ -606,13 +641,7 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info)
|
|
|
if (info->mic)
|
|
|
arizona_stop_mic(info);
|
|
|
|
|
|
- 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);
|
|
|
+ arizona_extcon_do_magic(info, 0x4000);
|
|
|
|
|
|
ret = regmap_update_bits(arizona->regmap,
|
|
|
ARIZONA_ACCESSORY_DETECT_MODE_1,
|
|
@@ -653,7 +682,8 @@ err:
|
|
|
static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
|
|
|
{
|
|
|
struct arizona *arizona = info->arizona;
|
|
|
- unsigned int val;
|
|
|
+ int hp_reading = 32;
|
|
|
+ bool mic;
|
|
|
int ret;
|
|
|
|
|
|
dev_dbg(arizona->dev, "Starting identification via HPDET\n");
|
|
@@ -663,32 +693,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
|
|
|
|
|
|
info->hpdet_active = true;
|
|
|
|
|
|
- arizona_extcon_pulse_micbias(info);
|
|
|
-
|
|
|
- mutex_lock(&arizona->dapm->card->dapm_mutex);
|
|
|
-
|
|
|
- ret = regmap_read(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, &val);
|
|
|
- if (ret != 0) {
|
|
|
- dev_err(arizona->dev, "Failed to read output enables: %d\n",
|
|
|
- ret);
|
|
|
- val = 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (!(val & (ARIZONA_OUT1L_ENA | ARIZONA_OUT1R_ENA))) {
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
- mutex_unlock(&arizona->dapm->card->dapm_mutex);
|
|
|
+ arizona_extcon_do_magic(info, 0x4000);
|
|
|
|
|
|
ret = regmap_update_bits(arizona->regmap,
|
|
|
ARIZONA_ACCESSORY_DETECT_MODE_1,
|
|
@@ -700,12 +705,18 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
|
|
|
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;
|
|
@@ -724,28 +735,58 @@ err:
|
|
|
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, lvl;
|
|
|
- int ret, i;
|
|
|
+ unsigned int val = 0, lvl;
|
|
|
+ int ret, i, key;
|
|
|
+
|
|
|
+ cancel_delayed_work_sync(&info->micd_timeout_work);
|
|
|
|
|
|
mutex_lock(&info->lock);
|
|
|
|
|
|
- ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val);
|
|
|
- if (ret != 0) {
|
|
|
- dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
|
|
|
- mutex_unlock(&info->lock);
|
|
|
- return IRQ_NONE;
|
|
|
- }
|
|
|
+ for (i = 0; i < 10 && !(val & 0x7fc); i++) {
|
|
|
+ ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val);
|
|
|
+ if (ret != 0) {
|
|
|
+ dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
|
|
|
+ mutex_unlock(&info->lock);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- dev_dbg(arizona->dev, "MICDET: %x\n", val);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (!(val & ARIZONA_MICD_VALID)) {
|
|
|
- dev_warn(arizona->dev, "Microphone detection state invalid\n");
|
|
|
+ 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 */
|
|
@@ -786,7 +827,7 @@ static irqreturn_t arizona_micdet(int irq, void *data)
|
|
|
* impedence then give up and report headphones.
|
|
|
*/
|
|
|
if (info->detecting && (val & 0x3f8)) {
|
|
|
- if (info->jack_flips >= info->micd_num_modes) {
|
|
|
+ if (info->jack_flips >= info->micd_num_modes * 10) {
|
|
|
dev_dbg(arizona->dev, "Detected HP/line\n");
|
|
|
arizona_identify_headphone(info);
|
|
|
|
|
@@ -816,12 +857,17 @@ static irqreturn_t arizona_micdet(int irq, void *data)
|
|
|
lvl = val & ARIZONA_MICD_LVL_MASK;
|
|
|
lvl >>= ARIZONA_MICD_LVL_SHIFT;
|
|
|
|
|
|
- for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
|
|
|
- if (lvl & arizona_lvl_to_key[i].status)
|
|
|
- input_report_key(info->input,
|
|
|
- arizona_lvl_to_key[i].report,
|
|
|
- 1);
|
|
|
- input_sync(info->input);
|
|
|
+ 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) {
|
|
|
+ key = info->micd_ranges[ffs(lvl) - 1].key;
|
|
|
+ input_report_key(info->input, key, 1);
|
|
|
+ input_sync(info->input);
|
|
|
+ }
|
|
|
|
|
|
} else if (info->detecting) {
|
|
|
dev_dbg(arizona->dev, "Headphone detected\n");
|
|
@@ -835,16 +881,41 @@ static irqreturn_t arizona_micdet(int irq, void *data)
|
|
|
}
|
|
|
} else {
|
|
|
dev_dbg(arizona->dev, "Mic button released\n");
|
|
|
- for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
|
|
|
+ for (i = 0; i < info->num_micd_ranges; i++)
|
|
|
input_report_key(info->input,
|
|
|
- arizona_lvl_to_key[i].report, 0);
|
|
|
+ info->micd_ranges[i].key, 0);
|
|
|
input_sync(info->input);
|
|
|
arizona_extcon_pulse_micbias(info);
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
}
|
|
@@ -865,11 +936,13 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|
|
struct arizona_extcon_info *info = data;
|
|
|
struct arizona *arizona = info->arizona;
|
|
|
unsigned int val, present, mask;
|
|
|
+ bool cancelled_hp, cancelled_mic;
|
|
|
int ret, i;
|
|
|
|
|
|
- pm_runtime_get_sync(info->dev);
|
|
|
+ cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work);
|
|
|
+ cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work);
|
|
|
|
|
|
- cancel_delayed_work_sync(&info->hpdet_work);
|
|
|
+ pm_runtime_get_sync(info->dev);
|
|
|
|
|
|
mutex_lock(&info->lock);
|
|
|
|
|
@@ -890,7 +963,22 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|
|
return IRQ_NONE;
|
|
|
}
|
|
|
|
|
|
- if ((val & mask) == present) {
|
|
|
+ val &= mask;
|
|
|
+ if (val == info->last_jackdet) {
|
|
|
+ dev_dbg(arizona->dev, "Suppressing duplicate JACKDET\n");
|
|
|
+ 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;
|
|
|
+
|
|
|
+ if (info->last_jackdet == present) {
|
|
|
dev_dbg(arizona->dev, "Detected jack\n");
|
|
|
ret = extcon_set_cable_state_(&info->edev,
|
|
|
ARIZONA_CABLE_MECHANICAL, true);
|
|
@@ -907,7 +995,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|
|
arizona_start_mic(info);
|
|
|
} else {
|
|
|
schedule_delayed_work(&info->hpdet_work,
|
|
|
- msecs_to_jiffies(250));
|
|
|
+ msecs_to_jiffies(HPDET_DEBOUNCE));
|
|
|
}
|
|
|
|
|
|
regmap_update_bits(arizona->regmap,
|
|
@@ -923,10 +1011,11 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|
|
info->hpdet_res[i] = 0;
|
|
|
info->mic = false;
|
|
|
info->hpdet_done = false;
|
|
|
+ info->hpdet_retried = false;
|
|
|
|
|
|
- for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
|
|
|
+ for (i = 0; i < info->num_micd_ranges; i++)
|
|
|
input_report_key(info->input,
|
|
|
- arizona_lvl_to_key[i].report, 0);
|
|
|
+ info->micd_ranges[i].key, 0);
|
|
|
input_sync(info->input);
|
|
|
|
|
|
ret = extcon_update_state(&info->edev, 0xffffffff, 0);
|
|
@@ -940,6 +1029,11 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|
|
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 |
|
|
@@ -947,6 +1041,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|
|
ARIZONA_JD1_FALL_TRIG_STS |
|
|
|
ARIZONA_JD1_RISE_TRIG_STS);
|
|
|
|
|
|
+out:
|
|
|
mutex_unlock(&info->lock);
|
|
|
|
|
|
pm_runtime_mark_last_busy(info->dev);
|
|
@@ -955,13 +1050,34 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
|
|
|
return IRQ_HANDLED;
|
|
|
}
|
|
|
|
|
|
+/* Map a level onto a slot in the register bank */
|
|
|
+static void arizona_micd_set_level(struct arizona *arizona, int index,
|
|
|
+ unsigned int level)
|
|
|
+{
|
|
|
+ int reg;
|
|
|
+ unsigned int mask;
|
|
|
+
|
|
|
+ reg = ARIZONA_MIC_DETECT_LEVEL_4 - (index / 2);
|
|
|
+
|
|
|
+ if (!(index % 2)) {
|
|
|
+ mask = 0x3f00;
|
|
|
+ level <<= 8;
|
|
|
+ } else {
|
|
|
+ mask = 0x3f;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Program the level itself */
|
|
|
+ regmap_update_bits(arizona->regmap, reg, mask, level);
|
|
|
+}
|
|
|
+
|
|
|
static int arizona_extcon_probe(struct platform_device *pdev)
|
|
|
{
|
|
|
struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
|
|
|
struct arizona_pdata *pdata;
|
|
|
struct arizona_extcon_info *info;
|
|
|
+ unsigned int val;
|
|
|
int jack_irq_fall, jack_irq_rise;
|
|
|
- int ret, mode, i;
|
|
|
+ int ret, mode, i, j;
|
|
|
|
|
|
if (!arizona->dapm || !arizona->dapm->card)
|
|
|
return -EPROBE_DEFER;
|
|
@@ -985,7 +1101,10 @@ static int arizona_extcon_probe(struct platform_device *pdev)
|
|
|
mutex_init(&info->lock);
|
|
|
info->arizona = arizona;
|
|
|
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) {
|
|
@@ -1014,6 +1133,17 @@ static int arizona_extcon_probe(struct platform_device *pdev)
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
+ info->input = devm_input_allocate_device(&pdev->dev);
|
|
|
+ if (!info->input) {
|
|
|
+ dev_err(arizona->dev, "Can't allocate input dev\n");
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto err_register;
|
|
|
+ }
|
|
|
+
|
|
|
+ info->input->name = "Headset";
|
|
|
+ info->input->phys = "arizona/extcon";
|
|
|
+ info->input->dev.parent = &pdev->dev;
|
|
|
+
|
|
|
if (pdata->num_micd_configs) {
|
|
|
info->micd_modes = pdata->micd_configs;
|
|
|
info->micd_num_modes = pdata->num_micd_configs;
|
|
@@ -1069,15 +1199,79 @@ static int arizona_extcon_probe(struct platform_device *pdev)
|
|
|
arizona->pdata.micd_dbtime
|
|
|
<< ARIZONA_MICD_DBTIME_SHIFT);
|
|
|
|
|
|
+ BUILD_BUG_ON(ARRAY_SIZE(arizona_micd_levels) != 0x40);
|
|
|
+
|
|
|
+ if (arizona->pdata.num_micd_ranges) {
|
|
|
+ info->micd_ranges = pdata->micd_ranges;
|
|
|
+ info->num_micd_ranges = pdata->num_micd_ranges;
|
|
|
+ } else {
|
|
|
+ info->micd_ranges = micd_default_ranges;
|
|
|
+ info->num_micd_ranges = ARRAY_SIZE(micd_default_ranges);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (arizona->pdata.num_micd_ranges > ARIZONA_MAX_MICD_RANGE) {
|
|
|
+ dev_err(arizona->dev, "Too many MICD ranges: %d\n",
|
|
|
+ arizona->pdata.num_micd_ranges);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (info->num_micd_ranges > 1) {
|
|
|
+ for (i = 1; i < info->num_micd_ranges; i++) {
|
|
|
+ if (info->micd_ranges[i - 1].max >
|
|
|
+ info->micd_ranges[i].max) {
|
|
|
+ dev_err(arizona->dev,
|
|
|
+ "MICD ranges must be sorted\n");
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto err_input;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Disable all buttons by default */
|
|
|
+ regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_2,
|
|
|
+ ARIZONA_MICD_LVL_SEL_MASK, 0x81);
|
|
|
+
|
|
|
+ /* Set up all the buttons the user specified */
|
|
|
+ for (i = 0; i < info->num_micd_ranges; i++) {
|
|
|
+ for (j = 0; j < ARRAY_SIZE(arizona_micd_levels); j++)
|
|
|
+ if (arizona_micd_levels[j] >= info->micd_ranges[i].max)
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (j == ARRAY_SIZE(arizona_micd_levels)) {
|
|
|
+ dev_err(arizona->dev, "Unsupported MICD level %d\n",
|
|
|
+ info->micd_ranges[i].max);
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto err_input;
|
|
|
+ }
|
|
|
+
|
|
|
+ dev_dbg(arizona->dev, "%d ohms for MICD threshold %d\n",
|
|
|
+ arizona_micd_levels[j], i);
|
|
|
+
|
|
|
+ arizona_micd_set_level(arizona, i, j);
|
|
|
+ input_set_capability(info->input, EV_KEY,
|
|
|
+ info->micd_ranges[i].key);
|
|
|
+
|
|
|
+ /* Enable reporting of that range */
|
|
|
+ regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_2,
|
|
|
+ 1 << i, 1 << i);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set all the remaining keys to a maximum */
|
|
|
+ for (; i < ARIZONA_MAX_MICD_RANGE; i++)
|
|
|
+ arizona_micd_set_level(arizona, i, 0x3f);
|
|
|
+
|
|
|
/*
|
|
|
* If we have a clamp use it, activating in conjunction with
|
|
|
* GPIO5 if that is connected for jack detect operation.
|
|
|
*/
|
|
|
if (info->micd_clamp) {
|
|
|
if (arizona->pdata.jd_gpio5) {
|
|
|
- /* Put the GPIO into input mode */
|
|
|
+ /* Put the GPIO into input mode with optional pull */
|
|
|
+ val = 0xc101;
|
|
|
+ if (arizona->pdata.jd_gpio5_nopull)
|
|
|
+ val &= ~ARIZONA_GPN_PU;
|
|
|
+
|
|
|
regmap_write(arizona->regmap, ARIZONA_GPIO5_CTRL,
|
|
|
- 0xc101);
|
|
|
+ val);
|
|
|
|
|
|
regmap_update_bits(arizona->regmap,
|
|
|
ARIZONA_MICD_CLAMP_CONTROL,
|
|
@@ -1096,20 +1290,6 @@ static int arizona_extcon_probe(struct platform_device *pdev)
|
|
|
|
|
|
arizona_extcon_set_mode(info, 0);
|
|
|
|
|
|
- info->input = devm_input_allocate_device(&pdev->dev);
|
|
|
- if (!info->input) {
|
|
|
- dev_err(arizona->dev, "Can't allocate input dev\n");
|
|
|
- ret = -ENOMEM;
|
|
|
- goto err_register;
|
|
|
- }
|
|
|
-
|
|
|
- for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
|
|
|
- input_set_capability(info->input, EV_KEY,
|
|
|
- arizona_lvl_to_key[i].report);
|
|
|
- info->input->name = "Headset";
|
|
|
- info->input->phys = "arizona/extcon";
|
|
|
- info->input->dev.parent = &pdev->dev;
|
|
|
-
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
pm_runtime_idle(&pdev->dev);
|
|
|
pm_runtime_get_sync(&pdev->dev);
|