// SPDX-License-Identifier: GPL-2.0+ // // Fuel gauge driver for Maxim 17042 / 8966 / 8997 // Note that Maxim 8966 and 8997 are mfd and this is its subdevice. // // Copyright (C) 2011 Samsung Electronics // MyungJoo Ham <myungjoo.ham@samsung.com> // // This driver is based on max17040_battery.c
staticenum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_VOLTAGE_MIN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_TEMP_MIN,
POWER_SUPPLY_PROP_TEMP_MAX,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, // these two have to be at the end on the list
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
};
staticint max17042_get_temperature(struct max17042_chip *chip, int *temp)
{ int ret;
u32 data; struct regmap *map = chip->regmap;
ret = regmap_read(map, MAX17042_TEMP, &data); if (ret < 0) return ret;
*temp = sign_extend32(data, 15); /* The value is converted into deci-centigrade scale */ /* Units of LSB = 1 / 256 degree Celsius */
*temp = *temp * 10 / 256; return 0;
}
staticint max17042_get_status(struct max17042_chip *chip, int *status)
{ int ret, charge_full, charge_now; int avg_current;
u32 data;
ret = power_supply_am_i_supplied(chip->battery); if (ret < 0) {
*status = POWER_SUPPLY_STATUS_UNKNOWN; return 0;
} if (ret == 0) {
*status = POWER_SUPPLY_STATUS_DISCHARGING; return 0;
}
/* * The MAX170xx has builtin end-of-charge detection and will update * FullCAP to match RepCap when it detects end of charging. * * When this cycle the battery gets charged to a higher (calculated) * capacity then the previous cycle then FullCAP will get updated * continuously once end-of-charge detection kicks in, so allow the * 2 to differ a bit.
*/
ret = regmap_read(chip->regmap, MAX17042_FullCAP, &charge_full); if (ret < 0) return ret;
ret = regmap_read(chip->regmap, MAX17042_RepCap, &charge_now); if (ret < 0) return ret;
/* * Even though we are supplied, we may still be discharging if the * supply is e.g. only delivering 5V 0.5A. Check current if available.
*/ if (!chip->pdata->enable_current_sense) {
*status = POWER_SUPPLY_STATUS_CHARGING; return 0;
}
ret = regmap_read(chip->regmap, MAX17042_AvgCurrent, &data); if (ret < 0) return ret;
switch (psp) { case POWER_SUPPLY_PROP_STATUS:
ret = max17042_get_status(chip, &val->intval); if (ret < 0) return ret; break; case POWER_SUPPLY_PROP_PRESENT:
ret = regmap_read(map, MAX17042_STATUS, &data); if (ret < 0) return ret;
if (data & MAX17042_STATUS_BattAbsent)
val->intval = 0; else
val->intval = 1; break; case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CYCLE_COUNT:
ret = regmap_read(map, MAX17042_Cycles, &data); if (ret < 0) return ret;
val->intval = data; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX:
ret = regmap_read(map, MAX17042_MinMaxVolt, &data); if (ret < 0) return ret;
val->intval = data >> 8;
val->intval *= 20000; /* Units of LSB = 20mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_MIN:
ret = regmap_read(map, MAX17042_MinMaxVolt, &data); if (ret < 0) return ret;
val->intval = (data & 0xff) * 20000; /* Units of 20mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
ret = regmap_read(map, MAX17042_V_empty, &data); else
ret = regmap_read(map, MAX17047_V_empty, &data); if (ret < 0) return ret;
val->intval = data >> 7;
val->intval *= 10000; /* Units of LSB = 10mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = regmap_read(map, MAX17042_VCELL, &data); if (ret < 0) return ret;
val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_VOLTAGE_AVG:
ret = regmap_read(map, MAX17042_AvgVCELL, &data); if (ret < 0) return ret;
val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_VOLTAGE_OCV:
ret = regmap_read(map, MAX17042_OCVInternal, &data); if (ret < 0) return ret;
val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_CAPACITY: if (chip->pdata->enable_current_sense)
ret = regmap_read(map, MAX17042_RepSOC, &data); else
ret = regmap_read(map, MAX17042_VFSOC, &data); if (ret < 0) return ret;
val->intval = data >> 8; break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
ret = regmap_read(map, MAX17042_DesignCap, &data); if (ret < 0) return ret;
data64 = data * 5000000ll;
do_div(data64, chip->pdata->r_sns);
val->intval = data64; break; case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = regmap_read(map, MAX17042_FullCAP, &data); if (ret < 0) return ret;
data64 = data * 5000000ll;
do_div(data64, chip->pdata->r_sns);
val->intval = data64; break; case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = regmap_read(map, MAX17042_RepCap, &data); if (ret < 0) return ret;
data64 = data * 5000000ll;
do_div(data64, chip->pdata->r_sns);
val->intval = data64; break; case POWER_SUPPLY_PROP_CHARGE_COUNTER:
ret = regmap_read(map, MAX17042_QH, &data); if (ret < 0) return ret;
data64 = sign_extend64(data, 15) * 5000000ll;
val->intval = div_s64(data64, chip->pdata->r_sns); break; case POWER_SUPPLY_PROP_TEMP:
ret = max17042_get_temperature(chip, &val->intval); if (ret < 0) return ret; break; case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
ret = regmap_read(map, MAX17042_TALRT_Th, &data); if (ret < 0) return ret; /* LSB is Alert Minimum. In deci-centigrade */
val->intval = sign_extend32(data & 0xff, 7) * 10; break; case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = regmap_read(map, MAX17042_TALRT_Th, &data); if (ret < 0) return ret; /* MSB is Alert Maximum. In deci-centigrade */
val->intval = sign_extend32(data >> 8, 7) * 10; break; case POWER_SUPPLY_PROP_TEMP_MIN:
val->intval = chip->pdata->temp_min; break; case POWER_SUPPLY_PROP_TEMP_MAX:
val->intval = chip->pdata->temp_max; break; case POWER_SUPPLY_PROP_HEALTH:
ret = max17042_get_battery_health(chip, &val->intval); if (ret < 0) return ret; break; case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM; break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (chip->pdata->enable_current_sense) {
ret = regmap_read(map, MAX17042_Current, &data); if (ret < 0) return ret;
data64 = sign_extend64(data, 15) * 1562500ll;
val->intval = div_s64(data64, chip->pdata->r_sns);
} else { return -EINVAL;
} break; case POWER_SUPPLY_PROP_CURRENT_AVG: if (chip->pdata->enable_current_sense) {
ret = regmap_read(map, MAX17042_AvgCurrent, &data); if (ret < 0) return ret;
data64 = sign_extend64(data, 15) * 1562500ll;
val->intval = div_s64(data64, chip->pdata->r_sns);
} else { return -EINVAL;
} break; case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
ret = regmap_read(map, MAX17042_ICHGTerm, &data); if (ret < 0) return ret;
data64 = data * 1562500ll;
val->intval = div_s64(data64, chip->pdata->r_sns); break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
ret = regmap_read(map, MAX17042_TTE, &data); if (ret < 0) return ret;
switch (psp) { case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
ret = regmap_read(map, MAX17042_TALRT_Th, &data); if (ret < 0) return ret;
/* Input in deci-centigrade, convert to centigrade */
temp = val->intval / 10; /* force min < max */ if (temp >= (int8_t)(data >> 8))
temp = (int8_t)(data >> 8) - 1; /* Write both MAX and MIN ALERT */
data = (data & 0xff00) + temp;
ret = regmap_write(map, MAX17042_TALRT_Th, data); break; case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = regmap_read(map, MAX17042_TALRT_Th, &data); if (ret < 0) return ret;
/* Input in Deci-Centigrade, convert to centigrade */
temp = val->intval / 10; /* force max > min */ if (temp <= (int8_t)(data & 0xff))
temp = (int8_t)(data & 0xff) + 1; /* Write both MAX and MIN ALERT */
data = (data & 0xff) + (temp << 8);
ret = regmap_write(map, MAX17042_TALRT_Th, data); break; default:
ret = -EINVAL;
}
return ret;
}
staticint max17042_property_is_writeable(struct power_supply *psy, enum power_supply_property psp)
{ int ret;
switch (psp) { case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = 1; break; default:
ret = 0;
}
return ret;
}
staticint max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
{ int retries = 8; int ret;
u32 read_value;
do {
ret = regmap_write(map, reg, value);
regmap_read(map, reg, &read_value); if (read_value != value) {
ret = -EIO;
retries--;
}
} while (retries && read_value != value);
if (ret < 0)
pr_err("%s: err %d\n", __func__, ret);
ret = max17042_model_data_compare(
chip,
chip->pdata->config_data->cell_char_tbl,
temp_data,
table_size);
max17042_lock_model(chip);
kfree(temp_data);
return ret;
}
staticint max17042_verify_model_lock(struct max17042_chip *chip)
{ int i; int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
u16 *temp_data; int ret = 0;
temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL); if (!temp_data) return -ENOMEM;
max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
table_size); for (i = 0; i < table_size; i++) if (temp_data[i])
ret = -EINVAL;
/* fg_vfSoc needs to shifted by 8 bits to get the * perc in 1% accuracy, to get the right rem_cap multiply * full_cap0, fg_vfSoc and devide by 100
*/
rem_cap = ((vfSoc >> 8) * full_cap0) / 100;
max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap);
/* * Block write all the override values coming from platform data. * This function MUST be called before the POR initialization procedure * specified by maxim.
*/ staticinlinevoid max17042_override_por_values(struct max17042_chip *chip)
{ struct regmap *map = chip->regmap; struct max17042_config_data *config = chip->pdata->config_data;
max17042_override_por_values(chip); /* After Power up, the MAX17042 requires 500mS in order * to perform signal debouncing and initial SOC reporting
*/
msleep(500);
/* program interrupt thresholds such that we should * get interrupt for every 'off' perc change in the soc
*/ if (chip->pdata->enable_current_sense)
regmap_read(map, MAX17042_RepSOC, &soc); else
regmap_read(map, MAX17042_VFSOC, &soc);
soc >>= 8;
soc_tr = (soc + off) << 8; if (off < soc)
soc_tr |= soc - off;
regmap_write(map, MAX17042_SALRT_Th, soc_tr);
}
/* Initialize registers according to values from the platform data */ if (chip->pdata->enable_por_init && chip->pdata->config_data) {
ret = max17042_init_chip(chip); if (ret) return;
}
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return NULL;
/* * Require current sense resistor value to be specified for * current-sense functionality to be enabled at all.
*/ if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
pdata->r_sns = prop;
pdata->enable_current_sense = true;
}
if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min))
pdata->temp_min = INT_MIN; if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max))
pdata->temp_max = INT_MAX; if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin))
pdata->vmin = INT_MIN; if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax))
pdata->vmax = INT_MAX;
return pdata;
} #endif
staticstruct max17042_reg_data max17047_default_pdata_init_regs[] = { /* * Some firmwares do not set FullSOCThr, Enable End-of-Charge Detection * when the voltage FG reports 95%, as recommended in the datasheet.
*/
{ MAX17047_FullSOCThr, MAX17042_BATTERY_FULL << 8 },
};
/* * The MAX17047 gets used on x86 where we might not have pdata, assume * the firmware will already have initialized the fuel-gauge and provide * default values for the non init bits to make things work.
*/
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return pdata;
ret = regmap_read(chip->regmap, MAX17042_MiscCFG, &misc_cfg); if (ret < 0) return NULL;
/* If bits 0-1 are set to 3 then only Voltage readings are used */ if ((misc_cfg & 0x3) == 0x3)
pdata->enable_current_sense = false; else
pdata->enable_current_sense = true;
/* When current is not measured,
* CURRENT_NOW and CURRENT_AVG properties should be invisible. */ if (!chip->pdata->enable_current_sense)
max17042_desc = &max17042_no_current_sense_psy_desc;
if (chip->pdata->r_sns == 0)
chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
if (chip->pdata->init_data) for (i = 0; i < chip->pdata->num_init_data; i++)
regmap_write(chip->regmap,
chip->pdata->init_data[i].addr,
chip->pdata->init_data[i].data);
ret = devm_request_threaded_irq(dev, irq,
NULL,
max17042_thread_handler, flags,
chip->battery->desc->name,
chip); if (!ret) {
regmap_update_bits(chip->regmap, MAX17042_CONFIG,
CFG_ALRT_BIT_ENBL,
CFG_ALRT_BIT_ENBL);
max17042_set_soc_threshold(chip, 1);
} else {
irq = 0; if (ret != -EBUSY)
dev_err(dev, "Failed to get IRQ\n");
}
} /* Not able to update the charge threshold when exceeded? -> disable */ if (!irq)
regmap_write(chip->regmap, MAX17042_SALRT_Th, 0xff00);
chip->irq = irq;
regmap_read(chip->regmap, MAX17042_STATUS, &val); if (val & STATUS_POR_BIT) {
ret = devm_work_autocancel(dev, &chip->work,
max17042_init_worker); if (ret) return ret;
schedule_work(&chip->work);
} else {
chip->init_complete = 1;
}
#ifdef CONFIG_OF /* * Device may be instantiated through parent MFD device and device matching is done * through platform_device_id. * * However if device's DT node contains proper clock compatible and driver is * built as a module, then the *module* matching will be done trough DT aliases. * This requires of_device_id table. In the same time this will not change the * actual *device* matching so do not add .of_match_table.
*/ staticconststruct of_device_id max17042_dt_match[] __used = {
{ .compatible = "maxim,max17042",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17042 },
{ .compatible = "maxim,max17047",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17047 },
{ .compatible = "maxim,max17050",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17050 },
{ .compatible = "maxim,max17055",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17055 },
{ .compatible = "maxim,max77705-battery",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17047 },
{ .compatible = "maxim,max77849-battery",
.data = (void *) MAXIM_DEVICE_TYPE_MAX17047 },
{ },
};
MODULE_DEVICE_TABLE(of, max17042_dt_match); #endif
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.