18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC 48c2ecf20Sopenharmony_ci * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: 78c2ecf20Sopenharmony_ci * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/extcon-provider.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/kernel.h> 138c2ecf20Sopenharmony_ci#include <linux/mfd/intel_soc_pmic.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/regmap.h> 188c2ecf20Sopenharmony_ci#include <linux/slab.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "extcon-intel.h" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#define CHT_WC_PHYCTRL 0x5e07 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0 0x5e16 258c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0) 268c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1) 278c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2) 288c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3) 298c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_TTLCK BIT(4) 308c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_CCSM_OFF BIT(5) 318c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_DBPOFF BIT(6) 328c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL0_CHR_WDT_NOKICK BIT(7) 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1 0x5e17 358c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_100 BIT(0) 368c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_150 BIT(1) 378c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_500 BIT(2) 388c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_900 BIT(3) 398c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_1500 BIT(4) 408c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FTEMP_EVENT BIT(5) 418c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_OTGMODE BIT(6) 428c2ecf20Sopenharmony_ci#define CHT_WC_CHGRCTRL1_DBPEN BIT(7) 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC 0x5e29 458c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0) 468c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_STS_SUCCESS 2 478c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_STS_FAIL 3 488c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_SHIFT 2 498c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2) 508c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_NONE 0 518c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_SDP 1 528c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_DCP 2 538c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_CDP 3 548c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_ACA 4 558c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_SE1 5 568c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_MHL 6 578c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_FLOATING 7 588c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_OTHER 8 598c2ecf20Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci#define CHT_WC_CHGDISCTRL 0x5e2f 628c2ecf20Sopenharmony_ci#define CHT_WC_CHGDISCTRL_OUT BIT(0) 638c2ecf20Sopenharmony_ci/* 0 - open drain, 1 - regular push-pull output */ 648c2ecf20Sopenharmony_ci#define CHT_WC_CHGDISCTRL_DRV BIT(4) 658c2ecf20Sopenharmony_ci/* 0 - pin is controlled by SW, 1 - by HW */ 668c2ecf20Sopenharmony_ci#define CHT_WC_CHGDISCTRL_FN BIT(6) 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_IRQ 0x6e03 698c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f 708c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_STS 0x6e1e 718c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_VBUS BIT(0) 728c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_DC BIT(1) 738c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_BATT BIT(2) 748c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_USBID_MASK GENMASK(4, 3) 758c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_USBID_SHIFT 3 768c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_RID_ACA 0 778c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_RID_GND 1 788c2ecf20Sopenharmony_ci#define CHT_WC_PWRSRC_RID_FLOAT 2 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO 0x6e2d 818c2ecf20Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0) 828c2ecf20Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO_DRV_OD BIT(4) 838c2ecf20Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO_DIR_OUT BIT(5) 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cienum cht_wc_mux_select { 868c2ecf20Sopenharmony_ci MUX_SEL_PMIC = 0, 878c2ecf20Sopenharmony_ci MUX_SEL_SOC, 888c2ecf20Sopenharmony_ci}; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic const unsigned int cht_wc_extcon_cables[] = { 918c2ecf20Sopenharmony_ci EXTCON_USB, 928c2ecf20Sopenharmony_ci EXTCON_USB_HOST, 938c2ecf20Sopenharmony_ci EXTCON_CHG_USB_SDP, 948c2ecf20Sopenharmony_ci EXTCON_CHG_USB_CDP, 958c2ecf20Sopenharmony_ci EXTCON_CHG_USB_DCP, 968c2ecf20Sopenharmony_ci EXTCON_CHG_USB_ACA, 978c2ecf20Sopenharmony_ci EXTCON_NONE, 988c2ecf20Sopenharmony_ci}; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistruct cht_wc_extcon_data { 1018c2ecf20Sopenharmony_ci struct device *dev; 1028c2ecf20Sopenharmony_ci struct regmap *regmap; 1038c2ecf20Sopenharmony_ci struct extcon_dev *edev; 1048c2ecf20Sopenharmony_ci unsigned int previous_cable; 1058c2ecf20Sopenharmony_ci bool usb_host; 1068c2ecf20Sopenharmony_ci}; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci switch ((pwrsrc_sts & CHT_WC_PWRSRC_USBID_MASK) >> CHT_WC_PWRSRC_USBID_SHIFT) { 1118c2ecf20Sopenharmony_ci case CHT_WC_PWRSRC_RID_GND: 1128c2ecf20Sopenharmony_ci return INTEL_USB_ID_GND; 1138c2ecf20Sopenharmony_ci case CHT_WC_PWRSRC_RID_FLOAT: 1148c2ecf20Sopenharmony_ci return INTEL_USB_ID_FLOAT; 1158c2ecf20Sopenharmony_ci case CHT_WC_PWRSRC_RID_ACA: 1168c2ecf20Sopenharmony_ci default: 1178c2ecf20Sopenharmony_ci /* 1188c2ecf20Sopenharmony_ci * Once we have IIO support for the GPADC we should read 1198c2ecf20Sopenharmony_ci * the USBID GPADC channel here and determine ACA role 1208c2ecf20Sopenharmony_ci * based on that. 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_ci return INTEL_USB_ID_FLOAT; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext, 1278c2ecf20Sopenharmony_ci bool ignore_errors) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci int ret, usbsrc, status; 1308c2ecf20Sopenharmony_ci unsigned long timeout; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci /* Charger detection can take upto 600ms, wait 800ms max. */ 1338c2ecf20Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(800); 1348c2ecf20Sopenharmony_ci do { 1358c2ecf20Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc); 1368c2ecf20Sopenharmony_ci if (ret) { 1378c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error reading usbsrc: %d\n", ret); 1388c2ecf20Sopenharmony_ci return ret; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci status = usbsrc & CHT_WC_USBSRC_STS_MASK; 1428c2ecf20Sopenharmony_ci if (status == CHT_WC_USBSRC_STS_SUCCESS || 1438c2ecf20Sopenharmony_ci status == CHT_WC_USBSRC_STS_FAIL) 1448c2ecf20Sopenharmony_ci break; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci msleep(50); /* Wait a bit before retrying */ 1478c2ecf20Sopenharmony_ci } while (time_before(jiffies, timeout)); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci if (status != CHT_WC_USBSRC_STS_SUCCESS) { 1508c2ecf20Sopenharmony_ci if (ignore_errors) 1518c2ecf20Sopenharmony_ci return EXTCON_CHG_USB_SDP; /* Save fallback */ 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if (status == CHT_WC_USBSRC_STS_FAIL) 1548c2ecf20Sopenharmony_ci dev_warn(ext->dev, "Could not detect charger type\n"); 1558c2ecf20Sopenharmony_ci else 1568c2ecf20Sopenharmony_ci dev_warn(ext->dev, "Timeout detecting charger type\n"); 1578c2ecf20Sopenharmony_ci return EXTCON_CHG_USB_SDP; /* Save fallback */ 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; 1618c2ecf20Sopenharmony_ci switch (usbsrc) { 1628c2ecf20Sopenharmony_ci default: 1638c2ecf20Sopenharmony_ci dev_warn(ext->dev, 1648c2ecf20Sopenharmony_ci "Unhandled charger type %d, defaulting to SDP\n", 1658c2ecf20Sopenharmony_ci ret); 1668c2ecf20Sopenharmony_ci return EXTCON_CHG_USB_SDP; 1678c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_SDP: 1688c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_FLOATING: 1698c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_OTHER: 1708c2ecf20Sopenharmony_ci return EXTCON_CHG_USB_SDP; 1718c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_CDP: 1728c2ecf20Sopenharmony_ci return EXTCON_CHG_USB_CDP; 1738c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_DCP: 1748c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: 1758c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */ 1768c2ecf20Sopenharmony_ci return EXTCON_CHG_USB_DCP; 1778c2ecf20Sopenharmony_ci case CHT_WC_USBSRC_TYPE_ACA: 1788c2ecf20Sopenharmony_ci return EXTCON_CHG_USB_ACA; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci int ret; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state); 1878c2ecf20Sopenharmony_ci if (ret) 1888c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error writing phyctrl: %d\n", ret); 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext, 1928c2ecf20Sopenharmony_ci bool enable) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci int ret, val; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* 1978c2ecf20Sopenharmony_ci * The 5V boost converter is enabled through a gpio on the PMIC, since 1988c2ecf20Sopenharmony_ci * there currently is no gpio driver we access the gpio reg directly. 1998c2ecf20Sopenharmony_ci */ 2008c2ecf20Sopenharmony_ci val = CHT_WC_VBUS_GPIO_CTLO_DRV_OD | CHT_WC_VBUS_GPIO_CTLO_DIR_OUT; 2018c2ecf20Sopenharmony_ci if (enable) 2028c2ecf20Sopenharmony_ci val |= CHT_WC_VBUS_GPIO_CTLO_OUTPUT; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, val); 2058c2ecf20Sopenharmony_ci if (ret) 2068c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cistatic void cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext, 2108c2ecf20Sopenharmony_ci bool enable) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci unsigned int val = enable ? CHT_WC_CHGRCTRL1_OTGMODE : 0; 2138c2ecf20Sopenharmony_ci int ret; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL1, 2168c2ecf20Sopenharmony_ci CHT_WC_CHGRCTRL1_OTGMODE, val); 2178c2ecf20Sopenharmony_ci if (ret) 2188c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n", ret); 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic void cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext, 2228c2ecf20Sopenharmony_ci bool enable) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci unsigned int val = enable ? 0 : CHT_WC_CHGDISCTRL_OUT; 2258c2ecf20Sopenharmony_ci int ret; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL, 2288c2ecf20Sopenharmony_ci CHT_WC_CHGDISCTRL_OUT, val); 2298c2ecf20Sopenharmony_ci if (ret) 2308c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error updating CHGDISCTRL reg: %d\n", ret); 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ 2348c2ecf20Sopenharmony_cistatic void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, 2358c2ecf20Sopenharmony_ci unsigned int cable, bool state) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci extcon_set_state_sync(ext->edev, cable, state); 2388c2ecf20Sopenharmony_ci if (cable == EXTCON_CHG_USB_SDP) 2398c2ecf20Sopenharmony_ci extcon_set_state_sync(ext->edev, EXTCON_USB, state); 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cistatic void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) 2438c2ecf20Sopenharmony_ci{ 2448c2ecf20Sopenharmony_ci int ret, pwrsrc_sts, id; 2458c2ecf20Sopenharmony_ci unsigned int cable = EXTCON_NONE; 2468c2ecf20Sopenharmony_ci /* Ignore errors in host mode, as the 5v boost converter is on then */ 2478c2ecf20Sopenharmony_ci bool ignore_get_charger_errors = ext->usb_host; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); 2508c2ecf20Sopenharmony_ci if (ret) { 2518c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); 2528c2ecf20Sopenharmony_ci return; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci id = cht_wc_extcon_get_id(ext, pwrsrc_sts); 2568c2ecf20Sopenharmony_ci if (id == INTEL_USB_ID_GND) { 2578c2ecf20Sopenharmony_ci cht_wc_extcon_enable_charging(ext, false); 2588c2ecf20Sopenharmony_ci cht_wc_extcon_set_otgmode(ext, true); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* The 5v boost causes a false VBUS / SDP detect, skip */ 2618c2ecf20Sopenharmony_ci goto charger_det_done; 2628c2ecf20Sopenharmony_ci } 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci cht_wc_extcon_set_otgmode(ext, false); 2658c2ecf20Sopenharmony_ci cht_wc_extcon_enable_charging(ext, true); 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci /* Plugged into a host/charger or not connected? */ 2688c2ecf20Sopenharmony_ci if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) { 2698c2ecf20Sopenharmony_ci /* Route D+ and D- to PMIC for future charger detection */ 2708c2ecf20Sopenharmony_ci cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); 2718c2ecf20Sopenharmony_ci goto set_state; 2728c2ecf20Sopenharmony_ci } 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors); 2758c2ecf20Sopenharmony_ci if (ret >= 0) 2768c2ecf20Sopenharmony_ci cable = ret; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_cicharger_det_done: 2798c2ecf20Sopenharmony_ci /* Route D+ and D- to SoC for the host or gadget controller */ 2808c2ecf20Sopenharmony_ci cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ciset_state: 2838c2ecf20Sopenharmony_ci if (cable != ext->previous_cable) { 2848c2ecf20Sopenharmony_ci cht_wc_extcon_set_state(ext, cable, true); 2858c2ecf20Sopenharmony_ci cht_wc_extcon_set_state(ext, ext->previous_cable, false); 2868c2ecf20Sopenharmony_ci ext->previous_cable = cable; 2878c2ecf20Sopenharmony_ci } 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci ext->usb_host = ((id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A)); 2908c2ecf20Sopenharmony_ci extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host); 2918c2ecf20Sopenharmony_ci} 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic irqreturn_t cht_wc_extcon_isr(int irq, void *data) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci struct cht_wc_extcon_data *ext = data; 2968c2ecf20Sopenharmony_ci int ret, irqs; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs); 2998c2ecf20Sopenharmony_ci if (ret) { 3008c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error reading irqs: %d\n", ret); 3018c2ecf20Sopenharmony_ci return IRQ_NONE; 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci cht_wc_extcon_pwrsrc_event(ext); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs); 3078c2ecf20Sopenharmony_ci if (ret) { 3088c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error writing irqs: %d\n", ret); 3098c2ecf20Sopenharmony_ci return IRQ_NONE; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3138c2ecf20Sopenharmony_ci} 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_cistatic int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable) 3168c2ecf20Sopenharmony_ci{ 3178c2ecf20Sopenharmony_ci int ret, mask, val; 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci val = enable ? 0 : CHT_WC_CHGDISCTRL_FN; 3208c2ecf20Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL, 3218c2ecf20Sopenharmony_ci CHT_WC_CHGDISCTRL_FN, val); 3228c2ecf20Sopenharmony_ci if (ret) 3238c2ecf20Sopenharmony_ci dev_err(ext->dev, 3248c2ecf20Sopenharmony_ci "Error setting sw control for CHGDIS pin: %d\n", 3258c2ecf20Sopenharmony_ci ret); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ci mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF; 3288c2ecf20Sopenharmony_ci val = enable ? mask : 0; 3298c2ecf20Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val); 3308c2ecf20Sopenharmony_ci if (ret) 3318c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error setting sw control: %d\n", ret); 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci return ret; 3348c2ecf20Sopenharmony_ci} 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_cistatic int cht_wc_extcon_probe(struct platform_device *pdev) 3378c2ecf20Sopenharmony_ci{ 3388c2ecf20Sopenharmony_ci struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); 3398c2ecf20Sopenharmony_ci struct cht_wc_extcon_data *ext; 3408c2ecf20Sopenharmony_ci unsigned long mask = ~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_USBID_MASK); 3418c2ecf20Sopenharmony_ci int pwrsrc_sts, id; 3428c2ecf20Sopenharmony_ci int irq, ret; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 3458c2ecf20Sopenharmony_ci if (irq < 0) 3468c2ecf20Sopenharmony_ci return irq; 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL); 3498c2ecf20Sopenharmony_ci if (!ext) 3508c2ecf20Sopenharmony_ci return -ENOMEM; 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ci ext->dev = &pdev->dev; 3538c2ecf20Sopenharmony_ci ext->regmap = pmic->regmap; 3548c2ecf20Sopenharmony_ci ext->previous_cable = EXTCON_NONE; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci /* Initialize extcon device */ 3578c2ecf20Sopenharmony_ci ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables); 3588c2ecf20Sopenharmony_ci if (IS_ERR(ext->edev)) 3598c2ecf20Sopenharmony_ci return PTR_ERR(ext->edev); 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci /* 3628c2ecf20Sopenharmony_ci * When a host-cable is detected the BIOS enables an external 5v boost 3638c2ecf20Sopenharmony_ci * converter to power connected devices there are 2 problems with this: 3648c2ecf20Sopenharmony_ci * 1) This gets seen by the external battery charger as a valid Vbus 3658c2ecf20Sopenharmony_ci * supply and it then tries to feed Vsys from this creating a 3668c2ecf20Sopenharmony_ci * feedback loop which causes aprox. 300 mA extra battery drain 3678c2ecf20Sopenharmony_ci * (and unless we drive the external-charger-disable pin high it 3688c2ecf20Sopenharmony_ci * also tries to charge the battery causing even more feedback). 3698c2ecf20Sopenharmony_ci * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply 3708c2ecf20Sopenharmony_ci * Since the external battery charger has its own 5v boost converter 3718c2ecf20Sopenharmony_ci * which does not have these issues, we simply turn the separate 3728c2ecf20Sopenharmony_ci * external 5v boost converter off and leave it off entirely. 3738c2ecf20Sopenharmony_ci */ 3748c2ecf20Sopenharmony_ci cht_wc_extcon_set_5v_boost(ext, false); 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci /* Enable sw control */ 3778c2ecf20Sopenharmony_ci ret = cht_wc_extcon_sw_control(ext, true); 3788c2ecf20Sopenharmony_ci if (ret) 3798c2ecf20Sopenharmony_ci goto disable_sw_control; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci /* Disable charging by external battery charger */ 3828c2ecf20Sopenharmony_ci cht_wc_extcon_enable_charging(ext, false); 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci /* Register extcon device */ 3858c2ecf20Sopenharmony_ci ret = devm_extcon_dev_register(ext->dev, ext->edev); 3868c2ecf20Sopenharmony_ci if (ret) { 3878c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error registering extcon device: %d\n", ret); 3888c2ecf20Sopenharmony_ci goto disable_sw_control; 3898c2ecf20Sopenharmony_ci } 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); 3928c2ecf20Sopenharmony_ci if (ret) { 3938c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); 3948c2ecf20Sopenharmony_ci goto disable_sw_control; 3958c2ecf20Sopenharmony_ci } 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci /* 3988c2ecf20Sopenharmony_ci * If no USB host or device connected, route D+ and D- to PMIC for 3998c2ecf20Sopenharmony_ci * initial charger detection 4008c2ecf20Sopenharmony_ci */ 4018c2ecf20Sopenharmony_ci id = cht_wc_extcon_get_id(ext, pwrsrc_sts); 4028c2ecf20Sopenharmony_ci if (id != INTEL_USB_ID_GND) 4038c2ecf20Sopenharmony_ci cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci /* Get initial state */ 4068c2ecf20Sopenharmony_ci cht_wc_extcon_pwrsrc_event(ext); 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_ci ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr, 4098c2ecf20Sopenharmony_ci IRQF_ONESHOT, pdev->name, ext); 4108c2ecf20Sopenharmony_ci if (ret) { 4118c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error requesting interrupt: %d\n", ret); 4128c2ecf20Sopenharmony_ci goto disable_sw_control; 4138c2ecf20Sopenharmony_ci } 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci /* Unmask irqs */ 4168c2ecf20Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, mask); 4178c2ecf20Sopenharmony_ci if (ret) { 4188c2ecf20Sopenharmony_ci dev_err(ext->dev, "Error writing irq-mask: %d\n", ret); 4198c2ecf20Sopenharmony_ci goto disable_sw_control; 4208c2ecf20Sopenharmony_ci } 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, ext); 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci return 0; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_cidisable_sw_control: 4278c2ecf20Sopenharmony_ci cht_wc_extcon_sw_control(ext, false); 4288c2ecf20Sopenharmony_ci return ret; 4298c2ecf20Sopenharmony_ci} 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_cistatic int cht_wc_extcon_remove(struct platform_device *pdev) 4328c2ecf20Sopenharmony_ci{ 4338c2ecf20Sopenharmony_ci struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci cht_wc_extcon_sw_control(ext, false); 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci return 0; 4388c2ecf20Sopenharmony_ci} 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_cistatic const struct platform_device_id cht_wc_extcon_table[] = { 4418c2ecf20Sopenharmony_ci { .name = "cht_wcove_pwrsrc" }, 4428c2ecf20Sopenharmony_ci {}, 4438c2ecf20Sopenharmony_ci}; 4448c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(platform, cht_wc_extcon_table); 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_cistatic struct platform_driver cht_wc_extcon_driver = { 4478c2ecf20Sopenharmony_ci .probe = cht_wc_extcon_probe, 4488c2ecf20Sopenharmony_ci .remove = cht_wc_extcon_remove, 4498c2ecf20Sopenharmony_ci .id_table = cht_wc_extcon_table, 4508c2ecf20Sopenharmony_ci .driver = { 4518c2ecf20Sopenharmony_ci .name = "cht_wcove_pwrsrc", 4528c2ecf20Sopenharmony_ci }, 4538c2ecf20Sopenharmony_ci}; 4548c2ecf20Sopenharmony_cimodule_platform_driver(cht_wc_extcon_driver); 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver"); 4578c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 4588c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 459