extcon: madera: Add support for Cirrus Logic Madera codecs
authorRichard Fitzgerald <rf@opensource.wolfsonmicro.com>
Thu, 20 Oct 2016 15:45:59 +0000 (16:45 +0100)
committer강신형 <s47.kang@samsung.com>
Tue, 19 Jun 2018 06:54:02 +0000 (15:54 +0900)
The Cirrus Logic Madera-class codecs include highly flexible support
for jack detection, accessory detection and accessory buttons.

Although the driver is very configurable, it is impossible to cover
in generic code every possible way in which the accessory detect block
could be used and every possible combination of external hardware. We
therefore provide enough configurability to cover all common cases but
we allow customers to provide custom replacements for some or all of
the detection state machine to provided added ability to handle unusual
hardware or applications without having to create a branched and modified
version of this driver.

Change-Id: Ibbef6dc9a5a139075daed8c1556698bfd8e6bb16
Signed-off-by: Richard Fitzgerald <rf@opensource.wolfsonmicro.com>
Signed-off-by: Stuart Henderson <stuarth@opensource.wolfsonmicro.com>
Documentation/devicetree/bindings/extcon/extcon-madera.txt [new file with mode: 0644]
MAINTAINERS
drivers/extcon/Kconfig
drivers/extcon/Makefile
drivers/extcon/extcon-madera.c [new file with mode: 0644]
include/dt-bindings/extcon/extcon-madera.h [new file with mode: 0644]
include/linux/extcon/extcon-madera-pdata.h [new file with mode: 0644]
include/linux/extcon/extcon-madera.h [new file with mode: 0644]
include/linux/mfd/madera/core.h
include/linux/mfd/madera/pdata.h

diff --git a/Documentation/devicetree/bindings/extcon/extcon-madera.txt b/Documentation/devicetree/bindings/extcon/extcon-madera.txt
new file mode 100644 (file)
index 0000000..7cd1cc6
--- /dev/null
@@ -0,0 +1,133 @@
+Cirrus Logic Madera class audio codecs extcon driver
+
+The extcon configuration settings are a child node of the parent MFD driver
+binding.
+See Documentation/devicetree/bindings/mfd/madera.txt
+
+This node contains one or more child nodes to describe the configuration for
+each accessory detect.
+
+Mandatory properties:
+  - compatible : must be "cirrus,madera-extcon"
+  - #address-cells : must be 1
+  - #size-cells : must be 0
+
+Optional properties:
+  - cirrus,gpsw : Settings for the general purpose switches, set as per the
+    SWn_MODE bits in the GP Switch 1 register. If given must be 2 cells.
+    First cell is the value for the SW1_MODE
+    Second cell is the value for the SW2_MODE (cs47l90, cs47l91)
+
+  - cirrus,micd-pol-gpios : GPIO specifier for the GPIO controlling the headset
+    polarity if one exists. One cell for each child node.
+
+Child node mandatory properties:
+  - reg : output number this configuration applies to, must be 1
+
+Child node optional properties:
+  - cirrus,micd-detect-debounce-ms : Additional software microphone detection
+    debounce specified in milliseconds
+
+  - cirrus,micd-manual-debounce : Additional software button detection
+    debounce specified as a number
+
+  - cirrus,micd-bias-start-time : Time allowed for MICBIAS to startup prior to
+    performing microphone detection, specified as per the MICD_BIAS_STARTTIME
+    bits in the register MIC_DETECT_1
+
+  - cirrus,micd-rate : Delay between successive microphone detection
+    measurements, specified as per the MICD_RATE bits in the register
+    MIC_DETECT_1
+
+  - cirrus,micd-dbtime : Microphone detection hardware debounce level, specified
+    as per the MICD_DBTIME bits in the register MIC_DETECT_1
+
+  - cirrus,micd-timeout-ms : Timeout for microphone detection, specified in
+    milliseconds
+
+  - cirrus,micd-force-micbias : Force MICBIAS continuously on during microphone
+    detection and button detection
+
+  - cirrus,micd-software-compare : Use a software comparison to determine mic
+    presence
+
+  - cirrus,jd-invert : Invert the polarity of the jack detection switch
+
+  - cirrus,jd-use-jd2 : Use JD2 input with JD1 for dual jack detection.
+
+  - cirrus,fixed-hpdet-imp : Do not perform any headphone detection, just use
+    the fixed value specified here as the headphone impedance. Value is in
+    hundredths-of-an-ohm (ohms * 100)
+
+  - cirrus,hpdet-ext-res : Impedance of external series resistor on hpdet.
+    Value is in hundredths-of-an-ohm (ohms * 100)
+
+  - cirrus,hpdet-short-circuit-imp : Specifies the maximum impedance in ohms
+    that will be considered as a short circuit
+
+  - cirrus,hpdet-channel : Set which channel is used for headphone impedance
+    measurement. 0 = left, 1 = right
+
+  - cirrus,micd-clamp-mode : Specifies the logic of the micdetect clamp block
+
+  - cirrus,hpd-pins : 4 cells specifying the clamp and sense pins to use.
+       <clamp_left sense_left clamp_right sense_right>
+    where clamp_x is the clamp pin for channel x and sense_x is the impedance
+    sense pin for channel x, as per the HPD_OUT_SEL field of HEADPHONE_DETECT_0
+    register. A value >0xFFFF means use the default.
+    (cs47l90, cs47l91)
+
+  - cirrus,micd-configs : Headset polarity configurations, variable length but
+    must be a multiple of 5 cells, each 5-cell group represents one
+    polarity configuration
+    first cell is the value of
+       ACCDET_SRC register field (CS47L35, CS47L85, WM1840),
+       MICD_SENSE_SEL register field (all other codecs)
+    second cell is the accessory detection ground as per the MICD_GND_SEL
+      register field
+    the third cell is the MICBIAS to be used as per the MICD_BIAS_SRC register
+      field
+    fourth cell is the value of the micd-pol-gpio pin, a non-zero value
+      indicates this should be on
+    fifth cell is
+      set to zero (cs47l35, cs47l85, wm1840)
+      value of HPn_GND_SEL register field (all other codecs)
+
+Example:
+
+codec: cs47l85@0 {
+       compatible = "cirrus,cs47l85";
+
+       accdet {
+               compatible = "cirrus,madera-extcon";
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               cirrus,gpsw = <3 0>;
+
+               cirrus,micd-pol-gpios = <&gpio 0>
+
+               acc@1 {
+                       reg = <1>;
+
+                       cirrus,micd-detect-debounce-ms = <10>;
+                       cirrus,micd-bias-start-time = <0x1>;
+                       cirrus,micd-rate = <0x1>;
+                       cirrus,micd-dbtime = <0x1>;
+                       cirrus,micd-timeout-ms = <10>;
+                       cirrus,micd-force-micbias;
+                       cirrus,micd-ranges = <
+                               11 0x100
+                               28 0x101
+                               54 0x102
+                               100 0x103
+                               186 0x104
+                               430 0x105
+                       >;
+                       cirrus,micd-configs = <
+                               0x1 0 1 0 2
+                               0x0 0 2 1 2
+                       >;
+               };
+       };
+};
index e6942d51a070fb4dc45332aad9e597cb9eeafbda..3f644518bfdf6731f003b1c57fcdd7c515dbe98a 100644 (file)
@@ -3399,11 +3399,14 @@ L:      patches@opensource.wolfsonmicro.com
 T:     git https://github.com/CirrusLogic/linux-drivers.git
 W:     https://github.com/CirrusLogic/linux-drivers/wiki
 S:     Supported
