staticvoid sbs_disable_charger_broadcasts(struct sbs_info *chip)
{ int val = sbs_read_word_data(chip->client, BATTERY_MODE_OFFSET); if (val < 0) gotoexit;
val |= BATTERY_MODE_CHARGER_MASK;
val = sbs_write_word_data(chip->client, BATTERY_MODE_OFFSET, val);
exit: if (val < 0)
dev_err(&chip->client->dev, "Failed to disable charger broadcasting: %d\n", val); else
dev_dbg(&chip->client->dev, "%s\n", __func__);
}
if (!is_present) {
chip->is_present = false; /* Disable PEC when no device is present */
client->flags &= ~I2C_CLIENT_PEC;
sbs_invalidate_cached_props(chip); return 0;
}
/* Check if device supports packet error checking and use it */ while (retries > 0) {
ret = i2c_smbus_read_word_data(client, REG_ADDR_SPEC_INFO); if (ret >= 0) break;
/* * Some batteries trigger the detection pin before the * I2C bus is properly connected. This works around the * issue.
*/
msleep(100);
retries--;
}
if (ret < 0) {
dev_dbg(&client->dev, "failed to read spec info: %d\n", ret);
/* fallback to old behaviour */
client->flags &= ~I2C_CLIENT_PEC;
chip->is_present = true;
return ret;
}
version = (ret & SPEC_INFO_VERSION_MASK) >> SPEC_INFO_VERSION_SHIFT;
dev_warn_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_BLOCK_DATA.\n" "Fallback method does not support PEC.\n");
/* Adapter needs to support these two functions */ if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_I2C_BLOCK)){ return -ENODEV;
}
/* Get the length of block data */ while (retries_length > 0) {
ret = i2c_smbus_read_byte_data(client, address); if (ret >= 0) break;
retries_length--;
}
if (ret < 0) {
dev_dbg(&client->dev, "%s: i2c read at address 0x%x failed\n",
__func__, address); return ret;
}
/* block_length does not include NULL terminator */
block_length = ret; if (block_length > I2C_SMBUS_BLOCK_MAX) {
dev_err(&client->dev, "%s: Returned block_length is longer than 0x%x\n",
__func__, I2C_SMBUS_BLOCK_MAX); return -EINVAL;
}
/* Get the block data */ while (retries_block > 0) {
ret = i2c_smbus_read_i2c_block_data(
client, address,
block_length + 1, block_buffer); if (ret >= 0) break;
retries_block--;
}
if (ret < 0) {
dev_dbg(&client->dev, "%s: i2c read at address 0x%x failed\n",
__func__, address); return ret;
}
while (retries > 0) {
ret = i2c_smbus_write_word_data(client, address, value); if (ret >= 0) break;
retries--;
}
if (ret < 0) {
dev_dbg(&client->dev, "%s: i2c write to address 0x%x failed\n",
__func__, address); return ret;
}
return 0;
}
staticint sbs_status_correct(struct i2c_client *client, int *intval)
{ int ret;
ret = sbs_read_word_data(client, sbs_data[REG_CURRENT_NOW].addr); if (ret < 0) return ret;
ret = (s16)ret;
/* Not drawing current -> not charging (i.e. idle) */ if (*intval != POWER_SUPPLY_STATUS_FULL && ret == 0)
*intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
if (*intval == POWER_SUPPLY_STATUS_FULL) { /* Drawing or providing current when full */ if (ret > 0)
*intval = POWER_SUPPLY_STATUS_CHARGING; elseif (ret < 0)
*intval = POWER_SUPPLY_STATUS_DISCHARGING;
}
return 0;
}
staticbool sbs_bat_needs_calibration(struct i2c_client *client)
{ int ret;
ret = sbs_read_word_data(client, sbs_data[REG_BATTERY_MODE].addr); if (ret < 0) returnfalse;
/* * Write to ManufacturerAccess with ManufacturerAccess command * and then read the status.
*/
ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr,
MANUFACTURER_ACCESS_STATUS); if (ret < 0) { if (psp == POWER_SUPPLY_PROP_PRESENT)
val->intval = 0; /* battery removed */ return ret;
}
ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); if (ret < 0) { if (psp == POWER_SUPPLY_PROP_PRESENT)
val->intval = 0; /* battery removed */ return ret;
}
if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value ||
ret > sbs_data[REG_MANUFACTURER_DATA].max_value) {
val->intval = 0; return 0;
}
/* Mask the upper nibble of 2nd byte and * lower byte of response then
* shift the result by 8 to get status*/
ret &= 0x0F00;
ret >>= 8; if (psp == POWER_SUPPLY_PROP_PRESENT) { if (ret == 0x0F) /* battery removed */
val->intval = 0; else
val->intval = 1;
} elseif (psp == POWER_SUPPLY_PROP_HEALTH) { if (ret == 0x09)
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; elseif (ret == 0x0B)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; elseif (ret == 0x0C)
val->intval = POWER_SUPPLY_HEALTH_DEAD; elseif (sbs_bat_needs_calibration(client))
val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED; else
val->intval = POWER_SUPPLY_HEALTH_GOOD;
}
return 0;
}
staticint sbs_get_battery_presence_and_health( struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val)
{ struct sbs_info *chip = i2c_get_clientdata(client); int ret;
if (chip->flags & SBS_FLAGS_TI_BQ20ZX5) return sbs_get_ti_battery_presence_and_health(client, psp, val);
/* Dummy command; if it succeeds, battery is present. */
ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
if (ret < 0) { /* battery not present*/ if (psp == POWER_SUPPLY_PROP_PRESENT) {
val->intval = 0; return 0;
} return ret;
}
if (psp == POWER_SUPPLY_PROP_PRESENT)
val->intval = 1; /* battery present */ else { /* POWER_SUPPLY_PROP_HEALTH */ if (sbs_bat_needs_calibration(client)) {
val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED;
} else { /* SBS spec doesn't have a general health command. */
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
return 0;
}
staticint sbs_get_battery_property(struct i2c_client *client, int reg_offset, enum power_supply_property psp, union power_supply_propval *val)
{ struct sbs_info *chip = i2c_get_clientdata(client);
s32 ret;
ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); if (ret < 0) return ret;
/* returned values are 16 bit */ if (sbs_data[reg_offset].min_value < 0)
ret = (s16)ret;
if (chip->poll_time == 0)
chip->last_state = val->intval; elseif (chip->last_state != val->intval) {
cancel_delayed_work_sync(&chip->work);
power_supply_changed(chip->power_supply);
chip->poll_time = 0;
}
} else { if (psp == POWER_SUPPLY_PROP_STATUS)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN; elseif (psp == POWER_SUPPLY_PROP_CAPACITY) /* sbs spec says that this can be >100 % * even if max value is 100 %
*/
val->intval = min(ret, 100); else
val->intval = 0;
}
return 0;
}
staticint sbs_get_property_index(struct i2c_client *client, enum power_supply_property psp)
{ int count;
for (count = 0; count < ARRAY_SIZE(sbs_data); count++) if (psp == sbs_data[count].psp) return count;
buf = sbs_get_string_buf(chip, psp); if (IS_ERR(buf)) return buf;
if (!buf[0]) {
ret = sbs_get_property_index(chip->client, psp); if (ret < 0) return ERR_PTR(ret);
addr = sbs_data[ret].addr;
ret = sbs_read_string_data(chip->client, addr, buf); if (ret < 0) return ERR_PTR(ret);
}
return buf;
}
staticvoid sbs_unit_adjustment(struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val)
{ #define BASE_UNIT_CONVERSION 1000 #define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION) #define TIME_UNIT_CONVERSION 60 #define TEMP_KELVIN_TO_CELSIUS 2731 switch (psp) { case POWER_SUPPLY_PROP_ENERGY_NOW: case POWER_SUPPLY_PROP_ENERGY_FULL: case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: /* sbs provides energy in units of 10mWh. * Convert to µWh
*/
val->intval *= BATTERY_MODE_CAP_MULT_WATT; break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_CHARGE_NOW: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: case POWER_SUPPLY_PROP_CHARGE_FULL: case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval *= BASE_UNIT_CONVERSION; break;
case POWER_SUPPLY_PROP_TEMP: /* sbs provides battery temperature in 0.1K * so convert it to 0.1°C
*/
val->intval -= TEMP_KELVIN_TO_CELSIUS; break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: /* sbs provides time to empty and time to full in minutes. * Convert to seconds
*/
val->intval *= TIME_UNIT_CONVERSION; break;
default:
dev_dbg(&client->dev, "%s: no need for unit conversion %d\n", __func__, psp);
}
}
case POWER_SUPPLY_PROP_ENERGY_NOW: case POWER_SUPPLY_PROP_ENERGY_FULL: case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: case POWER_SUPPLY_PROP_CHARGE_NOW: case POWER_SUPPLY_PROP_CHARGE_FULL: case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
ret = sbs_get_property_index(client, psp); if (ret < 0) break;
/* sbs_get_battery_capacity() will change the battery mode * temporarily to read the requested attribute. Ensure we stay * in the desired mode for the duration of the attribute read.
*/
mutex_lock(&chip->mode_lock);
ret = sbs_get_battery_capacity(client, ret, psp, val);
mutex_unlock(&chip->mode_lock); break;
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
ret = sbs_get_battery_serial_number(client, val); break;
case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_CAPACITY_LEVEL: case POWER_SUPPLY_PROP_CYCLE_COUNT: case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN:
ret = sbs_get_property_index(client, psp); if (ret < 0) break;
ret = sbs_get_battery_property(client, ret, psp, val); break;
case POWER_SUPPLY_PROP_MODEL_NAME: case POWER_SUPPLY_PROP_MANUFACTURER:
str = sbs_get_constant_string(chip, psp); if (IS_ERR(str))
ret = PTR_ERR(str); else
val->strval = str; break;
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
ret = sbs_get_battery_manufacture_date(client, psp, val); break;
if (!chip->gpio_detect && chip->is_present != (ret >= 0)) { bool old_present = chip->is_present; union power_supply_propval val; int err = sbs_get_battery_presence_and_health(
client, POWER_SUPPLY_PROP_PRESENT, &val);
sbs_update_presence(chip, !err && val.intval);
if (old_present != chip->is_present)
power_supply_changed(chip->power_supply);
}
done: if (!ret) { /* Convert units to match requirements for power supply class */
sbs_unit_adjustment(client, psp, val);
dev_dbg(&client->dev, "%s: property = %d, value = %x\n", __func__,
psp, val->intval);
} elseif (!chip->is_present) { /* battery not present, so return NODATA for properties */
ret = -ENODATA;
} return ret;
}
ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr); /* if the read failed, give up on this work */ if (ret < 0) {
chip->poll_time = 0; return;
}
if (ret & BATTERY_FULL_CHARGED)
ret = POWER_SUPPLY_STATUS_FULL; elseif (ret & BATTERY_DISCHARGING)
ret = POWER_SUPPLY_STATUS_DISCHARGING; else
ret = POWER_SUPPLY_STATUS_CHARGING;
/* use pdata if available, fall back to DT properties, * or hardcoded defaults if not
*/
rc = device_property_read_u32(&client->dev, "sbs,i2c-retry-count",
&chip->i2c_retry_count); if (rc)
chip->i2c_retry_count = 0;
rc = device_property_read_u32(&client->dev, "sbs,poll-retry-count",
&chip->poll_retry_count); if (rc)
chip->poll_retry_count = 0;
chip->gpio_detect = devm_gpiod_get_optional(&client->dev, "sbs,battery-detect", GPIOD_IN); if (IS_ERR(chip->gpio_detect)) return dev_err_probe(&client->dev, PTR_ERR(chip->gpio_detect), "Failed to get gpio\n");
i2c_set_clientdata(client, chip);
if (!chip->gpio_detect) goto skip_gpio;
irq = gpiod_to_irq(chip->gpio_detect); if (irq <= 0) {
dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); goto skip_gpio;
}
skip_gpio: /* * Before we register, we might need to make sure we can actually talk * to the battery.
*/ if (!(force_load || chip->gpio_detect)) { union power_supply_propval val;
rc = sbs_get_battery_presence_and_health(
client, POWER_SUPPLY_PROP_PRESENT, &val); if (rc < 0 || !val.intval) return dev_err_probe(&client->dev, -ENODEV, "Failed to get present status\n");
}
rc = devm_delayed_work_autocancel(&client->dev, &chip->work,
sbs_delayed_work); if (rc) return rc;
chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc,
&psy_cfg); if (IS_ERR(chip->power_supply)) return dev_err_probe(&client->dev, PTR_ERR(chip->power_supply), "Failed to register power supply\n");
dev_info(&client->dev, "%s: battery gas gauge device registered\n", client->name);
if (chip->poll_time > 0)
cancel_delayed_work_sync(&chip->work);
if (chip->flags & SBS_FLAGS_TI_BQ20ZX5) { /* Write to manufacturer access with sleep command. */
ret = sbs_write_word_data(client,
sbs_data[REG_MANUFACTURER_DATA].addr,
MANUFACTURER_ACCESS_SLEEP); if (chip->is_present && ret < 0) return ret;
}
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.