// SPDX-License-Identifier: GPL-2.0-only /* * Motorola CPCAP PMIC battery charger driver * * Copyright (C) 2017 Tony Lindgren <tony@atomide.com> * * Rewritten for Linux power framework with some parts based on * earlier driver found in the Motorola Linux kernel: * * Copyright (C) 2009-2010 Motorola, Inc.
*/
switch (psp) { case POWER_SUPPLY_PROP_STATUS:
val->intval = ddata->status; break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = ddata->limit_current; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
val->intval = ddata->voltage; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: if (ddata->status == POWER_SUPPLY_STATUS_CHARGING)
val->intval = cpcap_charger_get_charge_voltage(ddata) *
1000; else
val->intval = 0; break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (ddata->status == POWER_SUPPLY_STATUS_CHARGING)
val->intval = cpcap_charger_get_charge_current(ddata) *
1000; else
val->intval = 0; break; case POWER_SUPPLY_PROP_ONLINE:
val->intval = ddata->status == POWER_SUPPLY_STATUS_CHARGING; break; default: return -EINVAL;
}
return 0;
}
staticint cpcap_charger_match_voltage(int voltage)
{ switch (voltage) { case 0 ... 4100000 - 1: return 3800000; case 4100000 ... 4120000 - 1: return 4100000; case 4120000 ... 4150000 - 1: return 4120000; case 4150000 ... 4170000 - 1: return 4150000; case 4170000 ... 4200000 - 1: return 4170000; case 4200000 ... 4230000 - 1: return 4200000; case 4230000 ... 4250000 - 1: return 4230000; case 4250000 ... 4270000 - 1: return 4250000; case 4270000 ... 4300000 - 1: return 4270000; case 4300000 ... 4330000 - 1: return 4300000; case 4330000 ... 4350000 - 1: return 4330000; case 4350000 ... 4380000 - 1: return 4350000; case 4380000 ... 4400000 - 1: return 4380000; case 4400000 ... 4420000 - 1: return 4400000; case 4420000 ... 4440000 - 1: return 4420000; case 4440000: return 4440000; default: return 0;
}
}
staticint
cpcap_charger_get_bat_const_charge_voltage(struct cpcap_charger_ddata *ddata)
{ union power_supply_propval prop; struct power_supply *battery; int voltage = ddata->voltage; int error;
battery = power_supply_get_by_name("battery"); if (battery) {
error = power_supply_get_property(battery,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
&prop); if (!error)
voltage = prop.intval;
power_supply_put(battery);
}
return voltage;
}
staticint cpcap_charger_current_to_regval(int microamp)
{ int miliamp = microamp / 1000; int res;
if (miliamp < 0) return -EINVAL; if (miliamp < 70) return CPCAP_REG_CRM_ICHRG(0x0); if (miliamp < 177) return CPCAP_REG_CRM_ICHRG(0x1); if (miliamp >= 1596) return CPCAP_REG_CRM_ICHRG(0xe);
res = microamp / 88666; if (res > 0xd)
res = 0xd; return CPCAP_REG_CRM_ICHRG(res);
}
staticvoid cpcap_charger_set_cable_path(struct cpcap_charger_ddata *ddata, bool enabled)
{ if (!ddata->gpio[0]) return;
gpiod_set_value(ddata->gpio[0], enabled);
}
staticvoid cpcap_charger_set_inductive_path(struct cpcap_charger_ddata *ddata, bool enabled)
{ if (!ddata->gpio[1]) return;
gpiod_set_value(ddata->gpio[1], enabled);
}
staticvoid cpcap_charger_update_state(struct cpcap_charger_ddata *ddata, int state)
{ constchar *status;
if (state > POWER_SUPPLY_STATUS_FULL) {
dev_warn(ddata->dev, "unknown state: %i\n", state);
return;
}
ddata->status = state;
switch (state) { case POWER_SUPPLY_STATUS_DISCHARGING:
status = "DISCONNECTED"; break; case POWER_SUPPLY_STATUS_NOT_CHARGING:
status = "DETECTING"; break; case POWER_SUPPLY_STATUS_CHARGING:
status = "CHARGING"; break; case POWER_SUPPLY_STATUS_FULL:
status = "DONE"; break; default: return;
}
dev_dbg(ddata->dev, "state: %s\n", status);
}
staticint cpcap_charger_disable(struct cpcap_charger_ddata *ddata)
{ int error;
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, 0x3fff,
CPCAP_REG_CRM_FET_OVRD |
CPCAP_REG_CRM_FET_CTRL); if (error)
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
return error;
}
staticint cpcap_charger_enable(struct cpcap_charger_ddata *ddata, int max_voltage, int charge_current, int trickle_current)
{ int error;
if (!max_voltage || !charge_current) return -EINVAL;
/* VBUS control functions for the USB PHY companion */ staticvoid cpcap_charger_vbus_work(struct work_struct *work)
{ struct cpcap_charger_ddata *ddata; bool vbus = false; int error;
staticvoid cpcap_charger_disconnect(struct cpcap_charger_ddata *ddata, int state, unsignedlong delay)
{ int error;
/* Update battery state before disconnecting the charger */ switch (state) { case POWER_SUPPLY_STATUS_DISCHARGING: case POWER_SUPPLY_STATUS_FULL:
power_supply_changed(ddata->usb); break; default: break;
}
error = cpcap_charger_disable(ddata); if (error) {
cpcap_charger_update_state(ddata, POWER_SUPPLY_STATUS_UNKNOWN); return;
}
error = cpcap_charger_get_ints_state(ddata, &s); if (error) return;
/* Just init the state if a charger is connected with no chrg_det set */ if (!s.chrg_det && s.chrgcurr1 && s.vbusvld) {
cpcap_charger_update_state(ddata,
POWER_SUPPLY_STATUS_NOT_CHARGING);
return;
}
/* * If battery voltage is higher than charge voltage, it may have been * charged to 4.35V by Android. Try again in 10 minutes.
*/ if (cpcap_charger_get_charge_voltage(ddata) > ddata->voltage) {
cpcap_charger_disconnect(ddata,
POWER_SUPPLY_STATUS_NOT_CHARGING,
HZ * 60 * 10);
return;
}
/* Delay for 80ms to avoid vbus bouncing when usb cable is plugged in */
usleep_range(80000, 120000);
/* Throttle chrgcurr2 interrupt for charger done and retry */ switch (ddata->status) { case POWER_SUPPLY_STATUS_CHARGING: if (s.chrgcurr2) break;
new_state = POWER_SUPPLY_STATUS_FULL;
if (s.chrgcurr1 && s.vbusvld) {
cpcap_charger_disconnect(ddata, new_state, HZ * 5); return;
} break; case POWER_SUPPLY_STATUS_FULL: if (!s.chrgcurr2) break; if (s.vbusvld)
new_state = POWER_SUPPLY_STATUS_NOT_CHARGING; else
new_state = POWER_SUPPLY_STATUS_DISCHARGING;
if (!ddata->feeding_vbus && cpcap_charger_vbus_valid(ddata) &&
s.chrgcurr1) { int max_current; int vchrg, ichrg; union power_supply_propval val; struct power_supply *battery;
battery = power_supply_get_by_name("battery"); if (!battery) {
dev_err(ddata->dev, "battery power_supply not available\n"); return;
}
error = power_supply_get_property(battery, POWER_SUPPLY_PROP_PRESENT, &val);
power_supply_put(battery); if (error) goto out_err;
if (val.intval) {
max_current = 1596000;
} else {
dev_info(ddata->dev, "battery not inserted, charging disabled\n");
max_current = 0;
}
if (max_current > ddata->limit_current)
max_current = ddata->limit_current;
staticint cpcap_usb_init_interrupts(struct platform_device *pdev, struct cpcap_charger_ddata *ddata)
{ int i, error;
for (i = 0; i < ARRAY_SIZE(cpcap_charger_irqs); i++) {
error = cpcap_usb_init_irq(pdev, ddata, cpcap_charger_irqs[i]); if (error) return error;
}
return 0;
}
staticvoid cpcap_charger_init_optional_gpios(struct cpcap_charger_ddata *ddata)
{ int i;
for (i = 0; i < 2; i++) {
ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode",
i, GPIOD_OUT_HIGH); if (IS_ERR(ddata->gpio[i])) {
dev_info(ddata->dev, "no mode change GPIO%i: %li\n",
i, PTR_ERR(ddata->gpio[i]));
ddata->gpio[i] = NULL;
}
}
}
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.