staticbool no_current_sense_res;
module_param(no_current_sense_res, bool, 0444);
MODULE_PARM_DESC(no_current_sense_res, "No (or broken) current sense resistor");
struct axp288_fg_info { struct device *dev; struct regmap *regmap; int irq[AXP288_FG_INTR_NUM]; struct iio_channel *iio_channel[IIO_CHANNEL_NUM]; struct power_supply *bat; struct mutex lock; int status; int max_volt; int pwr_op; int low_cap; struct dentry *debug_file;
char valid; /* zero until following fields are valid */ unsignedlong last_updated; /* in jiffies */
int pwr_stat; int fg_res; int bat_volt; int d_curr; int c_curr; int ocv; int fg_cc_mtr1; int fg_des_cap1;
};
staticenum power_supply_property fuel_gauge_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,
POWER_SUPPLY_PROP_TECHNOLOGY, /* The 3 props below are not used when no_current_sense_res is set */
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
staticint fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
{ unsignedint val; int ret;
ret = iosf_mbi_block_punit_i2c_access(); if (ret < 0) return ret;
ret = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS); if (ret < 0) goto out;
info->pwr_stat = ret;
if (no_current_sense_res)
ret = fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG); else
ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES); if (ret < 0) goto out;
info->fg_res = ret;
ret = iio_read_channel_raw(info->iio_channel[BAT_VOLT], &info->bat_volt); if (ret < 0) goto out;
ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG); if (ret < 0) goto out;
info->ocv = ret;
if (no_current_sense_res) goto out_no_current_sense_res;
if (info->pwr_stat & PS_STAT_BAT_CHRG_DIR) {
info->d_curr = 0;
ret = iio_read_channel_raw(info->iio_channel[BAT_CHRG_CURR], &info->c_curr); if (ret < 0) goto out;
} else {
info->c_curr = 0;
ret = iio_read_channel_raw(info->iio_channel[BAT_D_CURR], &info->d_curr); if (ret < 0) goto out;
}
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG); if (ret < 0) goto out;
info->fg_cc_mtr1 = ret;
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG); if (ret < 0) goto out;
info->fg_des_cap1 = ret;
/* * Sometimes the charger turns itself off before fg-res reaches 100%. * When this happens the AXP288 reports a not-charging status and * 0 mA discharge current.
*/ if (fg_res < 90 || (pwr_stat & PS_STAT_BAT_CHRG_DIR) || no_current_sense_res) goto not_full;
if (curr == 0) {
info->status = POWER_SUPPLY_STATUS_FULL; return;
}
staticint fuel_gauge_battery_health(struct axp288_fg_info *info)
{ int vocv = VOLTAGE_FROM_ADC(info->ocv); int health = POWER_SUPPLY_HEALTH_UNKNOWN;
if (vocv > info->max_volt)
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else
health = POWER_SUPPLY_HEALTH_GOOD;
return health;
}
staticint fuel_gauge_get_property(struct power_supply *ps, enum power_supply_property prop, union power_supply_propval *val)
{ struct axp288_fg_info *info = power_supply_get_drvdata(ps); int ret, value;
mutex_lock(&info->lock);
ret = fuel_gauge_update_registers(info); if (ret < 0) goto out;
switch (prop) { case POWER_SUPPLY_PROP_STATUS:
fuel_gauge_get_status(info);
val->intval = info->status; break; case POWER_SUPPLY_PROP_HEALTH:
val->intval = fuel_gauge_battery_health(info); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW:
value = VOLTAGE_FROM_ADC(info->bat_volt);
val->intval = PROP_VOLT(value); break; case POWER_SUPPLY_PROP_VOLTAGE_OCV:
value = VOLTAGE_FROM_ADC(info->ocv);
val->intval = PROP_VOLT(value); break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (info->d_curr > 0)
value = -1 * info->d_curr; else
value = info->c_curr;
val->intval = PROP_CURR(value); break; case POWER_SUPPLY_PROP_PRESENT: if (info->pwr_op & CHRG_STAT_BAT_PRESENT)
val->intval = 1; else
val->intval = 0; break; case POWER_SUPPLY_PROP_CAPACITY: if (!(info->fg_res & FG_REP_CAP_VALID))
dev_err(info->dev, "capacity measurement not valid\n");
val->intval = (info->fg_res & FG_REP_CAP_VAL_MASK); break; case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
val->intval = (info->low_cap & 0x0f); break; case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CHARGE_NOW:
val->intval = info->fg_cc_mtr1 * FG_DES_CAP_RES_LSB; break; case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = info->fg_des_cap1 * FG_DES_CAP_RES_LSB; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = PROP_VOLT(info->max_volt); break; default:
ret = -EINVAL;
}
for (i = 0; i < AXP288_FG_INTR_NUM; i++) { if (info->irq[i] == irq) break;
}
if (i >= AXP288_FG_INTR_NUM) {
dev_warn(info->dev, "spurious interrupt!!\n"); return IRQ_NONE;
}
switch (i) { case QWBTU_IRQ:
dev_info(info->dev, "Quit Battery under temperature in work mode IRQ (QWBTU)\n"); break; case WBTU_IRQ:
dev_info(info->dev, "Battery under temperature in work mode IRQ (WBTU)\n"); break; case QWBTO_IRQ:
dev_info(info->dev, "Quit Battery over temperature in work mode IRQ (QWBTO)\n"); break; case WBTO_IRQ:
dev_info(info->dev, "Battery over temperature in work mode IRQ (WBTO)\n"); break; case WL2_IRQ:
dev_info(info->dev, "Low Batt Warning(2) INTR\n"); break; case WL1_IRQ:
dev_info(info->dev, "Low Batt Warning(1) INTR\n"); break; default:
dev_warn(info->dev, "Spurious Interrupt!!!\n");
}
mutex_lock(&info->lock);
info->valid = 0; /* Force updating of the cached registers */
mutex_unlock(&info->lock);
/* * Some devices have no battery (HDMI sticks) and the axp288 battery's * detection reports one despite it not being there. * Please keep this listed sorted alphabetically.
*/ staticconststruct dmi_system_id axp288_quirks[] = {
{ /* ACEPC T8 Cherry Trail Z8350 mini PC */
.matches = {
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T8"), /* also match on somewhat unique bios-version */
DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* ACEPC T11 Cherry Trail Z8350 mini PC */
.matches = {
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "To be filled by O.E.M."),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "T11"), /* also match on somewhat unique bios-version */
DMI_EXACT_MATCH(DMI_BIOS_VERSION, "1.000"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* Intel Bay Trail Compute Stick */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel"), /* Partial match for STCK1A32WFC STCK1A32FC, STCK1A8LFC variants */
DMI_MATCH(DMI_PRODUCT_NAME, "STCK1A"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* Intel Cherry Trail Compute Stick */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel"), /* Partial match for STK1AW32SC and STK1A32SC variants */
DMI_MATCH(DMI_PRODUCT_NAME, "STK1A"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* Meegopad T02 */
.matches = {
DMI_MATCH(DMI_PRODUCT_NAME, "MEEGOPAD T02"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* Mele PCG03 Mini PC */
.matches = {
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Mini PC"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Mini PC"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* Minix Neo Z83-4 mini PC */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "MINIX"),
DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* * One Mix 1, this uses the "T3 MRD" boardname used by * generic mini PCs, but it is a mini laptop so it does * actually have a battery!
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"),
DMI_MATCH(DMI_BIOS_DATE, "06/14/2018"),
},
.driver_data = NULL,
},
{ /* Radxa ROCK Pi X Single Board Computer */
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "ROCK Pi X"),
DMI_MATCH(DMI_BOARD_VENDOR, "Radxa"),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{ /* * Various Ace PC/Meegopad/MinisForum/Wintel Mini-PCs/HDMI-sticks * This entry must be last because it is generic, this allows * adding more specifuc quirks overriding this generic entry.
*/
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"),
DMI_MATCH(DMI_CHASSIS_TYPE, "3"),
DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
},
.driver_data = (void *)AXP288_QUIRK_NO_BATTERY,
},
{}
};
staticint axp288_fuel_gauge_read_initial_regs(struct axp288_fg_info *info)
{ unsignedint val; int ret;
/* * On some devices the fuelgauge and charger parts of the axp288 are * not used, check that the fuelgauge is enabled (CC_CTRL != 0).
*/
ret = regmap_read(info->regmap, AXP20X_CC_CTRL, &val); if (ret < 0) return ret; if (val == 0) return -ENODEV;
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
if (ret < 0)
return ret;
if (!(ret & FG_DES_CAP1_VALID)) {
dev_err(info->dev, "axp288 not configured by firmware\n");
return -ENODEV;
}
ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
if (ret < 0)
return ret;
switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) {
case CHRG_CCCV_CV_4100MV:
info->max_volt = 4100;
break;
case CHRG_CCCV_CV_4150MV:
info->max_volt = 4150;
break;
case CHRG_CCCV_CV_4200MV:
info->max_volt = 4200;
break;
case CHRG_CCCV_CV_4350MV:
info->max_volt = 4350;
break;
}
ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
if (ret < 0)
return ret;
info->pwr_op = ret;
ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
if (ret < 0)
return ret;
info->low_cap = ret;
/*
* Normally the native AXP288 fg/charger drivers are preferred but
* on some devices the ACPI drivers should be used instead.
*/
if (!acpi_quirk_skip_acpi_ac_and_battery())
return -ENODEV;
dmi_id = dmi_first_match(axp288_quirks);
if (dmi_id)
quirks = (unsigned long)dmi_id->driver_data;
if (quirks & AXP288_QUIRK_NO_BATTERY)
return -ENODEV;
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
pirq = platform_get_irq(pdev, i);
if (pirq < 0)
continue;
ret = regmap_irq_get_virq(axp20x->regmap_irqc, pirq);
if (ret < 0)
return dev_err_probe(dev, ret, "getting vIRQ %d\n", pirq);
info->irq[i] = ret;
}
for (i = 0; i < IIO_CHANNEL_NUM; i++) {
/*
* Note cannot use devm_iio_channel_get because x86 systems
* lack the device<->channel maps which iio_channel_get will
* try to use when passed a non NULL device pointer.
*/
info->iio_channel[i] =
iio_channel_get(NULL, iio_chan_name[i]);
if (IS_ERR(info->iio_channel[i])) {
ret = PTR_ERR(info->iio_channel[i]);
dev_dbg(dev, "error getting iiochan %s: %d\n", iio_chan_name[i], ret);
/* Wait for axp288_adc to load */
if (ret == -ENODEV)
ret = -EPROBE_DEFER;
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.