// SPDX-License-Identifier: GPL-2.0-or-later /* * AXP20x PMIC USB power supply status driver * * Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com> * Copyright (C) 2014 Bruno Prémont <bonbons@linux-vserver.org>
*/
/* * Note do not raise the debounce time, we must report Vusb high within * 100ms otherwise we get Vbus errors in musb.
*/ #define DEBOUNCE_TIME msecs_to_jiffies(50)
staticbool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power)
{ /* * Polling is only necessary while VBUS is offline. While online, a * present->absent transition implies an online->offline transition * and will trigger the VBUS_REMOVAL IRQ.
*/ if (power->axp_data->vbus_needs_polling && !power->online) returntrue;
ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &val); if (ret) goto out;
val &= (AXP20X_PWR_STATUS_VBUS_PRESENT | AXP20X_PWR_STATUS_VBUS_USED); if (val != power->old_status)
power_supply_changed(power->supply);
if (power->usb_bc_en_bit && (val & AXP20X_PWR_STATUS_VBUS_PRESENT) !=
(power->old_status & AXP20X_PWR_STATUS_VBUS_PRESENT)) {
dev_dbg(power->dev, "Cable status changed, re-enabling USB BC");
ret = regmap_field_write(power->usb_bc_en_bit, 1); if (ret)
dev_err(power->dev, "failed to enable USB BC: errno %d",
ret);
}
power->old_status = val;
power->online = val & AXP20X_PWR_STATUS_VBUS_USED;
out: if (axp20x_usb_vbus_needs_polling(power))
mod_delayed_work(system_power_efficient_wq, &power->vbus_detect, DEBOUNCE_TIME);
}
switch (psp) { case POWER_SUPPLY_PROP_VOLTAGE_MIN:
ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v); if (ret) return ret;
val->intval = AXP20X_VBUS_VHOLD_uV(v); return 0; case POWER_SUPPLY_PROP_VOLTAGE_NOW: if (IS_ENABLED(CONFIG_AXP20X_ADC)) { /* * IIO framework gives mV but Power Supply framework * gives uV.
*/
ret = iio_read_channel_processed_scale(power->vbus_v,
&val->intval, 1000); if (ret) return ret;
return 0;
}
ret = axp20x_read_variable_width(power->regmap,
AXP20X_VBUS_V_ADC_H, 12); if (ret < 0) return ret;
val->intval = ret * 1700; /* 1 step = 1.7 mV */ return 0; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = regmap_field_read(power->curr_lim_fld, &v); if (ret) return ret;
if (v < power->axp_data->curr_lim_table_size)
val->intval = power->axp_data->curr_lim_table[v]; else
val->intval = power->axp_data->curr_lim_table[
power->axp_data->curr_lim_table_size - 1]; return 0; case POWER_SUPPLY_PROP_CURRENT_NOW: if (IS_ENABLED(CONFIG_AXP20X_ADC)) { /* * IIO framework gives mA but Power Supply framework * gives uA.
*/
ret = iio_read_channel_processed_scale(power->vbus_i,
&val->intval, 1000); if (ret) return ret;
return 0;
}
ret = axp20x_read_variable_width(power->regmap,
AXP20X_VBUS_I_ADC_H, 12); if (ret < 0) return ret;
val->intval = ret * 375; /* 1 step = 0.375 mA */ return 0;
case POWER_SUPPLY_PROP_USB_TYPE: return axp20x_get_usb_type(power, val); default: break;
}
/* All the properties below need the input-status reg value */
ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input); if (ret) return ret;
switch (psp) { case POWER_SUPPLY_PROP_HEALTH: if (!(input & AXP20X_PWR_STATUS_VBUS_PRESENT)) {
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; break;
}
val->intval = POWER_SUPPLY_HEALTH_GOOD;
if (power->vbus_valid_bit) {
ret = regmap_field_read(power->vbus_valid_bit, &v); if (ret) return ret;
if (v == 0)
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
}
staticint axp717_usb_power_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
{ struct axp20x_usb_power *power = power_supply_get_drvdata(psy); unsignedint v; int ret;
switch (psp) { case POWER_SUPPLY_PROP_HEALTH:
val->intval = POWER_SUPPLY_HEALTH_GOOD;
ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &v); if (ret) return ret;
if (!(v & AXP717_PWR_STATUS_VBUS_GOOD))
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
ret = regmap_read(power->regmap, AXP717_PMU_FAULT_VBUS, &v); if (ret) return ret;
v &= (AXP717_PMU_FAULT_VBUS | AXP717_PMU_FAULT_VSYS); if (v) {
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
regmap_write(power->regmap, AXP717_PMU_FAULT_VBUS, v);
}
break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = regmap_read(power->regmap, AXP717_INPUT_CUR_LIMIT_CTRL, &v); if (ret) return ret;
/* 50ma step size with 100ma offset. */
v &= AXP717_INPUT_CUR_LIMIT_MASK;
val->intval = (v * 50000) + 100000; break; case POWER_SUPPLY_PROP_ONLINE: case POWER_SUPPLY_PROP_PRESENT:
ret = regmap_read(power->regmap, AXP717_ON_INDICATE, &v); if (ret) return ret;
val->intval = !!(v & AXP717_PWR_STATUS_VBUS_GOOD); break; case POWER_SUPPLY_PROP_USB_TYPE: return axp20x_get_usb_type(power, val); case POWER_SUPPLY_PROP_VOLTAGE_MIN:
ret = regmap_read(power->regmap, AXP717_INPUT_VOL_LIMIT_CTRL, &v); if (ret) return ret;
/* 80mv step size with 3.88v offset. */
v &= AXP717_INPUT_VOL_LIMIT_MASK;
val->intval = (v * 80000) + 3880000; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: if (IS_ENABLED(CONFIG_AXP20X_ADC)) { /* * IIO framework gives mV but Power Supply framework * gives uV.
*/
ret = iio_read_channel_processed_scale(power->vbus_v,
&val->intval, 1000); if (ret) return ret;
return 0;
}
ret = axp20x_read_variable_width(power->regmap,
AXP717_VBUS_V_H, 16); if (ret < 0) return ret;
staticint axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, int intval)
{ int val;
switch (intval) { case 4000000: case 4100000: case 4200000: case 4300000: case 4400000: case 4500000: case 4600000: case 4700000:
val = (intval - 4000000) / 100000; return regmap_update_bits(power->regmap,
AXP20X_VBUS_IPSOUT_MGMT,
AXP20X_VBUS_VHOLD_MASK,
val << AXP20X_VBUS_VHOLD_OFFSET); default: return -EINVAL;
}
return -EINVAL;
}
staticint axp717_usb_power_set_voltage_min(struct axp20x_usb_power *power, int intval)
{ int val;
/* Minimum value of 3.88v and maximum of 5.08v. */ if (intval < 3880000 || intval > 5080000) return -EINVAL;
/* step size of 80ma with 3.88v offset. */
val = (intval - 3880000) / 80000; return regmap_update_bits(power->regmap,
AXP717_INPUT_VOL_LIMIT_CTRL,
AXP717_INPUT_VOL_LIMIT_MASK, val);
}
staticint axp20x_usb_power_set_input_current_limit(struct axp20x_usb_power *power, int intval)
{ int ret; unsignedint reg; constunsignedint max = power->axp_data->curr_lim_table_size;
if (intval == -1) return -EINVAL;
if (power->max_input_cur && (intval > power->max_input_cur)) {
dev_warn(power->dev, "requested current %d clamped to max current %d\n",
intval, power->max_input_cur);
intval = power->max_input_cur;
}
/* * BC1.2 detection can cause a race condition if we try to set a current * limit while it's in progress. When it finishes it will overwrite the * current limit we just set.
*/ if (power->usb_bc_en_bit) {
dev_dbg(power->dev, "disabling BC1.2 detection because current limit was set");
ret = regmap_field_write(power->usb_bc_en_bit, 0); if (ret) return ret;
}
for (reg = max - 1; reg > 0; reg--) if (power->axp_data->curr_lim_table[reg] <= intval) break;
dev_dbg(power->dev, "setting input current limit reg to %d (%d uA), requested %d uA",
reg, power->axp_data->curr_lim_table[reg], intval);
staticint axp717_usb_power_set_input_current_limit(struct axp20x_usb_power *power, int intval)
{ int tmp;
/* Minimum value of 100mA and maximum value of 3.25A*/ if (intval < 100000 || intval > 3250000) return -EINVAL;
if (power->max_input_cur && (intval > power->max_input_cur)) {
dev_warn(power->dev, "requested current %d clamped to max current %d\n",
intval, power->max_input_cur);
intval = power->max_input_cur;
}
/* Minimum value of 100mA with step size of 50mA. */
tmp = (intval - 100000) / 50000; return regmap_update_bits(power->regmap,
AXP717_INPUT_CUR_LIMIT_CTRL,
AXP717_INPUT_CUR_LIMIT_MASK, tmp);
}
/* * The VBUS path select flag works differently on AXP288 and newer: * - On AXP20x and AXP22x, the flag enables VBUS (ignoring N_VBUSEN). * - On AXP288 and AXP8xx, the flag disables VBUS (ignoring N_VBUSEN). * We only expose the control on variants where it can be used to force * the VBUS input offline.
*/ if (psp == POWER_SUPPLY_PROP_ONLINE) return power->vbus_disable_bit != NULL;
#ifdef CONFIG_PM_SLEEP staticint axp20x_usb_power_suspend(struct device *dev)
{ struct axp20x_usb_power *power = dev_get_drvdata(dev); int i = 0;
/* * Allow wake via VBUS_PLUGIN only. * * As nested threaded IRQs are not automatically disabled during * suspend, we must explicitly disable the remainder of the IRQs.
*/ if (device_may_wakeup(&power->supply->dev))
enable_irq_wake(power->irqs[i++]); while (i < power->num_irqs)
disable_irq(power->irqs[i++]);
return 0;
}
staticint axp20x_usb_power_resume(struct device *dev)
{ struct axp20x_usb_power *power = dev_get_drvdata(dev); int i = 0;
if (device_may_wakeup(&power->supply->dev))
disable_irq_wake(power->irqs[i++]); while (i < power->num_irqs)
enable_irq(power->irqs[i++]);
field = devm_regmap_field_alloc(dev, regmap, fdesc); if (IS_ERR(field)) return PTR_ERR(field);
*fieldp = field; return 0;
}
/* Optionally allow users to specify a maximum charging current. */ staticvoid axp20x_usb_power_parse_dt(struct device *dev, struct axp20x_usb_power *power)
{ int ret;
ret = device_property_read_u32(dev, "input-current-limit-microamp",
&power->max_input_cur); if (ret)
dev_dbg(dev, "%s() no input-current-limit specified\n", __func__);
}
power->curr_lim_fld = devm_regmap_field_alloc(&pdev->dev, power->regmap,
axp_data->curr_lim_fld); if (IS_ERR(power->curr_lim_fld)) return PTR_ERR(power->curr_lim_fld);
axp20x_usb_power_parse_dt(&pdev->dev, power);
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
axp_data->vbus_valid_bit,
&power->vbus_valid_bit); if (ret) return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
axp_data->vbus_mon_bit,
&power->vbus_mon_bit); if (ret) return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
axp_data->usb_bc_en_bit,
&power->usb_bc_en_bit); if (ret) return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
axp_data->usb_bc_det_fld,
&power->usb_bc_det_fld); if (ret) return ret;
ret = axp20x_regmap_field_alloc_optional(&pdev->dev, power->regmap,
axp_data->vbus_disable_bit,
&power->vbus_disable_bit); if (ret) return ret;
ret = devm_delayed_work_autocancel(&pdev->dev, &power->vbus_detect,
axp_data->axp20x_read_vbus); if (ret) return ret;
if (power->vbus_mon_bit) { /* Enable vbus valid checking */
ret = regmap_field_write(power->vbus_mon_bit, 1); if (ret) return ret;
if (IS_ENABLED(CONFIG_AXP20X_ADC))
ret = axp_data->axp20x_cfg_iio_chan(pdev, power); else
ret = axp_data->axp20x_cfg_adc_reg(power);
if (ret) return ret;
}
if (power->usb_bc_en_bit) { /* Enable USB Battery Charging specification detection */
ret = regmap_field_write(power->usb_bc_en_bit, 1); if (ret) return ret;
}
power->supply = devm_power_supply_register(&pdev->dev,
axp_data->power_desc,
&psy_cfg); if (IS_ERR(power->supply)) return PTR_ERR(power->supply);
/* Request irqs after registering, as irqs may trigger immediately */ for (i = 0; i < axp_data->num_irq_names; i++) {
irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]); if (irq < 0) return irq;
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.