// SPDX-License-Identifier: GPL-2.0-only /* * Battery driver for CPCAP PMIC * * Copyright (C) 2017 Tony Lindgren <tony@atomide.com> * * Some parts of the code based on earlier Motorola mapphone Linux kernel * drivers: * * Copyright (C) 2009-2010 Motorola, Inc.
*/
/* * Register bit defines for CPCAP_REG_BPEOL. Some of these seem to * map to MC13783UG.pdf "Table 5-19. Register 13, Power Control 0" * to enable BATTDETEN, LOBAT and EOL features. We currently use * LOBAT interrupts instead of EOL.
*/ #define CPCAP_REG_BPEOL_BIT_EOL9 BIT(9) /* Set for EOL irq */ #define CPCAP_REG_BPEOL_BIT_EOL8 BIT(8) /* Set for EOL irq */ #define CPCAP_REG_BPEOL_BIT_UNKNOWN7 BIT(7) #define CPCAP_REG_BPEOL_BIT_UNKNOWN6 BIT(6) #define CPCAP_REG_BPEOL_BIT_UNKNOWN5 BIT(5) #define CPCAP_REG_BPEOL_BIT_EOL_MULTI BIT(4) /* Set for multiple EOL irqs */ #define CPCAP_REG_BPEOL_BIT_UNKNOWN3 BIT(3) #define CPCAP_REG_BPEOL_BIT_UNKNOWN2 BIT(2) #define CPCAP_REG_BPEOL_BIT_BATTDETEN BIT(1) /* Enable battery detect */ #define CPCAP_REG_BPEOL_BIT_EOLSEL BIT(0) /* BPDET = 0, EOL = 1 */
/* * Register bit defines for CPCAP_REG_CCC1. These seem similar to the twl6030 * coulomb counter registers rather than the mc13892 registers. Both twl6030 * and mc13892 set bits 2 and 1 to reset and clear registers. But mc13892 * sets bit 0 to start the coulomb counter while twl6030 sets bit 0 to stop * the coulomb counter like cpcap does. So for now, we use the twl6030 style * naming for the registers.
*/ #define CPCAP_REG_CCC1_ACTIVE_MODE1 BIT(4) /* Update rate */ #define CPCAP_REG_CCC1_ACTIVE_MODE0 BIT(3) /* Update rate */ #define CPCAP_REG_CCC1_AUTOCLEAR BIT(2) /* Resets sample registers */ #define CPCAP_REG_CCC1_CAL_EN BIT(1) /* Clears after write in 1s */ #define CPCAP_REG_CCC1_PAUSE BIT(0) /* Stop counters, allow write */ #define CPCAP_REG_CCC1_RESET_MASK (CPCAP_REG_CCC1_AUTOCLEAR | \
CPCAP_REG_CCC1_CAL_EN)
struct cpcap_battery_state_data { int voltage; int current_ua; int counter_uah; int temperature;
ktime_t time; struct cpcap_coulomb_counter_data cc;
};
/** * cpcap_battery_cc_raw_div - calculate and divide coulomb counter μAms values * @ddata: device driver data * @sample: coulomb counter sample value * @accumulator: coulomb counter integrator value * @offset: coulomb counter offset value * @divider: conversion divider * * Note that cc_lsb and cc_dur values are from Motorola Linux kernel * function data_get_avg_curr_ua() and seem to be based on measured test * results. It also has the following comment: * * Adjustment factors are applied here as a temp solution per the test * results. Need to work out a formal solution for this adjustment. * * A coulomb counter for similar hardware seems to be documented in * "TWL6030 Gas Gauging Basics (Rev. A)" swca095a.pdf in chapter * "10 Calculating Accumulated Current". We however follow what the * Motorola mapphone Linux kernel is doing as there may be either a * TI or ST coulomb counter in the PMIC.
*/ staticint cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
s32 sample, s32 accumulator,
s16 offset, u32 divider)
{
s64 acc;
/** * cpcap_battery_read_accumulated - reads cpcap coulomb counter * @ddata: device driver data * @ccd: coulomb counter values * * Based on Motorola mapphone kernel function data_read_regs(). * Looking at the registers, the coulomb counter seems similar to * the coulomb counter in TWL6030. See "TWL6030 Gas Gauging Basics * (Rev. A) swca095a.pdf for "10 Calculating Accumulated Current". * * Note that swca095a.pdf instructs to stop the coulomb counter * before reading to avoid values changing. Motorola mapphone * Linux kernel does not do it, so let's assume they've verified * the data produced is correct.
*/ staticint
cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata, struct cpcap_coulomb_counter_data *ccd)
{
u16 buf[7]; /* CPCAP_REG_CCS1 to CCI */ int error;
/* * Based on the values from Motorola mapphone Linux kernel for the * stock Droid 4 battery eb41. In the Motorola mapphone Linux * kernel tree the value for pm_cd_factor is passed to the kernel * via device tree. If it turns out to be something device specific * we can consider that too later. These values are also fine for * Bionic's hw4x. * * And looking at the battery full and shutdown values for the stock * kernel on droid 4, full is 4351000 and software initiates shutdown * at 3078000. The device will die around 2743000.
*/ staticconststruct cpcap_battery_config cpcap_battery_eb41_data = {
.cd_factor = 0x3cc,
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
.info.voltage_max_design = 4351000,
.info.voltage_min_design = 3100000,
.info.charge_full_design = 1740000,
.bat.constant_charge_voltage_max_uv = 4200000,
};
staticint cpcap_battery_get_charger_status(struct cpcap_battery_ddata *ddata, int *val)
{ union power_supply_propval prop; struct power_supply *charger; int error;
charger = power_supply_get_by_name("usb"); if (!charger) return -ENODEV;
error = cpcap_battery_get_charger_status(ddata, &val); if (!error) { switch (val) { case POWER_SUPPLY_STATUS_DISCHARGING:
dev_dbg(ddata->dev, "charger disconnected\n");
ddata->is_full = 0; break; case POWER_SUPPLY_STATUS_FULL:
dev_dbg(ddata->dev, "charger full status\n");
ddata->is_full = 1; break; default: break;
}
}
/* * The full battery voltage here can be inaccurate, it's used just to * filter out any trickle charging events. We clear the is_full status * on charger disconnect above anyways.
*/
vfull = ddata->config.bat.constant_charge_voltage_max_uv - 120000;
if (ddata->is_full && state->voltage < vfull)
ddata->is_full = 0;
full = cpcap_battery_get_full(ddata); if (full->voltage) {
full->voltage = 0;
ddata->charge_full =
empty->counter_uah - full->counter_uah;
}
}
return 0;
}
/* * Update battery status when cpcap-charger calls power_supply_changed(). * This allows us to detect battery full condition before the charger * disconnects.
*/ staticvoid cpcap_battery_external_power_changed(struct power_supply *psy)
{ union power_supply_propval prop;
if (ddata->check_nvmem)
cpcap_battery_detect_battery_type(ddata);
switch (psp) { case POWER_SUPPLY_PROP_PRESENT: if (latest->temperature > CPCAP_NO_BATTERY || ignore_temperature_probe)
val->intval = 1; else
val->intval = 0; break; case POWER_SUPPLY_PROP_STATUS: if (cpcap_battery_full(ddata)) {
val->intval = POWER_SUPPLY_STATUS_FULL; break;
} if (cpcap_battery_cc_get_avg_current(ddata) < 0)
val->intval = POWER_SUPPLY_STATUS_CHARGING; else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING; break; case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = ddata->config.info.technology; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = cpcap_battery_get_voltage(ddata); break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = ddata->config.info.voltage_max_design; break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = ddata->config.info.voltage_min_design; break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
val->intval = ddata->config.bat.constant_charge_voltage_max_uv; break; case POWER_SUPPLY_PROP_CURRENT_AVG:
sample = latest->cc.sample - previous->cc.sample; if (!sample) {
val->intval = cpcap_battery_cc_get_avg_current(ddata); break;
}
accumulator = latest->cc.accumulator - previous->cc.accumulator;
val->intval = cpcap_battery_cc_to_ua(ddata, sample,
accumulator,
latest->cc.offset); break; case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = latest->current_ua; break; case POWER_SUPPLY_PROP_CHARGE_COUNTER:
val->intval = latest->counter_uah; break; case POWER_SUPPLY_PROP_POWER_NOW:
tmp = (latest->voltage / 10000) * latest->current_ua;
val->intval = div64_s64(tmp, 100); break; case POWER_SUPPLY_PROP_POWER_AVG:
sample = latest->cc.sample - previous->cc.sample; if (!sample) {
tmp = cpcap_battery_cc_get_avg_current(ddata);
tmp *= (latest->voltage / 10000);
val->intval = div64_s64(tmp, 100); break;
}
accumulator = latest->cc.accumulator - previous->cc.accumulator;
tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator,
latest->cc.offset);
tmp *= ((latest->voltage + previous->voltage) / 20000);
val->intval = div64_s64(tmp, 100); break; case POWER_SUPPLY_PROP_CAPACITY:
empty = cpcap_battery_get_empty(ddata); if (!empty->voltage || !ddata->charge_full) return -ENODATA; /* (ddata->charge_full / 200) is needed for rounding */
val->intval = empty->counter_uah - latest->counter_uah +
ddata->charge_full / 200;
val->intval = clamp(val->intval, 0, ddata->charge_full);
val->intval = val->intval * 100 / ddata->charge_full; break; case POWER_SUPPLY_PROP_CAPACITY_LEVEL: if (cpcap_battery_full(ddata))
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; elseif (latest->voltage >= 3750000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; elseif (latest->voltage >= 3300000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; elseif (latest->voltage > 3100000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; elseif (latest->voltage <= 3100000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; else
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; break; case POWER_SUPPLY_PROP_CHARGE_NOW:
empty = cpcap_battery_get_empty(ddata); if (!empty->voltage) return -ENODATA;
val->intval = empty->counter_uah - latest->counter_uah; if (val->intval < 0) { /* Assume invalid config if CHARGE_NOW is -20% */ if (ddata->charge_full && abs(val->intval) > ddata->charge_full/5) {
empty->voltage = 0;
ddata->charge_full = 0; return -ENODATA;
}
val->intval = 0;
} elseif (ddata->charge_full && ddata->charge_full < val->intval) { /* Assume invalid config if CHARGE_NOW exceeds CHARGE_FULL by 20% */ if (val->intval > (6*ddata->charge_full)/5) {
empty->voltage = 0;
ddata->charge_full = 0; return -ENODATA;
}
val->intval = ddata->charge_full;
} break; case POWER_SUPPLY_PROP_CHARGE_FULL: if (!ddata->charge_full) return -ENODATA;
val->intval = ddata->charge_full; break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = ddata->config.info.charge_full_design; break; case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM; break; case POWER_SUPPLY_PROP_TEMP: if (ignore_temperature_probe) return -ENODATA;
val->intval = latest->temperature; break; default: return -EINVAL;
}
return 0;
}
staticint cpcap_battery_update_charger(struct cpcap_battery_ddata *ddata, int const_charge_voltage)
{ union power_supply_propval prop; union power_supply_propval val; struct power_supply *charger; int error;
charger = power_supply_get_by_name("usb"); if (!charger) return -ENODEV;
error = power_supply_get_property(charger,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
&prop); if (error) goto out_put;
/* Allow charger const voltage lower than battery const voltage */ if (const_charge_voltage > prop.intval) goto out_put;
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.