3426 lines
82 KiB
C
Executable File
3426 lines
82 KiB
C
Executable File
/*
|
|
* 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 <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/input.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/switch.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/math64.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
#include <linux/extcon/extcon-madera.h>
|
|
#include <linux/extcon/extcon-madera-pdata.h>
|
|
#include <dt-bindings/extcon/extcon-madera.h>
|
|
|
|
#include <linux/mfd/madera/core.h>
|
|
#include <linux/mfd/madera/pdata.h>
|
|
#include <linux/mfd/madera/registers.h>
|
|
|
|
#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 <ckeepax@opensource.wolfsonmicro.com>");
|
|
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.wolfsonmicro.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:switch-madera");
|