Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Tony Lindgren | 3689 | 78.21% | 17 | 54.84% |
Arthur Demchenkov | 495 | 10.49% | 4 | 12.90% |
Carl Philipp Klemm | 459 | 9.73% | 3 | 9.68% |
Sebastian Reichel | 53 | 1.12% | 1 | 3.23% |
Guangqing Zhu | 10 | 0.21% | 1 | 3.23% |
Krzysztof Kozlowski | 5 | 0.11% | 2 | 6.45% |
Arvind Yadav | 3 | 0.06% | 1 | 3.23% |
Thomas Gleixner | 2 | 0.04% | 1 | 3.23% |
Colin Ian King | 1 | 0.02% | 1 | 3.23% |
Total | 4717 | 31 |
// 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. */ #include <linux/delay.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/reboot.h> #include <linux/regmap.h> #include <linux/nvmem-consumer.h> #include <linux/moduleparam.h> #include <linux/iio/consumer.h> #include <linux/iio/types.h> #include <linux/mfd/motorola-cpcap.h> /* * 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) #define CPCAP_REG_CCCC2_RATE1 BIT(5) #define CPCAP_REG_CCCC2_RATE0 BIT(4) #define CPCAP_REG_CCCC2_ENABLE BIT(3) #define CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS 250 #define CPCAP_BATTERY_EB41_HW4X_ID 0x9E #define CPCAP_BATTERY_BW8X_ID 0x98 enum { CPCAP_BATTERY_IIO_BATTDET, CPCAP_BATTERY_IIO_VOLTAGE, CPCAP_BATTERY_IIO_CHRG_CURRENT, CPCAP_BATTERY_IIO_BATT_CURRENT, CPCAP_BATTERY_IIO_NR, }; enum cpcap_battery_irq_action { CPCAP_BATTERY_IRQ_ACTION_NONE, CPCAP_BATTERY_IRQ_ACTION_CC_CAL_DONE, CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW, CPCAP_BATTERY_IRQ_ACTION_POWEROFF, }; struct cpcap_interrupt_desc { const char *name; struct list_head node; int irq; enum cpcap_battery_irq_action action; }; struct cpcap_battery_config { int cd_factor; struct power_supply_info info; struct power_supply_battery_info bat; }; struct cpcap_coulomb_counter_data { s32 sample; /* 24 or 32 bits */ s32 accumulator; s16 offset; /* 9 bits */ s16 integrator; /* 13 or 16 bits */ }; enum cpcap_battery_state { CPCAP_BATTERY_STATE_PREVIOUS, CPCAP_BATTERY_STATE_LATEST, CPCAP_BATTERY_STATE_EMPTY, CPCAP_BATTERY_STATE_FULL, CPCAP_BATTERY_STATE_NR, }; struct cpcap_battery_state_data { int voltage; int current_ua; int counter_uah; int temperature; ktime_t time; struct cpcap_coulomb_counter_data cc; }; struct cpcap_battery_ddata { struct device *dev; struct regmap *reg; struct list_head irq_list; struct iio_channel *channels[CPCAP_BATTERY_IIO_NR]; struct power_supply *psy; struct cpcap_battery_config config; struct cpcap_battery_state_data state[CPCAP_BATTERY_STATE_NR]; u32 cc_lsb; /* μAms per LSB */ atomic_t active; int charge_full; int status; u16 vendor; bool check_nvmem; unsigned int is_full:1; }; #define CPCAP_NO_BATTERY -400 static bool ignore_temperature_probe; module_param(ignore_temperature_probe, bool, 0660); static struct cpcap_battery_state_data * cpcap_battery_get_state(struct cpcap_battery_ddata *ddata, enum cpcap_battery_state state) { if (state >= CPCAP_BATTERY_STATE_NR) return NULL; return &ddata->state[state]; } static struct cpcap_battery_state_data * cpcap_battery_latest(struct cpcap_battery_ddata *ddata) { return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_LATEST); } static struct cpcap_battery_state_data * cpcap_battery_previous(struct cpcap_battery_ddata *ddata) { return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_PREVIOUS); } static struct cpcap_battery_state_data * cpcap_battery_get_empty(struct cpcap_battery_ddata *ddata) { return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_EMPTY); } static struct cpcap_battery_state_data * cpcap_battery_get_full(struct cpcap_battery_ddata *ddata) { return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_FULL); } static int cpcap_charger_battery_temperature(struct cpcap_battery_ddata *ddata, int *value) { struct iio_channel *channel; int error; channel = ddata->channels[CPCAP_BATTERY_IIO_BATTDET]; error = iio_read_channel_processed(channel, value); if (error < 0) { if (!ignore_temperature_probe) dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); *value = CPCAP_NO_BATTERY; return error; } *value /= 100; return 0; } static int cpcap_battery_get_voltage(struct cpcap_battery_ddata *ddata) { struct iio_channel *channel; int error, value = 0; channel = ddata->channels[CPCAP_BATTERY_IIO_VOLTAGE]; error = iio_read_channel_processed(channel, &value); if (error < 0) { dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); return 0; } return value * 1000; } static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata) { struct iio_channel *channel; int error, value = 0; channel = ddata->channels[CPCAP_BATTERY_IIO_BATT_CURRENT]; error = iio_read_channel_processed(channel, &value); if (error < 0) { dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); return 0; } return value * 1000; } /** * 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. */ static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, s32 sample, s32 accumulator, s16 offset, u32 divider) { s64 acc; if (!divider) return 0; acc = accumulator; acc -= (s64)sample * offset; acc *= ddata->cc_lsb; acc *= -1; acc = div_s64(acc, divider); return acc; } /* 3600000μAms = 1μAh */ static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata, s32 sample, s32 accumulator, s16 offset) { return cpcap_battery_cc_raw_div(ddata, sample, accumulator, offset, 3600000); } static int cpcap_battery_cc_to_ua(struct cpcap_battery_ddata *ddata, s32 sample, s32 accumulator, s16 offset) { return cpcap_battery_cc_raw_div(ddata, sample, accumulator, offset, sample * CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS); } /** * 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. */ static int 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; ccd->sample = 0; ccd->accumulator = 0; ccd->offset = 0; ccd->integrator = 0; /* Read coulomb counter register range */ error = regmap_bulk_read(ddata->reg, CPCAP_REG_CCS1, buf, ARRAY_SIZE(buf)); if (error) return 0; /* Sample value CPCAP_REG_CCS1 & 2 */ ccd->sample = (buf[1] & 0x0fff) << 16; ccd->sample |= buf[0]; if (ddata->vendor == CPCAP_VENDOR_TI) ccd->sample = sign_extend32(24, ccd->sample); /* Accumulator value CPCAP_REG_CCA1 & 2 */ ccd->accumulator = ((s16)buf[3]) << 16; ccd->accumulator |= buf[2]; /* * Coulomb counter calibration offset is CPCAP_REG_CCM, * REG_CCO seems unused */ ccd->offset = buf[4]; ccd->offset = sign_extend32(ccd->offset, 9); /* Integrator register CPCAP_REG_CCI */ if (ddata->vendor == CPCAP_VENDOR_TI) ccd->integrator = sign_extend32(buf[6], 13); else ccd->integrator = (s16)buf[6]; return cpcap_battery_cc_to_uah(ddata, ccd->sample, ccd->accumulator, ccd->offset); } /* * 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. */ static const struct 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, }; /* Values for the extended Droid Bionic battery bw8x. */ static const struct cpcap_battery_config cpcap_battery_bw8x_data = { .cd_factor = 0x3cc, .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, .info.voltage_max_design = 4200000, .info.voltage_min_design = 3200000, .info.charge_full_design = 2760000, .bat.constant_charge_voltage_max_uv = 4200000, }; /* * Safe values for any lipo battery likely to fit into a mapphone * battery bay. */ static const struct cpcap_battery_config cpcap_battery_unkown_data = { .cd_factor = 0x3cc, .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, .info.voltage_max_design = 4200000, .info.voltage_min_design = 3200000, .info.charge_full_design = 3000000, .bat.constant_charge_voltage_max_uv = 4200000, }; static int cpcap_battery_match_nvmem(struct device *dev, const void *data) { if (strcmp(dev_name(dev), "89-500029ba0f73") == 0) return 1; else return 0; } static void cpcap_battery_detect_battery_type(struct cpcap_battery_ddata *ddata) { struct nvmem_device *nvmem; u8 battery_id = 0; ddata->check_nvmem = false; nvmem = nvmem_device_find(NULL, &cpcap_battery_match_nvmem); if (IS_ERR_OR_NULL(nvmem)) { ddata->check_nvmem = true; dev_info_once(ddata->dev, "Can not find battery nvmem device. Assuming generic lipo battery\n"); } else if (nvmem_device_read(nvmem, 2, 1, &battery_id) < 0) { battery_id = 0; ddata->check_nvmem = true; dev_warn(ddata->dev, "Can not read battery nvmem device. Assuming generic lipo battery\n"); } switch (battery_id) { case CPCAP_BATTERY_EB41_HW4X_ID: ddata->config = cpcap_battery_eb41_data; break; case CPCAP_BATTERY_BW8X_ID: ddata->config = cpcap_battery_bw8x_data; break; default: ddata->config = cpcap_battery_unkown_data; } } /** * cpcap_battery_cc_get_avg_current - read cpcap coulumb counter * @ddata: cpcap battery driver device data */ static int cpcap_battery_cc_get_avg_current(struct cpcap_battery_ddata *ddata) { int value, acc, error; s32 sample; s16 offset; /* Coulomb counter integrator */ error = regmap_read(ddata->reg, CPCAP_REG_CCI, &value); if (error) return error; if (ddata->vendor == CPCAP_VENDOR_TI) { acc = sign_extend32(value, 13); sample = 1; } else { acc = (s16)value; sample = 4; } /* Coulomb counter calibration offset */ error = regmap_read(ddata->reg, CPCAP_REG_CCM, &value); if (error) return error; offset = sign_extend32(value, 9); return cpcap_battery_cc_to_ua(ddata, sample, acc, offset); } static int 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 = power_supply_get_property(charger, POWER_SUPPLY_PROP_STATUS, &prop); if (error) *val = POWER_SUPPLY_STATUS_UNKNOWN; else *val = prop.intval; power_supply_put(charger); return error; } static bool cpcap_battery_full(struct cpcap_battery_ddata *ddata) { struct cpcap_battery_state_data *state = cpcap_battery_latest(ddata); unsigned int vfull; int error, val; 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; return ddata->is_full; } static bool cpcap_battery_low(struct cpcap_battery_ddata *ddata) { struct cpcap_battery_state_data *state = cpcap_battery_latest(ddata); static bool is_low; if (state->current_ua > 0 && (state->voltage <= 3350000 || is_low)) is_low = true; else is_low = false; return is_low; } static int cpcap_battery_update_status(struct cpcap_battery_ddata *ddata) { struct cpcap_battery_state_data state, *latest, *previous, *empty, *full; ktime_t now; int error; memset(&state, 0, sizeof(state)); now = ktime_get(); latest = cpcap_battery_latest(ddata); if (latest) { s64 delta_ms = ktime_to_ms(ktime_sub(now, latest->time)); if (delta_ms < CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS) return delta_ms; } state.time = now; state.voltage = cpcap_battery_get_voltage(ddata); state.current_ua = cpcap_battery_get_current(ddata); state.counter_uah = cpcap_battery_read_accumulated(ddata, &state.cc); error = cpcap_charger_battery_temperature(ddata, &state.temperature); if (error) return error; previous = cpcap_battery_previous(ddata); memcpy(previous, latest, sizeof(*previous)); memcpy(latest, &state, sizeof(*latest)); if (cpcap_battery_full(ddata)) { full = cpcap_battery_get_full(ddata); memcpy(full, latest, sizeof(*full)); empty = cpcap_battery_get_empty(ddata); if (empty->voltage && empty->voltage != -1) { empty->voltage = -1; ddata->charge_full = empty->counter_uah - full->counter_uah; } else if (ddata->charge_full) { empty->voltage = -1; empty->counter_uah = full->counter_uah + ddata->charge_full; } } else if (cpcap_battery_low(ddata)) { empty = cpcap_battery_get_empty(ddata); memcpy(empty, latest, sizeof(*empty)); 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. */ static void cpcap_battery_external_power_changed(struct power_supply *psy) { union power_supply_propval prop; power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &prop); } static enum power_supply_property cpcap_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_COUNTER, POWER_SUPPLY_PROP_POWER_NOW, POWER_SUPPLY_PROP_POWER_AVG, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_TEMP, }; static int cpcap_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct cpcap_battery_ddata *ddata = power_supply_get_drvdata(psy); struct cpcap_battery_state_data *latest, *previous, *empty; u32 sample; s32 accumulator; int cached; s64 tmp; cached = cpcap_battery_update_status(ddata); if (cached < 0) return cached; latest = cpcap_battery_latest(ddata); previous = cpcap_battery_previous(ddata); 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; else if (latest->voltage >= 3750000) val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; else if (latest->voltage >= 3300000) val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; else if (latest->voltage > 3100000) val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; else if (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; } else if (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; } static int 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; val.intval = const_charge_voltage; error = power_supply_set_property(charger, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, &val); out_put: power_supply_put(charger); return error; } static int cpcap_battery_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct cpcap_battery_ddata *ddata = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: if (val->intval < ddata->config.info.voltage_min_design) return -EINVAL; if (val->intval > ddata->config.info.voltage_max_design) return -EINVAL; ddata->config.bat.constant_charge_voltage_max_uv = val->intval; return cpcap_battery_update_charger(ddata, val->intval); case POWER_SUPPLY_PROP_CHARGE_FULL: if (val->intval < 0) return -EINVAL; if (val->intval > (6*ddata->config.info.charge_full_design)/5) return -EINVAL; ddata->charge_full = val->intval; return 0; default: return -EINVAL; } return 0; } static int cpcap_battery_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_CHARGE_FULL: return 1; default: return 0; } } static irqreturn_t cpcap_battery_irq_thread(int irq, void *data) { struct cpcap_battery_ddata *ddata = data; struct cpcap_battery_state_data *latest; struct cpcap_interrupt_desc *d; if (!atomic_read(&ddata->active)) return IRQ_NONE; list_for_each_entry(d, &ddata->irq_list, node) { if (irq == d->irq) break; } if (list_entry_is_head(d, &ddata->irq_list, node)) return IRQ_NONE; latest = cpcap_battery_latest(ddata); switch (d->action) { case CPCAP_BATTERY_IRQ_ACTION_CC_CAL_DONE: dev_info(ddata->dev, "Coulomb counter calibration done\n"); break; case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW: if (latest->current_ua >= 0) dev_warn(ddata->dev, "Battery low at %imV!\n", latest->voltage / 1000); break; case CPCAP_BATTERY_IRQ_ACTION_POWEROFF: if (latest->current_ua >= 0 && latest->voltage <= 3200000) { dev_emerg(ddata->dev, "Battery empty at %imV, powering off\n", latest->voltage / 1000); orderly_poweroff(true); } break; default: break; } power_supply_changed(ddata->psy); return IRQ_HANDLED; } static int cpcap_battery_init_irq(struct platform_device *pdev, struct cpcap_battery_ddata *ddata, const char *name) { struct cpcap_interrupt_desc *d; int irq, error; irq = platform_get_irq_byname(pdev, name); if (irq < 0) return irq; error = devm_request_threaded_irq(ddata->dev, irq, NULL, cpcap_battery_irq_thread, IRQF_SHARED | IRQF_ONESHOT, name, ddata); if (error) { dev_err(ddata->dev, "could not get irq %s: %i\n", name, error); return error; } d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); if (!d) return -ENOMEM; d->name = name; d->irq = irq; if (!strncmp(name, "cccal", 5)) d->action = CPCAP_BATTERY_IRQ_ACTION_CC_CAL_DONE; else if (!strncmp(name, "lowbph", 6)) d->action = CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW; else if (!strncmp(name, "lowbpl", 6)) d->action = CPCAP_BATTERY_IRQ_ACTION_POWEROFF; list_add(&d->node, &ddata->irq_list); return 0; } static int cpcap_battery_init_interrupts(struct platform_device *pdev, struct cpcap_battery_ddata *ddata) { static const char * const cpcap_battery_irqs[] = { "eol", "lowbph", "lowbpl", "chrgcurr1", "battdetb" }; int i, error; for (i = 0; i < ARRAY_SIZE(cpcap_battery_irqs); i++) { error = cpcap_battery_init_irq(pdev, ddata, cpcap_battery_irqs[i]); if (error) return error; } /* Enable calibration interrupt if already available in dts */ cpcap_battery_init_irq(pdev, ddata, "cccal"); /* Enable low battery interrupts for 3.3V high and 3.1V low */ error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL, 0xffff, CPCAP_REG_BPEOL_BIT_BATTDETEN); if (error) return error; return 0; } static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata) { const char * const names[CPCAP_BATTERY_IIO_NR] = { "battdetb", "battp", "chg_isense", "batti", }; int error, i; for (i = 0; i < CPCAP_BATTERY_IIO_NR; i++) { ddata->channels[i] = devm_iio_channel_get(ddata->dev, names[i]); if (IS_ERR(ddata->channels[i])) { error = PTR_ERR(ddata->channels[i]); goto out_err; } if (!ddata->channels[i]->indio_dev) { error = -ENXIO; goto out_err; } } return 0; out_err: return dev_err_probe(ddata->dev, error, "could not initialize VBUS or ID IIO\n"); } /* Calibrate coulomb counter */ static int cpcap_battery_calibrate(struct cpcap_battery_ddata *ddata) { int error, ccc1, value; unsigned long timeout; error = regmap_read(ddata->reg, CPCAP_REG_CCC1, &ccc1); if (error) return error; timeout = jiffies + msecs_to_jiffies(6000); /* Start calibration */ error = regmap_update_bits(ddata->reg, CPCAP_REG_CCC1, 0xffff, CPCAP_REG_CCC1_CAL_EN); if (error) goto restore; while (time_before(jiffies, timeout)) { error = regmap_read(ddata->reg, CPCAP_REG_CCC1, &value); if (error) goto restore; if (!(value & CPCAP_REG_CCC1_CAL_EN)) break; error = regmap_read(ddata->reg, CPCAP_REG_CCM, &value); if (error) goto restore; msleep(300); } /* Read calibration offset from CCM */ error = regmap_read(ddata->reg, CPCAP_REG_CCM, &value); if (error) goto restore; dev_info(ddata->dev, "calibration done: 0x%04x\n", value); restore: if (error) dev_err(ddata->dev, "%s: error %i\n", __func__, error); error = regmap_update_bits(ddata->reg, CPCAP_REG_CCC1, 0xffff, ccc1); if (error) dev_err(ddata->dev, "%s: restore error %i\n", __func__, error); return error; } #ifdef CONFIG_OF static const struct of_device_id cpcap_battery_id_table[] = { { .compatible = "motorola,cpcap-battery", }, {}, }; MODULE_DEVICE_TABLE(of, cpcap_battery_id_table); #endif static const struct power_supply_desc cpcap_charger_battery_desc = { .name = "battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = cpcap_battery_props, .num_properties = ARRAY_SIZE(cpcap_battery_props), .get_property = cpcap_battery_get_property, .set_property = cpcap_battery_set_property, .property_is_writeable = cpcap_battery_property_is_writeable, .external_power_changed = cpcap_battery_external_power_changed, }; static int cpcap_battery_probe(struct platform_device *pdev) { struct cpcap_battery_ddata *ddata; struct power_supply_config psy_cfg = {}; int error; ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; cpcap_battery_detect_battery_type(ddata); INIT_LIST_HEAD(&ddata->irq_list); ddata->dev = &pdev->dev; ddata->reg = dev_get_regmap(ddata->dev->parent, NULL); if (!ddata->reg) return -ENODEV; error = cpcap_get_vendor(ddata->dev, ddata->reg, &ddata->vendor); if (error) return error; switch (ddata->vendor) { case CPCAP_VENDOR_ST: ddata->cc_lsb = 95374; /* μAms per LSB */ break; case CPCAP_VENDOR_TI: ddata->cc_lsb = 91501; /* μAms per LSB */ break; default: return -EINVAL; } ddata->cc_lsb = (ddata->cc_lsb * ddata->config.cd_factor) / 1000; platform_set_drvdata(pdev, ddata); error = cpcap_battery_init_interrupts(pdev, ddata); if (error) return error; error = cpcap_battery_init_iio(ddata); if (error) return error; psy_cfg.of_node = pdev->dev.of_node; psy_cfg.drv_data = ddata; ddata->psy = devm_power_supply_register(ddata->dev, &cpcap_charger_battery_desc, &psy_cfg); error = PTR_ERR_OR_ZERO(ddata->psy); if (error) { dev_err(ddata->dev, "failed to register power supply\n"); return error; } atomic_set(&ddata->active, 1); error = cpcap_battery_calibrate(ddata); if (error) return error; return 0; } static int cpcap_battery_remove(struct platform_device *pdev) { struct cpcap_battery_ddata *ddata = platform_get_drvdata(pdev); int error; atomic_set(&ddata->active, 0); error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL, 0xffff, 0); if (error) dev_err(&pdev->dev, "could not disable: %i\n", error); return 0; } static struct platform_driver cpcap_battery_driver = { .driver = { .name = "cpcap_battery", .of_match_table = of_match_ptr(cpcap_battery_id_table), }, .probe = cpcap_battery_probe, .remove = cpcap_battery_remove, }; module_platform_driver(cpcap_battery_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); MODULE_DESCRIPTION("CPCAP PMIC Battery Driver");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1