/* * switch-madera.c - Switch driver for Cirrus Logic Madera codecs * * Copyright 2015-2016 Cirrus Logic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MADERA_MAX_MICD_RANGE 8 #define MADERA_MICD_CLAMP_MODE_JD1L 0x4 #define MADERA_MICD_CLAMP_MODE_JD1H 0x5 #define MADERA_MICD_CLAMP_MODE_JD1L_JD2L 0x8 #define MADERA_MICD_CLAMP_MODE_JD1L_JD2H 0x9 #define MADERA_MICD_CLAMP_MODE_JD1H_JD2H 0xb #define MADERA_HPDET_MAX 10000 #define MADERA_HP_SHORT_IMPEDANCE_MIN 4 #define MADERA_HPDET_DEBOUNCE_MS 500 #define MADERA_DEFAULT_MICD_TIMEOUT_MS 2000 #define MADERA_MICROPHONE_MIN_OHM 1258 #define MADERA_MICROPHONE_MAX_OHM 30000 #define MADERA_MIC_MUTE 1 #define MADERA_MIC_UNMUTE 0 #define MADERA_HP_TUNING_INVALID -1 /* Conversion between ohms and hundredths of an ohm. */ #define HOHM_TO_OHM(X) (((X) == INT_MAX || (X) == MADERA_HP_Z_OPEN) ? \ (X) : ((X) + 50) / 100) #define OHM_TO_HOHM(X) ((X) * 100) struct madera_micd_bias { unsigned int bias; bool enabled; }; struct madera_hpdet_trims { int off_x4; int grad_x4; }; struct madera_extcon_info { struct device *dev; struct madera *madera; const struct madera_accdet_pdata *pdata; struct mutex lock; struct regulator *micvdd; struct input_dev *input; struct switch_dev edev; u16 last_jackdet; int hp_tuning_level; const struct madera_hpdet_calibration_data* hpdet_ranges; int num_hpdet_ranges; const struct madera_hpdet_trims *hpdet_trims; int micd_mode; const struct madera_micd_config *micd_modes; int micd_num_modes; const struct madera_micd_range *micd_ranges; int num_micd_ranges; int micd_res_old; int micd_debounce; int micd_count; int moisture_count; struct completion manual_mic_completion; struct delayed_work micd_detect_work; bool have_mic; bool detecting; int jack_flips; const struct madera_jd_state *state; const struct madera_jd_state *old_state; struct delayed_work state_timeout_work; struct wakeup_source detection_wake_lock; struct madera_micd_bias micd_bias; }; static const struct madera_micd_config cs47l85_micd_default_modes[] = { { MADERA_ACCD_SENSE_MICDET2, 0, MADERA_ACCD_BIAS_SRC_MICBIAS1, 0, 0 }, { MADERA_ACCD_SENSE_MICDET1, 0, MADERA_ACCD_BIAS_SRC_MICBIAS2, 1, 0 }, }; static const struct madera_micd_config madera_micd_default_modes[] = { { MADERA_MICD1_SENSE_MICDET1, MADERA_MICD1_GND_MICDET2, MADERA_MICD_BIAS_SRC_MICBIAS1A, 0, MADERA_HPD_GND_HPOUTFB1 }, { MADERA_MICD1_SENSE_MICDET2, MADERA_MICD1_GND_MICDET1, MADERA_MICD_BIAS_SRC_MICBIAS1B, 1, MADERA_HPD_GND_HPOUTFB1 }, }; static const unsigned int madera_default_hpd_pins[4] = { [0] = MADERA_HPD_OUT_OUT1L, [1] = MADERA_HPD_SENSE_HPDET1, [2] = MADERA_HPD_OUT_OUT1R, [3] = MADERA_HPD_SENSE_HPDET1, }; static struct madera_micd_range madera_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 }, { .max = -1, .key = -1 }, { .max = -1, .key = -1 }, }; /* The number of levels in madera_micd_levels valid for button thresholds */ #define MADERA_NUM_MICD_BUTTON_LEVELS 64 static const int madera_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, 30000, }; /* These values are copied from Android WiredAccessoryObserver */ enum headset_state { BIT_NO_HEADSET = 0, BIT_HEADSET = (1 << 0), BIT_HEADSET_NO_MIC = (1 << 1), }; struct madera_hpdet_calibration_data { int min; int max; s64 C0; /* value * 1000000 */ s64 C1; /* value * 10000 */ s64 C2; /* not multiplied */ s64 C3; /* value * 1000000 */ s64 C4_x_C3; /* value * 1000000 */ s64 C5; /* value * 1000000 */ s64 dacval_adjust; }; static const struct madera_hpdet_calibration_data cs47l85_hpdet_ranges[] = { { 4, 30, 1007000, -7200, 4003, 69300000, 381150, 250000, 500000}, { 8, 100, 1007000, -7200, 7975, 69600000, 382800, 250000, 500000}, { 100, 1000, 9696000, -79500, 7300, 62900000, 345950, 250000, 500000}, { 1000, 10000, 100684000, -949400, 7300, 63200000, 347600, 250000, 500000}, }; static const struct madera_hpdet_calibration_data madera_hpdet_ranges[] = { { 4, 30, 1014000, -4300, 3950, 69300000, 381150, 700000, 500000}, { 8, 100, 1014000, -8600, 7975, 69600000, 382800, 700000, 500000}, { 100, 1000, 9744000, -79500, 7300, 62900000, 345950, 700000, 500000}, { 1000, 10000, 101158000, -949400, 7300, 63200000, 347600, 700000, 500000}, }; struct madera_hp_tuning { int max_ohm; const struct reg_sequence *patch; int patch_len; }; static const struct reg_sequence cs47l35_low_impedance_patch[] = { { 0x460, 0x0C40 }, { 0x461, 0xCD1A }, { 0x462, 0x0C40 }, { 0x463, 0xB53B }, { 0x464, 0x0C41 }, { 0x465, 0x4826 }, { 0x466, 0x0C41 }, { 0x467, 0x2EDA }, { 0x468, 0x0C41 }, { 0x469, 0x203A }, { 0x46A, 0x0841 }, { 0x46B, 0x121F }, { 0x46C, 0x0446 }, { 0x46D, 0x0B6F }, { 0x46E, 0x0446 }, { 0x46F, 0x0818 }, { 0x470, 0x04C6 }, { 0x471, 0x05BB }, { 0x472, 0x04C6 }, { 0x473, 0x040F }, { 0x474, 0x04CE }, { 0x475, 0x0339 }, { 0x476, 0x05DF }, { 0x477, 0x028F }, { 0x478, 0x05DF }, { 0x479, 0x0209 }, { 0x47A, 0x05DF }, { 0x47B, 0x00CF }, { 0x47C, 0x05DF }, { 0x47D, 0x0001 }, { 0x47E, 0x07FF }, }; static const struct reg_sequence cs47l35_normal_impedance_patch[] = { { 0x460, 0x0C40 }, { 0x461, 0xCD1A }, { 0x462, 0x0C40 }, { 0x463, 0xB53B }, { 0x464, 0x0C40 }, { 0x465, 0x7503 }, { 0x466, 0x0C40 }, { 0x467, 0x4A41 }, { 0x468, 0x0041 }, { 0x469, 0x3491 }, { 0x46A, 0x0841 }, { 0x46B, 0x1F50 }, { 0x46C, 0x0446 }, { 0x46D, 0x14ED }, { 0x46E, 0x0446 }, { 0x46F, 0x1455 }, { 0x470, 0x04C6 }, { 0x471, 0x1220 }, { 0x472, 0x04C6 }, { 0x473, 0x040F }, { 0x474, 0x04CE }, { 0x475, 0x0339 }, { 0x476, 0x05DF }, { 0x477, 0x028F }, { 0x478, 0x05DF }, { 0x479, 0x0209 }, { 0x47A, 0x05DF }, { 0x47B, 0x00CF }, { 0x47C, 0x05DF }, { 0x47D, 0x0001 }, { 0x47E, 0x07FF }, }; static const struct madera_hp_tuning cs47l35_hp_tuning[] = { { 13, cs47l35_low_impedance_patch, ARRAY_SIZE(cs47l35_low_impedance_patch), }, { MADERA_HPDET_MAX, cs47l35_normal_impedance_patch, ARRAY_SIZE(cs47l35_normal_impedance_patch), }, }; static const struct reg_sequence cs47l85_low_impedance_patch[] = { { 0x465, 0x4C6D }, { 0x467, 0x3950 }, { 0x469, 0x2D86 }, { 0x46B, 0x1E6D }, { 0x46D, 0x199A }, { 0x46F, 0x1456 }, { 0x483, 0x0826 }, }; static const struct reg_sequence cs47l85_normal_impedance_patch[] = { { 0x465, 0x8A43 }, { 0x467, 0x7259 }, { 0x469, 0x65EA }, { 0x46B, 0x50F4 }, { 0x46D, 0x41CD }, { 0x46F, 0x199A }, { 0x483, 0x0023 }, }; static const struct madera_hp_tuning cs47l85_hp_tuning[] = { { 13, cs47l85_low_impedance_patch, ARRAY_SIZE(cs47l85_low_impedance_patch), }, { MADERA_HPDET_MAX, cs47l85_normal_impedance_patch, ARRAY_SIZE(cs47l85_normal_impedance_patch), }, }; static const struct reg_sequence cs47l90_low_impedance_patch[] = { { 0x460, 0x0C21 }, { 0x461, 0xB53C }, { 0x462, 0x0C21 }, { 0x463, 0xA186 }, { 0x464, 0x0C21 }, { 0x465, 0x8FF6 }, { 0x466, 0x0C24 }, { 0x467, 0x804E }, { 0x468, 0x0C24 }, { 0x469, 0x725A }, { 0x46A, 0x0C24 }, { 0x46B, 0x5AD5 }, { 0x46C, 0x0C28 }, { 0x46D, 0x50F4 }, { 0x46E, 0x0C2C }, { 0x46F, 0x4827 }, { 0x470, 0x0C31 }, { 0x471, 0x404E }, { 0x472, 0x0020 }, { 0x473, 0x3950 }, { 0x474, 0x0028 }, { 0x475, 0x3314 }, { 0x476, 0x0030 }, { 0x477, 0x2893 }, { 0x478, 0x003F }, { 0x479, 0x2429 }, { 0x47A, 0x0830 }, { 0x47B, 0x203A }, { 0x47C, 0x0420 }, { 0x47D, 0x1027 }, { 0x47E, 0x0430 }, }; static const struct reg_sequence cs47l90_normal_impedance_patch[] = { { 0x460, 0x0C21 }, { 0x461, 0xB53C }, { 0x462, 0x0C25 }, { 0x463, 0xA186 }, { 0x464, 0x0C26 }, { 0x465, 0x8FF6 }, { 0x466, 0x0C28 }, { 0x467, 0x804E }, { 0x468, 0x0C30 }, { 0x469, 0x725A }, { 0x46A, 0x0C30 }, { 0x46B, 0x65EA }, { 0x46C, 0x0028 }, { 0x46D, 0x5AD5 }, { 0x46E, 0x0028 }, { 0x46F, 0x50F4 }, { 0x470, 0x0030 }, { 0x471, 0x4827 }, { 0x472, 0x0030 }, { 0x473, 0x404E }, { 0x474, 0x003F }, { 0x475, 0x3950 }, { 0x476, 0x0830 }, { 0x477, 0x3314 }, { 0x478, 0x0420 }, { 0x479, 0x2D86 }, { 0x47A, 0x0428 }, { 0x47B, 0x2893 }, { 0x47C, 0x0428 }, { 0x47D, 0x203A }, { 0x47E, 0x0428 }, }; static const struct reg_sequence cs47l90_high_impedance_patch[] = { { 0x460, 0x0C21 }, { 0x461, 0xB53C }, { 0x462, 0x0C26 }, { 0x463, 0xA186 }, { 0x464, 0x0C28 }, { 0x465, 0x8FF6 }, { 0x466, 0x0C2A }, { 0x467, 0x804E }, { 0x468, 0x0025 }, { 0x469, 0x725A }, { 0x46A, 0x0030 }, { 0x46B, 0x65EA }, { 0x46C, 0x0030 }, { 0x46D, 0x5AD5 }, { 0x46E, 0x003F }, { 0x46F, 0x50F4 }, { 0x470, 0x003F }, { 0x471, 0x4827 }, { 0x472, 0x0830 }, { 0x473, 0x404E }, { 0x474, 0x083F }, { 0x475, 0x3950 }, { 0x476, 0x0420 }, { 0x477, 0x3314 }, { 0x478, 0x0430 }, { 0x479, 0x2D86 }, { 0x47A, 0x0430 }, { 0x47B, 0x2893 }, { 0x47C, 0x0430 }, { 0x47D, 0x203A }, { 0x47E, 0x0430 }, }; static const struct madera_hp_tuning cs47l90_hp_tuning[] = { { 14, cs47l90_low_impedance_patch, ARRAY_SIZE(cs47l90_low_impedance_patch), }, { 24, cs47l90_normal_impedance_patch, ARRAY_SIZE(cs47l90_normal_impedance_patch), }, { MADERA_HPDET_MAX, cs47l90_high_impedance_patch, ARRAY_SIZE(cs47l90_high_impedance_patch), }, }; static const struct reg_sequence cs47l92_low_impedance_patch[] = { { 0x460, 0x0C21 }, { 0x461, 0xB53C }, { 0x462, 0x0C21 }, { 0x463, 0xA186 }, { 0x464, 0x0C21 }, { 0x465, 0x8FF6 }, { 0x466, 0x0C24 }, { 0x467, 0x804E }, { 0x468, 0x0C24 }, { 0x469, 0x725A }, { 0x46A, 0x0C24 }, { 0x46B, 0x5AD5 }, { 0x46C, 0x0C28 }, { 0x46D, 0x50F4 }, { 0x46E, 0x0C2C }, { 0x46F, 0x4827 }, { 0x470, 0x0C31 }, { 0x471, 0x404E }, { 0x472, 0x0020 }, { 0x473, 0x3950 }, { 0x474, 0x0028 }, { 0x475, 0x3314 }, { 0x476, 0x0030 }, { 0x477, 0x2893 }, { 0x478, 0x0030 }, { 0x479, 0x2429 }, { 0x47A, 0x0830 }, { 0x47B, 0x203A }, { 0x47C, 0x0420 }, { 0x47D, 0x1027 }, { 0x47E, 0x0430 }, }; static const struct reg_sequence cs47l92_normal_impedance_patch[] = { { 0x460, 0x0C21 }, { 0x461, 0xB53C }, { 0x462, 0x0C25 }, { 0x463, 0xA186 }, { 0x464, 0x0C26 }, { 0x465, 0x8FF6 }, { 0x466, 0x0C28 }, { 0x467, 0x804E }, { 0x468, 0x0C30 }, { 0x469, 0x725A }, { 0x46A, 0x0C30 }, { 0x46B, 0x65EA }, { 0x46C, 0x0028 }, { 0x46D, 0x5AD5 }, { 0x46E, 0x0028 }, { 0x46F, 0x50F4 }, { 0x470, 0x0030 }, { 0x471, 0x4827 }, { 0x472, 0x0030 }, { 0x473, 0x404E }, { 0x474, 0x0030 }, { 0x475, 0x3950 }, { 0x476, 0x0830 }, { 0x477, 0x3314 }, { 0x478, 0x0420 }, { 0x479, 0x2D86 }, { 0x47A, 0x0428 }, { 0x47B, 0x2893 }, { 0x47C, 0x0428 }, { 0x47D, 0x203A }, { 0x47E, 0x0428 }, }; static const struct reg_sequence cs47l92_high_impedance_patch[] = { { 0x460, 0x0C21 }, { 0x461, 0xB53C }, { 0x462, 0x0C26 }, { 0x463, 0xA186 }, { 0x464, 0x0C28 }, { 0x465, 0x8FF6 }, { 0x466, 0x0C2A }, { 0x467, 0x804E }, { 0x468, 0x0025 }, { 0x469, 0x725A }, { 0x46A, 0x0030 }, { 0x46B, 0x65EA }, { 0x46C, 0x0030 }, { 0x46D, 0x5AD5 }, { 0x46E, 0x0030 }, { 0x46F, 0x50F4 }, { 0x470, 0x0030 }, { 0x471, 0x4827 }, { 0x472, 0x0830 }, { 0x473, 0x404E }, { 0x474, 0x0830 }, { 0x475, 0x3950 }, { 0x476, 0x0420 }, { 0x477, 0x3314 }, { 0x478, 0x0430 }, { 0x479, 0x2D86 }, { 0x47A, 0x0430 }, { 0x47B, 0x2893 }, { 0x47C, 0x0430 }, { 0x47D, 0x203A }, { 0x47E, 0x0430 }, }; static const struct madera_hp_tuning cs47l92_hp_tuning[] = { { 16, cs47l92_low_impedance_patch, ARRAY_SIZE(cs47l92_low_impedance_patch), }, { 32, cs47l92_normal_impedance_patch, ARRAY_SIZE(cs47l92_normal_impedance_patch), }, { MADERA_HPDET_MAX, cs47l92_high_impedance_patch, ARRAY_SIZE(cs47l92_high_impedance_patch), }, }; static ssize_t madera_extcon_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct madera_extcon_info *info = platform_get_drvdata(pdev); return scnprintf(buf, PAGE_SIZE, "%d\n", info->madera->hp_impedance_x100[0]); } static DEVICE_ATTR(hp1_impedance, S_IRUGO, madera_extcon_show, NULL); inline void madera_extcon_report(struct madera_extcon_info *info, int state) { dev_dbg(info->madera->dev, "Switch Report: %d\n", state); switch_set_state(&info->edev, state); } EXPORT_SYMBOL_GPL(madera_extcon_report); static enum madera_accdet_mode madera_jds_get_mode(struct madera_extcon_info *info) { if (info->state) return info->state->mode; else return MADERA_ACCDET_MODE_INVALID; } int madera_jds_set_state(struct madera_extcon_info *info, const struct madera_jd_state *new_state) { int ret = 0; if (new_state != info->state) { if (info->state) info->state->stop(info); info->state = new_state; if (info->state) { ret = info->state->start(info); if (ret < 0) info->state = NULL; } } return ret; } EXPORT_SYMBOL_GPL(madera_jds_set_state); static void madera_jds_reading(struct madera_extcon_info *info, int val) { int ret; ret = info->state->reading(info, val); if (ret == -EAGAIN && info->state->restart) info->state->restart(info); } static inline bool madera_jds_cancel_timeout(struct madera_extcon_info *info) { return cancel_delayed_work_sync(&info->state_timeout_work); } static void madera_jds_start_timeout(struct madera_extcon_info *info) { const struct madera_jd_state *state = info->state; if (!state) return; if (state->timeout_ms && state->timeout) { int ms = state->timeout_ms(info); schedule_delayed_work(&info->state_timeout_work, msecs_to_jiffies(ms)); } } static void madera_jds_timeout_work(struct work_struct *work) { struct madera_extcon_info *info = container_of(work, struct madera_extcon_info, state_timeout_work.work); mutex_lock(&info->lock); if (!info->state) { dev_warn(info->madera->dev, "Spurious timeout in idle state\n"); } else if (!info->state->timeout) { dev_warn(info->madera->dev, "Spurious timeout state.mode=%d\n", info->state->mode); } else { info->state->timeout(info); madera_jds_start_timeout(info); } mutex_unlock(&info->lock); } static void madera_extcon_hp_clamp(struct madera_extcon_info *info, bool clamp) { struct madera *madera = info->madera; unsigned int mask, val = 0; unsigned int edre_reg = 0, edre_val = 0; unsigned int ep_sel = 0; int ret; snd_soc_dapm_mutex_lock(madera->dapm); switch (madera->type) { case CS47L35: case CS47L92: case CS47L93: /* check whether audio is routed to EPOUT, do not disable OUT1 * in that case */ regmap_read(madera->regmap, MADERA_OUTPUT_ENABLES_1, &ep_sel); ep_sel &= MADERA_EP_SEL_MASK; break; default: break; }; switch (madera->type) { case CS47L35: case CS47L85: case WM1840: edre_reg = MADERA_EDRE_MANUAL; mask = MADERA_HP1L_SHRTO | MADERA_HP1L_FLWR | MADERA_HP1L_SHRTI; if (clamp) { val = MADERA_HP1L_SHRTO; edre_val = 0x3; } else { val = MADERA_HP1L_FLWR | MADERA_HP1L_SHRTI; edre_val = 0; } break; default: mask = MADERA_HPD_OVD_ENA_SEL_MASK; if (clamp) val = MADERA_HPD_OVD_ENA_SEL_MASK; else val = 0; break; }; madera->hpdet_clamp[0] = clamp; /* Keep the HP output stages disabled while doing the clamp */ if (clamp && !ep_sel) { ret = regmap_update_bits(madera->regmap, MADERA_OUTPUT_ENABLES_1, (MADERA_OUT1L_ENA | MADERA_OUT1R_ENA) << (2 * (info->pdata->output - 1)), 0); if (ret) dev_warn(madera->dev, "Failed to disable headphone outputs: %d\n", ret); } if (edre_reg && !ep_sel) { ret = regmap_write(madera->regmap, edre_reg, edre_val); if (ret) dev_warn(madera->dev, "Failed to set EDRE Manual: %d\n", ret); } dev_dbg(madera->dev, "%sing clamp mask=0x%x val=0x%x\n", clamp ? "Set" : "Clear", mask, val); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: ret = regmap_update_bits(madera->regmap, MADERA_HP_CTRL_1L, mask, val); if (ret) dev_warn(madera->dev, "Failed to do clamp: %d\n", ret); ret = regmap_update_bits(madera->regmap, MADERA_HP_CTRL_1R, mask, val); if (ret) dev_warn(madera->dev, "Failed to do clamp: %d\n", ret); break; default: ret = regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_0, MADERA_HPD_OVD_ENA_SEL_MASK, val); if (ret) dev_warn(madera->dev, "Failed to do clamp: %d\n", ret); break; } /* Restore the desired state while not doing the clamp */ if (!clamp && (HOHM_TO_OHM(madera->hp_impedance_x100[0]) > info->pdata->hpdet_short_circuit_imp) && !ep_sel) { ret = regmap_update_bits(madera->regmap, MADERA_OUTPUT_ENABLES_1, (MADERA_OUT1L_ENA | MADERA_OUT1R_ENA) << (2 * (info->pdata->output - 1)), madera->hp_ena); if (ret) dev_warn(madera->dev, "Failed to restore headphone outputs: %d\n", ret); } snd_soc_dapm_mutex_unlock(madera->dapm); } static const char *madera_extcon_get_micbias_src(struct madera_extcon_info *info) { struct madera *madera = info->madera; unsigned int bias = info->micd_modes[info->micd_mode].bias; switch (madera->type) { case CS47L35: case CS47L85: case WM1840: return NULL; case CS47L90: case CS47L91: switch (bias) { case 0: case 1: case 2: case 3: return "MICBIAS1"; case 4: case 5: case 6: case 7: return "MICBIAS2"; default: return "MICVDD"; } break; default: switch (bias) { case 0: case 1: case 2: case 3: return "MICBIAS1"; case 4: case 5: return "MICBIAS2"; default: return "MICVDD"; } break; } } static const char *madera_extcon_get_micbias(struct madera_extcon_info *info) { struct madera *madera = info->madera; unsigned int bias = info->micd_modes[info->micd_mode].bias; switch (madera->type) { case CS47L35: switch (bias) { case 1: return "MICBIAS1A"; case 2: return "MICBIAS1B"; case 3: return "MICBIAS2A"; default: return "MICVDD"; } case CS47L85: case WM1840: switch (bias) { case 1: return "MICBIAS1"; case 2: return "MICBIAS2"; case 3: return "MICBIAS3"; case 4: return "MICBIAS4"; default: return "MICVDD"; } case CS47L90: case CS47L91: switch (bias) { case 0: return "MICBIAS1A"; case 1: return "MICBIAS1B"; case 2: return "MICBIAS1C"; case 3: return "MICBIAS1D"; case 4: return "MICBIAS2A"; case 5: return "MICBIAS2B"; case 6: return "MICBIAS2C"; case 7: return "MICBIAS2D"; default: return "MICVDD"; } default: switch (bias) { case 0: return "MICBIAS1A"; case 1: return "MICBIAS1B"; case 2: return "MICBIAS1C"; case 3: return "MICBIAS1D"; case 4: return "MICBIAS2A"; case 5: return "MICBIAS2B"; default: return "MICVDD"; } } } static void madera_extcon_enable_micbias_pin(struct madera_extcon_info *info, const char *widget) { struct madera *madera = info->madera; struct snd_soc_dapm_context *dapm = madera->dapm; int ret; ret = snd_soc_dapm_force_enable_pin(dapm, widget); if (ret) dev_warn(madera->dev, "Failed to enable %s: %d\n", widget, ret); snd_soc_dapm_sync(dapm); dev_dbg(madera->dev, "Enabled %s\n", widget); } static void madera_extcon_disable_micbias_pin(struct madera_extcon_info *info, const char *widget) { struct madera *madera = info->madera; struct snd_soc_dapm_context *dapm = madera->dapm; int ret; ret = snd_soc_dapm_disable_pin(dapm, widget); if (ret) dev_warn(madera->dev, "Failed to enable %s: %d\n", widget, ret); snd_soc_dapm_sync(dapm); dev_dbg(madera->dev, "Disabled %s\n", widget); } static void madera_extcon_set_micd_bias(struct madera_extcon_info *info, bool enable) { int old_bias = info->micd_bias.bias; int new_bias = info->micd_modes[info->micd_mode].bias; const char *widget; info->micd_bias.bias = new_bias; if ((new_bias == old_bias) && (info->micd_bias.enabled == enable)) return; widget = madera_extcon_get_micbias_src(info); BUG_ON(!widget); if (info->micd_bias.enabled) madera_extcon_disable_micbias_pin(info, widget); if (enable) madera_extcon_enable_micbias_pin(info, widget); info->micd_bias.enabled = enable; } static void madera_extcon_enable_micbias(struct madera_extcon_info *info) { struct madera *madera = info->madera; const char *widget = madera_extcon_get_micbias(info); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: break; default: madera_extcon_set_micd_bias(info, true); break; } /* If forced we must manually control the pin state, otherwise * the codec will manage this automatically */ if (info->pdata->micd_force_micbias) madera_extcon_enable_micbias_pin(info, widget); } static void madera_extcon_disable_micbias(struct madera_extcon_info *info) { struct madera *madera = info->madera; const char *widget = madera_extcon_get_micbias(info); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: break; default: madera_extcon_set_micd_bias(info, false); break; } /* If forced we must manually control the pin state, otherwise * the codec will manage this automatically */ if (info->pdata->micd_force_micbias) madera_extcon_disable_micbias_pin(info, widget); } static void madera_extcon_set_mode(struct madera_extcon_info *info, int mode) { struct madera *madera = info->madera; dev_dbg(madera->dev, "mic_mode[%d] src=0x%x gnd=0x%x bias=0x%x gpio=%d\n", mode, info->micd_modes[mode].src, info->micd_modes[mode].gnd, info->micd_modes[mode].bias, info->micd_modes[mode].gpio); if (info->pdata->micd_pol_gpio > 0) gpio_set_value_cansleep(info->pdata->micd_pol_gpio, info->micd_modes[mode].gpio); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_BIAS_SRC_MASK, info->micd_modes[mode].bias << MADERA_MICD_BIAS_SRC_SHIFT); regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_SRC, info->micd_modes[mode].src << MADERA_ACCDET_SRC_SHIFT); break; default: regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_BIAS_SRC_MASK, info->micd_modes[mode].bias << MADERA_MICD_BIAS_SRC_SHIFT); regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_0, MADERA_MICD1_SENSE_MASK, info->micd_modes[mode].src << MADERA_MICD1_SENSE_SHIFT); regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_0, MADERA_MICD1_GND_MASK, info->micd_modes[mode].gnd << MADERA_MICD1_GND_SHIFT); regmap_update_bits(madera->regmap, MADERA_OUTPUT_PATH_CONFIG_1 + (8 * (info->pdata->output - 1)), MADERA_HP1_GND_SEL_MASK, info->micd_modes[mode].gnd << MADERA_HP1_GND_SEL_SHIFT); break; } info->micd_mode = mode; } static void madera_extcon_next_mode(struct madera_extcon_info *info) { struct madera *madera = info->madera; int old_mode = info->micd_mode; int new_mode; bool change_bias = false; new_mode = (old_mode + 1) % info->micd_num_modes; dev_dbg(madera->dev, "change micd mode %d->%d (bias %d->%d)\n", old_mode, new_mode, info->micd_modes[old_mode].bias, info->micd_modes[new_mode].bias); if (info->micd_modes[old_mode].bias != info->micd_modes[new_mode].bias) { change_bias = true; madera_extcon_disable_micbias(info); } madera_extcon_set_mode(info, new_mode); if (change_bias) madera_extcon_enable_micbias(info); } static int madera_micd_adc_read(struct madera_extcon_info *info) { struct madera *madera = info->madera; unsigned int val = 0; int ret; /* Must disable MICD before we read the ADCVAL */ ret = regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_ENA, 0); if (ret) { dev_err(madera->dev, "Failed to disable MICD: %d\n", ret); return ret; } ret = regmap_read(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_4, &val); if (ret) { dev_err(madera->dev, "Failed to read MICDET_ADCVAL: %d\n", ret); return ret; } dev_dbg(madera->dev, "MICDET_ADCVAL: 0x%x\n", val); val &= MADERA_MICDET_ADCVAL_MASK; if (val < ARRAY_SIZE(madera_micd_levels)) val = madera_micd_levels[val]; else val = INT_MAX; return val; } static int madera_micd_read(struct madera_extcon_info *info) { struct madera *madera = info->madera; unsigned int val = 0; int ret, i; for (i = 0; i < 10 && !(val & MADERA_MICD_LVL_0_TO_8); i++) { ret = regmap_read(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_3, &val); if (ret) { dev_err(madera->dev, "Failed to read MICDET: %d\n", ret); return ret; } dev_dbg(madera->dev, "MICDET: 0x%x\n", val); if (!(val & MADERA_MICD_VALID)) { dev_warn(madera->dev, "Microphone detection state invalid\n"); return -EINVAL; } } if (i == 10 && !(val & MADERA_MICD_LVL_0_TO_8)) { dev_warn(madera->dev, "Failed to get valid MICDET value\n"); return -EINVAL; } if (!(val & MADERA_MICD_STS)) { val = INT_MAX; } else if (!(val & MADERA_MICD_LVL_0_TO_7)) { val = madera_micd_levels[ARRAY_SIZE(madera_micd_levels) - 1]; } else { int lvl; lvl = (val & MADERA_MICD_LVL_MASK) >> MADERA_MICD_LVL_SHIFT; lvl = ffs(lvl) - 1; if (lvl < info->num_micd_ranges) { val = info->micd_ranges[lvl].max; } else { i = ARRAY_SIZE(madera_micd_levels) - 2; val = madera_micd_levels[i]; } } return val; } static void madera_extcon_notify_micd(const struct madera_extcon_info *info, bool present, unsigned int impedance) { struct madera_micdet_notify_data data; data.present = present; data.impedance_x100 = OHM_TO_HOHM(impedance); data.out_num = 1; madera_call_notifiers(info->madera, MADERA_NOTIFY_MICDET, &data); } static int madera_hpdet_calc_calibration(const struct madera_extcon_info *info, int dacval, const struct madera_hpdet_trims *trims, const struct madera_hpdet_calibration_data *calib) { int grad_x4 = trims->grad_x4; int off_x4 = trims->off_x4; s64 val = dacval; s64 n; val = (val * 1000000) + calib->dacval_adjust; val = div64_s64(val, calib->C2); n = div_s64(1000000000000LL, calib->C3 + ((calib->C4_x_C3 * grad_x4) / 4)); n = val - n; if (n <= 0) return MADERA_HPDET_MAX; val = calib->C0 + ((calib->C1 * off_x4) / 4); val *= 1000000; val = div_s64(val, n); val -= calib->C5; /* Round up */ val += 500000; val = div_s64(val, 1000000); if (val < 0) return 0; else if (val > MADERA_HPDET_MAX) return MADERA_HPDET_MAX; return (int)val; } static int madera_hpdet_calibrate(struct madera_extcon_info *info, unsigned int range, unsigned int *val) { struct madera *madera = info->madera; unsigned int dacval, dacval_down; int ret; ret = regmap_read(madera->regmap, MADERA_HEADPHONE_DETECT_3, &dacval); if (ret) { dev_err(madera->dev, "Failed to read HP DACVAL: %d\n", ret); return -EAGAIN; } dacval = (dacval >> MADERA_HP_DACVAL_SHIFT) & MADERA_HP_DACVAL_MASK; switch (madera->type) { case CS47L35: case CS47L85: case WM1840: ret = regmap_read(madera->regmap, MADERA_HEADPHONE_DETECT_5, &dacval_down); if (ret) { dev_err(madera->dev, "Failed to read HP DACVAL_down: %d\n", ret); return -EAGAIN; } dacval_down = (dacval_down >> MADERA_HP_DACVAL_DOWN_SHIFT) & MADERA_HP_DACVAL_DOWN_MASK; dacval = (dacval + dacval_down) / 2; break; default: break; } dev_dbg(info->madera->dev, "hpdet_d calib range %d dac %d\n", range, dacval); *val = madera_hpdet_calc_calibration(info, dacval, &info->hpdet_trims[range], &info->hpdet_ranges[range]); return 0; } static int madera_hpdet_read(struct madera_extcon_info *info) { struct madera *madera = info->madera; unsigned int val, range, sense_pin; int ret; bool is_jdx_micdetx_pin = false; dev_dbg(madera->dev, "HPDET read\n"); ret = regmap_read(madera->regmap, MADERA_HEADPHONE_DETECT_2, &val); if (ret) { dev_err(madera->dev, "Failed to read HPDET status: %d\n", ret); return ret; } if (!(val & MADERA_HP_DONE_B)) { dev_warn(madera->dev, "HPDET did not complete: %x\n", val); return -EAGAIN; } switch (madera->type) { case CS47L35: case CS47L85: case WM1840: break; default: regmap_read(madera->regmap, MADERA_HEADPHONE_DETECT_0, &sense_pin); sense_pin = (sense_pin & MADERA_HPD_SENSE_SEL_MASK) >> MADERA_HPD_SENSE_SEL_SHIFT; switch (sense_pin) { case MADERA_HPD_SENSE_HPDET1: case MADERA_HPD_SENSE_HPDET2: is_jdx_micdetx_pin = false; break; default: dev_dbg(madera->dev, "is_jdx_micdetx_pin\n"); is_jdx_micdetx_pin = true; } break; } val &= MADERA_HP_LVL_B_MASK; /* Convert to ohms, the value is in 0.5 ohm increments */ val /= 2; if (is_jdx_micdetx_pin) goto done; regmap_read(madera->regmap, MADERA_HEADPHONE_DETECT_1, &range); range = (range & MADERA_HP_IMPEDANCE_RANGE_MASK) >> MADERA_HP_IMPEDANCE_RANGE_SHIFT; /* Skip up a range, or report? */ if (range < info->num_hpdet_ranges - 1 && (val >= info->hpdet_ranges[range].max)) { range++; dev_dbg(madera->dev, "Moving to HPDET range %d-%d\n", info->hpdet_ranges[range].min, info->hpdet_ranges[range].max); regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_IMPEDANCE_RANGE_MASK, range << MADERA_HP_IMPEDANCE_RANGE_SHIFT); return -EAGAIN; } if (info->hpdet_trims) { /* Perform calibration */ ret = madera_hpdet_calibrate(info, range, &val); if (ret) return ret; } else { /* Use uncalibrated reading */ if (range && (val < info->hpdet_ranges[range].min)) { dev_dbg(madera->dev, "Reporting range boundary %d\n", info->hpdet_ranges[range].min); val = info->hpdet_ranges[range].min; } } if (info->pdata->hpdet_ext_res_x100) { if (info->pdata->hpdet_ext_res_x100 >= OHM_TO_HOHM(val)) { dev_dbg(madera->dev, "External resistor (%d.%02d) >= measurement (%d.00)\n", info->pdata->hpdet_ext_res_x100 / 100, info->pdata->hpdet_ext_res_x100 % 100, val); val = 0; /* treat as a short */ } else { dev_dbg(madera->dev, "Compensating for external %d.%02d ohm resistor\n", info->pdata->hpdet_ext_res_x100 / 100, info->pdata->hpdet_ext_res_x100 % 100); val -= HOHM_TO_OHM(info->pdata->hpdet_ext_res_x100); } } done: dev_dbg(madera->dev, "HP impedance %d ohms\n", val); return (int)val; } static int madera_tune_headphone(struct madera_extcon_info *info, int reading) { struct madera *madera = info->madera; const struct madera_hp_tuning *tuning; int n_tunings; int i, ret; switch (madera->type) { case CS47L35: tuning = cs47l35_hp_tuning; n_tunings = ARRAY_SIZE(cs47l35_hp_tuning); break; case CS47L85: case WM1840: tuning = cs47l85_hp_tuning; n_tunings = ARRAY_SIZE(cs47l85_hp_tuning); break; case CS47L90: case CS47L91: tuning = cs47l90_hp_tuning; n_tunings = ARRAY_SIZE(cs47l90_hp_tuning); break; case CS47L92: case CS47L93: tuning = cs47l92_hp_tuning; n_tunings = ARRAY_SIZE(cs47l92_hp_tuning); break; default: return 0; } if (reading <= info->pdata->hpdet_short_circuit_imp) { /* Headphones are always off here so just mark them */ dev_warn(madera->dev, "Possible HP short, disabling\n"); return 0; } /* Check for tuning, we don't need to compare against the last * tuning entry because we always select that if reading is not * in range of the lower tunings */ for (i = 0; i < n_tunings - 1; ++i) { if (reading <= tuning[i].max_ohm) break; } if (info->hp_tuning_level != i) { dev_dbg(madera->dev, "New tuning level %d\n", i); info->hp_tuning_level = i; ret = regmap_multi_reg_write(madera->regmap, tuning[i].patch, tuning[i].patch_len); if (ret) { dev_err(madera->dev, "Failed to apply HP tuning %d\n", ret); return ret; } } return 0; } void madera_set_headphone_imp(struct madera_extcon_info *info, int imp) { struct madera *madera = info->madera; struct madera_hpdet_notify_data data; madera->hp_impedance_x100[0] = imp; data.impedance_x100 = imp; madera_call_notifiers(madera, MADERA_NOTIFY_HPDET, &data); madera_tune_headphone(info, HOHM_TO_OHM(imp)); } EXPORT_SYMBOL_GPL(madera_set_headphone_imp); static void madera_hpdet_start_micd(struct madera_extcon_info *info) { struct madera *madera = info->madera; regmap_update_bits(madera->regmap, MADERA_IRQ1_MASK_6, MADERA_IM_MICDET1_EINT1_MASK, MADERA_IM_MICDET1_EINT1); regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_0, MADERA_MICD1_ADC_MODE_MASK, MADERA_MICD1_ADC_MODE_MASK); regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_BIAS_STARTTIME_MASK | MADERA_MICD_RATE_MASK | MADERA_MICD_DBTIME_MASK | MADERA_MICD_ENA, MADERA_MICD_ENA); } static void madera_hpdet_stop_micd(struct madera_extcon_info *info) { struct madera *madera = info->madera; unsigned int start_time = 1, dbtime = 1, rate = 1; if (info->pdata->micd_bias_start_time) start_time = info->pdata->micd_bias_start_time; if (info->pdata->micd_rate) rate = info->pdata->micd_rate; if (info->pdata->micd_dbtime) dbtime = info->pdata->micd_dbtime; regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_BIAS_STARTTIME_MASK | MADERA_MICD_RATE_MASK | MADERA_MICD_DBTIME_MASK | MADERA_MICD_ENA, start_time << MADERA_MICD_BIAS_STARTTIME_SHIFT | rate << MADERA_MICD_RATE_SHIFT | dbtime << MADERA_MICD_DBTIME_SHIFT); udelay(100); /* Clear any spurious IRQs that have happened */ regmap_write(madera->regmap, MADERA_IRQ1_STATUS_6, MADERA_MICDET1_EINT1); regmap_update_bits(madera->regmap, MADERA_IRQ1_MASK_6, MADERA_IM_MICDET1_EINT1_MASK, 0); } int madera_hpdet_start(struct madera_extcon_info *info) { struct madera *madera = info->madera; int ret; unsigned int hpd_sense, hpd_clamp, val, hpd_gnd; dev_dbg(madera->dev, "Starting HPDET\n"); /* If we specified to assume a fixed impedance skip HPDET */ if (info->pdata->fixed_hpdet_imp_x100) { madera_set_headphone_imp(info, info->pdata->fixed_hpdet_imp_x100); ret = -EEXIST; goto skip; } /* Make sure we keep the device enabled during the measurement */ pm_runtime_get_sync(info->dev); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: madera_extcon_hp_clamp(info, true); ret = regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_MODE_MASK, info->state->mode); if (ret) { dev_err(madera->dev, "Failed to set HPDET mode (%d): %d\n", info->state->mode, ret); goto err; } break; default: if (info->state->mode == MADERA_ACCDET_MODE_HPL) { hpd_clamp = info->pdata->hpd_pins[0]; hpd_sense = info->pdata->hpd_pins[1]; } else { hpd_clamp = info->pdata->hpd_pins[2]; hpd_sense = info->pdata->hpd_pins[3]; } hpd_gnd = info->micd_modes[info->micd_mode].gnd; val = (hpd_sense << MADERA_HPD_SENSE_SEL_SHIFT) | (hpd_clamp << MADERA_HPD_OUT_SEL_SHIFT) | (hpd_sense << MADERA_HPD_FRC_SEL_SHIFT) | (hpd_gnd << MADERA_HPD_GND_SEL_SHIFT); ret = regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_0, MADERA_HPD_GND_SEL_MASK | MADERA_HPD_SENSE_SEL_MASK | MADERA_HPD_FRC_SEL_MASK | MADERA_HPD_OUT_SEL_MASK, val); if (ret) { dev_err(madera->dev, "Failed to set HPDET sense: %d\n", ret); goto err; } madera_extcon_hp_clamp(info, true); madera_hpdet_start_micd(info); break; } ret = regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_POLL, MADERA_HP_POLL); if (ret) { dev_err(madera->dev, "Can't start HPDET measurement: %d\n", ret); goto err; } return 0; err: madera_extcon_hp_clamp(info, false); pm_runtime_put_autosuspend(info->dev); skip: return ret; } EXPORT_SYMBOL_GPL(madera_hpdet_start); void madera_hpdet_restart(struct madera_extcon_info *info) { struct madera *madera = info->madera; /* Reset back to starting range */ regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_ENA_MASK, 0); regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_IMPEDANCE_RANGE_MASK | MADERA_HP_POLL, 0); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: break; default: regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_ENA_MASK, MADERA_MICD_ENA); break; } regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_POLL, MADERA_HP_POLL); } EXPORT_SYMBOL_GPL(madera_hpdet_restart); void madera_hpdet_stop(struct madera_extcon_info *info) { struct madera *madera = info->madera; dev_dbg(madera->dev, "Stopping HPDET\n"); /* Reset back to starting range */ madera_hpdet_stop_micd(info); regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_IMPEDANCE_RANGE_MASK | MADERA_HP_POLL, 0); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: /* Reset to default mode */ regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_MODE_MASK, 0); break; default: break; } madera_extcon_hp_clamp(info, false); pm_runtime_mark_last_busy(info->dev); pm_runtime_put_autosuspend(info->dev); } EXPORT_SYMBOL_GPL(madera_hpdet_stop); int madera_hpdet_reading(struct madera_extcon_info *info, int val) { dev_dbg(info->madera->dev, "Reading HPDET %d\n", val); if (val < 0) return val; madera_set_headphone_imp(info, val); if (info->have_mic) { madera_extcon_report(info, BIT_HEADSET); madera_jds_set_state(info, &madera_micd_button); } else { madera_extcon_report(info, BIT_HEADSET_NO_MIC); madera_jds_set_state(info, NULL); } return 0; } EXPORT_SYMBOL_GPL(madera_hpdet_reading); static int madera_hpdet_moisture_start(struct madera_extcon_info *info) { struct madera *madera = info->madera; unsigned int hpd_sense, hpd_gnd, val; int ret; /* Make sure we keep the device enabled during the measurement */ pm_runtime_get_sync(info->dev); ret = regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_RATE_MASK, 0x2 << MADERA_HP_RATE_SHIFT); if (ret) { dev_err(madera->dev, "Failed to set HPDET rate: %d\n", ret); goto err; } switch (madera->type) { case CS47L35: case CS47L85: case WM1840: ret = regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_MODE_MASK, info->pdata->moisture_pin); if (ret) { dev_err(madera->dev, "Failed to set HPDET mode (%d): %d\n", info->pdata->moisture_pin, ret); goto err; } break; default: hpd_sense = info->pdata->moisture_pin; hpd_gnd = info->micd_modes[info->micd_mode].gnd; val = (hpd_sense << MADERA_HPD_SENSE_SEL_SHIFT) | (hpd_sense << MADERA_HPD_FRC_SEL_SHIFT) | (hpd_gnd << MADERA_HPD_GND_SEL_SHIFT); ret = regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_0, MADERA_HPD_GND_SEL_MASK | MADERA_HPD_SENSE_SEL_MASK | MADERA_HPD_FRC_SEL_MASK, val); if (ret != 0) { dev_err(madera->dev, "Failed to set HPDET sense: %d\n", ret); goto err; } madera_hpdet_start_micd(info); ret = regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_POLL, MADERA_HP_POLL); if (ret != 0) { dev_err(madera->dev, "Can't start HPDET measurement: %d\n", ret); goto err; } break; } return ret; err: pm_runtime_put_autosuspend(info->dev); return ret; } static void madera_hpdet_moisture_stop(struct madera_extcon_info *info) { struct madera *madera = info->madera; switch (madera->type) { case CS47L35: case CS47L85: case WM1840: regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_MODE_MASK, 0); break; default: madera_hpdet_stop_micd(info); break; } regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_IMPEDANCE_RANGE_MASK | MADERA_HP_POLL, 0); regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, MADERA_HP_RATE_MASK, 0); pm_runtime_mark_last_busy(info->dev); pm_runtime_put_autosuspend(info->dev); } static int madera_hpdet_moisture_reading(struct madera_extcon_info *info, int val) { struct madera *madera = info->madera; int debounce_lim = info->pdata->moisture_debounce; val = HOHM_TO_OHM(val); /* Extra precision not required. */ if (val < 0) { return val; } else if (val < info->pdata->moisture_imp) { if (info->pdata->micd_software_compare) madera_jds_set_state(info, &madera_micd_adc_mic); else madera_jds_set_state(info, &madera_micd_microphone); } else { if (debounce_lim) { if (++info->moisture_count < debounce_lim) { dev_dbg(madera->dev, "Moisture software debounce: %d, %x\n", info->moisture_count, val); return -EAGAIN; } info->moisture_count = 0; } dev_warn(madera->dev, "Jack detection due to moisture, ignoring\n"); madera_jds_set_state(info, NULL); } return 0; } int madera_micd_start(struct madera_extcon_info *info) { struct madera *madera = info->madera; int ret; unsigned int micd_mode; /* Microphone detection can't use idle mode */ pm_runtime_get_sync(info->dev); dev_dbg(madera->dev, "Disabling MICD_OVD\n"); regmap_update_bits(madera->regmap, MADERA_MICD_CLAMP_CONTROL, MADERA_MICD_CLAMP_OVD_MASK, 0); ret = regulator_enable(info->micvdd); if (ret) dev_err(madera->dev, "Failed to enable MICVDD: %d\n", ret); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_MODE_MASK, info->state->mode); break; default: if (info->state->mode == MADERA_ACCDET_MODE_ADC) micd_mode = MADERA_MICD1_ADC_MODE_MASK; else micd_mode = 0; regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_0, MADERA_MICD1_ADC_MODE_MASK, micd_mode); break; } madera_extcon_enable_micbias(info); regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_ENA, MADERA_MICD_ENA); return 0; } EXPORT_SYMBOL_GPL(madera_micd_start); void madera_micd_stop(struct madera_extcon_info *info) { struct madera *madera = info->madera; regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_ENA, 0); madera_extcon_disable_micbias(info); switch (madera->type) { case CS47L35: case CS47L85: case WM1840: /* Reset to default mode */ regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_MODE_MASK, 0); break; default: break; } regulator_disable(info->micvdd); dev_dbg(madera->dev, "Enabling MICD_OVD\n"); regmap_update_bits(madera->regmap, MADERA_MICD_CLAMP_CONTROL, MADERA_MICD_CLAMP_OVD_MASK, MADERA_MICD_CLAMP_OVD); pm_runtime_mark_last_busy(info->dev); pm_runtime_put_autosuspend(info->dev); } EXPORT_SYMBOL_GPL(madera_micd_stop); static void madera_micd_restart(struct madera_extcon_info *info) { struct madera *madera = info->madera; regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_ENA, 0); regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_ENA, MADERA_MICD_ENA); } static int madera_micd_button_debounce(struct madera_extcon_info *info, int val) { struct madera *madera = info->madera; int debounce_lim = info->pdata->micd_manual_debounce; if (debounce_lim) { if (info->micd_debounce != val) info->micd_count = 0; info->micd_debounce = val; info->micd_count++; if (info->micd_count == debounce_lim) { info->micd_count = 0; if (val == info->micd_res_old) return 0; info->micd_res_old = val; } else { dev_dbg(madera->dev, "Software debounce: %d,%x\n", info->micd_count, val); madera_micd_restart(info); return -EAGAIN; } } return 0; } static int madera_micd_button_process(struct madera_extcon_info *info, int val) { struct madera *madera = info->madera; int i, key; if (val < MADERA_MICROPHONE_MIN_OHM) { dev_dbg(madera->dev, "Mic button detected\n"); for (i = 0; i < info->num_micd_ranges; i++) input_report_key(info->input, info->micd_ranges[i].key, 0); for (i = 0; i < info->num_micd_ranges; i++) { if (val <= info->micd_ranges[i].max) { key = info->micd_ranges[i].key; dev_dbg(madera->dev, "Key %d down\n", key); input_report_key(info->input, key, 1); input_sync(info->input); break; } } if (i == info->num_micd_ranges) dev_warn(madera->dev, "Button level %u out of range\n", val); } else { dev_dbg(madera->dev, "Mic button released\n"); for (i = 0; i < info->num_micd_ranges; i++) input_report_key(info->input, info->micd_ranges[i].key, 0); input_sync(info->input); } return 0; } int madera_micd_button_reading(struct madera_extcon_info *info, int val) { int ret; if (val < 0) return val; val = HOHM_TO_OHM(val); ret = madera_micd_button_debounce(info, val); if (ret < 0) return ret; return madera_micd_button_process(info, val); } EXPORT_SYMBOL_GPL(madera_micd_button_reading); int madera_micd_mic_start(struct madera_extcon_info *info) { struct madera *madera = info->madera; int ret; info->detecting = true; ret = regulator_allow_bypass(info->micvdd, false); if (ret) dev_err(madera->dev, "Failed to regulate MICVDD: %d\n", ret); return madera_micd_start(info); } EXPORT_SYMBOL_GPL(madera_micd_mic_start); void madera_micd_mic_stop(struct madera_extcon_info *info) { struct madera *madera = info->madera; int ret; madera_micd_stop(info); ret = regulator_allow_bypass(info->micvdd, true); if (ret) dev_err(madera->dev, "Failed to bypass MICVDD: %d\n", ret); info->detecting = false; } EXPORT_SYMBOL_GPL(madera_micd_mic_stop); int madera_micd_mic_reading(struct madera_extcon_info *info, int val) { struct madera *madera = info->madera; int ret; if (val < 0) return val; val = HOHM_TO_OHM(val); /* Due to jack detect this should never happen */ if (val > MADERA_MICROPHONE_MAX_OHM) { dev_warn(madera->dev, "Detected open circuit\n"); info->have_mic = info->pdata->micd_open_circuit_declare; goto done; } /* If we got a high impedence we should have a headset, report it. */ if (val >= MADERA_MICROPHONE_MIN_OHM) { dev_dbg(madera->dev, "Detected headset\n"); info->have_mic = true; goto done; } /* If we detected a lower impedence during initial startup * then we probably have the wrong polarity, flip it. Don't * do this for the lowest impedences to speed up detection of * plain headphones. If both polarities report a low * impedence then give up and report headphones. */ if (val > info->micd_ranges[0].max && info->micd_num_modes > 1) { if (info->jack_flips >= info->micd_num_modes * 10) { dev_dbg(madera->dev, "Detected HP/line\n"); goto done; } else { madera_extcon_next_mode(info); info->jack_flips++; return -EAGAIN; } } /* * If we're still detecting and we detect a short then we've * got a headphone. */ dev_dbg(madera->dev, "Headphone detected\n"); done: pm_runtime_mark_last_busy(info->dev); if (info->pdata->hpdet_channel) ret = madera_jds_set_state(info, &madera_hpdet_right); else ret = madera_jds_set_state(info, &madera_hpdet_left); if (ret < 0) { if (info->have_mic) madera_extcon_report(info, BIT_HEADSET); else madera_extcon_report(info, BIT_HEADSET_NO_MIC); } madera_extcon_notify_micd(info, info->have_mic, val); return 0; } EXPORT_SYMBOL_GPL(madera_micd_mic_reading); int madera_micd_mic_timeout_ms(struct madera_extcon_info *info) { if (info->pdata->micd_timeout_ms) return info->pdata->micd_timeout_ms; else return MADERA_DEFAULT_MICD_TIMEOUT_MS; } EXPORT_SYMBOL_GPL(madera_micd_mic_timeout_ms); void madera_micd_mic_timeout(struct madera_extcon_info *info) { int ret; dev_dbg(info->madera->dev, "MICD timed out, reporting HP\n"); if (info->pdata->hpdet_channel) ret = madera_jds_set_state(info, &madera_hpdet_right); else ret = madera_jds_set_state(info, &madera_hpdet_left); if (ret < 0) madera_extcon_report(info, BIT_HEADSET_NO_MIC); } EXPORT_SYMBOL_GPL(madera_micd_mic_timeout); static int madera_jack_present(struct madera_extcon_info *info, unsigned int *jack_val) { struct madera *madera = info->madera; unsigned int present, val; int ret; ret = regmap_read(madera->regmap, MADERA_IRQ1_RAW_STATUS_7, &val); if (ret) { dev_err(madera->dev, "Failed to read jackdet status: %d\n", ret); return ret; } dev_dbg(madera->dev, "IRQ1_RAW_STATUS_7=0x%x\n", val); if (info->pdata->jd_use_jd2) { val &= MADERA_MICD_CLAMP_RISE_STS1; present = 0; } else if (info->pdata->jd_invert) { val &= MADERA_JD1_FALL_STS1_MASK; present = MADERA_JD1_FALL_STS1; } else { val &= MADERA_JD1_RISE_STS1_MASK; present = MADERA_JD1_RISE_STS1; } dev_dbg(madera->dev, "jackdet val=0x%x present=0x%x\n", val, present); if (jack_val) *jack_val = val; if (val == present) return 1; else return 0; } static irqreturn_t madera_hpdet_handler(int irq, void *data) { struct madera_extcon_info *info = data; struct madera *madera = info->madera; int ret; dev_dbg(madera->dev, "HPDET handler\n"); madera_jds_cancel_timeout(info); mutex_lock(&info->lock); switch (madera_jds_get_mode(info)) { case MADERA_ACCDET_MODE_HPL: case MADERA_ACCDET_MODE_HPR: case MADERA_ACCDET_MODE_HPM: /* Fall through to spurious if no jack present */ if (madera_jack_present(info, NULL) > 0) break; default: dev_warn(madera->dev, "Spurious HPDET IRQ\n"); madera_jds_start_timeout(info); mutex_unlock(&info->lock); return IRQ_NONE; } ret = madera_hpdet_read(info); if (ret == -EAGAIN) goto out; ret = OHM_TO_HOHM(ret); madera_jds_reading(info, ret); out: madera_jds_start_timeout(info); pm_runtime_mark_last_busy(info->dev); mutex_unlock(&info->lock); return IRQ_HANDLED; } static void madera_micd_handler(struct work_struct *work) { struct madera_extcon_info *info = container_of(work, struct madera_extcon_info, micd_detect_work.work); struct madera *madera = info->madera; enum madera_accdet_mode mode; int ret; madera_jds_cancel_timeout(info); mutex_lock(&info->lock); /* Must check that we are in a micd state before accessing * any codec registers */ mode = madera_jds_get_mode(info); switch (mode) { case MADERA_ACCDET_MODE_MIC: case MADERA_ACCDET_MODE_ADC: break; default: goto spurious; } if (madera_jack_present(info, NULL) <= 0) goto spurious; switch (mode) { case MADERA_ACCDET_MODE_MIC: ret = madera_micd_read(info); break; case MADERA_ACCDET_MODE_ADC: ret = madera_micd_adc_read(info); break; default: /* we can't get here but compiler still warns */ ret = 0; break; } if (ret == -EAGAIN) goto out; dev_dbg(madera->dev, "Mic impedance %d ohms\n", ret); madera_jds_reading(info, OHM_TO_HOHM(ret)); out: madera_jds_start_timeout(info); pm_runtime_mark_last_busy(info->dev); mutex_unlock(&info->lock); return; spurious: dev_warn(madera->dev, "Spurious MICDET IRQ\n"); madera_jds_start_timeout(info); mutex_unlock(&info->lock); } static irqreturn_t madera_micdet(int irq, void *data) { struct madera_extcon_info *info = data; struct madera *madera = info->madera; int debounce = info->pdata->micd_detect_debounce_ms; dev_dbg(madera->dev, "micdet IRQ"); cancel_delayed_work_sync(&info->micd_detect_work); mutex_lock(&info->lock); if (!info->detecting) debounce = 0; mutex_unlock(&info->lock); /* Defer to the workqueue to ensure serialization * and prevent race conditions if an IRQ occurs while * running the delayed work */ schedule_delayed_work(&info->micd_detect_work, msecs_to_jiffies(debounce)); return IRQ_HANDLED; } const struct madera_jd_state madera_hpdet_left = { .mode = MADERA_ACCDET_MODE_HPL, .start = madera_hpdet_start, .reading = madera_hpdet_reading, .stop = madera_hpdet_stop, }; EXPORT_SYMBOL_GPL(madera_hpdet_left); const struct madera_jd_state madera_hpdet_right = { .mode = MADERA_ACCDET_MODE_HPR, .start = madera_hpdet_start, .reading = madera_hpdet_reading, .stop = madera_hpdet_stop, }; EXPORT_SYMBOL_GPL(madera_hpdet_right); const struct madera_jd_state madera_hpdet_moisture = { .mode = MADERA_ACCDET_MODE_HPL, /* Just a dummy, set by moisture-pin */ .start = madera_hpdet_moisture_start, .restart = madera_hpdet_restart, .reading = madera_hpdet_moisture_reading, .stop = madera_hpdet_moisture_stop, }; EXPORT_SYMBOL_GPL(madera_hpdet_moisture); const struct madera_jd_state madera_micd_button = { .mode = MADERA_ACCDET_MODE_MIC, .start = madera_micd_start, .reading = madera_micd_button_reading, .stop = madera_micd_stop, }; EXPORT_SYMBOL_GPL(madera_micd_button); const struct madera_jd_state madera_micd_adc_mic = { .mode = MADERA_ACCDET_MODE_ADC, .start = madera_micd_mic_start, .restart = madera_micd_restart, .reading = madera_micd_mic_reading, .stop = madera_micd_mic_stop, .timeout_ms = madera_micd_mic_timeout_ms, .timeout = madera_micd_mic_timeout, }; EXPORT_SYMBOL_GPL(madera_micd_adc_mic); const struct madera_jd_state madera_micd_microphone = { .mode = MADERA_ACCDET_MODE_MIC, .start = madera_micd_mic_start, .reading = madera_micd_mic_reading, .stop = madera_micd_mic_stop, .timeout_ms = madera_micd_mic_timeout_ms, .timeout = madera_micd_mic_timeout, }; EXPORT_SYMBOL_GPL(madera_micd_microphone); static irqreturn_t madera_jackdet(int irq, void *data) { struct madera_extcon_info *info = data; struct madera *madera = info->madera; unsigned int val, mask; bool cancelled_state; int i, present; dev_dbg(madera->dev, "jackdet IRQ"); cancelled_state = madera_jds_cancel_timeout(info); pm_runtime_get_sync(info->dev); mutex_lock(&info->lock); val = 0; present = madera_jack_present(info, &val); if (present < 0) { mutex_unlock(&info->lock); pm_runtime_put_autosuspend(info->dev); return IRQ_NONE; } if (val == info->last_jackdet) { dev_dbg(madera->dev, "Suppressing duplicate JACKDET\n"); if (cancelled_state) madera_jds_start_timeout(info); goto out; } info->last_jackdet = val; mask = MADERA_MICD_CLAMP_DB | MADERA_JD1_DB; if (info->pdata->jd_use_jd2) mask |= MADERA_JD2_DB; if (present) { dev_dbg(madera->dev, "Detected jack\n"); if (info->pdata->jd_wake_time) __pm_wakeup_event(&info->detection_wake_lock, info->pdata->jd_wake_time); info->have_mic = false; info->jack_flips = 0; if (info->pdata->init_delay) msleep(info->pdata->init_delay); if (info->pdata->custom_jd) madera_jds_set_state(info, info->pdata->custom_jd); else if (info->pdata->moisture_imp) madera_jds_set_state(info, &madera_hpdet_moisture); else if (info->pdata->micd_software_compare) madera_jds_set_state(info, &madera_micd_adc_mic); else madera_jds_set_state(info, &madera_micd_microphone); madera_jds_start_timeout(info); regmap_update_bits(madera->regmap, MADERA_INTERRUPT_DEBOUNCE_7, mask, 0); } else { dev_dbg(madera->dev, "Detected jack removal\n"); info->have_mic = false; info->micd_res_old = 0; info->micd_debounce = 0; info->micd_count = 0; info->moisture_count = 0; madera_jds_set_state(info, NULL); for (i = 0; i < info->num_micd_ranges; i++) input_report_key(info->input, info->micd_ranges[i].key, 0); input_sync(info->input); madera_extcon_report(info, BIT_NO_HEADSET); regmap_update_bits(madera->regmap, MADERA_INTERRUPT_DEBOUNCE_7, mask, mask); madera_set_headphone_imp(info, MADERA_HP_Z_OPEN); madera_extcon_notify_micd(info, false, 0); } out: mutex_unlock(&info->lock); pm_runtime_mark_last_busy(info->dev); pm_runtime_put_autosuspend(info->dev); return IRQ_HANDLED; } /* Map a level onto a slot in the register bank */ static void madera_micd_set_level(struct madera *madera, int index, unsigned int level) { int reg; unsigned int mask; reg = MADERA_MIC_DETECT_1_LEVEL_4 - (index / 2); if (!(index % 2)) { mask = 0x3f00; level <<= 8; } else { mask = 0x3f; } /* Program the level itself */ regmap_update_bits(madera->regmap, reg, mask, level); } #ifdef CONFIG_OF static void madera_extcon_of_get_int(struct device_node *node, const char *prop, int *value) { u32 v; if (of_property_read_u32(node, prop, &v) == 0) *value = v; } static int madera_extcon_of_get_u32_num_groups(struct madera *madera, struct device_node *node, const char *prop, int group_size) { int len_prop, num_groups; if (!of_get_property(node, prop, &len_prop)) return -EINVAL; num_groups = len_prop / (group_size * sizeof(u32)); if (num_groups * group_size * sizeof(u32) != len_prop) { dev_err(madera->dev, "DT property %s is malformed: %d\n", prop, -EOVERFLOW); return -EOVERFLOW; } return num_groups; } static int madera_extcon_of_read_part_array(struct madera *madera, struct device_node *node, const char *prop, int index, u32 *out, int num) { int ret; for (; num > 0; --num) { ret = of_property_read_u32_index(node, prop, index++, out++); if (ret < 0) return ret; } return 0; } static int madera_extcon_of_get_micd_ranges(struct madera *madera, struct device_node *node, struct madera_accdet_pdata *pdata) { struct madera_micd_range *micd_ranges; u32 values[2]; int nranges, i; int ret = 0; nranges = madera_extcon_of_get_u32_num_groups(madera, node, "cirrus,micd-ranges", 2); if (nranges < 0) return nranges; micd_ranges = devm_kcalloc(madera->dev, nranges, sizeof(struct madera_micd_range), GFP_KERNEL); for (i = 0; i < nranges; ++i) { ret = madera_extcon_of_read_part_array(madera, node, "cirrus,micd-ranges", i * 2, values, 2); if (ret < 0) goto error; micd_ranges[i].max = values[0]; micd_ranges[i].key = values[1]; } pdata->micd_ranges = micd_ranges; pdata->num_micd_ranges = nranges; return ret; error: devm_kfree(madera->dev, micd_ranges); dev_err(madera->dev, "DT property cirrus,micd-ranges is malformed: %d\n", ret); return ret; } static int madera_extcon_of_get_micd_configs(struct madera *madera, struct device_node *node, struct madera_accdet_pdata *pdata) { struct madera_micd_config *micd_configs; u32 values[5]; int nconfigs, i; int ret = 0; nconfigs = madera_extcon_of_get_u32_num_groups(madera, node, "cirrus,micd-configs", ARRAY_SIZE(values)); if (nconfigs < 0) return nconfigs; micd_configs = devm_kcalloc(madera->dev, nconfigs, sizeof(struct madera_micd_config), GFP_KERNEL); for (i = 0; i < nconfigs; ++i) { ret = madera_extcon_of_read_part_array(madera, node, "cirrus,micd-configs", i * ARRAY_SIZE(values), values, ARRAY_SIZE(values)); if (ret < 0) goto error; micd_configs[i].src = values[0]; micd_configs[i].gnd = values[1]; micd_configs[i].bias = values[2]; micd_configs[i].gpio = values[3]; micd_configs[i].hp_gnd = values[4]; } pdata->micd_configs = micd_configs; pdata->num_micd_configs = nconfigs; return ret; error: devm_kfree(madera->dev, micd_configs); dev_err(madera->dev, "DT property cirrus,micd-configs is malformed: %d\n", ret); return ret; } static void madera_extcon_of_get_hpd_pins(struct madera *madera, struct device_node *node, struct madera_accdet_pdata *pdata) { u32 values[ARRAY_SIZE(pdata->hpd_pins)]; int i, ret; BUILD_BUG_ON(ARRAY_SIZE(pdata->hpd_pins) != ARRAY_SIZE(madera_default_hpd_pins)); memcpy(pdata->hpd_pins, madera_default_hpd_pins, sizeof(pdata->hpd_pins)); ret = of_property_read_u32_array(node, "cirrus,hpd-pins", values, sizeof(values)); if (ret) { if (ret != -EINVAL) dev_err(madera->dev, "Malformed cirrus,hpd-pins: %d\n", ret); return; } /* copy values, supplying defaults where requested */ for (i = 0; i < ARRAY_SIZE(values); ++i) { if (values[i] > 0xFFFF) pdata->hpd_pins[i] = madera_default_hpd_pins[i]; else pdata->hpd_pins[i] = values[i]; } } static void madera_extcon_of_process(struct madera *madera, struct device_node *node) { struct madera_accdet_pdata *pdata; u32 out_num; int ret, i; ret = of_property_read_u32_index(node, "reg", 0, &out_num); if (ret != 0) { dev_err(madera->dev, "failed to read reg property from %s (%d)\n", node->name, ret); return; } if (out_num == 0) { dev_warn(madera->dev, "accdet node illegal reg %u\n", out_num); return; } for (i = 0; i < ARRAY_SIZE(madera->pdata.accdet); i++) if (!madera->pdata.accdet[i].enabled) break; if (i == ARRAY_SIZE(madera->pdata.accdet)) { dev_warn(madera->dev, "Too many accdet nodes: %d\n", i + 1); return; } dev_dbg(madera->dev, "processing: %s reg=%u\n", node->name, out_num); pdata = &madera->pdata.accdet[i]; pdata->enabled = true; /* implied by presence of DT node */ pdata->output = out_num; madera_extcon_of_get_int(node, "cirrus,micd-detect-debounce-ms", &pdata->micd_detect_debounce_ms); madera_extcon_of_get_int(node, "cirrus,micd-manual-debounce", &pdata->micd_manual_debounce); pdata->micd_pol_gpio = of_get_named_gpio(node, "cirrus,micd-pol-gpios", 0); if (pdata->micd_pol_gpio < 0) { if (pdata->micd_pol_gpio != -ENOENT) dev_warn(madera->dev, "Malformed cirrus,micd-pol-gpios ignored: %d\n", pdata->micd_pol_gpio); pdata->micd_pol_gpio = 0; } madera_extcon_of_get_micd_ranges(madera, node, pdata); madera_extcon_of_get_micd_configs(madera, node, pdata); madera_extcon_of_get_int(node, "cirrus,micd-bias-start-time", &pdata->micd_bias_start_time); madera_extcon_of_get_int(node, "cirrus,micd-rate", &pdata->micd_rate); madera_extcon_of_get_int(node, "cirrus,micd-dbtime", &pdata->micd_dbtime); madera_extcon_of_get_int(node, "cirrus,micd-timeout-ms", &pdata->micd_timeout_ms); pdata->micd_force_micbias = of_property_read_bool(node, "cirrus,micd-force-micbias"); pdata->micd_software_compare = of_property_read_bool(node, "cirrus,micd-software-compare"); pdata->micd_open_circuit_declare = of_property_read_bool(node, "cirrus,micd-open-circuit-declare"); pdata->jd_use_jd2 = of_property_read_bool(node, "cirrus,jd-use-jd2"); pdata->jd_invert = of_property_read_bool(node, "cirrus,jd-invert"); madera_extcon_of_get_int(node, "cirrus,fixed-hpdet-imp", &pdata->fixed_hpdet_imp_x100); madera_extcon_of_get_int(node, "cirrus,hpdet-short-circuit-imp", &pdata->hpdet_short_circuit_imp); madera_extcon_of_get_int(node, "cirrus,hpdet-channel", &pdata->hpdet_channel); madera_extcon_of_get_int(node, "cirrus,jd-wake-time", &pdata->jd_wake_time); madera_extcon_of_get_int(node, "cirrus,micd-clamp-mode", &pdata->micd_clamp_mode); madera_extcon_of_get_hpd_pins(madera, node, pdata); madera_extcon_of_get_int(node, "cirrus,hpdet-ext-res", &pdata->hpdet_ext_res_x100); /* Set sensible default for moisture-pin */ switch (madera->type) { case CS47L35: case CS47L85: case WM1840: break; default: pdata->moisture_pin = MADERA_HPD_SENSE_JD2; break; } madera_extcon_of_get_int(node, "cirrus,moisture-pin", &pdata->moisture_pin); madera_extcon_of_get_int(node, "cirrus,moisture-imp", &pdata->moisture_imp); madera_extcon_of_get_int(node, "cirrus,moisture-debounce", &pdata->moisture_debounce); madera_extcon_of_get_int(node, "cirrus,init-delay-ms", &pdata->init_delay); } static int madera_extcon_of_get_pdata(struct madera *madera) { struct device_node *parent, *child; /* a GPSW is not necessarily exclusive to a single accessory detect * channel so this is stored in the global device pdata */ madera_of_read_uint_array(madera, "cirrus,gpsw", false, madera->pdata.gpsw, 0, ARRAY_SIZE(madera->pdata.gpsw)); parent = of_get_child_by_name(madera->dev->of_node, "cirrus,accdet"); if (!parent) { dev_dbg(madera->dev, "No DT nodes\n"); return 0; } for_each_child_of_node(parent, child) { madera_extcon_of_process(madera, child); } of_node_put(parent); return 0; } #else static inline int madera_extcon_of_get_pdata(struct madera *madera) { return 0; } #endif #ifdef DEBUG #define MADERA_EXTCON_DUMP(x, f) \ dev_dbg(madera->dev, "\t" #x ": " f "\n", pdata->x) static void madera_extcon_dump_pdata(struct madera *madera) { const struct madera_accdet_pdata *pdata; int i, j; dev_dbg(madera->dev, "extcon pdata gpsw=[0x%x 0x%x]\n", madera->pdata.gpsw[0], madera->pdata.gpsw[1]); for (i = 0; i < ARRAY_SIZE(madera->pdata.accdet); ++i) { pdata = &madera->pdata.accdet[i]; dev_dbg(madera->dev, "extcon pdata OUT%u\n", i + 1); MADERA_EXTCON_DUMP(enabled, "%u"); MADERA_EXTCON_DUMP(jd_wake_time, "%d"); MADERA_EXTCON_DUMP(jd_use_jd2, "%u"); MADERA_EXTCON_DUMP(jd_invert, "%u"); MADERA_EXTCON_DUMP(fixed_hpdet_imp_x100, "%d"); MADERA_EXTCON_DUMP(hpdet_ext_res_x100, "%d"); MADERA_EXTCON_DUMP(hpdet_short_circuit_imp, "%d"); MADERA_EXTCON_DUMP(hpdet_channel, "%d"); MADERA_EXTCON_DUMP(micd_detect_debounce_ms, "%d"); MADERA_EXTCON_DUMP(hpdet_short_circuit_imp, "%d"); MADERA_EXTCON_DUMP(hpdet_channel, "%d"); MADERA_EXTCON_DUMP(micd_detect_debounce_ms, "%d"); MADERA_EXTCON_DUMP(micd_manual_debounce, "%d"); MADERA_EXTCON_DUMP(micd_pol_gpio, "%d"); MADERA_EXTCON_DUMP(micd_bias_start_time, "%d"); MADERA_EXTCON_DUMP(micd_rate, "%d"); MADERA_EXTCON_DUMP(micd_dbtime, "%d"); MADERA_EXTCON_DUMP(micd_timeout_ms, "%d"); MADERA_EXTCON_DUMP(micd_clamp_mode, "%u"); MADERA_EXTCON_DUMP(micd_force_micbias, "%u"); MADERA_EXTCON_DUMP(micd_open_circuit_declare, "%u"); MADERA_EXTCON_DUMP(micd_software_compare, "%u"); dev_dbg(madera->dev, "\tmicd_ranges {\n"); for (j = 0; j < pdata->num_micd_ranges; ++j) dev_dbg(madera->dev, "\t\tmax: %d key: %d\n", pdata->micd_ranges[j].max, pdata->micd_ranges[j].key); dev_dbg(madera->dev, "\t}\n"); dev_dbg(madera->dev, "\tmicd_configs {\n"); for (j = 0; j < pdata->num_micd_configs; ++j) dev_dbg(madera->dev, "\t\tsrc: 0x%x gnd: 0x%x bias: %u gpio: %u\n", pdata->micd_configs[j].src, pdata->micd_configs[j].gnd, pdata->micd_configs[j].bias, pdata->micd_configs[j].gpio); dev_dbg(madera->dev, "\t}\n"); dev_dbg(madera->dev, "\thpd_pins: %u %u %u %u\n", pdata->hpd_pins[0], pdata->hpd_pins[1], pdata->hpd_pins[2], pdata->hpd_pins[3]); } } #else static inline void madera_extcon_dump_pdata(struct madera *madera) { } #endif static int madera_extcon_read_calibration(struct madera_extcon_info *info) { struct madera *madera = info->madera; struct madera_hpdet_trims *trims; int ret = -EIO; unsigned int offset, gradient, interim_val; unsigned int otp_hpdet_calib_1, otp_hpdet_calib_2; switch (madera->type) { case CS47L35: otp_hpdet_calib_1 = CS47L35_OTP_HPDET_CAL_1; otp_hpdet_calib_2 = CS47L35_OTP_HPDET_CAL_2; break; case CS47L85: case WM1840: otp_hpdet_calib_1 = CS47L85_OTP_HPDET_CAL_1; otp_hpdet_calib_2 = CS47L85_OTP_HPDET_CAL_2; break; default: otp_hpdet_calib_1 = MADERA_OTP_HPDET_CAL_1; otp_hpdet_calib_2 = MADERA_OTP_HPDET_CAL_2; break; } ret = regmap_read(madera->regmap_32bit, otp_hpdet_calib_1, &offset); if (ret) { dev_err(madera->dev, "Failed to read HP CALIB OFFSET value: %d\n", ret); return ret; } ret = regmap_read(madera->regmap_32bit, otp_hpdet_calib_2, &gradient); if (ret) { dev_err(madera->dev, "Failed to read HP CALIB OFFSET value: %d\n", ret); return ret; } if (((offset == 0) && (gradient == 0)) || ((offset == 0xFFFFFFFF) && (gradient == 0xFFFFFFFF))) { dev_warn(madera->dev, "No HP trims\n"); return 0; } trims = devm_kcalloc(info->dev, 4, sizeof(struct madera_hpdet_trims), GFP_KERNEL); if (!trims) { dev_err(madera->dev, "Failed to alloc hpdet trims\n"); return -ENOMEM; } interim_val = (offset & MADERA_OTP_HPDET_CALIB_OFFSET_00_MASK) >> MADERA_OTP_HPDET_CALIB_OFFSET_00_SHIFT; trims[0].off_x4 = 128 - interim_val; interim_val = (gradient & MADERA_OTP_HPDET_GRADIENT_0X_MASK) >> MADERA_OTP_HPDET_GRADIENT_0X_SHIFT; trims[0].grad_x4 = 128 - interim_val; interim_val = (offset & MADERA_OTP_HPDET_CALIB_OFFSET_01_MASK) >> MADERA_OTP_HPDET_CALIB_OFFSET_01_SHIFT; trims[1].off_x4 = 128 - interim_val; trims[1].grad_x4 = trims[0].grad_x4; interim_val = (offset & MADERA_OTP_HPDET_CALIB_OFFSET_10_MASK) >> MADERA_OTP_HPDET_CALIB_OFFSET_10_SHIFT; trims[2].off_x4 = 128 - interim_val; interim_val = (gradient & MADERA_OTP_HPDET_GRADIENT_1X_MASK) >> MADERA_OTP_HPDET_GRADIENT_1X_SHIFT; trims[2].grad_x4 = 128 - interim_val; interim_val = (offset & MADERA_OTP_HPDET_CALIB_OFFSET_11_MASK) >> MADERA_OTP_HPDET_CALIB_OFFSET_11_SHIFT; trims[3].off_x4 = 128 - interim_val; trims[3].grad_x4 = trims[2].grad_x4; info->hpdet_trims = trims; dev_dbg(madera->dev, "trims_x_4: %u,%u %u,%u %u,%u %u,%u\n", trims[0].off_x4, trims[0].grad_x4, trims[1].off_x4, trims[1].grad_x4, trims[2].off_x4, trims[2].grad_x4, trims[3].off_x4, trims[3].grad_x4); return 0; } static void madera_extcon_set_micd_clamp_mode(struct madera_extcon_info *info) { unsigned int clamp_ctrl_val; /* If the user has supplied a micd_clamp_mode, assume they know * what they are doing and just write it out */ if (info->pdata->micd_clamp_mode) { clamp_ctrl_val = info->pdata->micd_clamp_mode; } else if (info->pdata->jd_use_jd2) { if (info->pdata->jd_invert) clamp_ctrl_val = MADERA_MICD_CLAMP_MODE_JD1H_JD2H; else clamp_ctrl_val = MADERA_MICD_CLAMP_MODE_JD1L_JD2L; } else { if (info->pdata->jd_invert) clamp_ctrl_val = MADERA_MICD_CLAMP_MODE_JD1H; else clamp_ctrl_val = MADERA_MICD_CLAMP_MODE_JD1L; } regmap_update_bits(info->madera->regmap, MADERA_MICD_CLAMP_CONTROL, MADERA_MICD_CLAMP_MODE_MASK, clamp_ctrl_val); regmap_update_bits(info->madera->regmap, MADERA_INTERRUPT_DEBOUNCE_7, MADERA_MICD_CLAMP_DB, MADERA_MICD_CLAMP_DB); } static int madera_extcon_add_micd_levels(struct madera_extcon_info *info) { struct madera *madera = info->madera; int i, j; int ret = 0; BUILD_BUG_ON(ARRAY_SIZE(madera_micd_levels) < MADERA_NUM_MICD_BUTTON_LEVELS); /* Disable all buttons by default */ regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_2, MADERA_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 < MADERA_NUM_MICD_BUTTON_LEVELS; j++) if (madera_micd_levels[j] >= info->micd_ranges[i].max) break; if (j == MADERA_NUM_MICD_BUTTON_LEVELS) { dev_err(madera->dev, "Unsupported MICD level %d\n", info->micd_ranges[i].max); ret = -EINVAL; goto err_input; } dev_dbg(madera->dev, "%d ohms for MICD threshold %d\n", madera_micd_levels[j], i); madera_micd_set_level(madera, i, j); if (info->micd_ranges[i].key > 0) input_set_capability(info->input, EV_KEY, info->micd_ranges[i].key); /* Enable reporting of that range */ regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_2, 1 << i, 1 << i); } /* Set all the remaining keys to a maximum */ for (; i < MADERA_MAX_MICD_RANGE; i++) madera_micd_set_level(madera, i, 0x3f); err_input: return ret; } static int madera_extcon_init_micd_ranges(struct madera_extcon_info *info) { struct madera *madera = info->madera; const struct madera_accdet_pdata *pdata = info->pdata; struct madera_micd_range *ranges; int i; if (pdata->num_micd_ranges == 0) { info->micd_ranges = madera_micd_default_ranges; info->num_micd_ranges = ARRAY_SIZE(madera_micd_default_ranges) - 2; return 0; } if (pdata->num_micd_ranges > MADERA_MAX_MICD_RANGE) { dev_err(madera->dev, "Too many MICD ranges: %d\n", pdata->num_micd_ranges); return -EINVAL; } ranges = devm_kmalloc_array(madera->dev, pdata->num_micd_ranges, sizeof(struct madera_micd_range), GFP_KERNEL); if (!ranges) { dev_err(madera->dev, "Failed to kalloc micd ranges\n"); return -ENOMEM; } memcpy(ranges, pdata->micd_ranges, sizeof(struct madera_micd_range) * pdata->num_micd_ranges); info->micd_ranges = ranges; info->num_micd_ranges = pdata->num_micd_ranges; for (i = 0; i < info->num_micd_ranges - 1; i++) { if (info->micd_ranges[i].max > info->micd_ranges[i + 1].max) { dev_err(madera->dev, "MICD ranges must be sorted\n"); goto err_free; } } return 0; err_free: devm_kfree(madera->dev, ranges); return -EINVAL; } static void madera_extcon_xlate_pdata(struct madera_accdet_pdata *pdata) { int i; BUILD_BUG_ON(ARRAY_SIZE(pdata->hpd_pins) != ARRAY_SIZE(madera_default_hpd_pins)); /* translate from pdata format where 0=default and >0xFFFF means 0 */ for (i = 0; i < ARRAY_SIZE(pdata->hpd_pins); ++i) { if (pdata->hpd_pins[i] == 0) pdata->hpd_pins[i] = madera_default_hpd_pins[i]; else if (pdata->hpd_pins[i] > 0xFFFF) pdata->hpd_pins[i] = 0; } } static int madera_extcon_probe(struct platform_device *pdev) { struct madera *madera = dev_get_drvdata(pdev->dev.parent); struct madera_accdet_pdata *pdata = &madera->pdata.accdet[0]; struct madera_extcon_info *info; unsigned int debounce_val, analog_val; int jack_irq_fall, jack_irq_rise; int ret, mode; /* quick exit if Madera irqchip driver hasn't completed probe */ if (!madera->irq_dev) { dev_dbg(madera->dev, "irqchip driver not ready\n"); return -EPROBE_DEFER; } if (!madera->dapm || !madera->dapm->card) return -EPROBE_DEFER; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; info->pdata = pdata; info->madera = madera; info->dev = &pdev->dev; if (IS_ENABLED(CONFIG_OF)) { if (!dev_get_platdata(madera->dev)) { ret = madera_extcon_of_get_pdata(madera); if (ret < 0) return ret; } } else { madera_extcon_xlate_pdata(pdata); } madera_extcon_dump_pdata(madera); if (pdata->hpdet_short_circuit_imp < MADERA_HP_SHORT_IMPEDANCE_MIN) pdata->hpdet_short_circuit_imp = MADERA_HP_SHORT_IMPEDANCE_MIN; switch (madera->type) { case CS47L35: pdata->micd_force_micbias = true; info->hpdet_ranges = cs47l85_hpdet_ranges; info->num_hpdet_ranges = ARRAY_SIZE(cs47l85_hpdet_ranges); break; case CS47L85: case WM1840: info->hpdet_ranges = cs47l85_hpdet_ranges; info->num_hpdet_ranges = ARRAY_SIZE(cs47l85_hpdet_ranges); break; default: info->hpdet_ranges = madera_hpdet_ranges; info->num_hpdet_ranges = ARRAY_SIZE(madera_hpdet_ranges); break; } /* Set of_node to parent from the SPI device to allow * location regulator supplies */ pdev->dev.of_node = madera->dev->of_node; info->micvdd = devm_regulator_get(&pdev->dev, "MICVDD"); if (IS_ERR(info->micvdd)) { ret = PTR_ERR(info->micvdd); dev_err(madera->dev, "Failed to get MICVDD: %d\n", ret); return ret; } mutex_init(&info->lock); init_completion(&info->manual_mic_completion); wakeup_source_init(&info->detection_wake_lock, "madera-jack-detection"); INIT_DELAYED_WORK(&info->micd_detect_work, madera_micd_handler); INIT_DELAYED_WORK(&info->state_timeout_work, madera_jds_timeout_work); platform_set_drvdata(pdev, info); madera->extcon_info = info; if (pdata->jd_invert) info->last_jackdet = ~(MADERA_MICD_CLAMP_RISE_STS1 | MADERA_JD1_FALL_STS1); else info->last_jackdet = ~(MADERA_MICD_CLAMP_RISE_STS1 | MADERA_JD1_RISE_STS1); info->edev.name = "h2w"; ret = switch_dev_register(&info->edev); if (ret < 0) { dev_err(madera->dev, "extcon_dev_register() failed: %d\n", ret); goto err_wakelock; } info->input = devm_input_allocate_device(&pdev->dev); if (!info->input) { dev_err(madera->dev, "Can't allocate input dev\n"); ret = -ENOMEM; goto err_register; } info->input->name = "Headset"; info->input->phys = "madera/switch"; 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; } else { switch (madera->type) { case CS47L35: case CS47L85: case WM1840: info->micd_modes = cs47l85_micd_default_modes; info->micd_num_modes = ARRAY_SIZE(cs47l85_micd_default_modes); break; default: info->micd_modes = madera_micd_default_modes; info->micd_num_modes = ARRAY_SIZE(madera_micd_default_modes); break; } } if (madera->pdata.gpsw[0] > 0) regmap_update_bits(madera->regmap, MADERA_GP_SWITCH_1, MADERA_SW1_MODE_MASK, madera->pdata.gpsw[0] << MADERA_SW1_MODE_SHIFT); switch (madera->type) { case CS47L90: case CS47L91: case CS47L92: case CS47L93: if (madera->pdata.gpsw[1] > 0) regmap_update_bits(madera->regmap, MADERA_GP_SWITCH_1, MADERA_SW2_MODE_MASK, madera->pdata.gpsw[1] << MADERA_SW2_MODE_SHIFT); break; default: break; } if (info->pdata->micd_pol_gpio > 0) { if (info->micd_modes[0].gpio) mode = GPIOF_OUT_INIT_HIGH; else mode = GPIOF_OUT_INIT_LOW; ret = devm_gpio_request_one(&pdev->dev, info->pdata->micd_pol_gpio, mode, "MICD polarity"); if (ret) { dev_err(madera->dev, "Failed to request GPIO%d: %d\n", info->pdata->micd_pol_gpio, ret); goto err_register; } } if (info->pdata->micd_bias_start_time) regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_BIAS_STARTTIME_MASK, info->pdata->micd_bias_start_time << MADERA_MICD_BIAS_STARTTIME_SHIFT); if (info->pdata->micd_rate) regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_RATE_MASK, info->pdata->micd_rate << MADERA_MICD_RATE_SHIFT); if (info->pdata->micd_dbtime) regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, MADERA_MICD_DBTIME_MASK, info->pdata->micd_dbtime << MADERA_MICD_DBTIME_SHIFT); ret = madera_extcon_init_micd_ranges(info); if (ret) goto err_input; ret = madera_extcon_add_micd_levels(info); if (ret) goto err_input; madera_extcon_set_micd_clamp_mode(info); madera_extcon_set_mode(info, 0); /* Invalidate the tuning level so that the first detection * will always apply a tuning */ info->hp_tuning_level = MADERA_HP_TUNING_INVALID; pm_runtime_enable(&pdev->dev); pm_runtime_idle(&pdev->dev); pm_runtime_get_sync(&pdev->dev); madera_extcon_read_calibration(info); if (info->hpdet_trims) { switch (madera->type) { case CS47L35: case CS47L85: case WM1840: /* set for accurate HP impedance detection */ regmap_update_bits(madera->regmap, MADERA_ACCESSORY_DETECT_MODE_1, MADERA_ACCDET_POLARITY_INV_ENA_MASK, 1 << MADERA_ACCDET_POLARITY_INV_ENA_SHIFT); break; default: break; } } if (info->pdata->jd_use_jd2) { jack_irq_rise = MADERA_IRQ_MICD_CLAMP_RISE; jack_irq_fall = MADERA_IRQ_MICD_CLAMP_FALL; } else { jack_irq_rise = MADERA_IRQ_JD1_RISE; jack_irq_fall = MADERA_IRQ_JD1_FALL; } ret = madera_request_irq(madera, jack_irq_rise, "JACKDET rise", madera_jackdet, info); if (ret) { dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n", ret); goto err_input; } ret = madera_set_irq_wake(madera, jack_irq_rise, 1); if (ret) { dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n", ret); goto err_rise; } ret = madera_request_irq(madera, jack_irq_fall, "JACKDET fall", madera_jackdet, info); if (ret) { dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret); goto err_rise_wake; } ret = madera_set_irq_wake(madera, jack_irq_fall, 1); if (ret) { dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n", ret); goto err_fall; } ret = madera_request_irq(madera, MADERA_IRQ_MICDET1, "MICDET", madera_micdet, info); if (ret) { dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret); goto err_fall_wake; } ret = madera_request_irq(madera, MADERA_IRQ_HPDET, "HPDET", madera_hpdet_handler, info); if (ret) { dev_err(&pdev->dev, "Failed to get HPDET IRQ: %d\n", ret); goto err_micdet; } if (info->pdata->jd_use_jd2) { debounce_val = MADERA_JD1_DB | MADERA_JD2_DB; analog_val = MADERA_JD1_ENA | MADERA_JD2_ENA; } else { debounce_val = MADERA_JD1_DB; analog_val = MADERA_JD1_ENA; } regmap_update_bits(madera->regmap, MADERA_INTERRUPT_DEBOUNCE_7, debounce_val, debounce_val); regmap_update_bits(madera->regmap, MADERA_JACK_DETECT_ANALOGUE, analog_val, analog_val); ret = regulator_allow_bypass(info->micvdd, true); if (ret) dev_warn(madera->dev, "Failed to set MICVDD to bypass: %d\n", ret); pm_runtime_put(&pdev->dev); ret = input_register_device(info->input); if (ret) { dev_err(&pdev->dev, "Can't register input device: %d\n", ret); goto err_hpdet; } ret = device_create_file(&pdev->dev, &dev_attr_hp1_impedance); if (ret) dev_warn(&pdev->dev, "Failed to create sysfs node for hp_impedance %d\n", ret); return 0; err_hpdet: madera_free_irq(madera, MADERA_IRQ_HPDET, info); err_micdet: madera_free_irq(madera, MADERA_IRQ_MICDET1, info); err_fall_wake: madera_set_irq_wake(madera, jack_irq_fall, 0); err_fall: madera_free_irq(madera, jack_irq_fall, info); err_rise_wake: madera_set_irq_wake(madera, jack_irq_rise, 0); err_rise: madera_free_irq(madera, jack_irq_rise, info); err_input: err_register: pm_runtime_disable(&pdev->dev); switch_dev_unregister(&info->edev); err_wakelock: wakeup_source_trash(&info->detection_wake_lock); return ret; } static int madera_extcon_remove(struct platform_device *pdev) { struct madera_extcon_info *info = platform_get_drvdata(pdev); struct madera *madera = info->madera; int jack_irq_rise, jack_irq_fall; pm_runtime_disable(&pdev->dev); regmap_update_bits(madera->regmap, MADERA_MICD_CLAMP_CONTROL, MADERA_MICD_CLAMP_MODE_MASK, 0); if (info->pdata->jd_use_jd2) { jack_irq_rise = MADERA_IRQ_MICD_CLAMP_RISE; jack_irq_fall = MADERA_IRQ_MICD_CLAMP_FALL; } else { jack_irq_rise = MADERA_IRQ_JD1_RISE; jack_irq_fall = MADERA_IRQ_JD1_FALL; } madera_set_irq_wake(madera, jack_irq_rise, 0); madera_set_irq_wake(madera, jack_irq_fall, 0); madera_free_irq(madera, MADERA_IRQ_HPDET, info); madera_free_irq(madera, MADERA_IRQ_MICDET1, info); madera_free_irq(madera, jack_irq_rise, info); madera_free_irq(madera, jack_irq_fall, info); regmap_update_bits(madera->regmap, MADERA_JACK_DETECT_ANALOGUE, MADERA_JD1_ENA | MADERA_JD2_ENA, 0); device_remove_file(&pdev->dev, &dev_attr_hp1_impedance); switch_dev_unregister(&info->edev); wakeup_source_trash(&info->detection_wake_lock); kfree(info->hpdet_trims); return 0; } static struct platform_driver madera_extcon_driver = { .driver = { .name = "madera-extcon", .owner = THIS_MODULE, }, .probe = madera_extcon_probe, .remove = madera_extcon_remove, }; module_platform_driver(madera_extcon_driver); MODULE_DESCRIPTION("Madera switch driver"); MODULE_AUTHOR("Charles Keepax "); MODULE_AUTHOR("Richard Fitzgerald "); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:switch-madera");