// SPDX-License-Identifier: GPL-2.0 /* * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> * * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
*/
staticint cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
{ switch ((pwrsrc_sts & CHT_WC_PWRSRC_USBID_MASK) >> CHT_WC_PWRSRC_USBID_SHIFT) { case CHT_WC_PWRSRC_RID_GND: return INTEL_USB_ID_GND; case CHT_WC_PWRSRC_RID_FLOAT: return INTEL_USB_ID_FLOAT; /* * According to the spec. we should read the USB-ID pin ADC value here * to determine the resistance of the used pull-down resister and then * return RID_A / RID_B / RID_C based on this. But all "Accessory * Charger Adapter"s (ACAs) which users can actually buy always use * a combination of a charging port with one or more USB-A ports, so * they should always use a resistor indicating RID_A. But the spec * is hard to read / badly-worded so some of them actually indicate * they are a RID_B ACA evnen though they clearly are a RID_A ACA. * To workaround this simply always return INTEL_USB_RID_A, which * matches all the ACAs which users can actually buy.
*/ case CHT_WC_PWRSRC_RID_ACA: return INTEL_USB_RID_A; default: return INTEL_USB_ID_FLOAT;
}
}
usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; switch (usbsrc) { default:
dev_warn(ext->dev, "Unhandled charger type %d, defaulting to SDP\n",
ret);
ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; return EXTCON_CHG_USB_SDP; case CHT_WC_USBSRC_TYPE_SDP: case CHT_WC_USBSRC_TYPE_FLOATING: case CHT_WC_USBSRC_TYPE_OTHER:
ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; return EXTCON_CHG_USB_SDP; case CHT_WC_USBSRC_TYPE_CDP:
ext->usb_type = POWER_SUPPLY_USB_TYPE_CDP; return EXTCON_CHG_USB_CDP; case CHT_WC_USBSRC_TYPE_DCP: case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
ext->usb_type = POWER_SUPPLY_USB_TYPE_DCP; return EXTCON_CHG_USB_DCP; case CHT_WC_USBSRC_TYPE_ACA:
ext->usb_type = POWER_SUPPLY_USB_TYPE_ACA; return EXTCON_CHG_USB_ACA;
}
}
staticvoid cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state)
{ int ret;
ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state); if (ret)
dev_err(ext->dev, "Error writing phyctrl: %d\n", ret);
}
staticvoid cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext, bool enable)
{ int ret, val;
/* * The 5V boost converter is enabled through a gpio on the PMIC, since * there currently is no gpio driver we access the gpio reg directly.
*/
val = CHT_WC_VBUS_GPIO_CTLO_DRV_OD | CHT_WC_VBUS_GPIO_CTLO_DIR_OUT; if (enable)
val |= CHT_WC_VBUS_GPIO_CTLO_OUTPUT;
ret = regmap_write(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, val); if (ret)
dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret);
}
staticvoid cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext, bool enable)
{ unsignedint val = enable ? CHT_WC_CHGRCTRL1_OTGMODE : 0; int ret;
ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL1,
CHT_WC_CHGRCTRL1_OTGMODE, val); if (ret)
dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n", ret);
if (ext->vbus_boost && ext->vbus_boost_enabled != enable) { if (enable)
ret = regulator_enable(ext->vbus_boost); else
ret = regulator_disable(ext->vbus_boost);
staticvoid cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext, bool enable)
{ unsignedint val = enable ? 0 : CHT_WC_CHGDISCTRL_OUT; int ret;
ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL,
CHT_WC_CHGDISCTRL_OUT, val); if (ret)
dev_err(ext->dev, "Error updating CHGDISCTRL reg: %d\n", ret);
}
/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ staticvoid cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, unsignedint cable, bool state)
{
extcon_set_state_sync(ext->edev, cable, state); if (cable == EXTCON_CHG_USB_SDP)
extcon_set_state_sync(ext->edev, EXTCON_USB, state);
}
staticvoid cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
{ int ret, pwrsrc_sts, id; unsignedint cable = EXTCON_NONE; /* Ignore errors in host mode, as the 5v boost converter is on then */ bool ignore_get_charger_errors = ext->usb_host; enum usb_role role;
ext->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); if (ret) {
dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); return;
}
id = cht_wc_extcon_get_id(ext, pwrsrc_sts); if (id == INTEL_USB_ID_GND) {
cht_wc_extcon_enable_charging(ext, false);
cht_wc_extcon_set_otgmode(ext, true);
/* The 5v boost causes a false VBUS / SDP detect, skip */ goto charger_det_done;
}
/* Plugged into a host/charger or not connected? */ if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) { /* Route D+ and D- to PMIC for future charger detection */
cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC); goto set_state;
}
ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors); if (ret >= 0)
cable = ret;
charger_det_done: /* Route D+ and D- to SoC for the host or gadget controller */
cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC);
if (ext->usb_host)
role = USB_ROLE_HOST; elseif (pwrsrc_sts & CHT_WC_PWRSRC_VBUS)
role = USB_ROLE_DEVICE; else
role = USB_ROLE_NONE;
/* Note: this is a no-op when ext->role_sw is NULL */
ret = usb_role_switch_set_role(ext->role_sw, role); if (ret)
dev_err(ext->dev, "Error setting USB-role: %d\n", ret);
val = enable ? 0 : CHT_WC_CHGDISCTRL_FN;
ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL,
CHT_WC_CHGDISCTRL_FN, val); if (ret)
dev_err(ext->dev, "Error setting sw control for CHGDIS pin: %d\n",
ret);
mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF;
val = enable ? mask : 0;
ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val); if (ret)
dev_err(ext->dev, "Error setting sw control: %d\n", ret);
/* Some boards require controlling the role-sw and Vbus based on the id-pin */ staticint cht_wc_extcon_get_role_sw_and_regulator(struct cht_wc_extcon_data *ext)
{ int ret;
ret = cht_wc_extcon_find_role_sw(ext); if (ret) return ret;
ret = devm_add_action_or_reset(ext->dev, cht_wc_extcon_put_role_sw, ext); if (ret) return ret;
/* * On x86/ACPI platforms the regulator <-> consumer link is provided * by platform_data passed to the regulator driver. This means that * this info is not available before the regulator driver has bound. * Use devm_regulator_get_optional() to avoid getting a dummy * regulator and wait for the regulator to show up if necessary.
*/
ext->vbus_boost = devm_regulator_get_optional(ext->dev, "vbus"); if (IS_ERR(ext->vbus_boost)) {
ret = PTR_ERR(ext->vbus_boost); if (ret == -ENODEV)
ret = -EPROBE_DEFER;
switch (pmic->cht_wc_model) { case INTEL_CHT_WC_GPD_WIN_POCKET: /* * When a host-cable is detected the BIOS enables an external 5v boost * converter to power connected devices there are 2 problems with this: * 1) This gets seen by the external battery charger as a valid Vbus * supply and it then tries to feed Vsys from this creating a * feedback loop which causes aprox. 300 mA extra battery drain * (and unless we drive the external-charger-disable pin high it * also tries to charge the battery causing even more feedback). * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply * Since the external battery charger has its own 5v boost converter * which does not have these issues, we simply turn the separate * external 5v boost converter off and leave it off entirely.
*/
cht_wc_extcon_set_5v_boost(ext, false); break; case INTEL_CHT_WC_LENOVO_YOGABOOK1: case INTEL_CHT_WC_LENOVO_YT3_X90: /* Do this first, as it may very well return -EPROBE_DEFER. */
ret = cht_wc_extcon_get_role_sw_and_regulator(ext); if (ret) return ret; /* * The bq25890 used here relies on this driver's BC-1.2 charger * detection, and the bq25890 driver expect this info to be * available through a parent power_supply class device which * models the detected charger (idem to how the Type-C TCPM code * registers a power_supply classdev for the connected charger).
*/
ret = cht_wc_extcon_register_psy(ext); if (ret) return ret; break; case INTEL_CHT_WC_XIAOMI_MIPAD2:
ret = cht_wc_extcon_get_role_sw_and_regulator(ext); if (ret) return ret; break; default: break;
}
/* Enable sw control */
ret = cht_wc_extcon_sw_control(ext, true); if (ret) goto disable_sw_control;
/* Disable charging by external battery charger */
cht_wc_extcon_enable_charging(ext, false);
ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts); if (ret) {
dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret); goto disable_sw_control;
}
/* * If no USB host or device connected, route D+ and D- to PMIC for * initial charger detection
*/
id = cht_wc_extcon_get_id(ext, pwrsrc_sts); if (id != INTEL_USB_ID_GND)
cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
/* Get initial state */
cht_wc_extcon_pwrsrc_event(ext);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.