162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC 462306a36Sopenharmony_ci * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: 762306a36Sopenharmony_ci * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/extcon-provider.h> 1162306a36Sopenharmony_ci#include <linux/interrupt.h> 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/mfd/intel_soc_pmic.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/power_supply.h> 1862306a36Sopenharmony_ci#include <linux/property.h> 1962306a36Sopenharmony_ci#include <linux/regmap.h> 2062306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 2162306a36Sopenharmony_ci#include <linux/slab.h> 2262306a36Sopenharmony_ci#include <linux/usb/role.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include "extcon-intel.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define CHT_WC_PHYCTRL 0x5e07 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0 0x5e16 2962306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0) 3062306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1) 3162306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2) 3262306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3) 3362306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_TTLCK BIT(4) 3462306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_CCSM_OFF BIT(5) 3562306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_DBPOFF BIT(6) 3662306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL0_CHR_WDT_NOKICK BIT(7) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1 0x5e17 3962306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_100 BIT(0) 4062306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_150 BIT(1) 4162306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_500 BIT(2) 4262306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_900 BIT(3) 4362306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FUSB_INLMT_1500 BIT(4) 4462306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_FTEMP_EVENT BIT(5) 4562306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_OTGMODE BIT(6) 4662306a36Sopenharmony_ci#define CHT_WC_CHGRCTRL1_DBPEN BIT(7) 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci#define CHT_WC_USBSRC 0x5e29 4962306a36Sopenharmony_ci#define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0) 5062306a36Sopenharmony_ci#define CHT_WC_USBSRC_STS_SUCCESS 2 5162306a36Sopenharmony_ci#define CHT_WC_USBSRC_STS_FAIL 3 5262306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_SHIFT 2 5362306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2) 5462306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_NONE 0 5562306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_SDP 1 5662306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_DCP 2 5762306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_CDP 3 5862306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_ACA 4 5962306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_SE1 5 6062306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_MHL 6 6162306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_FLOATING 7 6262306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_OTHER 8 6362306a36Sopenharmony_ci#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci#define CHT_WC_CHGDISCTRL 0x5e2f 6662306a36Sopenharmony_ci#define CHT_WC_CHGDISCTRL_OUT BIT(0) 6762306a36Sopenharmony_ci/* 0 - open drain, 1 - regular push-pull output */ 6862306a36Sopenharmony_ci#define CHT_WC_CHGDISCTRL_DRV BIT(4) 6962306a36Sopenharmony_ci/* 0 - pin is controlled by SW, 1 - by HW */ 7062306a36Sopenharmony_ci#define CHT_WC_CHGDISCTRL_FN BIT(6) 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci#define CHT_WC_PWRSRC_IRQ 0x6e03 7362306a36Sopenharmony_ci#define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f 7462306a36Sopenharmony_ci#define CHT_WC_PWRSRC_STS 0x6e1e 7562306a36Sopenharmony_ci#define CHT_WC_PWRSRC_VBUS BIT(0) 7662306a36Sopenharmony_ci#define CHT_WC_PWRSRC_DC BIT(1) 7762306a36Sopenharmony_ci#define CHT_WC_PWRSRC_BATT BIT(2) 7862306a36Sopenharmony_ci#define CHT_WC_PWRSRC_USBID_MASK GENMASK(4, 3) 7962306a36Sopenharmony_ci#define CHT_WC_PWRSRC_USBID_SHIFT 3 8062306a36Sopenharmony_ci#define CHT_WC_PWRSRC_RID_ACA 0 8162306a36Sopenharmony_ci#define CHT_WC_PWRSRC_RID_GND 1 8262306a36Sopenharmony_ci#define CHT_WC_PWRSRC_RID_FLOAT 2 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO 0x6e2d 8562306a36Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0) 8662306a36Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO_DRV_OD BIT(4) 8762306a36Sopenharmony_ci#define CHT_WC_VBUS_GPIO_CTLO_DIR_OUT BIT(5) 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cienum cht_wc_mux_select { 9062306a36Sopenharmony_ci MUX_SEL_PMIC = 0, 9162306a36Sopenharmony_ci MUX_SEL_SOC, 9262306a36Sopenharmony_ci}; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic const unsigned int cht_wc_extcon_cables[] = { 9562306a36Sopenharmony_ci EXTCON_USB, 9662306a36Sopenharmony_ci EXTCON_USB_HOST, 9762306a36Sopenharmony_ci EXTCON_CHG_USB_SDP, 9862306a36Sopenharmony_ci EXTCON_CHG_USB_CDP, 9962306a36Sopenharmony_ci EXTCON_CHG_USB_DCP, 10062306a36Sopenharmony_ci EXTCON_CHG_USB_ACA, 10162306a36Sopenharmony_ci EXTCON_NONE, 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistruct cht_wc_extcon_data { 10562306a36Sopenharmony_ci struct device *dev; 10662306a36Sopenharmony_ci struct regmap *regmap; 10762306a36Sopenharmony_ci struct extcon_dev *edev; 10862306a36Sopenharmony_ci struct usb_role_switch *role_sw; 10962306a36Sopenharmony_ci struct regulator *vbus_boost; 11062306a36Sopenharmony_ci struct power_supply *psy; 11162306a36Sopenharmony_ci enum power_supply_usb_type usb_type; 11262306a36Sopenharmony_ci unsigned int previous_cable; 11362306a36Sopenharmony_ci bool usb_host; 11462306a36Sopenharmony_ci bool vbus_boost_enabled; 11562306a36Sopenharmony_ci}; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci switch ((pwrsrc_sts & CHT_WC_PWRSRC_USBID_MASK) >> CHT_WC_PWRSRC_USBID_SHIFT) { 12062306a36Sopenharmony_ci case CHT_WC_PWRSRC_RID_GND: 12162306a36Sopenharmony_ci return INTEL_USB_ID_GND; 12262306a36Sopenharmony_ci case CHT_WC_PWRSRC_RID_FLOAT: 12362306a36Sopenharmony_ci return INTEL_USB_ID_FLOAT; 12462306a36Sopenharmony_ci /* 12562306a36Sopenharmony_ci * According to the spec. we should read the USB-ID pin ADC value here 12662306a36Sopenharmony_ci * to determine the resistance of the used pull-down resister and then 12762306a36Sopenharmony_ci * return RID_A / RID_B / RID_C based on this. But all "Accessory 12862306a36Sopenharmony_ci * Charger Adapter"s (ACAs) which users can actually buy always use 12962306a36Sopenharmony_ci * a combination of a charging port with one or more USB-A ports, so 13062306a36Sopenharmony_ci * they should always use a resistor indicating RID_A. But the spec 13162306a36Sopenharmony_ci * is hard to read / badly-worded so some of them actually indicate 13262306a36Sopenharmony_ci * they are a RID_B ACA evnen though they clearly are a RID_A ACA. 13362306a36Sopenharmony_ci * To workaround this simply always return INTEL_USB_RID_A, which 13462306a36Sopenharmony_ci * matches all the ACAs which users can actually buy. 13562306a36Sopenharmony_ci */ 13662306a36Sopenharmony_ci case CHT_WC_PWRSRC_RID_ACA: 13762306a36Sopenharmony_ci return INTEL_USB_RID_A; 13862306a36Sopenharmony_ci default: 13962306a36Sopenharmony_ci return INTEL_USB_ID_FLOAT; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext, 14462306a36Sopenharmony_ci bool ignore_errors) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci int ret, usbsrc, status; 14762306a36Sopenharmony_ci unsigned long timeout; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci /* Charger detection can take upto 600ms, wait 800ms max. */ 15062306a36Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(800); 15162306a36Sopenharmony_ci do { 15262306a36Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc); 15362306a36Sopenharmony_ci if (ret) { 15462306a36Sopenharmony_ci dev_err(ext->dev, "Error reading usbsrc: %d\n", ret); 15562306a36Sopenharmony_ci return ret; 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci status = usbsrc & CHT_WC_USBSRC_STS_MASK; 15962306a36Sopenharmony_ci if (status == CHT_WC_USBSRC_STS_SUCCESS || 16062306a36Sopenharmony_ci status == CHT_WC_USBSRC_STS_FAIL) 16162306a36Sopenharmony_ci break; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci msleep(50); /* Wait a bit before retrying */ 16462306a36Sopenharmony_ci } while (time_before(jiffies, timeout)); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci if (status != CHT_WC_USBSRC_STS_SUCCESS) { 16762306a36Sopenharmony_ci if (!ignore_errors) { 16862306a36Sopenharmony_ci if (status == CHT_WC_USBSRC_STS_FAIL) 16962306a36Sopenharmony_ci dev_warn(ext->dev, "Could not detect charger type\n"); 17062306a36Sopenharmony_ci else 17162306a36Sopenharmony_ci dev_warn(ext->dev, "Timeout detecting charger type\n"); 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* Safe fallback */ 17562306a36Sopenharmony_ci usbsrc = CHT_WC_USBSRC_TYPE_SDP << CHT_WC_USBSRC_TYPE_SHIFT; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; 17962306a36Sopenharmony_ci switch (usbsrc) { 18062306a36Sopenharmony_ci default: 18162306a36Sopenharmony_ci dev_warn(ext->dev, 18262306a36Sopenharmony_ci "Unhandled charger type %d, defaulting to SDP\n", 18362306a36Sopenharmony_ci ret); 18462306a36Sopenharmony_ci ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; 18562306a36Sopenharmony_ci return EXTCON_CHG_USB_SDP; 18662306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_SDP: 18762306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_FLOATING: 18862306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_OTHER: 18962306a36Sopenharmony_ci ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; 19062306a36Sopenharmony_ci return EXTCON_CHG_USB_SDP; 19162306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_CDP: 19262306a36Sopenharmony_ci ext->usb_type = POWER_SUPPLY_USB_TYPE_CDP; 19362306a36Sopenharmony_ci return EXTCON_CHG_USB_CDP; 19462306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_DCP: 19562306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: 19662306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */ 19762306a36Sopenharmony_ci ext->usb_type = POWER_SUPPLY_USB_TYPE_DCP; 19862306a36Sopenharmony_ci return EXTCON_CHG_USB_DCP; 19962306a36Sopenharmony_ci case CHT_WC_USBSRC_TYPE_ACA: 20062306a36Sopenharmony_ci ext->usb_type = POWER_SUPPLY_USB_TYPE_ACA; 20162306a36Sopenharmony_ci return EXTCON_CHG_USB_ACA; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci int ret; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state); 21062306a36Sopenharmony_ci if (ret) 21162306a36Sopenharmony_ci dev_err(ext->dev, "Error writing phyctrl: %d\n", ret); 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext, 21562306a36Sopenharmony_ci bool enable) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci int ret, val; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* 22062306a36Sopenharmony_ci * The 5V boost converter is enabled through a gpio on the PMIC, since 22162306a36Sopenharmony_ci * there currently is no gpio driver we access the gpio reg directly. 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_ci val = CHT_WC_VBUS_GPIO_CTLO_DRV_OD | CHT_WC_VBUS_GPIO_CTLO_DIR_OUT; 22462306a36Sopenharmony_ci if (enable) 22562306a36Sopenharmony_ci val |= CHT_WC_VBUS_GPIO_CTLO_OUTPUT; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, val); 22862306a36Sopenharmony_ci if (ret) 22962306a36Sopenharmony_ci dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret); 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic void cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext, 23362306a36Sopenharmony_ci bool enable) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci unsigned int val = enable ? CHT_WC_CHGRCTRL1_OTGMODE : 0; 23662306a36Sopenharmony_ci int ret; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL1, 23962306a36Sopenharmony_ci CHT_WC_CHGRCTRL1_OTGMODE, val); 24062306a36Sopenharmony_ci if (ret) 24162306a36Sopenharmony_ci dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n", ret); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (ext->vbus_boost && ext->vbus_boost_enabled != enable) { 24462306a36Sopenharmony_ci if (enable) 24562306a36Sopenharmony_ci ret = regulator_enable(ext->vbus_boost); 24662306a36Sopenharmony_ci else 24762306a36Sopenharmony_ci ret = regulator_disable(ext->vbus_boost); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci if (ret) 25062306a36Sopenharmony_ci dev_err(ext->dev, "Error updating Vbus boost regulator: %d\n", ret); 25162306a36Sopenharmony_ci else 25262306a36Sopenharmony_ci ext->vbus_boost_enabled = enable; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_cistatic void cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext, 25762306a36Sopenharmony_ci bool enable) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci unsigned int val = enable ? 0 : CHT_WC_CHGDISCTRL_OUT; 26062306a36Sopenharmony_ci int ret; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL, 26362306a36Sopenharmony_ci CHT_WC_CHGDISCTRL_OUT, val); 26462306a36Sopenharmony_ci if (ret) 26562306a36Sopenharmony_ci dev_err(ext->dev, "Error updating CHGDISCTRL reg: %d\n", ret); 26662306a36Sopenharmony_ci} 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ 26962306a36Sopenharmony_cistatic void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, 27062306a36Sopenharmony_ci unsigned int cable, bool state) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci extcon_set_state_sync(ext->edev, cable, state); 27362306a36Sopenharmony_ci if (cable == EXTCON_CHG_USB_SDP) 27462306a36Sopenharmony_ci extcon_set_state_sync(ext->edev, EXTCON_USB, state); 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_cistatic void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci int ret, pwrsrc_sts, id; 28062306a36Sopenharmony_ci unsigned int cable = EXTCON_NONE; 28162306a36Sopenharmony_ci /* Ignore errors in host mode, as the 5v boost converter is on then */ 28262306a36Sopenharmony_ci bool ignore_get_charger_errors = ext->usb_host; 28362306a36Sopenharmony_ci enum usb_role role; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci ext->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); 28862306a36Sopenharmony_ci if (ret) { 28962306a36Sopenharmony_ci dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); 29062306a36Sopenharmony_ci return; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci id = cht_wc_extcon_get_id(ext, pwrsrc_sts); 29462306a36Sopenharmony_ci if (id == INTEL_USB_ID_GND) { 29562306a36Sopenharmony_ci cht_wc_extcon_enable_charging(ext, false); 29662306a36Sopenharmony_ci cht_wc_extcon_set_otgmode(ext, true); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci /* The 5v boost causes a false VBUS / SDP detect, skip */ 29962306a36Sopenharmony_ci goto charger_det_done; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci cht_wc_extcon_set_otgmode(ext, false); 30362306a36Sopenharmony_ci cht_wc_extcon_enable_charging(ext, true); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* Plugged into a host/charger or not connected? */ 30662306a36Sopenharmony_ci if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) { 30762306a36Sopenharmony_ci /* Route D+ and D- to PMIC for future charger detection */ 30862306a36Sopenharmony_ci cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); 30962306a36Sopenharmony_ci goto set_state; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors); 31362306a36Sopenharmony_ci if (ret >= 0) 31462306a36Sopenharmony_ci cable = ret; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cicharger_det_done: 31762306a36Sopenharmony_ci /* Route D+ and D- to SoC for the host or gadget controller */ 31862306a36Sopenharmony_ci cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ciset_state: 32162306a36Sopenharmony_ci if (cable != ext->previous_cable) { 32262306a36Sopenharmony_ci cht_wc_extcon_set_state(ext, cable, true); 32362306a36Sopenharmony_ci cht_wc_extcon_set_state(ext, ext->previous_cable, false); 32462306a36Sopenharmony_ci ext->previous_cable = cable; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci ext->usb_host = ((id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A)); 32862306a36Sopenharmony_ci extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci if (ext->usb_host) 33162306a36Sopenharmony_ci role = USB_ROLE_HOST; 33262306a36Sopenharmony_ci else if (pwrsrc_sts & CHT_WC_PWRSRC_VBUS) 33362306a36Sopenharmony_ci role = USB_ROLE_DEVICE; 33462306a36Sopenharmony_ci else 33562306a36Sopenharmony_ci role = USB_ROLE_NONE; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci /* Note: this is a no-op when ext->role_sw is NULL */ 33862306a36Sopenharmony_ci ret = usb_role_switch_set_role(ext->role_sw, role); 33962306a36Sopenharmony_ci if (ret) 34062306a36Sopenharmony_ci dev_err(ext->dev, "Error setting USB-role: %d\n", ret); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (ext->psy) 34362306a36Sopenharmony_ci power_supply_changed(ext->psy); 34462306a36Sopenharmony_ci} 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_cistatic irqreturn_t cht_wc_extcon_isr(int irq, void *data) 34762306a36Sopenharmony_ci{ 34862306a36Sopenharmony_ci struct cht_wc_extcon_data *ext = data; 34962306a36Sopenharmony_ci int ret, irqs; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs); 35262306a36Sopenharmony_ci if (ret) { 35362306a36Sopenharmony_ci dev_err(ext->dev, "Error reading irqs: %d\n", ret); 35462306a36Sopenharmony_ci return IRQ_NONE; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci cht_wc_extcon_pwrsrc_event(ext); 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs); 36062306a36Sopenharmony_ci if (ret) { 36162306a36Sopenharmony_ci dev_err(ext->dev, "Error writing irqs: %d\n", ret); 36262306a36Sopenharmony_ci return IRQ_NONE; 36362306a36Sopenharmony_ci } 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci return IRQ_HANDLED; 36662306a36Sopenharmony_ci} 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_cistatic int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci int ret, mask, val; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci val = enable ? 0 : CHT_WC_CHGDISCTRL_FN; 37362306a36Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL, 37462306a36Sopenharmony_ci CHT_WC_CHGDISCTRL_FN, val); 37562306a36Sopenharmony_ci if (ret) 37662306a36Sopenharmony_ci dev_err(ext->dev, 37762306a36Sopenharmony_ci "Error setting sw control for CHGDIS pin: %d\n", 37862306a36Sopenharmony_ci ret); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF; 38162306a36Sopenharmony_ci val = enable ? mask : 0; 38262306a36Sopenharmony_ci ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val); 38362306a36Sopenharmony_ci if (ret) 38462306a36Sopenharmony_ci dev_err(ext->dev, "Error setting sw control: %d\n", ret); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci return ret; 38762306a36Sopenharmony_ci} 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_cistatic int cht_wc_extcon_find_role_sw(struct cht_wc_extcon_data *ext) 39062306a36Sopenharmony_ci{ 39162306a36Sopenharmony_ci const struct software_node *swnode; 39262306a36Sopenharmony_ci struct fwnode_handle *fwnode; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci swnode = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); 39562306a36Sopenharmony_ci if (!swnode) 39662306a36Sopenharmony_ci return -EPROBE_DEFER; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci fwnode = software_node_fwnode(swnode); 39962306a36Sopenharmony_ci ext->role_sw = usb_role_switch_find_by_fwnode(fwnode); 40062306a36Sopenharmony_ci fwnode_handle_put(fwnode); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci return ext->role_sw ? 0 : -EPROBE_DEFER; 40362306a36Sopenharmony_ci} 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_cistatic void cht_wc_extcon_put_role_sw(void *data) 40662306a36Sopenharmony_ci{ 40762306a36Sopenharmony_ci struct cht_wc_extcon_data *ext = data; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci usb_role_switch_put(ext->role_sw); 41062306a36Sopenharmony_ci} 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci/* Some boards require controlling the role-sw and Vbus based on the id-pin */ 41362306a36Sopenharmony_cistatic int cht_wc_extcon_get_role_sw_and_regulator(struct cht_wc_extcon_data *ext) 41462306a36Sopenharmony_ci{ 41562306a36Sopenharmony_ci int ret; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci ret = cht_wc_extcon_find_role_sw(ext); 41862306a36Sopenharmony_ci if (ret) 41962306a36Sopenharmony_ci return ret; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci ret = devm_add_action_or_reset(ext->dev, cht_wc_extcon_put_role_sw, ext); 42262306a36Sopenharmony_ci if (ret) 42362306a36Sopenharmony_ci return ret; 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci /* 42662306a36Sopenharmony_ci * On x86/ACPI platforms the regulator <-> consumer link is provided 42762306a36Sopenharmony_ci * by platform_data passed to the regulator driver. This means that 42862306a36Sopenharmony_ci * this info is not available before the regulator driver has bound. 42962306a36Sopenharmony_ci * Use devm_regulator_get_optional() to avoid getting a dummy 43062306a36Sopenharmony_ci * regulator and wait for the regulator to show up if necessary. 43162306a36Sopenharmony_ci */ 43262306a36Sopenharmony_ci ext->vbus_boost = devm_regulator_get_optional(ext->dev, "vbus"); 43362306a36Sopenharmony_ci if (IS_ERR(ext->vbus_boost)) { 43462306a36Sopenharmony_ci ret = PTR_ERR(ext->vbus_boost); 43562306a36Sopenharmony_ci if (ret == -ENODEV) 43662306a36Sopenharmony_ci ret = -EPROBE_DEFER; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci return dev_err_probe(ext->dev, ret, "getting Vbus regulator"); 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci return 0; 44262306a36Sopenharmony_ci} 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_cistatic int cht_wc_extcon_psy_get_prop(struct power_supply *psy, 44562306a36Sopenharmony_ci enum power_supply_property psp, 44662306a36Sopenharmony_ci union power_supply_propval *val) 44762306a36Sopenharmony_ci{ 44862306a36Sopenharmony_ci struct cht_wc_extcon_data *ext = power_supply_get_drvdata(psy); 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci switch (psp) { 45162306a36Sopenharmony_ci case POWER_SUPPLY_PROP_USB_TYPE: 45262306a36Sopenharmony_ci val->intval = ext->usb_type; 45362306a36Sopenharmony_ci break; 45462306a36Sopenharmony_ci case POWER_SUPPLY_PROP_ONLINE: 45562306a36Sopenharmony_ci val->intval = ext->usb_type ? 1 : 0; 45662306a36Sopenharmony_ci break; 45762306a36Sopenharmony_ci default: 45862306a36Sopenharmony_ci return -EINVAL; 45962306a36Sopenharmony_ci } 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci return 0; 46262306a36Sopenharmony_ci} 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_cistatic const enum power_supply_usb_type cht_wc_extcon_psy_usb_types[] = { 46562306a36Sopenharmony_ci POWER_SUPPLY_USB_TYPE_SDP, 46662306a36Sopenharmony_ci POWER_SUPPLY_USB_TYPE_CDP, 46762306a36Sopenharmony_ci POWER_SUPPLY_USB_TYPE_DCP, 46862306a36Sopenharmony_ci POWER_SUPPLY_USB_TYPE_ACA, 46962306a36Sopenharmony_ci POWER_SUPPLY_USB_TYPE_UNKNOWN, 47062306a36Sopenharmony_ci}; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_cistatic const enum power_supply_property cht_wc_extcon_psy_props[] = { 47362306a36Sopenharmony_ci POWER_SUPPLY_PROP_USB_TYPE, 47462306a36Sopenharmony_ci POWER_SUPPLY_PROP_ONLINE, 47562306a36Sopenharmony_ci}; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_cistatic const struct power_supply_desc cht_wc_extcon_psy_desc = { 47862306a36Sopenharmony_ci .name = "cht_wcove_pwrsrc", 47962306a36Sopenharmony_ci .type = POWER_SUPPLY_TYPE_USB, 48062306a36Sopenharmony_ci .usb_types = cht_wc_extcon_psy_usb_types, 48162306a36Sopenharmony_ci .num_usb_types = ARRAY_SIZE(cht_wc_extcon_psy_usb_types), 48262306a36Sopenharmony_ci .properties = cht_wc_extcon_psy_props, 48362306a36Sopenharmony_ci .num_properties = ARRAY_SIZE(cht_wc_extcon_psy_props), 48462306a36Sopenharmony_ci .get_property = cht_wc_extcon_psy_get_prop, 48562306a36Sopenharmony_ci}; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_cistatic int cht_wc_extcon_register_psy(struct cht_wc_extcon_data *ext) 48862306a36Sopenharmony_ci{ 48962306a36Sopenharmony_ci struct power_supply_config psy_cfg = { .drv_data = ext }; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci ext->psy = devm_power_supply_register(ext->dev, 49262306a36Sopenharmony_ci &cht_wc_extcon_psy_desc, 49362306a36Sopenharmony_ci &psy_cfg); 49462306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(ext->psy); 49562306a36Sopenharmony_ci} 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_cistatic int cht_wc_extcon_probe(struct platform_device *pdev) 49862306a36Sopenharmony_ci{ 49962306a36Sopenharmony_ci struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); 50062306a36Sopenharmony_ci struct cht_wc_extcon_data *ext; 50162306a36Sopenharmony_ci unsigned long mask = ~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_USBID_MASK); 50262306a36Sopenharmony_ci int pwrsrc_sts, id; 50362306a36Sopenharmony_ci int irq, ret; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 50662306a36Sopenharmony_ci if (irq < 0) 50762306a36Sopenharmony_ci return irq; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL); 51062306a36Sopenharmony_ci if (!ext) 51162306a36Sopenharmony_ci return -ENOMEM; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci ext->dev = &pdev->dev; 51462306a36Sopenharmony_ci ext->regmap = pmic->regmap; 51562306a36Sopenharmony_ci ext->previous_cable = EXTCON_NONE; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci /* Initialize extcon device */ 51862306a36Sopenharmony_ci ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables); 51962306a36Sopenharmony_ci if (IS_ERR(ext->edev)) 52062306a36Sopenharmony_ci return PTR_ERR(ext->edev); 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci switch (pmic->cht_wc_model) { 52362306a36Sopenharmony_ci case INTEL_CHT_WC_GPD_WIN_POCKET: 52462306a36Sopenharmony_ci /* 52562306a36Sopenharmony_ci * When a host-cable is detected the BIOS enables an external 5v boost 52662306a36Sopenharmony_ci * converter to power connected devices there are 2 problems with this: 52762306a36Sopenharmony_ci * 1) This gets seen by the external battery charger as a valid Vbus 52862306a36Sopenharmony_ci * supply and it then tries to feed Vsys from this creating a 52962306a36Sopenharmony_ci * feedback loop which causes aprox. 300 mA extra battery drain 53062306a36Sopenharmony_ci * (and unless we drive the external-charger-disable pin high it 53162306a36Sopenharmony_ci * also tries to charge the battery causing even more feedback). 53262306a36Sopenharmony_ci * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply 53362306a36Sopenharmony_ci * Since the external battery charger has its own 5v boost converter 53462306a36Sopenharmony_ci * which does not have these issues, we simply turn the separate 53562306a36Sopenharmony_ci * external 5v boost converter off and leave it off entirely. 53662306a36Sopenharmony_ci */ 53762306a36Sopenharmony_ci cht_wc_extcon_set_5v_boost(ext, false); 53862306a36Sopenharmony_ci break; 53962306a36Sopenharmony_ci case INTEL_CHT_WC_LENOVO_YOGABOOK1: 54062306a36Sopenharmony_ci case INTEL_CHT_WC_LENOVO_YT3_X90: 54162306a36Sopenharmony_ci /* Do this first, as it may very well return -EPROBE_DEFER. */ 54262306a36Sopenharmony_ci ret = cht_wc_extcon_get_role_sw_and_regulator(ext); 54362306a36Sopenharmony_ci if (ret) 54462306a36Sopenharmony_ci return ret; 54562306a36Sopenharmony_ci /* 54662306a36Sopenharmony_ci * The bq25890 used here relies on this driver's BC-1.2 charger 54762306a36Sopenharmony_ci * detection, and the bq25890 driver expect this info to be 54862306a36Sopenharmony_ci * available through a parent power_supply class device which 54962306a36Sopenharmony_ci * models the detected charger (idem to how the Type-C TCPM code 55062306a36Sopenharmony_ci * registers a power_supply classdev for the connected charger). 55162306a36Sopenharmony_ci */ 55262306a36Sopenharmony_ci ret = cht_wc_extcon_register_psy(ext); 55362306a36Sopenharmony_ci if (ret) 55462306a36Sopenharmony_ci return ret; 55562306a36Sopenharmony_ci break; 55662306a36Sopenharmony_ci case INTEL_CHT_WC_XIAOMI_MIPAD2: 55762306a36Sopenharmony_ci ret = cht_wc_extcon_get_role_sw_and_regulator(ext); 55862306a36Sopenharmony_ci if (ret) 55962306a36Sopenharmony_ci return ret; 56062306a36Sopenharmony_ci break; 56162306a36Sopenharmony_ci default: 56262306a36Sopenharmony_ci break; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci /* Enable sw control */ 56662306a36Sopenharmony_ci ret = cht_wc_extcon_sw_control(ext, true); 56762306a36Sopenharmony_ci if (ret) 56862306a36Sopenharmony_ci goto disable_sw_control; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci /* Disable charging by external battery charger */ 57162306a36Sopenharmony_ci cht_wc_extcon_enable_charging(ext, false); 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci /* Register extcon device */ 57462306a36Sopenharmony_ci ret = devm_extcon_dev_register(ext->dev, ext->edev); 57562306a36Sopenharmony_ci if (ret) { 57662306a36Sopenharmony_ci dev_err(ext->dev, "Error registering extcon device: %d\n", ret); 57762306a36Sopenharmony_ci goto disable_sw_control; 57862306a36Sopenharmony_ci } 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); 58162306a36Sopenharmony_ci if (ret) { 58262306a36Sopenharmony_ci dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); 58362306a36Sopenharmony_ci goto disable_sw_control; 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci /* 58762306a36Sopenharmony_ci * If no USB host or device connected, route D+ and D- to PMIC for 58862306a36Sopenharmony_ci * initial charger detection 58962306a36Sopenharmony_ci */ 59062306a36Sopenharmony_ci id = cht_wc_extcon_get_id(ext, pwrsrc_sts); 59162306a36Sopenharmony_ci if (id != INTEL_USB_ID_GND) 59262306a36Sopenharmony_ci cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci /* Get initial state */ 59562306a36Sopenharmony_ci cht_wc_extcon_pwrsrc_event(ext); 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr, 59862306a36Sopenharmony_ci IRQF_ONESHOT, pdev->name, ext); 59962306a36Sopenharmony_ci if (ret) { 60062306a36Sopenharmony_ci dev_err(ext->dev, "Error requesting interrupt: %d\n", ret); 60162306a36Sopenharmony_ci goto disable_sw_control; 60262306a36Sopenharmony_ci } 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci /* Unmask irqs */ 60562306a36Sopenharmony_ci ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, mask); 60662306a36Sopenharmony_ci if (ret) { 60762306a36Sopenharmony_ci dev_err(ext->dev, "Error writing irq-mask: %d\n", ret); 60862306a36Sopenharmony_ci goto disable_sw_control; 60962306a36Sopenharmony_ci } 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci platform_set_drvdata(pdev, ext); 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci return 0; 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_cidisable_sw_control: 61662306a36Sopenharmony_ci cht_wc_extcon_sw_control(ext, false); 61762306a36Sopenharmony_ci return ret; 61862306a36Sopenharmony_ci} 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic int cht_wc_extcon_remove(struct platform_device *pdev) 62162306a36Sopenharmony_ci{ 62262306a36Sopenharmony_ci struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci cht_wc_extcon_sw_control(ext, false); 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci return 0; 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_cistatic const struct platform_device_id cht_wc_extcon_table[] = { 63062306a36Sopenharmony_ci { .name = "cht_wcove_pwrsrc" }, 63162306a36Sopenharmony_ci {}, 63262306a36Sopenharmony_ci}; 63362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, cht_wc_extcon_table); 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_cistatic struct platform_driver cht_wc_extcon_driver = { 63662306a36Sopenharmony_ci .probe = cht_wc_extcon_probe, 63762306a36Sopenharmony_ci .remove = cht_wc_extcon_remove, 63862306a36Sopenharmony_ci .id_table = cht_wc_extcon_table, 63962306a36Sopenharmony_ci .driver = { 64062306a36Sopenharmony_ci .name = "cht_wcove_pwrsrc", 64162306a36Sopenharmony_ci }, 64262306a36Sopenharmony_ci}; 64362306a36Sopenharmony_cimodule_platform_driver(cht_wc_extcon_driver); 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver"); 64662306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 64762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 648