Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

extcon: intel-cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver

Add a driver for charger detection / control on the Intel Cherrytrail
Whiskey Cove PMIC.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>

authored by

Hans de Goede and committed by
Chanwoo Choi
db0f3baa 01944321

+361
+7
drivers/extcon/Kconfig
··· 52 52 This ACPI device is typically found on Intel Baytrail or Cherrytrail 53 53 based tablets, or other Baytrail / Cherrytrail devices. 54 54 55 + config EXTCON_INTEL_CHT_WC 56 + tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver" 57 + depends on INTEL_SOC_PMIC_CHTWC 58 + help 59 + Say Y here to enable extcon support for charger detection / control 60 + on the Intel Cherrytrail Whiskey Cove PMIC. 61 + 55 62 config EXTCON_MAX14577 56 63 tristate "Maxim MAX14577/77836 EXTCON Support" 57 64 depends on MFD_MAX14577
+1
drivers/extcon/Makefile
··· 9 9 obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o 10 10 obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o 11 11 obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o 12 + obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o 12 13 obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o 13 14 obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o 14 15 obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
+353
drivers/extcon/extcon-intel-cht-wc.c
··· 1 + /* 2 + * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC 3 + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> 4 + * 5 + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: 6 + * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. 7 + * 8 + * This program is free software; you can redistribute it and/or modify it 9 + * under the terms and conditions of the GNU General Public License, 10 + * version 2, as published by the Free Software Foundation. 11 + * 12 + * This program is distributed in the hope it will be useful, but WITHOUT 13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 + * more details. 16 + */ 17 + 18 + #include <linux/extcon.h> 19 + #include <linux/interrupt.h> 20 + #include <linux/kernel.h> 21 + #include <linux/mfd/intel_soc_pmic.h> 22 + #include <linux/module.h> 23 + #include <linux/platform_device.h> 24 + #include <linux/regmap.h> 25 + #include <linux/slab.h> 26 + 27 + #define CHT_WC_PHYCTRL 0x5e07 28 + 29 + #define CHT_WC_CHGRCTRL0 0x5e16 30 + #define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0) 31 + #define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1) 32 + #define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2) 33 + #define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3) 34 + #define CHT_WC_CHGRCTRL0_TTLCK_MASK BIT(4) 35 + #define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK BIT(5) 36 + #define CHT_WC_CHGRCTRL0_DBPOFF_MASK BIT(6) 37 + #define CHT_WC_CHGRCTRL0_WDT_NOKICK BIT(7) 38 + 39 + #define CHT_WC_CHGRCTRL1 0x5e17 40 + 41 + #define CHT_WC_USBSRC 0x5e29 42 + #define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0) 43 + #define CHT_WC_USBSRC_STS_SUCCESS 2 44 + #define CHT_WC_USBSRC_STS_FAIL 3 45 + #define CHT_WC_USBSRC_TYPE_SHIFT 2 46 + #define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2) 47 + #define CHT_WC_USBSRC_TYPE_NONE 0 48 + #define CHT_WC_USBSRC_TYPE_SDP 1 49 + #define CHT_WC_USBSRC_TYPE_DCP 2 50 + #define CHT_WC_USBSRC_TYPE_CDP 3 51 + #define CHT_WC_USBSRC_TYPE_ACA 4 52 + #define CHT_WC_USBSRC_TYPE_SE1 5 53 + #define CHT_WC_USBSRC_TYPE_MHL 6 54 + #define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN 7 55 + #define CHT_WC_USBSRC_TYPE_OTHER 8 56 + #define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9 57 + 58 + #define CHT_WC_PWRSRC_IRQ 0x6e03 59 + #define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f 60 + #define CHT_WC_PWRSRC_STS 0x6e1e 61 + #define CHT_WC_PWRSRC_VBUS BIT(0) 62 + #define CHT_WC_PWRSRC_DC BIT(1) 63 + #define CHT_WC_PWRSRC_BAT BIT(2) 64 + #define CHT_WC_PWRSRC_ID_GND BIT(3) 65 + #define CHT_WC_PWRSRC_ID_FLOAT BIT(4) 66 + 67 + enum cht_wc_usb_id { 68 + USB_ID_OTG, 69 + USB_ID_GND, 70 + USB_ID_FLOAT, 71 + USB_RID_A, 72 + USB_RID_B, 73 + USB_RID_C, 74 + }; 75 + 76 + enum cht_wc_mux_select { 77 + MUX_SEL_PMIC = 0, 78 + MUX_SEL_SOC, 79 + }; 80 + 81 + static const unsigned int cht_wc_extcon_cables[] = { 82 + EXTCON_USB, 83 + EXTCON_USB_HOST, 84 + EXTCON_CHG_USB_SDP, 85 + EXTCON_CHG_USB_CDP, 86 + EXTCON_CHG_USB_DCP, 87 + EXTCON_CHG_USB_ACA, 88 + EXTCON_NONE, 89 + }; 90 + 91 + struct cht_wc_extcon_data { 92 + struct device *dev; 93 + struct regmap *regmap; 94 + struct extcon_dev *edev; 95 + unsigned int previous_cable; 96 + }; 97 + 98 + static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) 99 + { 100 + if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND) 101 + return USB_ID_GND; 102 + if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT) 103 + return USB_ID_FLOAT; 104 + 105 + /* 106 + * Once we have iio support for the gpadc we should read the USBID 107 + * gpadc channel here and determine ACA role based on that. 108 + */ 109 + return USB_ID_FLOAT; 110 + } 111 + 112 + static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext) 113 + { 114 + int ret, usbsrc, status; 115 + unsigned long timeout; 116 + 117 + /* Charger detection can take upto 600ms, wait 800ms max. */ 118 + timeout = jiffies + msecs_to_jiffies(800); 119 + do { 120 + ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc); 121 + if (ret) { 122 + dev_err(ext->dev, "Error reading usbsrc: %d\n", ret); 123 + return ret; 124 + } 125 + 126 + status = usbsrc & CHT_WC_USBSRC_STS_MASK; 127 + if (status == CHT_WC_USBSRC_STS_SUCCESS || 128 + status == CHT_WC_USBSRC_STS_FAIL) 129 + break; 130 + 131 + msleep(50); /* Wait a bit before retrying */ 132 + } while (time_before(jiffies, timeout)); 133 + 134 + if (status != CHT_WC_USBSRC_STS_SUCCESS) { 135 + if (status == CHT_WC_USBSRC_STS_FAIL) 136 + dev_warn(ext->dev, "Could not detect charger type\n"); 137 + else 138 + dev_warn(ext->dev, "Timeout detecting charger type\n"); 139 + return EXTCON_CHG_USB_SDP; /* Save fallback */ 140 + } 141 + 142 + usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; 143 + switch (usbsrc) { 144 + default: 145 + dev_warn(ext->dev, 146 + "Unhandled charger type %d, defaulting to SDP\n", 147 + ret); 148 + /* Fall through, treat as SDP */ 149 + case CHT_WC_USBSRC_TYPE_SDP: 150 + case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN: 151 + case CHT_WC_USBSRC_TYPE_OTHER: 152 + return EXTCON_CHG_USB_SDP; 153 + case CHT_WC_USBSRC_TYPE_CDP: 154 + return EXTCON_CHG_USB_CDP; 155 + case CHT_WC_USBSRC_TYPE_DCP: 156 + case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: 157 + case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */ 158 + return EXTCON_CHG_USB_DCP; 159 + case CHT_WC_USBSRC_TYPE_ACA: 160 + return EXTCON_CHG_USB_ACA; 161 + } 162 + } 163 + 164 + static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state) 165 + { 166 + int ret; 167 + 168 + ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state); 169 + if (ret) 170 + dev_err(ext->dev, "Error writing phyctrl: %d\n", ret); 171 + } 172 + 173 + /* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ 174 + static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, 175 + unsigned int cable, bool state) 176 + { 177 + extcon_set_state_sync(ext->edev, cable, state); 178 + if (cable == EXTCON_CHG_USB_SDP) 179 + extcon_set_state_sync(ext->edev, EXTCON_USB, state); 180 + } 181 + 182 + static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) 183 + { 184 + int ret, pwrsrc_sts, id; 185 + unsigned int cable = EXTCON_NONE; 186 + 187 + ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); 188 + if (ret) { 189 + dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); 190 + return; 191 + } 192 + 193 + id = cht_wc_extcon_get_id(ext, pwrsrc_sts); 194 + if (id == USB_ID_GND) { 195 + /* The 5v boost causes a false VBUS / SDP detect, skip */ 196 + goto charger_det_done; 197 + } 198 + 199 + /* Plugged into a host/charger or not connected? */ 200 + if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) { 201 + /* Route D+ and D- to PMIC for future charger detection */ 202 + cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); 203 + goto set_state; 204 + } 205 + 206 + ret = cht_wc_extcon_get_charger(ext); 207 + if (ret >= 0) 208 + cable = ret; 209 + 210 + charger_det_done: 211 + /* Route D+ and D- to SoC for the host or gadget controller */ 212 + cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC); 213 + 214 + set_state: 215 + if (cable != ext->previous_cable) { 216 + cht_wc_extcon_set_state(ext, cable, true); 217 + cht_wc_extcon_set_state(ext, ext->previous_cable, false); 218 + ext->previous_cable = cable; 219 + } 220 + 221 + extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, 222 + id == USB_ID_GND || id == USB_RID_A); 223 + } 224 + 225 + static irqreturn_t cht_wc_extcon_isr(int irq, void *data) 226 + { 227 + struct cht_wc_extcon_data *ext = data; 228 + int ret, irqs; 229 + 230 + ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs); 231 + if (ret) { 232 + dev_err(ext->dev, "Error reading irqs: %d\n", ret); 233 + return IRQ_NONE; 234 + } 235 + 236 + cht_wc_extcon_pwrsrc_event(ext); 237 + 238 + ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs); 239 + if (ret) { 240 + dev_err(ext->dev, "Error writing irqs: %d\n", ret); 241 + return IRQ_NONE; 242 + } 243 + 244 + return IRQ_HANDLED; 245 + } 246 + 247 + static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable) 248 + { 249 + int ret, mask, val; 250 + 251 + mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK; 252 + val = enable ? mask : 0; 253 + ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val); 254 + if (ret) 255 + dev_err(ext->dev, "Error setting sw control: %d\n", ret); 256 + 257 + return ret; 258 + } 259 + 260 + static int cht_wc_extcon_probe(struct platform_device *pdev) 261 + { 262 + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); 263 + struct cht_wc_extcon_data *ext; 264 + int irq, ret; 265 + 266 + irq = platform_get_irq(pdev, 0); 267 + if (irq < 0) 268 + return irq; 269 + 270 + ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL); 271 + if (!ext) 272 + return -ENOMEM; 273 + 274 + ext->dev = &pdev->dev; 275 + ext->regmap = pmic->regmap; 276 + ext->previous_cable = EXTCON_NONE; 277 + 278 + /* Initialize extcon device */ 279 + ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables); 280 + if (IS_ERR(ext->edev)) 281 + return PTR_ERR(ext->edev); 282 + 283 + /* Enable sw control */ 284 + ret = cht_wc_extcon_sw_control(ext, true); 285 + if (ret) 286 + return ret; 287 + 288 + /* Register extcon device */ 289 + ret = devm_extcon_dev_register(ext->dev, ext->edev); 290 + if (ret) { 291 + dev_err(ext->dev, "Error registering extcon device: %d\n", ret); 292 + goto disable_sw_control; 293 + } 294 + 295 + /* Route D+ and D- to PMIC for initial charger detection */ 296 + cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); 297 + 298 + /* Get initial state */ 299 + cht_wc_extcon_pwrsrc_event(ext); 300 + 301 + ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr, 302 + IRQF_ONESHOT, pdev->name, ext); 303 + if (ret) { 304 + dev_err(ext->dev, "Error requesting interrupt: %d\n", ret); 305 + goto disable_sw_control; 306 + } 307 + 308 + /* Unmask irqs */ 309 + ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, 310 + (int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND | 311 + CHT_WC_PWRSRC_ID_FLOAT)); 312 + if (ret) { 313 + dev_err(ext->dev, "Error writing irq-mask: %d\n", ret); 314 + goto disable_sw_control; 315 + } 316 + 317 + platform_set_drvdata(pdev, ext); 318 + 319 + return 0; 320 + 321 + disable_sw_control: 322 + cht_wc_extcon_sw_control(ext, false); 323 + return ret; 324 + } 325 + 326 + static int cht_wc_extcon_remove(struct platform_device *pdev) 327 + { 328 + struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); 329 + 330 + cht_wc_extcon_sw_control(ext, false); 331 + 332 + return 0; 333 + } 334 + 335 + static const struct platform_device_id cht_wc_extcon_table[] = { 336 + { .name = "cht_wcove_pwrsrc" }, 337 + {}, 338 + }; 339 + MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table); 340 + 341 + static struct platform_driver cht_wc_extcon_driver = { 342 + .probe = cht_wc_extcon_probe, 343 + .remove = cht_wc_extcon_remove, 344 + .id_table = cht_wc_extcon_table, 345 + .driver = { 346 + .name = "cht_wcove_pwrsrc", 347 + }, 348 + }; 349 + module_platform_driver(cht_wc_extcon_driver); 350 + 351 + MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver"); 352 + MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 353 + MODULE_LICENSE("GPL v2");