| /* |
| * extcon-madera.c - Extcon driver for Cirrus Logic Madera codecs |
| * |
| * Copyright 2015-2017 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/device.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/extcon.h> |
| #include <linux/gpio.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/math64.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/property.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <linux/regulator/consumer.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_OHM 10000 |
| #define MADERA_HPDET_MAX_HOHM (MADERA_HPDET_MAX_OHM * 100) |
| #define MADERA_HP_SHORT_IMPEDANCE_MIN 4 |
| |
| #define MADERA_HPDET_DEBOUNCE_MS 500 |
| #define MADERA_DEFAULT_MICD_TIMEOUT_MS 2000 |
| |
| #define MADERA_HPDONE_PROBE_INTERVAL_MS 20 |
| #define MADERA_HPDONE_PROBE_COUNT 15 |
| |
| #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 |
| |
| static const unsigned int madera_cable[] = { |
| EXTCON_MECHANICAL, |
| EXTCON_JACK_MICROPHONE, |
| EXTCON_JACK_HEADPHONE, |
| EXTCON_NONE, |
| }; |
| |
| 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_HPOUTFB2 }, |
| { 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 = 70, .key = KEY_MEDIA }, |
| { .max = 186, .key = KEY_VOICECOMMAND }, |
| { .max = 295, .key = KEY_VOLUMEUP }, |
| { .max = 681, .key = KEY_VOLUMEDOWN }, |
| }; |
| |
| /* The number of levels in madera_micd_levels valid for button thresholds */ |
| #define MADERA_NUM_MICD_BUTTON_LEVELS 64 |
| |
| /* ohms for each micd level */ |
| 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, |
| }; |
| |
| /* |
| * HP calibration data |
| * See the datasheet for the meanings of the constants and their values |
| */ |
| struct madera_hpdet_calibration_data { |
| int min; /* ohms */ |
| int max; /* ohms */ |
| 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 cs47l15_hpdet_ranges[] = { |
| {}, |
| { 33, 123, 1000000, -4300, 7975, 69600000, 382800, 33350000, |
| 500000}, |
| { 123, 1033, 9633000, -79500, 7300, 62900000, 283050, 33350000, |
| 500000}, |
| { 1033, 10033, 100684000, -949400, 7300, 63200000, 284400, 33350000, |
| 500000}, |
| }; |
| |
| |
| 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}, |
| }; |
| |
| static const struct madera_hpdet_calibration_data cs47l92_hpdet_ranges[] = { |
| { 4, 30, 1007000, -7200, 4005, 69300000, 381150, 600000, 500000}, |
| { 8, 100, 1007000, -7200, 7975, 69600000, 382800, 600000, 500000}, |
| { 100, 1000, 9744000, -79500, 7300, 62900000, 345950, 600000, 500000}, |
| { 1000, 10000, 100684000, -949400, 7300, 63200000, 347600, 600000, 500000}, |
| }; |
| |
| struct madera_hp_tuning { |
| int max_hohm; |
| const struct reg_sequence *patch; |
| int patch_len; |
| }; |
| |
| static const struct reg_sequence cs47l15_low_impedance_patch[] = { |
| { 0x460, 0x0C00 }, |
| { 0x461, 0xCB59 }, |
| { 0x462, 0x0C00 }, |
| { 0x463, 0x6037 }, |
| { 0x464, 0x0C01 }, |
| { 0x465, 0x2D86 }, |
| { 0x466, 0x0801 }, |
| { 0x467, 0x264E }, |
| { 0x468, 0x0801 }, |
| { 0x469, 0x1E6D }, |
| { 0x46A, 0x0802 }, |
| { 0x46B, 0x199A }, |
| { 0x46C, 0x0802 }, |
| { 0x46D, 0x1220 }, |
| { 0x46E, 0x0802 }, |
| { 0x46F, 0x0E65 }, |
| { 0x470, 0x0806 }, |
| { 0x471, 0x0A31 }, |
| { 0x472, 0x080E }, |
| { 0x473, 0x040F }, |
| { 0x474, 0x080E }, |
| { 0x475, 0x0339 }, |
| { 0x476, 0x080E }, |
| { 0x477, 0x028F }, |
| { 0x478, 0x080E }, |
| { 0x479, 0x0209 }, |
| { 0x47A, 0x080E }, |
| { 0x47B, 0x00CF }, |
| { 0x47C, 0x080E }, |
| { 0x47D, 0x0001 }, |
| { 0x47E, 0x081F }, |
| }; |
| |
| static const struct reg_sequence cs47l15_normal_impedance_patch[] = { |
| { 0x460, 0x0C00 }, |
| { 0x461, 0xCB59 }, |
| { 0x462, 0x0C00 }, |
| { 0x463, 0xB53C }, |
| { 0x464, 0x0C01 }, |
| { 0x465, 0x4827 }, |
| { 0x466, 0x0801 }, |
| { 0x467, 0x3950 }, |
| { 0x468, 0x0801 }, |
| { 0x469, 0x264E }, |
| { 0x46A, 0x0802 }, |
| { 0x46B, 0x1E6D }, |
| { 0x46C, 0x0802 }, |
| { 0x46D, 0x199A }, |
| { 0x46E, 0x0802 }, |
| { 0x46F, 0x1456 }, |
| { 0x470, 0x0806 }, |
| { 0x471, 0x1220 }, |
| { 0x472, 0x080E }, |
| { 0x473, 0x040F }, |
| { 0x474, 0x080E }, |
| { 0x475, 0x0339 }, |
| { 0x476, 0x080E }, |
| { 0x477, 0x028F }, |
| { 0x478, 0x080E }, |
| { 0x479, 0x0209 }, |
| { 0x47A, 0x080E }, |
| { 0x47B, 0x00CF }, |
| { 0x47C, 0x080E }, |
| { 0x47D, 0x0001 }, |
| { 0x47E, 0x081F }, |
| }; |
| |
| static const struct reg_sequence cs47l15_high_impedance_patch[] = { |
| { 0x460, 0x0C00 }, |
| { 0x461, 0xCB59 }, |
| { 0x462, 0x0C00 }, |
| { 0x463, 0xB53C }, |
| { 0x464, 0x0C01 }, |
| { 0x465, 0x6037 }, |
| { 0x466, 0x0801 }, |
| { 0x467, 0x4827 }, |
| { 0x468, 0x0801 }, |
| { 0x469, 0x3950 }, |
| { 0x46A, 0x0802 }, |
| { 0x46B, 0x264E }, |
| { 0x46C, 0x0802 }, |
| { 0x46D, 0x1E6D }, |
| { 0x46E, 0x0802 }, |
| { 0x46F, 0x199A }, |
| { 0x470, 0x0806 }, |
| { 0x471, 0x1220 }, |
| { 0x472, 0x080E }, |
| { 0x473, 0x040F }, |
| { 0x474, 0x080E }, |
| { 0x475, 0x0339 }, |
| { 0x476, 0x080E }, |
| { 0x477, 0x028F }, |
| { 0x478, 0x080E }, |
| { 0x479, 0x0209 }, |
| { 0x47A, 0x080E }, |
| { 0x47B, 0x00CF }, |
| { 0x47C, 0x080E }, |
| { 0x47D, 0x0001 }, |
| { 0x47E, 0x081F }, |
| }; |
| |
| static const struct madera_hp_tuning cs47l15_hp_tuning[] = { |
| { |
| 1600, |
| cs47l15_low_impedance_patch, |
| ARRAY_SIZE(cs47l15_low_impedance_patch), |
| }, |
| { |
| 3200, |
| cs47l15_normal_impedance_patch, |
| ARRAY_SIZE(cs47l15_normal_impedance_patch), |
| }, |
| { |
| MADERA_HPDET_MAX_HOHM, |
| cs47l15_high_impedance_patch, |
| ARRAY_SIZE(cs47l15_high_impedance_patch), |
| }, |
| }; |
| |
| 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[] = { |
| { |
| 1300, |
| cs47l35_low_impedance_patch, |
| ARRAY_SIZE(cs47l35_low_impedance_patch), |
| }, |
| { |
| MADERA_HPDET_MAX_HOHM, |
| 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[] = { |
| { |
| 1300, |
| cs47l85_low_impedance_patch, |
| ARRAY_SIZE(cs47l85_low_impedance_patch), |
| }, |
| { |
| MADERA_HPDET_MAX_HOHM, |
| 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[] = { |
| { |
| 1400, |
| cs47l90_low_impedance_patch, |
| ARRAY_SIZE(cs47l90_low_impedance_patch), |
| }, |
| { 2400, |
| cs47l90_normal_impedance_patch, |
| ARRAY_SIZE(cs47l90_normal_impedance_patch), |
| }, |
| { MADERA_HPDET_MAX_HOHM, |
| 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, 0x0C21 }, |
| { 0x467, 0x804E }, |
| { 0x468, 0x0C21 }, |
| { 0x469, 0x725A }, |
| { 0x46A, 0x0C21 }, |
| { 0x46B, 0x5AD5 }, |
| { 0x46C, 0x0C21 }, |
| { 0x46D, 0x50F4 }, |
| { 0x46E, 0x0C21 }, |
| { 0x46F, 0x4827 }, |
| { 0x470, 0x0C21 }, |
| { 0x471, 0x404E }, |
| { 0x472, 0x0020 }, |
| { 0x473, 0x3950 }, |
| { 0x474, 0x0021 }, |
| { 0x475, 0x3314 }, |
| { 0x476, 0x0021 }, |
| { 0x477, 0x2893 }, |
| { 0x478, 0x0021 }, |
| { 0x479, 0x2429 }, |
| { 0x47A, 0x0821 }, |
| { 0x47B, 0x203A }, |
| { 0x47C, 0x0420 }, |
| { 0x47D, 0x1027 }, |
| { 0x47E, 0x0421 }, |
| }; |
| |
| static const struct reg_sequence cs47l92_normal_impedance_patch[] = { |
| { 0x460, 0x0C21 }, |
| { 0x461, 0xB53C }, |
| { 0x462, 0x0C21 }, |
| { 0x463, 0xA186 }, |
| { 0x464, 0x0C21 }, |
| { 0x465, 0x8FF6 }, |
| { 0x466, 0x0C21 }, |
| { 0x467, 0x804E }, |
| { 0x468, 0x0C21 }, |
| { 0x469, 0x725A }, |
| { 0x46A, 0x0C21 }, |
| { 0x46B, 0x65EA }, |
| { 0x46C, 0x0021 }, |
| { 0x46D, 0x5AD5 }, |
| { 0x46E, 0x0021 }, |
| { 0x46F, 0x50F4 }, |
| { 0x470, 0x0021 }, |
| { 0x471, 0x4827 }, |
| { 0x472, 0x0021 }, |
| { 0x473, 0x404E }, |
| { 0x474, 0x0021 }, |
| { 0x475, 0x3950 }, |
| { 0x476, 0x0821 }, |
| { 0x477, 0x3314 }, |
| { 0x478, 0x0420 }, |
| { 0x479, 0x2D86 }, |
| { 0x47A, 0x0421 }, |
| { 0x47B, 0x2893 }, |
| { 0x47C, 0x0421 }, |
| { 0x47D, 0x203A }, |
| { 0x47E, 0x0421 }, |
| }; |
| |
| static const struct reg_sequence cs47l92_high_impedance_patch[] = { |
| { 0x460, 0x0C21 }, |
| { 0x461, 0xB53C }, |
| { 0x462, 0x0C21 }, |
| { 0x463, 0xA186 }, |
| { 0x464, 0x0C21 }, |
| { 0x465, 0x8FF6 }, |
| { 0x466, 0x0021 }, |
| { 0x467, 0x804E }, |
| { 0x468, 0x0021 }, |
| { 0x469, 0x725A }, |
| { 0x46A, 0x0021 }, |
| { 0x46B, 0x65EA }, |
| { 0x46C, 0x0021 }, |
| { 0x46D, 0x5AD5 }, |
| { 0x46E, 0x0021 }, |
| { 0x46F, 0x50F4 }, |
| { 0x470, 0x0021 }, |
| { 0x471, 0x4827 }, |
| { 0x472, 0x0821 }, |
| { 0x473, 0x404E }, |
| { 0x474, 0x0821 }, |
| { 0x475, 0x3950 }, |
| { 0x476, 0x0420 }, |
| { 0x477, 0x3314 }, |
| { 0x478, 0x0421 }, |
| { 0x479, 0x2D86 }, |
| { 0x47A, 0x0421 }, |
| { 0x47B, 0x2893 }, |
| { 0x47C, 0x0421 }, |
| { 0x47D, 0x203A }, |
| { 0x47E, 0x0421 }, |
| }; |
| |
| static const struct madera_hp_tuning cs47l92_hp_tuning[] = { |
| { |
| 1400, |
| cs47l92_low_impedance_patch, |
| ARRAY_SIZE(cs47l92_low_impedance_patch), |
| }, |
| { 2400, |
| cs47l92_normal_impedance_patch, |
| ARRAY_SIZE(cs47l92_normal_impedance_patch), |
| }, |
| { MADERA_HPDET_MAX_HOHM, |
| 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 = platform_get_drvdata(pdev); |
| |
| return scnprintf(buf, PAGE_SIZE, "%d\n", |
| info->madera->hp_impedance_x100[0]); |
| } |
| |
| static DEVICE_ATTR(hp1_impedance, 0444, madera_extcon_show, NULL); |
| |
| static void madera_micd_manual_timeout(struct madera_extcon *info) |
| { |
| dev_dbg(info->madera->dev, "Manual MICD timed out\n"); |
| |
| info->mic_impedance = -EINVAL; |
| madera_jds_set_state(info, info->old_state); |
| complete(&info->manual_mic_completion); |
| } |
| |
| static int madera_micd_manual_reading(struct madera_extcon *info, int val) |
| { |
| info->mic_impedance = val; |
| madera_jds_set_state(info, info->old_state); |
| complete(&info->manual_mic_completion); |
| |
| return val; |
| } |
| |
| static const struct madera_jd_state madera_micd_manual = { |
| .mode = MADERA_ACCDET_MODE_ADC, |
| .start = madera_micd_mic_start, |
| .reading = madera_micd_manual_reading, |
| .stop = madera_micd_mic_stop, |
| |
| .timeout_ms = madera_micd_mic_timeout_ms, |
| .timeout = madera_micd_manual_timeout, |
| }; |
| |
| int madera_extcon_manual_mic_reading(struct madera_extcon *info) |
| { |
| mutex_lock(&info->lock); |
| info->old_state = info->state; |
| madera_jds_set_state(info, &madera_micd_manual); |
| mutex_unlock(&info->lock); |
| |
| wait_for_completion(&info->manual_mic_completion); |
| |
| return madera_hohm_to_ohm(info->mic_impedance); |
| } |
| EXPORT_SYMBOL_GPL(madera_extcon_manual_mic_reading); |
| |
| static ssize_t madera_extcon_mic_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev = to_platform_device(dev); |
| struct madera_extcon *info = platform_get_drvdata(pdev); |
| int mic_impedance = madera_extcon_manual_mic_reading(info); |
| |
| return scnprintf(buf, PAGE_SIZE, "%d\n", mic_impedance); |
| } |
| static DEVICE_ATTR(mic_impedance, S_IRUGO, madera_extcon_mic_show, NULL); |
| |
| void madera_extcon_report(struct madera_extcon *info, int which, bool attached) |
| { |
| int ret; |
| |
| dev_info(info->dev, "Extcon report: %d is %s\n", |
| which, attached ? "attached" : "removed"); |
| |
| ret = extcon_set_state_sync(info->edev, which, attached); |
| if (ret != 0) |
| dev_warn(info->dev, "Failed to report cable state: %d\n", ret); |
| |
| if (IS_ENABLED(CONFIG_EXTCON_MADERA_INPUT_EVENT)) { |
| switch (which) { |
| case EXTCON_MECHANICAL: |
| input_report_switch(info->input, |
| SW_JACK_PHYSICAL_INSERT, |
| attached); |
| break; |
| case EXTCON_JACK_HEADPHONE: |
| input_report_switch(info->input, |
| SW_HEADPHONE_INSERT, |
| attached); |
| break; |
| case EXTCON_JACK_MICROPHONE: |
| input_report_switch(info->input, |
| SW_MICROPHONE_INSERT, |
| attached); |
| break; |
| default: |
| return; |
| } |
| |
| input_sync(info->input); |
| } |
| } |
| EXPORT_SYMBOL_GPL(madera_extcon_report); |
| |
| static |
| enum madera_accdet_mode madera_jds_get_mode(struct madera_extcon *info) |
| { |
| if (info->state) |
| return info->state->mode; |
| else |
| return MADERA_ACCDET_MODE_INVALID; |
| } |
| |
| int madera_jds_set_state(struct madera_extcon *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, 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) |
| { |
| return cancel_delayed_work_sync(&info->state_timeout_work); |
| } |
| |
| static void madera_jds_start_timeout(struct madera_extcon *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 = |
| container_of(work, struct madera_extcon, |
| state_timeout_work.work); |
| |
| mutex_lock(&info->lock); |
| |
| if (!info->state) { |
| dev_warn(info->dev, "Spurious timeout in idle state\n"); |
| } else if (!info->state->timeout) { |
| dev_warn(info->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, 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 CS47L15: |
| 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 = MADERA_EDRE_OUT1L_MANUAL | |
| MADERA_EDRE_OUT1R_MANUAL; |
| } 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->out_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(info->dev, |
| "Failed to disable headphone outputs: %d\n", |
| ret); |
| } |
| |
| if (edre_reg && !ep_sel) { |
| ret = regmap_update_bits(madera->regmap, edre_reg, |
| MADERA_EDRE_OUT1L_MANUAL_MASK | |
| MADERA_EDRE_OUT1R_MANUAL_MASK, |
| edre_val); |
| if (ret) |
| dev_warn(info->dev, |
| "Failed to set EDRE Manual: %d\n", ret); |
| } |
| |
| dev_info(info->dev, "%s clamp mask=0x%x val=0x%x\n", |
| clamp ? "Setting" : "Clearing", 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(info->dev, "Failed to do clamp: %d\n", ret); |
| |
| ret = regmap_update_bits(madera->regmap, |
| MADERA_HP_CTRL_1R, mask, val); |
| if (ret) |
| dev_warn(info->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(info->dev, "Failed to do clamp: %d\n", ret); |
| break; |
| } |
| |
| /* Restore the desired state while not doing the clamp */ |
| if (!clamp) { |
| madera->out_shorted[0] = (madera->hp_impedance_x100[0] <= |
| info->hpdet_short_x100); |
| |
| if (!madera->out_shorted[0] && !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(info->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) |
| { |
| struct madera *madera = info->madera; |
| unsigned int bias = info->micd_modes[info->micd_mode].bias; |
| |
| switch (madera->type) { |
| case CS47L15: |
| switch (bias) { |
| case 0: |
| case 1: |
| case 2: |
| return "MICBIAS1"; |
| default: |
| return "MICVDD"; |
| } |
| break; |
| 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; |
| case CS47L92: |
| case CS47L93: |
| switch (bias) { |
| case 0: |
| case 1: |
| case 2: |
| case 3: |
| return "MICBIAS1"; |
| case 4: |
| case 5: |
| return "MICBIAS2"; |
| default: |
| return "MICVDD"; |
| } |
| break; |
| default: |
| return NULL; |
| } |
| } |
| |
| static const char *madera_extcon_get_micbias(struct madera_extcon *info) |
| { |
| struct madera *madera = info->madera; |
| unsigned int bias = info->micd_modes[info->micd_mode].bias; |
| |
| switch (madera->type) { |
| case CS47L15: |
| switch (bias) { |
| case 0: |
| return "MICBIAS1A"; |
| case 1: |
| return "MICBIAS1B"; |
| case 2: |
| return "MICBIAS1C"; |
| default: |
| return "MICVDD"; |
| } |
| 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"; |
| } |
| case CS47L92: |
| case CS47L93: |
| 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"; |
| } |
| default: |
| return NULL; |
| } |
| } |
| |
| static void madera_extcon_enable_micbias_pin(struct madera_extcon *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(info->dev, "Failed to enable %s: %d\n", widget, ret); |
| |
| snd_soc_dapm_sync(dapm); |
| |
| dev_dbg(info->dev, "Enabled %s\n", widget); |
| } |
| |
| static void madera_extcon_disable_micbias_pin(struct madera_extcon *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(info->dev, "Failed to enable %s: %d\n", widget, ret); |
| |
| snd_soc_dapm_sync(dapm); |
| |
| dev_dbg(info->dev, "Disabled %s\n", widget); |
| } |
| |
| static void madera_extcon_set_micd_bias(struct madera_extcon *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); |
| WARN_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) |
| { |
| 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) |
| { |
| 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, int mode) |
| { |
| struct madera *madera = info->madera; |
| |
| dev_info(info->dev, |
| "set mic_mode[%d] src=0x%x gnd=0x%x bias=0x%x gpio=%d hp_gnd=%d\n", |
| mode, info->micd_modes[mode].src, info->micd_modes[mode].gnd, |
| info->micd_modes[mode].bias, info->micd_modes[mode].gpio, |
| info->micd_modes[mode].hp_gnd); |
| |
| if (info->micd_pol_gpio) |
| gpiod_set_value_cansleep(info->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_HEADPHONE_DETECT_0, |
| MADERA_HPD_GND_SEL_MASK, |
| info->micd_modes[mode].gnd << |
| MADERA_HPD_GND_SEL_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].hp_gnd << |
| MADERA_HP1_GND_SEL_SHIFT); |
| break; |
| } |
| |
| info->micd_mode = mode; |
| } |
| |
| static void madera_extcon_next_mode(struct madera_extcon *info) |
| { |
| int old_mode = info->micd_mode; |
| int new_mode; |
| bool change_bias = false; |
| |
| new_mode = (old_mode + 1) % info->num_micd_modes; |
| |
| dev_dbg(info->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) |
| { |
| 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(info->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(info->dev, "Failed to read MICDET_ADCVAL: %d\n", ret); |
| return ret; |
| } |
| |
| dev_info(info->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) |
| { |
| 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(info->dev, |
| "Failed to read MICDET: %d\n", ret); |
| return ret; |
| } |
| |
| dev_info(info->dev, "MICDET: 0x%x\n", val); |
| |
| if (!(val & MADERA_MICD_VALID)) { |
| dev_warn(info->dev, |
| "Microphone detection state invalid\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (i == 10 && !(val & MADERA_MICD_LVL_0_TO_8)) { |
| dev_warn(info->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, |
| bool present, |
| unsigned int impedance) |
| { |
| struct madera_micdet_notify_data data; |
| |
| data.present = present; |
| data.impedance_x100 = madera_ohm_to_hohm(impedance); |
| data.out_num = info->pdata->output; |
| |
| madera_call_notifiers(info->madera, MADERA_NOTIFY_MICDET, &data); |
| } |
| |
| static int madera_hpdet_calc_calibration(const struct madera_extcon *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_HOHM; |
| |
| val = calib->C0 + ((calib->C1 * off_x4) / 4); |
| val *= 1000000; |
| |
| val = div_s64(val, n); |
| val -= calib->C5; |
| |
| /* Round up and divide to get hundredths of an ohm */ |
| val += 500000; |
| val = div_s64(val, 10000); |
| |
| if (val < 0) |
| return 0; |
| else if (val > MADERA_HPDET_MAX_HOHM) |
| return MADERA_HPDET_MAX_HOHM; |
| |
| return (int)val; |
| } |
| |
| static int madera_hpdet_calibrate(struct madera_extcon *info, |
| unsigned int range, unsigned int *ohms_x100) |
| { |
| 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(info->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(info->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->dev, |
| "hpdet_d calib range %d dac %d\n", range, dacval); |
| |
| *ohms_x100 = 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) |
| { |
| struct madera *madera = info->madera; |
| unsigned int val, range, sense_pin, ohms_x100; |
| int ret; |
| bool is_jdx_micdetx_pin = false; |
| int hpdet_ext_res_x100; |
| |
| ret = regmap_read(madera->regmap, MADERA_HEADPHONE_DETECT_2, &val); |
| if (ret) { |
| dev_err(info->dev, "Failed to read HPDET status: %d\n", ret); |
| return ret; |
| } |
| |
| dev_info(info->dev, "HPDET read: 0x%x\n", val); |
| |
| if (!(val & MADERA_HP_DONE_MASK)) { |
| dev_warn(info->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(info->dev, "is_jdx_micdetx_pin\n"); |
| is_jdx_micdetx_pin = true; |
| } |
| break; |
| } |
| |
| val &= MADERA_HP_LVL_MASK; |
| /* The value is in 0.5 ohm increments, get it in hundredths */ |
| ohms_x100 = val * 50; |
| |
| 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 / 2) >= info->hpdet_ranges[range].max)) { |
| range++; |
| dev_dbg(info->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, &ohms_x100); |
| if (ret) |
| return ret; |
| } else { |
| /* Use uncalibrated reading */ |
| if (range && ((val / 2) < info->hpdet_ranges[range].min)) { |
| dev_dbg(info->dev, |
| "Reporting range boundary %d\n", |
| info->hpdet_ranges[range].min); |
| ohms_x100 = |
| madera_ohm_to_hohm(info->hpdet_ranges[range].min); |
| } |
| } |
| |
| hpdet_ext_res_x100 = info->pdata->hpdet_ext_res_x100; |
| if (hpdet_ext_res_x100) { |
| if (hpdet_ext_res_x100 >= ohms_x100) { |
| dev_dbg(info->dev, |
| "External resistor (%d.%02d) >= measurement (%d.00)\n", |
| hpdet_ext_res_x100 / 100, |
| hpdet_ext_res_x100 % 100, |
| val); |
| ohms_x100 = 0; /* treat as a short */ |
| } else { |
| dev_dbg(info->dev, |
| "Compensating for external %d.%02d ohm resistor\n", |
| hpdet_ext_res_x100 / 100, |
| hpdet_ext_res_x100 % 100); |
| |
| ohms_x100 -= hpdet_ext_res_x100; |
| } |
| } |
| |
| done: |
| dev_dbg(info->dev, "HP impedance %d.%02d ohms\n", |
| ohms_x100 / 100, ohms_x100 % 100); |
| |
| return (int)ohms_x100; |
| } |
| |
| static void madera_hs_mic_control(struct madera_extcon *info, int state) |
| { |
| struct madera *madera = info->madera; |
| unsigned int addr = MADERA_ADC_DIGITAL_VOLUME_1L; |
| unsigned int val, in_bit; |
| int ret; |
| |
| if (!info->pdata->hs_mic) |
| return; |
| |
| addr += (info->pdata->hs_mic - 1) * 4; |
| |
| switch (state) { |
| case MADERA_MIC_MUTE: |
| dev_dbg(info->dev, "Mute headset mic: 0x%04x\n", addr); |
| snd_soc_dapm_mutex_lock(madera->dapm); |
| regmap_update_bits(madera->regmap, |
| addr, |
| MADERA_IN1L_MUTE_MASK, |
| MADERA_MIC_MUTE << MADERA_IN1L_MUTE_SHIFT); |
| madera->hs_mic_muted = true; |
| snd_soc_dapm_mutex_unlock(madera->dapm); |
| break; |
| case MADERA_MIC_UNMUTE: |
| dev_dbg(info->dev, "Unmute headset mic: 0x%04x\n", addr); |
| |
| in_bit = 1 << ((info->pdata->hs_mic - 1) ^ 1); |
| |
| snd_soc_dapm_mutex_lock(madera->dapm); |
| |
| ret = regmap_read(madera->regmap, MADERA_INPUT_ENABLES, &val); |
| if (ret) |
| dev_err(info->dev, |
| "Failed to read input status: %d\n", ret); |
| |
| madera->hs_mic_muted = false; |
| |
| if (!ret && (in_bit & val)) |
| regmap_update_bits(madera->regmap, |
| addr, |
| MADERA_IN1L_MUTE_MASK, |
| MADERA_MIC_UNMUTE << |
| MADERA_IN1L_MUTE_SHIFT); |
| |
| snd_soc_dapm_mutex_unlock(madera->dapm); |
| break; |
| default: |
| dev_err(info->dev, |
| "Unknown headset mic control state: %d\n", state); |
| return; |
| } |
| |
| } |
| |
| static int madera_tune_headphone(struct madera_extcon *info, int reading) |
| { |
| struct madera *madera = info->madera; |
| const struct madera_hp_tuning *tuning; |
| int n_tunings; |
| int i, ret; |
| |
| switch (madera->type) { |
| case CS47L15: |
| tuning = cs47l15_hp_tuning; |
| n_tunings = ARRAY_SIZE(cs47l15_hp_tuning); |
| break; |
| 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->hpdet_short_x100) { |
| /* Headphones are always off here so just mark them */ |
| dev_warn(info->dev, "Possible HP short, disabling\n"); |
| return 0; |
| } |
| |
| if (reading == MADERA_HP_Z_OPEN) { |
| if (info->hp_tuning_level == 1) |
| return 0; |
| |
| dev_dbg(info->dev, "No jack: Setting tuning level 1\n"); |
| |
| info->hp_tuning_level = 1; |
| |
| ret = regmap_multi_reg_write(madera->regmap, |
| tuning[1].patch, |
| tuning[1].patch_len); |
| return ret; |
| } |
| |
| /* |
| * 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_hohm) |
| break; |
| } |
| |
| if (info->hp_tuning_level != i) { |
| dev_dbg(info->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(info->dev, |
| "Failed to apply HP tuning %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void madera_set_headphone_imp(struct madera_extcon *info, int ohms_x100) |
| { |
| struct madera *madera = info->madera; |
| struct madera_hpdet_notify_data data; |
| |
| madera->hp_impedance_x100[0] = ohms_x100; |
| |
| data.impedance_x100 = ohms_x100; |
| madera_call_notifiers(madera, MADERA_NOTIFY_HPDET, &data); |
| |
| madera_tune_headphone(info, ohms_x100); |
| } |
| EXPORT_SYMBOL_GPL(madera_set_headphone_imp); |
| |
| static void madera_hpdet_start_micd(struct madera_extcon *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) |
| { |
| 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); |
| |
| usleep_range(100, 400); |
| |
| /* 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) |
| { |
| struct madera *madera = info->madera; |
| int ret; |
| unsigned int hpd_sense, hpd_clamp, val, hpd_gnd; |
| |
| dev_dbg(info->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(info->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_MIC_DETECT_1_CONTROL_0, |
| MADERA_MICD1_GND_MASK, |
| hpd_gnd << MADERA_MICD1_GND_SHIFT); |
| if (ret) { |
| dev_err(madera->dev, "Failed to set MICD_GND: %d\n", |
| ret); |
| goto err; |
| } |
| |
| 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(info->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(info->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) |
| { |
| 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, |
| info->hpdet_init_range << |
| MADERA_HP_IMPEDANCE_RANGE_SHIFT); |
| |
| 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); |
| |
| static int madera_hpdet_wait(struct madera_extcon *info) |
| { |
| struct madera *madera = info->madera; |
| unsigned int val; |
| int i, ret; |
| |
| for (i = 0; i < MADERA_HPDONE_PROBE_COUNT; i++) { |
| ret = regmap_read(madera->regmap, MADERA_HEADPHONE_DETECT_2, |
| &val); |
| if (ret) { |
| dev_err(madera->dev, "Failed to read HPDET state: %d\n", |
| ret); |
| return ret; |
| } |
| |
| if (val & MADERA_HP_DONE_MASK) |
| return 0; |
| |
| msleep(MADERA_HPDONE_PROBE_INTERVAL_MS); |
| } |
| |
| dev_err(madera->dev, "HPDET did not appear to complete\n"); |
| |
| return -ETIMEDOUT; |
| } |
| |
| void madera_hpdet_stop(struct madera_extcon *info) |
| { |
| struct madera *madera = info->madera; |
| |
| dev_dbg(info->dev, "Stopping HPDET\n"); |
| |
| /* |
| * If the jack was removed we abort this state. |
| * Ensure that the detect hardware has returned to idle |
| */ |
| madera_hpdet_wait(info); |
| |
| /* 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, |
| info->hpdet_init_range << |
| MADERA_HP_IMPEDANCE_RANGE_SHIFT); |
| |
| 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, int val) |
| { |
| dev_dbg(info->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, EXTCON_JACK_MICROPHONE, true); |
| else |
| madera_extcon_report(info, EXTCON_JACK_HEADPHONE, true); |
| |
| if (info->have_mic) |
| madera_jds_set_state(info, &madera_micd_button); |
| else |
| madera_jds_set_state(info, NULL); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(madera_hpdet_reading); |
| |
| static int madera_hpdet_moisture_start(struct madera_extcon *info) |
| { |
| struct madera *madera = info->madera; |
| unsigned int hpd_sense, hpd_gnd, val; |
| int ret; |
| |
| dev_info(info->dev, "Start moisture det\n"); |
| |
| /* 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(info->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(info->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(info->dev, "Failed to set HPDET sense: %d\n", |
| ret); |
| goto err; |
| } |
| |
| madera_hpdet_start_micd(info); |
| break; |
| } |
| |
| ret = regmap_update_bits(madera->regmap, |
| MADERA_HEADPHONE_DETECT_1, |
| MADERA_HP_POLL, MADERA_HP_POLL); |
| if (ret != 0) { |
| dev_err(info->dev, "Can't start HPDET measurement: %d\n", ret); |
| goto err; |
| } |
| |
| return ret; |
| |
| err: |
| pm_runtime_put_autosuspend(info->dev); |
| return ret; |
| } |
| |
| static void madera_hpdet_moisture_stop(struct madera_extcon *info) |
| { |
| struct madera *madera = info->madera; |
| |
| /* Wait for any running detect to finish */ |
| madera_hpdet_wait(info); |
| |
| 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, int val) |
| { |
| int debounce_lim = info->pdata->moisture_debounce; |
| unsigned int ohms; |
| |
| if (val < 0) |
| return val; |
| |
| ohms = madera_hohm_to_ohm(val); /* Extra precision not required. */ |
| |
| if (ohms < info->pdata->moisture_imp) { |
| madera_extcon_report(info, EXTCON_MECHANICAL, true); |
| |
| 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(info->dev, |
| "Moisture software debounce: %u, %x\n", |
| info->moisture_count, ohms); |
| return -EAGAIN; |
| } |
| |
| info->moisture_count = 0; |
| } |
| |
| info->madera->moisture_detected = true; |
| dev_warn(info->dev, |
| "Jack detection due to moisture, ignoring\n"); |
| madera_jds_set_state(info, NULL); |
| } |
| |
| return 0; |
| } |
| |
| int madera_micd_start(struct madera_extcon *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_info(info->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(info->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) |
| { |
| struct madera *madera = info->madera; |
| |
| regmap_update_bits(madera->regmap, MADERA_MIC_DETECT_1_CONTROL_1, |
| MADERA_MICD_ENA, 0); |
| |
| if (info->wait_for_buttons) { |
| /* ensure we cancel mute */ |
| info->wait_for_buttons = false; |
| madera_hs_mic_control(info, MADERA_MIC_UNMUTE); |
| } |
| |
| 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_info(info->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) |
| { |
| 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); |
| } |
| |
| int madera_micd_button_start(struct madera_extcon *info) |
| { |
| int ret; |
| |
| info->wait_for_buttons = true; |
| |
| ret = madera_micd_start(info); |
| |
| if (ret != 0) |
| info->wait_for_buttons = false; |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(madera_micd_button_start); |
| |
| static int madera_micd_button_debounce(struct madera_extcon *info, int val) |
| { |
| 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(info->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, int val) |
| { |
| int i, key; |
| |
| if (val < MADERA_MICROPHONE_MIN_OHM) { |
| dev_info(info->dev, "Mic button detected\n"); |
| |
| madera_hs_mic_control(info, MADERA_MIC_MUTE); |
| |
| 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(info->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(info->dev, |
| "Button level %u out of range\n", val); |
| } else { |
| dev_info(info->dev, "Mic button released\n"); |
| |
| madera_hs_mic_control(info, MADERA_MIC_UNMUTE); |
| |
| 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, int val) |
| { |
| int ret; |
| unsigned int ohms; |
| |
| if (val < 0) |
| return val; |
| |
| ohms = madera_hohm_to_ohm((unsigned int)val); |
| |
| ret = madera_micd_button_debounce(info, ohms); |
| if (ret < 0) |
| return ret; |
| |
| return madera_micd_button_process(info, ohms); |
| } |
| EXPORT_SYMBOL_GPL(madera_micd_button_reading); |
| |
| int madera_micd_mic_start(struct madera_extcon *info) |
| { |
| int ret; |
| |
| info->detecting = true; |
| |
| ret = regulator_allow_bypass(info->micvdd, false); |
| if (ret) |
| dev_err(info->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) |
| { |
| int ret; |
| |
| madera_micd_stop(info); |
| |
| ret = regulator_allow_bypass(info->micvdd, true); |
| if (ret) |
| dev_err(info->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, int val) |
| { |
| unsigned int ohms; |
| |
| if (val < 0) |
| return val; |
| |
| ohms = madera_hohm_to_ohm((unsigned int)val); |
| |
| /* Due to jack detect this should never happen */ |
| if (ohms > MADERA_MICROPHONE_MAX_OHM) { |
| dev_warn(info->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 (ohms >= MADERA_MICROPHONE_MIN_OHM) { |
| dev_dbg(info->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 (ohms > info->micd_ranges[0].max && |
| info->num_micd_modes > 1) { |
| if (info->jack_flips >= info->num_micd_modes * 10) { |
| dev_dbg(info->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(info->dev, "Headphone detected\n"); |
| |
| done: |
| pm_runtime_mark_last_busy(info->dev); |
| |
| if (info->pdata->hpdet_channel) |
| madera_jds_set_state(info, &madera_hpdet_right); |
| else |
| madera_jds_set_state(info, &madera_hpdet_left); |
| |
| madera_extcon_notify_micd(info, info->have_mic, ohms); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(madera_micd_mic_reading); |
| |
| int madera_micd_mic_timeout_ms(struct madera_extcon *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) |
| { |
| int ret; |
| |
| dev_dbg(info->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, EXTCON_JACK_MICROPHONE, false); |
| } |
| EXPORT_SYMBOL_GPL(madera_micd_mic_timeout); |
| |
| static int madera_jack_present(struct madera_extcon *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(info->dev, "Failed to read jackdet status: %d\n", ret); |
| return ret; |
| } |
| |
| dev_dbg(info->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(info->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 = data; |
| int ret; |
| |
| dev_info(info->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_info(info->dev, "HPDET Status: %u\n", |
| madera_jds_get_mode(info)); |
| madera_jds_start_timeout(info); |
| mutex_unlock(&info->lock); |
| return IRQ_NONE; |
| } |
| |
| ret = madera_hpdet_read(info); |
| if (ret == -EAGAIN) { |
| dev_info(info->dev, "Repeat HPDET\n"); |
| goto out; |
| } |
| |
| 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 = container_of(work, |
| struct madera_extcon, |
| micd_detect_work.work); |
| enum madera_accdet_mode mode; |
| int ret; |
| |
| dev_info(info->dev, "MICDET IRQ\n"); |
| |
| 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) { |
| dev_info(info->dev, "Repeat MICDET\n"); |
| goto out; |
| } |
| |
| if (ret >= 0) { |
| dev_dbg(info->dev, "Mic impedance %d ohms\n", ret); |
| ret = madera_ohm_to_hohm((unsigned int)ret); |
| } |
| |
| madera_jds_reading(info, ret); |
| |
| out: |
| madera_jds_start_timeout(info); |
| |
| pm_runtime_mark_last_busy(info->dev); |
| |
| mutex_unlock(&info->lock); |
| |
| return; |
| |
| spurious: |
| dev_warn(info->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 = data; |
| int debounce = info->pdata->micd_detect_debounce_ms; |
| |
| dev_dbg(info->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_button_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 = data; |
| struct madera *madera = info->madera; |
| unsigned int val, mask; |
| bool cancelled_state; |
| int i, present; |
| |
| dev_info(info->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) { |
| dev_err(info->dev, "Failed to check jack status: %d\n", |
| present); |
| mutex_unlock(&info->lock); |
| pm_runtime_put_autosuspend(info->dev); |
| return IRQ_NONE; |
| } |
| |
| if (val == info->last_jackdet) { |
| dev_dbg(info->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(info->dev, "Detected jack\n"); |
| |
| if (info->pdata->jd_wake_time) |
| __pm_wakeup_event(&info->detection_wake_lock, |
| info->pdata->jd_wake_time); |
| |
| /* |
| * if we're doing moisture detect delay reporting an insert |
| * until we've decided that it's real |
| */ |
| if (!info->pdata->moisture_imp) |
| madera_extcon_report(info, EXTCON_MECHANICAL, true); |
| |
| 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(info->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); |
| |
| for (i = 0; i < ARRAY_SIZE(madera_cable) - 1; i++) |
| madera_extcon_report(info, madera_cable[i], false); |
| |
| 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); |
| } |
| |
| static void madera_extcon_of_get_micd_ranges(struct madera_extcon *info, |
| struct fwnode_handle *node, |
| struct madera_accdet_pdata *pdata) |
| { |
| struct madera_micd_range *micd_ranges; |
| u32 *values; |
| int nvalues, nranges, i, j; |
| int ret; |
| |
| nvalues = fwnode_property_read_u32_array(node, "cirrus,micd-ranges", |
| NULL, 0); |
| if (nvalues < 0) |
| return; |
| |
| values = kmalloc_array(nvalues, sizeof(u32), GFP_KERNEL); |
| if (!values) |
| return; |
| |
| ret = fwnode_property_read_u32_array(node, "cirrus,micd-ranges", |
| values, nvalues); |
| if (ret < 0) |
| goto err; |
| |
| nranges = nvalues / 2; |
| micd_ranges = devm_kcalloc(info->dev, |
| nranges, |
| sizeof(struct madera_micd_range), |
| GFP_KERNEL); |
| |
| for (i = 0, j = 0; i < nranges; ++i) { |
| micd_ranges[i].max = values[j++]; |
| micd_ranges[i].key = values[j++]; |
| } |
| |
| pdata->micd_ranges = micd_ranges; |
| pdata->num_micd_ranges = nranges; |
| |
| err: |
| kfree(values); |
| } |
| |
| static void madera_extcon_get_micd_configs(struct madera_extcon *info, |
| struct fwnode_handle *node) |
| { |
| struct madera_micd_config *micd_configs; |
| u32 *values; |
| int nvalues, nconfigs, i, j; |
| int ret; |
| |
| nvalues = fwnode_property_read_u32_array(node, |
| "cirrus,micd-configs", |
| NULL, 0); |
| if (nvalues == -EINVAL) { |
| return; /* not found */ |
| } else if ((nvalues < 0) || (nvalues % 5)) { |
| dev_warn(info->dev, "cirrus,micd-configs is malformed\n"); |
| return; |
| } |
| |
| values = kmalloc_array(nvalues, sizeof(u32), GFP_KERNEL); |
| if (!values) |
| return; |
| |
| ret = fwnode_property_read_u32_array(node, |
| "cirrus,micd-configs", |
| values, nvalues); |
| if (ret < 0) |
| goto err; |
| |
| nconfigs = nvalues / 5; |
| micd_configs = devm_kcalloc(info->dev, |
| nconfigs, |
| sizeof(struct madera_micd_config), |
| GFP_KERNEL); |
| if (!micd_configs) |
| goto err; |
| |
| for (i = 0, j = 0; i < nconfigs; ++i) { |
| micd_configs[i].src = values[j++]; |
| micd_configs[i].gnd = values[j++]; |
| micd_configs[i].bias = values[j++]; |
| micd_configs[i].gpio = values[j++]; |
| micd_configs[i].hp_gnd = values[j++]; |
| } |
| |
| info->micd_modes = micd_configs; |
| info->num_micd_modes = nconfigs; |
| |
| err: |
| kfree(values); |
| } |
| |
| static void madera_extcon_get_hpd_pins(struct madera_extcon *info, |
| struct fwnode_handle *node, |
| struct madera_accdet_pdata *pdata) |
| { |
| 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 = fwnode_property_read_u32_array(node, |
| "cirrus,hpd-pins", |
| pdata->hpd_pins, |
| ARRAY_SIZE(pdata->hpd_pins)); |
| if (ret) { |
| if (ret != -EINVAL) |
| dev_warn(info->dev, |
| "Malformed cirrus,hpd-pins: %d\n", ret); |
| return; |
| } |
| |
| /* supply defaults where requested */ |
| for (i = 0; i < ARRAY_SIZE(pdata->hpd_pins); ++i) |
| if (pdata->hpd_pins[i] > 0xFFFF) |
| pdata->hpd_pins[i] = madera_default_hpd_pins[i]; |
| } |
| |
| static void madera_extcon_process_accdet_node(struct madera_extcon *info, |
| struct fwnode_handle *node) |
| { |
| struct madera *madera = info->madera; |
| struct madera_accdet_pdata *pdata; |
| u32 out_num; |
| int i, ret; |
| enum gpiod_flags gpio_status; |
| |
| ret = fwnode_property_read_u32(node, "reg", &out_num); |
| if (ret < 0) { |
| dev_warn(info->dev, |
| "failed to read reg property (%d)\n", |
| ret); |
| return; |
| } |
| |
| if (out_num == 0) { |
| dev_warn(info->dev, "accdet node illegal reg %u\n", out_num); |
| return; |
| } |
| |
| dev_dbg(info->dev, "processing accdet reg=%u\n", out_num); |
| |
| 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; |
| } |
| |
| pdata = &madera->pdata.accdet[i]; |
| pdata->enabled = true; /* implied by presence of properties node */ |
| pdata->output = out_num; |
| |
| fwnode_property_read_u32(node, "cirrus,micd-detect-debounce-ms", |
| &pdata->micd_detect_debounce_ms); |
| |
| fwnode_property_read_u32(node, "cirrus,micd-manual-debounce", |
| &pdata->micd_manual_debounce); |
| |
| fwnode_property_read_u32(node, "cirrus,micd-bias-start-time", |
| &pdata->micd_bias_start_time); |
| |
| fwnode_property_read_u32(node, "cirrus,micd-rate", |
| &pdata->micd_rate); |
| |
| fwnode_property_read_u32(node, "cirrus,micd-dbtime", |
| &pdata->micd_dbtime); |
| |
| fwnode_property_read_u32(node, "cirrus,micd-timeout-ms", |
| &pdata->micd_timeout_ms); |
| |
| /* don't override any preset force_micbias enable */ |
| if (fwnode_property_present(node, "cirrus,micd-force-micbias")) |
| pdata->micd_force_micbias = true; |
| |
| pdata->micd_software_compare = |
| fwnode_property_present(node, |
| "cirrus,micd-software-compare"); |
| |
| pdata->micd_open_circuit_declare = |
| fwnode_property_present(node, |
| "cirrus,micd-open-circuit-declare"); |
| |
| pdata->jd_use_jd2 = fwnode_property_present(node, |
| "cirrus,jd-use-jd2"); |
| |
| pdata->jd_invert = fwnode_property_present(node, |
| "cirrus,jd-invert"); |
| |
| fwnode_property_read_u32(node, "cirrus,fixed-hpdet-imp", |
| &pdata->fixed_hpdet_imp_x100); |
| |
| fwnode_property_read_u32(node, "cirrus,hpdet-short-circuit-imp", |
| &pdata->hpdet_short_circuit_imp); |
| |
| fwnode_property_read_u32(node, "cirrus,hpdet-channel", |
| &pdata->hpdet_channel); |
| |
| fwnode_property_read_u32(node, "cirrus,jd-wake-time", |
| &pdata->jd_wake_time); |
| |
| fwnode_property_read_u32(node, "cirrus,micd-clamp-mode", |
| &pdata->micd_clamp_mode); |
| |
| fwnode_property_read_u32(node, "cirrus,hpdet-ext-res", |
| &pdata->hpdet_ext_res_x100); |
| |
| madera_extcon_get_hpd_pins(info, node, pdata); |
| madera_extcon_get_micd_configs(info, node); |
| madera_extcon_of_get_micd_ranges(info, node, pdata); |
| |
| if (info->micd_modes[0].gpio) |
| gpio_status = GPIOD_OUT_HIGH; |
| else |
| gpio_status = GPIOD_OUT_LOW; |
| |
| info->micd_pol_gpio = devm_fwnode_get_gpiod_from_child(info->dev, |
| "cirrus,micd-pol", |
| node, |
| gpio_status, |
| "cirrus,micd-pol"); |
| if (IS_ERR(info->micd_pol_gpio)) { |
| if (PTR_ERR(info->micd_pol_gpio) != -ENOENT) |
| dev_warn(info->dev, |
| "Malformed cirrus,micd-pol-gpios ignored: %ld\n", |
| PTR_ERR(info->micd_pol_gpio)); |
| info->micd_pol_gpio = NULL; |
| } |
| |
| |
| fwnode_property_read_u32(node, "cirrus,init-delay-ms", |
| &pdata->init_delay); |
| |
| fwnode_property_read_u32(node, "cirrus,hs-mic", &pdata->hs_mic); |
| if (pdata->hs_mic > MADERA_MAX_INPUT) |
| pdata->hs_mic = 0; |
| |
| /* 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; |
| } |
| fwnode_property_read_u32(node, "cirrus,moisture-pin", |
| &pdata->moisture_pin); |
| fwnode_property_read_u32(node, "cirrus,moisture-imp", |
| &pdata->moisture_imp); |
| fwnode_property_read_u32(node, "cirrus,moisture-debounce", |
| &pdata->moisture_debounce); |
| } |
| |
| static int madera_extcon_get_device_pdata(struct madera_extcon *info) |
| { |
| struct device_node *parent, *child; |
| struct madera *madera = info->madera; |
| |
| /* |
| * a GPSW is not necessarily exclusive to a single accessory detect |
| * channel so is not in the subnodes |
| */ |
| device_property_read_u32_array(info->madera->dev, "cirrus,gpsw", |
| info->madera->pdata.gpsw, |
| ARRAY_SIZE(info->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_process_accdet_node(info, &child->fwnode); |
| |
| of_node_put(parent); |
| |
| return 0; |
| } |
| |
| #ifdef DEBUG |
| #define MADERA_EXTCON_PDATA_DUMP(x, f) \ |
| dev_dbg(info->dev, "\t" #x ": " f "\n", pdata->x) |
| |
| static void madera_extcon_dump_config(struct madera_extcon *info) |
| { |
| const struct madera_accdet_pdata *pdata; |
| int i, j; |
| |
| dev_dbg(info->dev, "extcon pdata gpsw=[0x%x 0x%x]\n", |
| info->madera->pdata.gpsw[0], info->madera->pdata.gpsw[1]); |
| |
| for (i = 0; i < ARRAY_SIZE(info->madera->pdata.accdet); ++i) { |
| pdata = &info->madera->pdata.accdet[i]; |
| |
| dev_dbg(info->dev, "extcon pdata OUT%u\n", pdata->output); |
| MADERA_EXTCON_PDATA_DUMP(enabled, "%u"); |
| MADERA_EXTCON_PDATA_DUMP(jd_wake_time, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(jd_use_jd2, "%u"); |
| MADERA_EXTCON_PDATA_DUMP(jd_invert, "%u"); |
| MADERA_EXTCON_PDATA_DUMP(fixed_hpdet_imp_x100, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(hpdet_ext_res_x100, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(hpdet_short_circuit_imp, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(hpdet_channel, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_detect_debounce_ms, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(hpdet_short_circuit_imp, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(hpdet_channel, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_detect_debounce_ms, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_manual_debounce, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_bias_start_time, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_rate, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_dbtime, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_timeout_ms, "%d"); |
| MADERA_EXTCON_PDATA_DUMP(micd_clamp_mode, "%u"); |
| MADERA_EXTCON_PDATA_DUMP(micd_force_micbias, "%u"); |
| MADERA_EXTCON_PDATA_DUMP(micd_open_circuit_declare, "%u"); |
| MADERA_EXTCON_PDATA_DUMP(micd_software_compare, "%u"); |
| |
| if (info->micd_pol_gpio) |
| dev_dbg(info->dev, "micd_pol_gpio: %d\n", |
| desc_to_gpio(info->micd_pol_gpio)); |
| else |
| dev_dbg(info->dev, "micd_pol_gpio: unused\n"); |
| |
| dev_dbg(info->dev, "\tmicd_ranges {\n"); |
| for (j = 0; j < info->num_micd_ranges; ++j) |
| dev_dbg(info->dev, "\t\tmax: %d key: %d\n", |
| info->micd_ranges[j].max, |
| info->micd_ranges[j].key); |
| dev_dbg(info->dev, "\t}\n"); |
| |
| dev_dbg(info->dev, "\tmicd_configs {\n"); |
| for (j = 0; j < info->num_micd_modes; ++j) |
| dev_dbg(info->dev, |
| "\t\tsrc: 0x%x gnd: 0x%x bias: %u gpio: %u hp_gnd: %d\n", |
| info->micd_modes[j].src, |
| info->micd_modes[j].gnd, |
| info->micd_modes[j].bias, |
| info->micd_modes[j].gpio, |
| info->micd_modes[j].hp_gnd); |
| dev_dbg(info->dev, "\t}\n"); |
| |
| dev_dbg(info->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_config(struct madera_extcon *info) |
| { |
| } |
| #endif |
| |
| /* See datasheet for a description of this calibration data */ |
| static int madera_extcon_read_calibration(struct madera_extcon *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(info->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(info->dev, |
| "Failed to read HP CALIB OFFSET value: %d\n", ret); |
| return ret; |
| } |
| |
| if (((offset == 0) && (gradient == 0)) || |
| ((offset == 0xFFFFFFFF) && (gradient == 0xFFFFFFFF))) { |
| dev_warn(info->dev, "No HP trims\n"); |
| return 0; |
| } |
| |
| trims = devm_kcalloc(info->dev, 4, |
| sizeof(struct madera_hpdet_trims), |
| GFP_KERNEL); |
| if (!trims) { |
| dev_err(info->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(info->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) |
| { |
| 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) |
| { |
| 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(info->dev, "Unsupported MICD level %d\n", |
| info->micd_ranges[i].max); |
| ret = -EINVAL; |
| goto err_input; |
| } |
| |
| dev_dbg(info->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) |
| { |
| 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); |
| return 0; |
| } |
| |
| if (pdata->num_micd_ranges > MADERA_MAX_MICD_RANGE) { |
| dev_err(info->dev, "Too many MICD ranges: %d\n", |
| pdata->num_micd_ranges); |
| return -EINVAL; |
| } |
| |
| ranges = devm_kmalloc_array(info->dev, |
| pdata->num_micd_ranges, |
| sizeof(struct madera_micd_range), |
| GFP_KERNEL); |
| if (!ranges) { |
| dev_err(info->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(info->dev, "MICD ranges must be sorted\n"); |
| goto err_free; |
| } |
| } |
| |
| return 0; |
| |
| err_free: |
| devm_kfree(info->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; |
| unsigned int debounce_val, analog_val; |
| int jack_irq_fall, jack_irq_rise; |
| int ret, mode, i, hpdet_short_measured; |
| |
| /* quick exit if Madera irqchip driver hasn't completed probe */ |
| if (!madera->irq_dev) { |
| dev_dbg(&pdev->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; |
| 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); |
| |
| switch (madera->type) { |
| case CS47L15: |
| info->hpdet_init_range = 1; /* range 0 not used on CS47L15 */ |
| info->hpdet_ranges = cs47l15_hpdet_ranges; |
| info->num_hpdet_ranges = ARRAY_SIZE(cs47l15_hpdet_ranges); |
| info->micd_modes = madera_micd_default_modes; |
| info->num_micd_modes = ARRAY_SIZE(madera_micd_default_modes); |
| break; |
| case CS47L35: |
| pdata->micd_force_micbias = true; |
| info->hpdet_ranges = cs47l85_hpdet_ranges; |
| info->num_hpdet_ranges = ARRAY_SIZE(cs47l85_hpdet_ranges); |
| info->micd_modes = cs47l85_micd_default_modes; |
| info->num_micd_modes = ARRAY_SIZE(cs47l85_micd_default_modes); |
| break; |
| case CS47L85: |
| case WM1840: |
| info->hpdet_ranges = cs47l85_hpdet_ranges; |
| info->num_hpdet_ranges = ARRAY_SIZE(cs47l85_hpdet_ranges); |
| info->micd_modes = cs47l85_micd_default_modes; |
| info->num_micd_modes = ARRAY_SIZE(cs47l85_micd_default_modes); |
| break; |
| case CS47L92: |
| case CS47L93: |
| info->hpdet_ranges = cs47l92_hpdet_ranges; |
| info->num_hpdet_ranges = ARRAY_SIZE(cs47l92_hpdet_ranges); |
| info->micd_modes = madera_micd_default_modes; |
| info->num_micd_modes = ARRAY_SIZE(madera_micd_default_modes); |
| break; |
| default: |
| info->hpdet_ranges = madera_hpdet_ranges; |
| info->num_hpdet_ranges = ARRAY_SIZE(madera_hpdet_ranges); |
| info->micd_modes = madera_micd_default_modes; |
| info->num_micd_modes = ARRAY_SIZE(madera_micd_default_modes); |
| break; |
| } |
| |
| if (dev_get_platdata(madera->dev)) { |
| madera_extcon_xlate_pdata(pdata); |
| |
| if (pdata->num_micd_configs) { |
| info->micd_modes = pdata->micd_configs; |
| info->num_micd_modes = pdata->num_micd_configs; |
| } |
| |
| if (info->micd_modes[0].gpio) |
| mode = GPIOF_OUT_INIT_HIGH; |
| else |
| mode = GPIOF_OUT_INIT_LOW; |
| |
| ret = devm_gpio_request_one(&pdev->dev, |
| pdata->micd_pol_gpio, |
| mode, |
| "MICD polarity"); |
| if (ret) { |
| dev_err(info->dev, "Failed to request GPIO%d: %d\n", |
| pdata->micd_pol_gpio, ret); |
| goto err_wakelock; |
| } |
| |
| info->micd_pol_gpio = gpio_to_desc(pdata->micd_pol_gpio); |
| } else { |
| ret = madera_extcon_get_device_pdata(info); |
| if (ret < 0) |
| goto err_wakelock; |
| } |
| |
| if (!pdata->enabled || pdata->output == 0) { |
| ret = -ENODEV; /* no accdet output configured */ |
| goto err_wakelock; |
| } |
| |
| info->hpdet_short_x100 = |
| madera_ohm_to_hohm(pdata->hpdet_short_circuit_imp); |
| |
| /* Actual measured short is increased by external resistance */ |
| hpdet_short_measured = pdata->hpdet_short_circuit_imp + |
| madera_hohm_to_ohm(pdata->hpdet_ext_res_x100); |
| |
| if (hpdet_short_measured < MADERA_HP_SHORT_IMPEDANCE_MIN) { |
| /* |
| * increase comparison threshold to minimum we can measure |
| * taking into account that threshold does not include external |
| * resistance |
| */ |
| info->hpdet_short_x100 = |
| madera_ohm_to_hohm(MADERA_HP_SHORT_IMPEDANCE_MIN) - |
| pdata->hpdet_ext_res_x100; |
| dev_warn(info->dev, |
| "Increasing HP short circuit impedance from %d to %d\n", |
| pdata->hpdet_short_circuit_imp, |
| madera_hohm_to_ohm(info->hpdet_short_x100)); |
| } |
| |
| info->micvdd = devm_regulator_get(&pdev->dev, "MICVDD"); |
| if (IS_ERR(info->micvdd)) { |
| ret = PTR_ERR(info->micvdd); |
| dev_err(info->dev, "Failed to get MICVDD: %d\n", ret); |
| goto err_wakelock; |
| } |
| |
| 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 = devm_extcon_dev_allocate(&pdev->dev, madera_cable); |
| if (IS_ERR(info->edev)) { |
| dev_err(&pdev->dev, "failed to allocate extcon device\n"); |
| ret = -ENOMEM; |
| goto err_wakelock; |
| } |
| |
| ret = devm_extcon_dev_register(&pdev->dev, info->edev); |
| if (ret < 0) { |
| dev_err(info->dev, "extcon_dev_register() failed: %d\n", ret); |
| goto err_wakelock; |
| } |
| |
| info->input = devm_input_allocate_device(&pdev->dev); |
| if (!info->input) { |
| dev_err(info->dev, "Can't allocate input dev\n"); |
| ret = -ENOMEM; |
| goto err_register; |
| } |
| |
| info->input->name = "Headset"; |
| info->input->phys = "madera/extcon"; |
| info->input->dev.parent = &pdev->dev; |
| |
| 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_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); |
| |
| if ((info->num_micd_modes > 2) && !info->micd_pol_gpio) |
| dev_warn(info->dev, "Have >1 mic_configs but no pol_gpio\n"); |
| |
| 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; |
| } |
| } else { |
| switch (madera->type) { |
| case CS47L15: |
| pdata->hpdet_ext_res_x100 += 3300; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* Skip any HPDET ranges less than the external resistance */ |
| for (i = info->hpdet_init_range; i < info->num_hpdet_ranges; ++i) { |
| if (madera_ohm_to_hohm(info->hpdet_ranges[i].max) >= |
| pdata->hpdet_ext_res_x100) { |
| info->hpdet_init_range = i; |
| break; |
| } |
| } |
| if (i == info->num_hpdet_ranges) { |
| dev_err(&pdev->dev, |
| "No possible range for external resistance %u.%02u\n", |
| pdata->hpdet_ext_res_x100 / 100, |
| pdata->hpdet_ext_res_x100 % 100); |
| goto err_input; |
| } |
| |
| regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1, |
| MADERA_HP_IMPEDANCE_RANGE_MASK, |
| info->hpdet_init_range << |
| MADERA_HP_IMPEDANCE_RANGE_SHIFT); |
| |
| 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_input; |
| } |
| |
| 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; |
| jack_irq_rise = MADERA_IRQ_MICD_CLAMP_RISE; |
| jack_irq_fall = MADERA_IRQ_MICD_CLAMP_FALL; |
| } else { |
| debounce_val = MADERA_JD1_DB; |
| analog_val = MADERA_JD1_ENA; |
| jack_irq_rise = MADERA_IRQ_JD1_RISE; |
| jack_irq_fall = MADERA_IRQ_JD1_FALL; |
| } |
| |
| 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 = 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_hpdet; |
| } |
| |
| 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 = regulator_allow_bypass(info->micvdd, true); |
| if (ret) |
| dev_warn(info->dev, |
| "Failed to set MICVDD to bypass: %d\n", ret); |
| |
| pm_runtime_put(&pdev->dev); |
| |
| if (IS_ENABLED(CONFIG_EXTCON_MADERA_INPUT_EVENT)) { |
| input_set_capability(info->input, |
| EV_SW, |
| SW_MICROPHONE_INSERT); |
| input_set_capability(info->input, |
| EV_SW, |
| SW_HEADPHONE_INSERT); |
| input_set_capability(info->input, |
| EV_SW, |
| SW_JACK_PHYSICAL_INSERT); |
| } |
| |
| ret = input_register_device(info->input); |
| if (ret) { |
| dev_err(&pdev->dev, "Can't register input device: %d\n", ret); |
| goto err_fall_wake; |
| } |
| |
| 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); |
| |
| madera_extcon_dump_config(info); |
| |
| ret = device_create_file(&pdev->dev, &dev_attr_mic_impedance); |
| if (ret) |
| dev_warn(&pdev->dev, |
| "Failed to create sysfs node for mic_impedance %d\n", |
| ret); |
| |
| madera->extcon_info = info; |
| |
| return 0; |
| |
| 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_hpdet: |
| madera_free_irq(madera, MADERA_IRQ_HPDET, info); |
| err_micdet: |
| madera_free_irq(madera, MADERA_IRQ_MICDET1, info); |
| err_input: |
| err_register: |
| pm_runtime_disable(&pdev->dev); |
| err_wakelock: |
| wakeup_source_trash(&info->detection_wake_lock); |
| |
| return ret; |
| } |
| |
| static int madera_extcon_remove(struct platform_device *pdev) |
| { |
| struct madera_extcon *info = platform_get_drvdata(pdev); |
| struct madera *madera = info->madera; |
| int jack_irq_rise, jack_irq_fall; |
| |
| pm_runtime_disable(&pdev->dev); |
| |
| madera->extcon_info = NULL; |
| |
| 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); |
| device_remove_file(&pdev->dev, &dev_attr_mic_impedance); |
| kfree(info->hpdet_trims); |
| wakeup_source_trash(&info->detection_wake_lock); |
| |
| return 0; |
| } |
| |
| static struct platform_driver madera_extcon_driver = { |
| .driver = { |
| .name = "madera-extcon", |
| .suppress_bind_attrs = true, |
| }, |
| .probe = madera_extcon_probe, |
| .remove = madera_extcon_remove, |
| }; |
| |
| module_platform_driver(madera_extcon_driver); |
| |
| MODULE_DESCRIPTION("Madera extcon driver"); |
| MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.wolfsonmicro.com>"); |
| MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.wolfsonmicro.com>"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("platform:extcon-madera"); |