| // 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.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_unknown_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_update_battery_data(struct cpcap_battery_ddata *ddata) | |
| { | |
| struct power_supply_battery_info *info; | |
| if (power_supply_get_battery_info(ddata->psy, &info) < 0) | |
| return; | |
| if (info->technology > 0) | |
| ddata->config.info.technology = info->technology; | |
| if (info->voltage_max_design_uv > 0) | |
| ddata->config.info.voltage_max_design = info->voltage_max_design_uv; | |
| if (info->voltage_min_design_uv > 0) | |
| ddata->config.info.voltage_min_design = info->voltage_min_design_uv; | |
| if (info->charge_full_design_uah > 0) | |
| ddata->config.info.charge_full_design = info->charge_full_design_uah; | |
| if (info->constant_charge_voltage_max_uv > 0) | |
| ddata->config.bat.constant_charge_voltage_max_uv = | |
| info->constant_charge_voltage_max_uv; | |
| } | |
| 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"); | |
| } | |
| nvmem_device_put(nvmem); | |
| } | |
| 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_unknown_data; | |
| } | |
| if (ddata->psy) | |
| cpcap_battery_update_battery_data(ddata); | |
| } | |
| /** | |
| * 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) | |
| 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_iio(ddata); | |
| if (error) | |
| return error; | |
| psy_cfg.fwnode = dev_fwnode(&pdev->dev); | |
| 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; | |
| } | |
| error = cpcap_battery_init_interrupts(pdev, ddata); | |
| if (error) | |
| return error; | |
| atomic_set(&ddata->active, 1); | |
| error = cpcap_battery_calibrate(ddata); | |
| if (error) | |
| return error; | |
| return 0; | |
| } | |
| static void 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); | |
| } | |
| 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"); | |
| MODULE_IMPORT_NS("IIO_CONSUMER"); |