val >>= CHRG_VBUS_ILIM_BIT_POS; switch (val) { case CHRG_VBUS_ILIM_100MA: return 100000; case CHRG_VBUS_ILIM_500MA: return 500000; case CHRG_VBUS_ILIM_900MA: return 900000; case CHRG_VBUS_ILIM_1500MA: return 1500000; case CHRG_VBUS_ILIM_2000MA: return 2000000; case CHRG_VBUS_ILIM_2500MA: return 2500000; case CHRG_VBUS_ILIM_3000MA: return 3000000; case CHRG_VBUS_ILIM_3500MA: return 3500000; default: /* All b1xxx values map to 4000 mA */ return 4000000;
}
}
staticinlineint axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info, int inlmt)
{ int ret;
u8 reg_val;
ret = regmap_update_bits(info->regmap, AXP20X_CHRG_BAK_CTRL,
CHRG_VBUS_ILIM_MASK, reg_val); if (ret < 0)
dev_err(&info->pdev->dev, "charger BAK control %d\n", ret);
return ret;
}
staticint axp288_charger_vbus_path_select(struct axp288_chrg_info *info, bool enable)
{ int ret;
if (enable)
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
VBUS_ISPOUT_VBUS_PATH_DIS, 0); else
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS);
staticint axp288_charger_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
{ struct axp288_chrg_info *info = power_supply_get_drvdata(psy); int ret;
mutex_lock(&info->lock);
ret = axp288_charger_usb_update_property(info); if (ret < 0) goto out;
switch (psp) { case POWER_SUPPLY_PROP_PRESENT: /* Check for OTG case first */ if (info->otg.id_short) {
val->intval = 0; break;
}
val->intval = (info->input_status & PS_STAT_VBUS_PRESENT) ? 1 : 0; break; case POWER_SUPPLY_PROP_ONLINE: /* Check for OTG case first */ if (info->otg.id_short) {
val->intval = 0; break;
}
val->intval = (info->input_status & PS_STAT_VBUS_VALID) ? 1 : 0; break; case POWER_SUPPLY_PROP_HEALTH:
val->intval = axp288_get_charger_health(info); break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
val->intval = info->cc * 1000; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = info->max_cc * 1000; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
val->intval = info->cv * 1000; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
val->intval = info->max_cv * 1000; break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = axp288_charger_get_vbus_inlmt(info); break; default:
ret = -EINVAL;
}
out:
mutex_unlock(&info->lock); return ret;
}
staticint axp288_charger_property_is_writeable(struct power_supply *psy, enum power_supply_property psp)
{ int ret;
switch (psp) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = 1; break; default:
ret = 0;
}
for (i = 0; i < CHRG_INTR_END; i++) { if (info->irq[i] == irq) break;
}
if (i >= CHRG_INTR_END) {
dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); return IRQ_NONE;
}
switch (i) { case VBUS_OV_IRQ:
dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n"); break; case CHARGE_DONE_IRQ:
dev_dbg(&info->pdev->dev, "Charging Done INTR\n"); break; case CHARGE_CHARGING_IRQ:
dev_dbg(&info->pdev->dev, "Start Charging IRQ\n"); break; case BAT_SAFE_QUIT_IRQ:
dev_dbg(&info->pdev->dev, "Quit Safe Mode(restart timer) Charging IRQ\n"); break; case BAT_SAFE_ENTER_IRQ:
dev_dbg(&info->pdev->dev, "Enter Safe Mode(timer expire) Charging IRQ\n"); break; case QCBTU_IRQ:
dev_dbg(&info->pdev->dev, "Quit Battery Under Temperature(CHRG) INTR\n"); break; case CBTU_IRQ:
dev_dbg(&info->pdev->dev, "Hit Battery Under Temperature(CHRG) INTR\n"); break; case QCBTO_IRQ:
dev_dbg(&info->pdev->dev, "Quit Battery Over Temperature(CHRG) INTR\n"); break; case CBTO_IRQ:
dev_dbg(&info->pdev->dev, "Hit Battery Over Temperature(CHRG) INTR\n"); break; default:
dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); goto out;
}
mutex_lock(&info->lock);
info->valid = false;
mutex_unlock(&info->lock);
power_supply_changed(info->psy_usb);
out: return IRQ_HANDLED;
}
/* * The HP Pavilion x2 10 series comes in a number of variants: * Bay Trail SoC + AXP288 PMIC, Micro-USB, DMI_BOARD_NAME: "8021" * Bay Trail SoC + AXP288 PMIC, Type-C, DMI_BOARD_NAME: "815D" * Cherry Trail SoC + AXP288 PMIC, Type-C, DMI_BOARD_NAME: "813E" * Cherry Trail SoC + TI PMIC, Type-C, DMI_BOARD_NAME: "827C" or "82F4" * * The variants with the AXP288 + Type-C connector are all kinds of special: * * 1. They use a Type-C connector which the AXP288 does not support, so when * using a Type-C charger it is not recognized. Unlike most AXP288 devices, * this model actually has mostly working ACPI AC / Battery code, the ACPI code * "solves" this by simply setting the input_current_limit to 3A. * There are still some issues with the ACPI code, so we use this native driver, * and to solve the charging not working (500mA is not enough) issue we hardcode * the 3A input_current_limit like the ACPI code does. * * 2. If no charger is connected the machine boots with the vbus-path disabled. * Normally this is done when a 5V boost converter is active to avoid the PMIC * trying to charge from the 5V boost converter's output. This is done when * an OTG host cable is inserted and the ID pin on the micro-B receptacle is * pulled low and the ID pin has an ACPI event handler associated with it * which re-enables the vbus-path when the ID pin is pulled high when the * OTG host cable is removed. The Type-C connector has no ID pin, there is * no ID pin handler and there appears to be no 5V boost converter, so we * end up not charging because the vbus-path is disabled, until we unplug * the charger which automatically clears the vbus-path disable bit and then * on the second plug-in of the adapter we start charging. To solve the not * charging on first charger plugin we unconditionally enable the vbus-path at * probe on this model, which is safe since there is no 5V boost converter.
*/ staticconststruct dmi_system_id axp288_hp_x2_dmi_ids[] = {
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "815D"),
},
},
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "813E"),
},
},
{} /* Terminating entry */
};
/* Determine cable/charger type */ if (dmi_check_system(axp288_hp_x2_dmi_ids)) { /* See comment above axp288_hp_x2_dmi_ids declaration */
dev_dbg(&info->pdev->dev, "HP X2 with Type-C, setting inlmt to 3A\n");
current_limit = 3000000;
} elseif (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) {
dev_dbg(&info->pdev->dev, "USB SDP charger is connected\n");
current_limit = 500000;
} elseif (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) {
dev_dbg(&info->pdev->dev, "USB CDP charger is connected\n");
current_limit = 1500000;
} elseif (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) {
dev_dbg(&info->pdev->dev, "USB DCP charger is connected\n");
current_limit = 2000000;
} else { /* Charger type detection still in progress, bail. */ return;
}
/* Set vbus current limit first, then enable charger */
ret = axp288_charger_set_vbus_inlmt(info, current_limit); if (ret == 0)
axp288_charger_enable_charger(info, true); else
dev_err(&info->pdev->dev, "error setting current limit (%d)\n", ret);
if (dmi_check_system(axp288_hp_x2_dmi_ids)) { /* See comment above axp288_hp_x2_dmi_ids declaration */
ret = axp288_charger_vbus_path_select(info, true); if (ret < 0) return ret;
} else { /* Set Vhold to the factory default / recommended 4.4V */
val = VBUS_ISPOUT_VHOLD_SET_4400MV << VBUS_ISPOUT_VHOLD_SET_BIT_POS;
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
VBUS_ISPOUT_VHOLD_SET_MASK, val); if (ret < 0) {
dev_err(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_VBUS_IPSOUT_MGMT, ret); return ret;
}
}
/* Read current charge voltage and current limit */
ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val); if (ret < 0) {
dev_err(&info->pdev->dev, "register(%x) read error(%d)\n",
AXP20X_CHRG_CTRL1, ret); return ret;
}
/* Determine charge voltage */
cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS; switch (cv) { case CHRG_CCCV_CV_4100MV:
info->cv = CV_4100MV; break; case CHRG_CCCV_CV_4150MV:
info->cv = CV_4150MV; break; case CHRG_CCCV_CV_4200MV:
info->cv = CV_4200MV; break; case CHRG_CCCV_CV_4350MV:
info->cv = CV_4350MV; break;
}
/* Determine charge current limit */
cc = (val & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS;
cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
info->cc = cc;
/* * Do not allow the user to configure higher settings then those * set by the firmware
*/
info->max_cv = info->cv;
info->max_cc = info->cc;
/* * Normally the native AXP288 fg/charger drivers are preferred but * on some devices the ACPI drivers should be used instead.
*/ if (!acpi_quirk_skip_acpi_ac_and_battery()) return -ENODEV;
/* * On some devices the fuelgauge and charger parts of the axp288 are * not used, check that the fuelgauge is enabled (CC_CTRL != 0).
*/
ret = regmap_read(axp20x->regmap, AXP20X_CC_CTRL, &val); if (ret < 0) return ret; if (val == 0) return -ENODEV;
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM;
/* * On devices with broken ACPI GPIO event handlers there also is no ACPI * "INT3496" (USB_HOST_EXTCON_HID) device. x86-android-tablets.ko * instantiates an "intel-int3496" extcon on these devs as a workaround.
*/ if (acpi_quirk_skip_gpio_event_handlers())
extcon_name = "intel-int3496"; elseif (acpi_dev_present(USB_HOST_EXTCON_HID, NULL, -1))
extcon_name = USB_HOST_EXTCON_NAME;
if (extcon_name) {
info->otg.cable = extcon_get_extcon_dev(extcon_name); if (IS_ERR(info->otg.cable)) {
dev_err_probe(dev, PTR_ERR(info->otg.cable), "extcon_get_extcon_dev(%s) failed\n",
USB_HOST_EXTCON_NAME); return PTR_ERR(info->otg.cable);
}
dev_info(dev, "Using " USB_HOST_EXTCON_HID " extcon for usb-id\n");
}
platform_set_drvdata(pdev, info);
ret = charger_init_hw_regs(info); if (ret) return ret;
/* Register with power supply class */
charger_cfg.drv_data = info;
info->psy_usb = devm_power_supply_register(dev, &axp288_charger_desc,
&charger_cfg); if (IS_ERR(info->psy_usb)) {
ret = PTR_ERR(info->psy_usb);
dev_err(dev, "failed to register power supply: %d\n", ret); return ret;
}
/* Cancel our work on cleanup, register this before the notifiers */
ret = devm_add_action(dev, axp288_charger_cancel_work, info); if (ret) return ret;
/* Register for extcon notification */
INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
ret = devm_extcon_register_notifier_all(dev, info->cable.edev,
&info->cable.nb); if (ret) {
dev_err(dev, "failed to register cable extcon notifier\n"); return ret;
}
schedule_work(&info->cable.work);
/* Register for OTG notification */
INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt; if (info->otg.cable) {
ret = devm_extcon_register_notifier(dev, info->otg.cable,
EXTCON_USB_HOST, &info->otg.id_nb); if (ret) {
dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n"); return ret;
}
schedule_work(&info->otg.work);
}
/* Register charger interrupts */ for (i = 0; i < CHRG_INTR_END; i++) {
pirq = platform_get_irq(info->pdev, i); if (pirq < 0) return pirq;
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); if (info->irq[i] < 0) {
dev_warn(&info->pdev->dev, "failed to get virtual interrupt=%d\n", pirq); return info->irq[i];
}
ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
NULL, axp288_charger_irq_thread_handler,
IRQF_ONESHOT, info->pdev->name, info); if (ret) {
dev_err(dev, "failed to request interrupt=%d\n",
info->irq[i]); return ret;
}
}
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.