+F:     Documentation/devicetree/bindings/extcon/extcon-madera*
 F:     Documentation/devicetree/bindings/gpio/gpio-madera.txt
 F:     Documentation/devicetree/bindings/mfd/madera.txt
 F:     Documentation/devicetree/bindings/pinctrl/cirrus,madera-pinctrl.txt
+F:     include/linux/extcon/extcon-madera*
 F:     include/linux/irqchip/irq-madera*
 F:     include/linux/mfd/madera/*
+F:     drivers/extcon/extcon-madera*
 F:     drivers/gpio/gpio-madera*
 F:     drivers/irqchip/irq-madera*
 F:     drivers/mfd/madera*
index a7bca4207f447a72caf33edd96327d0ecd811c0f..f70fffbb3d70fd1c884c3d804a5bcd875ca8844b 100644 (file)
@@ -59,6 +59,13 @@ config EXTCON_INTEL_CHT_WC
          Say Y here to enable extcon support for charger detection / control
          on the Intel Cherrytrail Whiskey Cove PMIC.
 
+config EXTCON_MADERA
+       tristate "Cirrus Logic Madera codec support"
+       depends on MFD_MADERA && INPUT && SND_SOC
+       help
+         Say Y here to enable support for external accessory detection
+         on Cirrus Logic Madera class codecs.
+
 config EXTCON_MAX14577
        tristate "Maxim MAX14577/77836 EXTCON Support"
        depends on MFD_MAX14577
index 0888fdeded7230de9ca368e4af3efa3529451211..1cd66c3cbcd0cd30bb32c1ca43a2ff2f6857c614 100644 (file)
@@ -11,6 +11,7 @@ obj-$(CONFIG_EXTCON_AXP288)   += extcon-axp288.o
 obj-$(CONFIG_EXTCON_GPIO)      += extcon-gpio.o
 obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
 obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
+obj-$(CONFIG_EXTCON_MADERA)    += extcon-madera.o
 obj-$(CONFIG_EXTCON_MAX14577)  += extcon-max14577.o
 obj-$(CONFIG_EXTCON_MAX3355)   += extcon-max3355.o
 obj-$(CONFIG_EXTCON_MAX77693)  += extcon-max77693.o
diff --git a/drivers/extcon/extcon-madera.c b/drivers/extcon/extcon-madera.c
new file mode 100644 (file)
index 0000000..490ac1a
--- /dev/null
@@ -0,0 +1,3024 @@
+/*
+ * 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_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 = BTN_0 },
+       { .max = 186, .key = BTN_1 },
+       { .max = 295, .key = BTN_2 },
+       { .max = 681, .key = BTN_3 },
+       { .max =  -1, .key = -1 },
+       { .max =  -1, .key = -1 },
+       { .max =  -1, .key = -1 },
+       { .max =  -1, .key = -1 },
+};
+
+/* The number of levels in madera_micd_levels valid for button thresholds */
+#define MADERA_NUM_MICD_BUTTON_LEVELS 64
+
+/* 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 cs47l85_hpdet_ranges[] = {
+       { 4,    30,    1007000,   -7200,   4003, 69300000, 381150, 250000, 500000},
+       { 8,    100,   1007000,   -7200,   7975, 69600000, 382800, 250000, 500000},
+       { 100,  1000,  9696000,   -79500,  7300, 62900000, 345950, 250000, 500000},
+       { 1000, 10000, 100684000, -949400, 7300, 63200000, 347600, 250000, 500000},
+};
+
+static const struct madera_hpdet_calibration_data madera_hpdet_ranges[] = {
+       { 4,    30,    1014000,   -4300,   3950, 69300000, 381150, 700000, 500000},
+       { 8,    100,   1014000,   -8600,   7975, 69600000, 382800, 700000, 500000},
+       { 100,  1000,  9744000,   -79500,  7300, 62900000, 345950, 700000, 500000},
+       { 1000, 10000, 101158000, -949400, 7300, 63200000, 347600, 700000, 500000},
+};
+
+struct madera_hp_tuning {
+       int max_hohm;
+       const struct reg_sequence *patch;
+       int patch_len;
+};
+
+static const struct reg_sequence cs47l35_low_impedance_patch[] = {
+       { 0x460, 0x0C40 },
+       { 0x461, 0xCD1A },
+       { 0x462, 0x0C40 },
+       { 0x463, 0xB53B },
+       { 0x464, 0x0C41 },
+       { 0x465, 0x4826 },
+       { 0x466, 0x0C41 },
+       { 0x467, 0x2EDA },
+       { 0x468, 0x0C41 },
+       { 0x469, 0x203A },
+       { 0x46A, 0x0841 },
+       { 0x46B, 0x121F },
+       { 0x46C, 0x0446 },
+       { 0x46D, 0x0B6F },
+       { 0x46E, 0x0446 },
+       { 0x46F, 0x0818 },
+       { 0x470, 0x04C6 },
+       { 0x471, 0x05BB },
+       { 0x472, 0x04C6 },
+       { 0x473, 0x040F },
+       { 0x474, 0x04CE },
+       { 0x475, 0x0339 },
+       { 0x476, 0x05DF },
+       { 0x477, 0x028F },
+       { 0x478, 0x05DF },
+       { 0x479, 0x0209 },
+       { 0x47A, 0x05DF },
+       { 0x47B, 0x00CF },
+       { 0x47C, 0x05DF },
+       { 0x47D, 0x0001 },
+       { 0x47E, 0x07FF },
+};
+
+static const struct reg_sequence cs47l35_normal_impedance_patch[] = {
+       { 0x460, 0x0C40 },
+       { 0x461, 0xCD1A },
+       { 0x462, 0x0C40 },
+       { 0x463, 0xB53B },
+       { 0x464, 0x0C40 },
+       { 0x465, 0x7503 },
+       { 0x466, 0x0C40 },
+       { 0x467, 0x4A41 },
+       { 0x468, 0x0041 },
+       { 0x469, 0x3491 },
+       { 0x46A, 0x0841 },
+       { 0x46B, 0x1F50 },
+       { 0x46C, 0x0446 },
+       { 0x46D, 0x14ED },
+       { 0x46E, 0x0446 },
+       { 0x46F, 0x1455 },
+       { 0x470, 0x04C6 },
+       { 0x471, 0x1220 },
+       { 0x472, 0x04C6 },
+       { 0x473, 0x040F },
+       { 0x474, 0x04CE },
+       { 0x475, 0x0339 },
+       { 0x476, 0x05DF },
+       { 0x477, 0x028F },
+       { 0x478, 0x05DF },
+       { 0x479, 0x0209 },
+       { 0x47A, 0x05DF },
+       { 0x47B, 0x00CF },
+       { 0x47C, 0x05DF },
+       { 0x47D, 0x0001 },
+       { 0x47E, 0x07FF },
+};
+
+static const struct madera_hp_tuning cs47l35_hp_tuning[] = {
+       {
+               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 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, S_IRUGO, madera_extcon_show, NULL);
+
+inline void madera_extcon_report(struct madera_extcon *info,
+                                int which, bool attached)
+{
+       int ret;
+
+       dev_dbg(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);
+}
+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 CS47L35:
+               /*
+                * 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;
+               /* fall through to next step to set common variables */
+       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, 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_dbg(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_hohm_to_ohm(madera->hp_impedance_x100[0]) >
+                       info->pdata->hpdet_short_circuit_imp) && !ep_sel) {
+               ret = regmap_update_bits(madera->regmap,
+                                        MADERA_OUTPUT_ENABLES_1,
+                                        MADERA_OUT1L_ENA | MADERA_OUT1R_ENA,
+                                        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 CS47L35:
+       case CS47L85:
+       case WM1840:
+               return NULL;
+       case CS47L90:
+       case CS47L91:
+               switch (bias) {
+               case 0:
+               case 1:
+               case 2:
+               case 3:
+                       return "MICBIAS1";
+               case 4:
+               case 5:
+               case 6:
+               case 7:
+                       return "MICBIAS2";
+               default:
+                       return "MICVDD";
+               }
+               break;
+       default:
+               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 CS47L35:
+               switch (bias) {
+               case 1:
+                       return "MICBIAS1A";
+               case 2:
+                       return "MICBIAS1B";
+               case 3:
+                       return "MICBIAS2A";
+               default:
+                       return "MICVDD";
+               }
+       case CS47L85:
+       case WM1840:
+               switch (bias) {
+               case 1:
+                       return "MICBIAS1";
+               case 2:
+                       return "MICBIAS2";
+               case 3:
+                       return "MICBIAS3";
+               case 4:
+                       return "MICBIAS4";
+               default:
+                       return "MICVDD";
+               }
+       case CS47L90:
+       case CS47L91:
+               switch (bias) {
+               case 0:
+                       return "MICBIAS1A";
+               case 1:
+                       return "MICBIAS1B";
+               case 2:
+                       return "MICBIAS1C";
+               case 3:
+                       return "MICBIAS1D";
+               case 4:
+                       return "MICBIAS2A";
+               case 5:
+                       return "MICBIAS2B";
+               case 6:
+                       return "MICBIAS2C";
+               case 7:
+                       return "MICBIAS2D";
+               default:
+                       return "MICVDD";
+               }
+       default:
+               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_dbg(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 > 0)
+               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,
+                                  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_dbg(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_dbg(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 = 1;
+
+       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;
+
+       dev_dbg(info->dev, "HPDET read\n");
+
+       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;
+       }
+
+       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);
+                       val = 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 int madera_tune_headphone(struct madera_extcon *info, int reading)
+{
+       struct madera *madera = info->madera;
+       const struct madera_hp_tuning *tuning;
+       unsigned int short_imp = info->pdata->hpdet_short_circuit_imp;
+       int n_tunings;
+       int i, ret;
+
+       switch (madera->type) {
+       case CS47L35:
+               tuning = cs47l35_hp_tuning;
+               n_tunings = ARRAY_SIZE(cs47l35_hp_tuning);
+               break;
+       case CS47L85:
+       case WM1840:
+               tuning = cs47l85_hp_tuning;
+               n_tunings = ARRAY_SIZE(cs47l85_hp_tuning);
+               break;
+       case CS47L90:
+       case CS47L91:
+               tuning = cs47l90_hp_tuning;
+               n_tunings = ARRAY_SIZE(cs47l90_hp_tuning);
+               break;
+       default:
+               return 0;
+       }
+
+       if (reading <= madera_ohm_to_hohm(short_imp)) {
+               /* Headphones are always off here so just mark them */
+               dev_warn(info->dev, "Possible HP short, disabling\n");
+               return 0;
+       }
+
+       /*
+        * Check for tuning, we don't need to compare against the last
+        * tuning entry because we always select that if reading is not
+        * in range of the lower tunings
+        */
+       for (i = 0; i < n_tunings - 1; ++i) {
+               if (reading <= tuning[i].max_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, 0);
+
+       switch (madera->type) {
+       case CS47L35:
+       case CS47L85:
+       case WM1840:
+               break;
+       default:
+               regmap_update_bits(madera->regmap,
+                                  MADERA_MIC_DETECT_1_CONTROL_1,
+                                  MADERA_MICD_ENA_MASK, MADERA_MICD_ENA);
+               break;
+       }
+
+       regmap_update_bits(madera->regmap, MADERA_HEADPHONE_DETECT_1,
+                          MADERA_HP_POLL, MADERA_HP_POLL);
+}
+EXPORT_SYMBOL_GPL(madera_hpdet_restart);
+
+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, 0);
+
+       switch (madera->type) {
+       case CS47L35:
+       case CS47L85:
+       case WM1840:
+               /* Reset to default mode */
+               regmap_update_bits(madera->regmap,
+                                  MADERA_ACCESSORY_DETECT_MODE_1,
+                                  MADERA_ACCDET_MODE_MASK, 0);
+               break;
+       default:
+               break;
+       }
+
+       madera_extcon_hp_clamp(info, false);
+
+       pm_runtime_mark_last_busy(info->dev);
+       pm_runtime_put_autosuspend(info->dev);
+}
+EXPORT_SYMBOL_GPL(madera_hpdet_stop);
+
+int madera_hpdet_reading(struct madera_extcon *info, int val)
+{
+       dev_dbg(info->dev, "Reading HPDET %d\n", val);
+
+       if (val < 0)
+               return val;
+
+       madera_set_headphone_imp(info, val);
+
+       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);
+
+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_dbg(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);
+
+       madera_extcon_disable_micbias(info);
+
+       switch (madera->type) {
+       case CS47L35:
+       case CS47L85:
+       case WM1840:
+               /* Reset to default mode */
+               regmap_update_bits(madera->regmap,
+                                  MADERA_ACCESSORY_DETECT_MODE_1,
+                                  MADERA_ACCDET_MODE_MASK, 0);
+               break;
+       default:
+               break;
+       }
+
+       regulator_disable(info->micvdd);
+
+       dev_dbg(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);
+}
+
+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_dbg(info->dev, "Mic button detected\n");
+
+               for (i = 0; i < info->num_micd_ranges; i++)
+                       input_report_key(info->input,
+                                        info->micd_ranges[i].key, 0);
+
+               for (i = 0; i < info->num_micd_ranges; i++) {
+                       if (val <= info->micd_ranges[i].max) {
+                               key = info->micd_ranges[i].key;
+                               dev_dbg(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_dbg(info->dev, "Mic button released\n");
+
+               for (i = 0; i < info->num_micd_ranges; i++)
+                       input_report_key(info->input,
+                                        info->micd_ranges[i].key, 0);
+               input_sync(info->input);
+       }
+
+       return 0;
+}
+
+int madera_micd_button_reading(struct madera_extcon *info, 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_report(info, EXTCON_JACK_MICROPHONE, info->have_mic);
+
+       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_dbg(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_warn(info->dev, "Spurious HPDET IRQ\n");
+               madera_jds_start_timeout(info);
+               mutex_unlock(&info->lock);
+               return IRQ_NONE;
+       }
+
+       ret = madera_hpdet_read(info);
+       if (ret == -EAGAIN)
+               goto out;
+
+       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;
+
+       madera_jds_cancel_timeout(info);
+
+       mutex_lock(&info->lock);
+
+       /*
+        * Must check that we are in a micd state before accessing
+        * any codec registers
+        */
+       mode = madera_jds_get_mode(info);
+       switch (mode) {
+       case MADERA_ACCDET_MODE_MIC:
+       case MADERA_ACCDET_MODE_ADC:
+               break;
+       default:
+               goto spurious;
+       }
+
+       if (madera_jack_present(info, NULL) <= 0)
+               goto spurious;
+
+       switch (mode) {
+       case MADERA_ACCDET_MODE_MIC:
+               ret = madera_micd_read(info);
+               break;
+       case MADERA_ACCDET_MODE_ADC:
+               ret = madera_micd_adc_read(info);
+               break;
+       default:        /* we can't get here but compiler still warns */
+               ret = 0;
+               break;
+       }
+
+       if (ret == -EAGAIN)
+               goto out;
+
+       dev_dbg(info->dev, "Mic impedance %d ohms\n", ret);
+
+       madera_jds_reading(info, madera_ohm_to_hohm((unsigned int)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_micd_button = {
+       .mode = MADERA_ACCDET_MODE_MIC,
+       .start = madera_micd_start,
+       .reading = madera_micd_button_reading,
+       .stop = madera_micd_stop,
+};
+EXPORT_SYMBOL_GPL(madera_micd_button);
+
+const struct madera_jd_state madera_micd_adc_mic = {
+       .mode = MADERA_ACCDET_MODE_ADC,
+       .start = madera_micd_mic_start,
+       .restart = madera_micd_restart,
+       .reading = madera_micd_mic_reading,
+       .stop = madera_micd_mic_stop,
+
+       .timeout_ms = madera_micd_mic_timeout_ms,
+       .timeout = madera_micd_mic_timeout,
+};
+EXPORT_SYMBOL_GPL(madera_micd_adc_mic);
+
+const struct madera_jd_state madera_micd_microphone = {
+       .mode = MADERA_ACCDET_MODE_MIC,
+       .start = madera_micd_mic_start,
+       .reading = madera_micd_mic_reading,
+       .stop = madera_micd_mic_stop,
+
+       .timeout_ms = madera_micd_mic_timeout_ms,
+       .timeout = madera_micd_mic_timeout,
+};
+EXPORT_SYMBOL_GPL(madera_micd_microphone);
+
+static irqreturn_t madera_jackdet(int irq, void *data)
+{
+       struct madera_extcon *info = data;
+       struct madera *madera = info->madera;
+       unsigned int val, mask;
+       bool cancelled_state;
+       int i, present, ret;
+
+       dev_dbg(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) {
+               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");
+
+               madera_extcon_report(info, EXTCON_MECHANICAL, true);
+
+               info->have_mic = false;
+               info->jack_flips = 0;
+
+               if (info->pdata->custom_jd)
+                       madera_jds_set_state(info, info->pdata->custom_jd);
+               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");
+
+               madera_extcon_report(info, EXTCON_MECHANICAL, false);
+
+               info->have_mic = false;
+               info->micd_res_old = 0;
+               info->micd_debounce = 0;
+               info->micd_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++) {
+                       ret = extcon_set_state_sync(info->edev,
+                                                   madera_cable[i],
+                                                   false);
+                       if (ret != 0)
+                               dev_err(info->dev,
+                                       "Removal report failed: %d\n", ret);
+               }
+
+               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_get_micd_configs(struct madera_extcon *info,
+                                          struct fwnode_handle *node,
+                                          struct madera_accdet_pdata *pdata)
+{
+       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, 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(madera->dev,
+                                                       "cirrus,micd-pol",
+                                                       node,
+                                                       gpio_status,
+                                                       "cirrus,micd-pol");
+       if (IS_ERR(info->micd_pol_gpio)) {
+               dev_warn(info->dev,
+                        "Malformed cirrus,micd-pol-gpios ignored: %ld\n",
+                        PTR_ERR(info->micd_pol_gpio));
+               info->micd_pol_gpio = 0;
+       }
+}
+
+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", i + 1);
+               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 > 0)
+                       dev_dbg(info->dev, "micd_pol_gpio: %d\n",
+                               desc_to_gpio(info->micd_pol_gpio));
+               else
+                       dev_dbg(info->dev, "micd_pol_gpio: 0\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) - 2;
+               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;
+
+       /* quick exit if Madera irqchip driver hasn't completed probe */
+       if (!madera->irq_dev) {
+               dev_dbg(info->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);
+       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 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;
+       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);
+                       return ret;
+               }
+
+               info->micd_pol_gpio = gpio_to_desc(pdata->micd_pol_gpio);
+       } else {
+               ret = madera_extcon_get_device_pdata(info);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (!pdata->enabled || pdata->output == 0) {
+               return -ENODEV; /* no accdet output configured */
+       } else if (pdata->output != 1) {
+               dev_err(info->dev, "Only OUT1 is supported, OUT%d requested\n",
+                       pdata->output);
+               return -ENODEV;
+       }
+
+       if (pdata->hpdet_short_circuit_imp < MADERA_HP_SHORT_IMPEDANCE_MIN)
+               pdata->hpdet_short_circuit_imp = MADERA_HP_SHORT_IMPEDANCE_MIN;
+
+       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);
+               return ret;
+       }
+
+       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");
+               return -ENOMEM;
+       }
+
+       ret = devm_extcon_dev_register(&pdev->dev, info->edev);
+       if (ret < 0) {
+               dev_err(info->dev, "extcon_dev_register() failed: %d\n", ret);
+               return ret;
+       }
+
+       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:
+               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;
+               }
+       }
+
+       if (info->pdata->jd_use_jd2) {
+               jack_irq_rise = MADERA_IRQ_MICD_CLAMP_RISE;
+               jack_irq_fall = MADERA_IRQ_MICD_CLAMP_FALL;
+       } else {
+               jack_irq_rise = MADERA_IRQ_JD1_RISE;
+               jack_irq_fall = MADERA_IRQ_JD1_FALL;
+       }
+
+       ret = madera_request_irq(madera, jack_irq_rise,
+                                "JACKDET rise", madera_jackdet, info);
+       if (ret) {
+               dev_err(&pdev->dev,
+                       "Failed to get JACKDET rise IRQ: %d\n", ret);
+               goto err_input;
+       }
+
+       ret = madera_set_irq_wake(madera, jack_irq_rise, 1);
+       if (ret) {
+               dev_err(&pdev->dev,
+                       "Failed to set JD rise IRQ wake: %d\n", ret);
+               goto err_rise;
+       }
+
+       ret = madera_request_irq(madera, jack_irq_fall,
+                                "JACKDET fall", madera_jackdet, info);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret);
+               goto err_rise_wake;
+       }
+
+       ret = madera_set_irq_wake(madera, jack_irq_fall, 1);
+       if (ret) {
+               dev_err(&pdev->dev,
+                       "Failed to set JD fall IRQ wake: %d\n", ret);
+               goto err_fall;
+       }
+
+       ret = madera_request_irq(madera, MADERA_IRQ_MICDET1,
+                                "MICDET", madera_micdet, info);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret);
+               goto err_fall_wake;
+       }
+
+       ret = madera_request_irq(madera, MADERA_IRQ_HPDET,
+                                "HPDET", madera_hpdet_handler, info);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to get HPDET IRQ: %d\n", ret);
+               goto err_micdet;
+       }
+
+       if (info->pdata->jd_use_jd2) {
+               debounce_val = MADERA_JD1_DB | MADERA_JD2_DB;
+               analog_val = MADERA_JD1_ENA | MADERA_JD2_ENA;
+       } else {
+               debounce_val = MADERA_JD1_DB;
+               analog_val = MADERA_JD1_ENA;
+       }
+
+       regmap_update_bits(madera->regmap, MADERA_INTERRUPT_DEBOUNCE_7,
+                          debounce_val, debounce_val);
+       regmap_update_bits(madera->regmap, MADERA_JACK_DETECT_ANALOGUE,
+                          analog_val, analog_val);
+
+       ret = regulator_allow_bypass(info->micvdd, true);
+       if (ret)
+               dev_warn(info->dev,
+                        "Failed to set MICVDD to bypass: %d\n", ret);
+
+       pm_runtime_put(&pdev->dev);
+
+       ret = input_register_device(info->input);
+       if (ret) {
+               dev_err(&pdev->dev, "Can't register input device: %d\n", ret);
+               goto err_hpdet;
+       }
+
+       ret = device_create_file(&pdev->dev, &dev_attr_hp1_impedance);
+       if (ret)
+               dev_warn(&pdev->dev,
+                        "Failed to create sysfs node for hp_impedance %d\n",
+                        ret);
+
+       madera_extcon_dump_config(info);
+
+       return 0;
+
+err_hpdet:
+       madera_free_irq(madera, MADERA_IRQ_HPDET, info);
+err_micdet:
+       madera_free_irq(madera, MADERA_IRQ_MICDET1, info);
+err_fall_wake:
+       madera_set_irq_wake(madera, jack_irq_fall, 0);
+err_fall:
+       madera_free_irq(madera, jack_irq_fall, info);
+err_rise_wake:
+       madera_set_irq_wake(madera, jack_irq_rise, 0);
+err_rise:
+       madera_free_irq(madera, jack_irq_rise, info);
+err_input:
+err_register:
+       pm_runtime_disable(&pdev->dev);
+
+       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);
+
+       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);
+       kfree(info->hpdet_trims);
+
+       return 0;
+}
+
+static struct platform_driver madera_extcon_driver = {
+       .driver         = {
+               .name   = "madera-extcon",
+       },
+       .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");
diff --git a/include/dt-bindings/extcon/extcon-madera.h b/include/dt-bindings/extcon/extcon-madera.h
new file mode 100644 (file)
index 0000000..298753c
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Device Tree defines for Madera codec extcon driver
+ *
+ * Copyright 2016 Cirrus Logic
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef DT_BINDINGS_EXTCON_MADERA_H
+#define DT_BINDINGS_EXTCON_MADERA_H
+
+/* output clamp pin for cirrus,hpd-pins */
+#define MADERA_HPD_OUT_OUT1L           0
+#define MADERA_HPD_OUT_OUT1R           1
+#define MADERA_HPD_OUT_OUT2L           2
+#define MADERA_HPD_OUT_OUT2R           3
+
+/* Sense pin selection for cirrus,hpd-pins */
+#define MADERA_HPD_SENSE_MICDET1       0
+#define MADERA_HPD_SENSE_MICDET2       1
+#define MADERA_HPD_SENSE_MICDET3       2
+#define MADERA_HPD_SENSE_MICDET4       3
+#define MADERA_HPD_SENSE_HPDET1                4
+#define MADERA_HPD_SENSE_HPDET2                5
+#define MADERA_HPD_SENSE_JD1           6
+#define MADERA_HPD_SENSE_JD2           7
+
+/* source setting for cirrus,micd-configs (cs47l35, cs47l85) */
+#define MADERA_ACCD_SENSE_MICDET1      0
+#define MADERA_ACCD_SENSE_MICDET2      1
+
+/* bias setting for  cirrus,micd-configs (cs47l35, cs47l85) */
+#define MADERA_ACCD_BIAS_SRC_MICBIAS1  1
+#define MADERA_ACCD_BIAS_SRC_MICBIAS2  2
+
+/* source setting for cirrus,micd-configs */
+#define MADERA_MICD1_SENSE_MICDET1     0
+#define MADERA_MICD1_SENSE_MICDET2     1
+#define MADERA_MICD1_SENSE_MICDET3     2
+#define MADERA_MICD1_SENSE_MICDET4     3
+
+/* ground setting for cirrus,micd-configs */
+#define MADERA_MICD1_GND_MICDET1       0
+#define MADERA_MICD1_GND_MICDET2       1
+#define MADERA_MICD1_GND_MICDET3       2
+#define MADERA_MICD1_GND_MICDET4       3
+
+/* bias setting for cirrus,micd-configs */
+#define MADERA_MICD_BIAS_SRC_MICBIAS1A 0x0
+#define MADERA_MICD_BIAS_SRC_MICBIAS1B 0x1
+#define MADERA_MICD_BIAS_SRC_MICBIAS1C 0x2
+#define MADERA_MICD_BIAS_SRC_MICBIAS1D 0x3
+#define MADERA_MICD_BIAS_SRC_MICBIAS2A 0x4
+#define MADERA_MICD_BIAS_SRC_MICBIAS2B 0x5
+#define MADERA_MICD_BIAS_SRC_MICBIAS2C 0x6
+#define MADERA_MICD_BIAS_SRC_MICBIAS2D 0x7
+#define MADERA_MICD_BIAS_SRC_MICVDD    0xF
+
+/* HP gnd setting for cirrus,micd-configs */
+#define MADERA_HPD_GND_HPOUTFB1                0
+#define MADERA_HPD_GND_HPOUTFB2                1
+#define MADERA_HPD_GND_HPOUTFB3                2
+#define MADERA_HPD_GND_HPOUTFB4                3
+
+#endif
+
diff --git a/include/linux/extcon/extcon-madera-pdata.h b/include/linux/extcon/extcon-madera-pdata.h
new file mode 100644 (file)
index 0000000..012cad8
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Platform data for Madera codecs
+ *
+ * Copyright 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.
+ */
+
+#ifndef EXTCON_MADERA_PDATA_H
+#define EXTCON_MADERA_PDATA_H
+
+#include <linux/kernel.h>
+
+#define MADERA_MAX_ACCESSORY           1
+
+/* Treat INT_MAX impedance as open circuit */
+#define MADERA_HP_Z_OPEN               INT_MAX
+
+struct madera_jd_state;
+
+/** Bias and sense configuration for probing MICDET */
+struct madera_micd_config {
+       /** cs47l35, cs47l85, WM1840: value of the ACCDET_SRC field
+        *  other codecs: value of MICDn_SENSE_SEL field
+        * See datasheet for field values
+        */
+       u32 src;
+
+       /** cs47l35, cs47l85, wm1840: unused
+        *  other codecs: value of MICDn_GND_SEL
+        *
+        * See datasheet for field values
+        */
+       u32 gnd;
+
+       /** MICBIAS number to enable */
+       u32 bias;
+
+       /** State of polarity gpio during probe (true == gpio asserted) */
+       bool gpio;
+
+       /** cs47l35, cs47l85, wm1840: unused
+        *  other codecs: value of HPn_GND_SEL
+        *
+        * See datasheet for field values
+        */
+       u32 hp_gnd;
+};
+
+struct madera_micd_range {
+       int max;  /** Ohms */
+       int key;  /** Key to report to input layer */
+};
+
+/** pdata sub-structure for accessory detect configuration */
+struct madera_accdet_pdata {
+       /** set to true to enable this accessory detect */
+       bool enabled;
+
+       /** which output this accdet is for (1 = OUT1, ...) */
+       u32 output;
+
+       /** Time in milliseconds to keep wake lock during jack detection */
+       u32 jd_wake_time;
+
+       /** Set whether JD2 is used for jack detection */
+       bool jd_use_jd2;
+
+       /** set to true if jackdet contact opens on insert */
+       bool jd_invert;
+
+       /** If non-zero don't run headphone detection, just report this value
+        * Specified as hundredths-of-an-ohm, that is (ohms * 100)
+        */
+       u32 fixed_hpdet_imp_x100;
+
+       /** Impedance of external series resistor on hpdet.
+        * Specified as hundredths-of-an-ohm, that is (ohms * 100)
+        */
+       u32 hpdet_ext_res_x100;
+
+       /** If non-zero, specifies the maximum impedance in ohms
+        * that will be considered as a short circuit.
+        */
+       u32 hpdet_short_circuit_imp;
+
+       /**
+        * Channel to use for headphone detection, valid values are 0 for
+        * left and 1 for right
+        */
+       u32 hpdet_channel;
+
+       /** Extra debounce timeout during initial mic detect (milliseconds) */
+       u32 micd_detect_debounce_ms;
+
+       /** Extra software debounces during button detection */
+       u32 micd_manual_debounce;
+
+       /** GPIO for mic detection polarity */
+       int micd_pol_gpio;
+
+       /** Mic detect ramp rate */
+       u32 micd_bias_start_time;
+
+       /** Setting for the codec MICD_RATE field
+        * See the  datasheet for documentation of the field values
+        */
+       u32 micd_rate;
+
+       /** Mic detect debounce level */
+       u32 micd_dbtime;
+
+       /** Mic detect timeout (milliseconds) */
+       u32 micd_timeout_ms;
+
+       /** Mic detect clamp function */
+       u32 micd_clamp_mode;
+
+       /** Force MICBIAS on for mic detect */
+       bool micd_force_micbias;
+
+       /** Declare an open circuit as a 4 pole jack */
+       bool micd_open_circuit_declare;
+
+       /** Use software comparison to determine mic presence */
+       bool micd_software_compare;
+
+       /** Mic detect level parameters */
+       const struct madera_micd_range *micd_ranges;
+       int num_micd_ranges;
+
+       /** Headset polarity configurations */
+       const struct madera_micd_config *micd_configs;
+       int num_micd_configs;
+
+       /**
+        * 4 entries:
+        * [HPL_clamp_pin, HPL_impedance_measurement_pin,
+        * HPR_clamp_pin, HPR_impedance_measurement_pin]
+        *
+        * 0 = use default setting
+        * values 0x1..0xffff = set to this value
+        * >0xffff = set to 0
+        */
+       u32 hpd_pins[4];
+
+       /** Override the normal jack detection */
+       const struct madera_jd_state *custom_jd;
+};
+
+#endif
diff --git a/include/linux/extcon/extcon-madera.h b/include/linux/extcon/extcon-madera.h
new file mode 100644 (file)
index 0000000..a9ea89d
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * extcon-madera.h - public extcon driver API 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.
+ */
+
+#ifndef EXTCON_MADERA_H
+#define EXTCON_MADERA_H
+
+#include <linux/mfd/madera/registers.h>
+
+/* Conversion between ohms and hundredths of an ohm. */
+static inline unsigned int madera_hohm_to_ohm(unsigned int hohms)
+{
+       if (hohms == INT_MAX)
+               return hohms;
+       else
+               return (hohms + 50) / 100;
+}
+
+static inline unsigned int madera_ohm_to_hohm(unsigned int ohms)
+{
+       if (ohms >= (INT_MAX / 100))
+               return INT_MAX;
+       else
+               return ohms * 100;
+}
+
+#define MADERA_MICD_LVL_1_TO_7 \
+               (MADERA_MICD_LVL_1 | MADERA_MICD_LVL_2 | \
+                MADERA_MICD_LVL_3 | MADERA_MICD_LVL_4 | \
+                MADERA_MICD_LVL_5 | MADERA_MICD_LVL_6 | \
+                MADERA_MICD_LVL_7)
+
+#define MADERA_MICD_LVL_0_TO_7 (MADERA_MICD_LVL_0 | MADERA_MICD_LVL_1_TO_7)
+
+#define MADERA_MICD_LVL_0_TO_8 (MADERA_MICD_LVL_0_TO_7 | MADERA_MICD_LVL_8)
+
+/* Notify data structure for MADERA_NOTIFY_HPDET */
+struct madera_hpdet_notify_data {
+       unsigned int impedance_x100;    /* ohms * 100 */
+};
+
+/* Notify data structure for MADERA_NOTIFY_MICDET */
+struct madera_micdet_notify_data {
+       unsigned int impedance_x100;    /* ohms * 100 */
+       bool present;
+       int out_num;                    /* 1 = OUT1, 2 = OUT2 */
+};
+
+struct madera_micd_bias {
+       unsigned int bias;
+       bool enabled;
+};
+
+struct madera_hpdet_trims {
+       int off_x4;
+       int grad_x4;
+};
+
+struct madera_extcon {
+       struct device *dev;
+       struct madera *madera;
+       const struct madera_accdet_pdata *pdata;
+       struct mutex lock;
+       struct regulator *micvdd;
+       struct input_dev *input;
+       struct extcon_dev *edev;
+       struct gpio_desc *micd_pol_gpio;
+
+       u16 last_jackdet;
+       int hp_tuning_level;
+
+       const struct madera_hpdet_calibration_data *hpdet_ranges;
+       int num_hpdet_ranges;
+       const struct madera_hpdet_trims *hpdet_trims;
+
+       int micd_mode;
+       const struct madera_micd_config *micd_modes;
+       int num_micd_modes;
+
+       const struct madera_micd_range *micd_ranges;
+       int num_micd_ranges;
+
+       int micd_res_old;
+       int micd_debounce;
+       int micd_count;
+
+       struct completion manual_mic_completion;
+
+       struct delayed_work micd_detect_work;
+
+       bool have_mic;
+       bool detecting;
+       int jack_flips;
+
+       const struct madera_jd_state *state;
+       const struct madera_jd_state *old_state;
+       struct delayed_work state_timeout_work;
+
+       struct madera_micd_bias micd_bias;
+};
+
+enum madera_accdet_mode {
+       MADERA_ACCDET_MODE_MIC,
+       MADERA_ACCDET_MODE_HPL,
+       MADERA_ACCDET_MODE_HPR,
+       MADERA_ACCDET_MODE_HPM,
+       MADERA_ACCDET_MODE_ADC,
+       MADERA_ACCDET_MODE_INVALID,
+};
+
+struct madera_jd_state {
+       enum madera_accdet_mode mode;
+
+       int (*start)(struct madera_extcon *);
+       void (*restart)(struct madera_extcon *);
+       int (*reading)(struct madera_extcon *, int);
+       void (*stop)(struct madera_extcon *);
+
+       int (*timeout_ms)(struct madera_extcon *);
+       void (*timeout)(struct madera_extcon *);
+};
+
+int madera_jds_set_state(struct madera_extcon *info,
+                        const struct madera_jd_state *new_state);
+
+void madera_set_headphone_imp(struct madera_extcon *info, int ohms_x100);
+
+extern const struct madera_jd_state madera_hpdet_left;
+extern const struct madera_jd_state madera_hpdet_right;
+extern const struct madera_jd_state madera_micd_button;
+extern const struct madera_jd_state madera_micd_microphone;
+extern const struct madera_jd_state madera_micd_adc_mic;
+
+int madera_hpdet_start(struct madera_extcon *info);
+void madera_hpdet_restart(struct madera_extcon *info);
+void madera_hpdet_stop(struct madera_extcon *info);
+int madera_hpdet_reading(struct madera_extcon *info, int val);
+
+int madera_micd_start(struct madera_extcon *info);
+void madera_micd_stop(struct madera_extcon *info);
+int madera_micd_button_reading(struct madera_extcon *info, int val);
+
+int madera_micd_mic_start(struct madera_extcon *info);
+void madera_micd_mic_stop(struct madera_extcon *info);
+int madera_micd_mic_reading(struct madera_extcon *info, int val);
+int madera_micd_mic_timeout_ms(struct madera_extcon *info);
+void madera_micd_mic_timeout(struct madera_extcon *info);
+
+void madera_extcon_report(struct madera_extcon *info, int which, bool attached);
+
+#endif
index 6609e3cac4a6a556fddf16e9d9d444fb4b4ca5b7..f3af76622228e4a57538d447560b4bf4ddd99120 100644 (file)
@@ -159,6 +159,7 @@ struct madera {
        unsigned int out_clamp[MADERA_MAX_OUTPUT];
        unsigned int out_shorted[MADERA_MAX_OUTPUT];
        unsigned int hp_ena;
+       unsigned int hp_impedance_x100[MADERA_MAX_ACCESSORY];
 
        struct snd_soc_dapm_context *dapm;
 
index eeebe6ce63c0f82a84dcbbc188720534bb47efe9..4139e9b444a16a3be1f6cbebfd22b285fa200117 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/regulator/arizona-micsupp.h>
 #include <linux/regulator/machine.h>
 #include <sound/madera-pdata.h>
+#include <linux/extcon/extcon-madera-pdata.h>
 
 #define MADERA_MAX_MICBIAS             4
 #define MADERA_MAX_CHILD_MICBIAS       4
@@ -85,6 +86,9 @@ struct madera_pdata {
        struct madera_codec_pdata codec;
 
        u32 gpsw[MADERA_MAX_GPSW];
+
+       /** Accessory detection configurations */
+       struct madera_accdet_pdata accdet[MADERA_MAX_ACCESSORY];
 };
 
 #endif