diff options
Diffstat (limited to 'drivers/power/max17040_battery.c')
-rw-r--r-- | drivers/power/max17040_battery.c | 497 |
1 files changed, 423 insertions, 74 deletions
diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 2f2f9a6..8ca350a 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -20,6 +20,10 @@ #include <linux/power_supply.h> #include <linux/max17040_battery.h> #include <linux/slab.h> +#include <linux/android_alarm.h> +#include <linux/suspend.h> +#include <linux/interrupt.h> +#include <linux/reboot.h> #define MAX17040_VCELL_MSB 0x02 #define MAX17040_VCELL_LSB 0x03 @@ -34,12 +38,21 @@ #define MAX17040_CMD_MSB 0xFE #define MAX17040_CMD_LSB 0xFF -#define MAX17040_DELAY 1000 -#define MAX17040_BATTERY_FULL 95 +#define MAX17040_BATTERY_FULL 100 + +#define HAS_ALERT_INTERRUPT(ver) (ver >= 3) + +#define FAST_POLL (1 * 60) +#define SLOW_POLL (10 * 60) + +#define STATUS_CHARGABLE 0x0 +#define STATUS_CHARGE_FULL 0x1 +#define STATUS_ABNORMAL_TEMP 0x2 +#define STATUS_CHARGE_TIMEOVER 0x3 struct max17040_chip { struct i2c_client *client; - struct delayed_work work; + struct work_struct work; struct power_supply battery; struct max17040_platform_data *pdata; @@ -51,6 +64,25 @@ struct max17040_chip { int soc; /* State Of Charge */ int status; + /* Health of Battery */ + int bat_health; + /* Temperature of Battery */ + int bat_temp; + + struct notifier_block pm_notifier; + struct wake_lock work_wake_lock; + + struct alarm alarm; + ktime_t last_poll; + int slow_poll; + int shutdown; + /* chip version */ + u16 ver; + + int charger_status; + unsigned long chg_limit_time; + + bool is_timer_flag; }; static int max17040_get_property(struct power_supply *psy, @@ -64,6 +96,9 @@ static int max17040_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_STATUS: val->intval = chip->status; break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->bat_health; + break; case POWER_SUPPLY_PROP_ONLINE: val->intval = chip->online; break; @@ -73,17 +108,25 @@ static int max17040_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CAPACITY: val->intval = chip->soc; break; + case POWER_SUPPLY_PROP_TEMP: + if (!chip->pdata->get_bat_temp) + return -ENODATA; + val->intval = chip->bat_temp; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; default: return -EINVAL; } return 0; } -static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) +static int max17040_write_reg(struct i2c_client *client, int reg, u16 val) { int ret; - ret = i2c_smbus_write_byte_data(client, reg, value); + ret = i2c_smbus_write_word_data(client, reg, cpu_to_be16(val)); if (ret < 0) dev_err(&client->dev, "%s: err %d\n", __func__, ret); @@ -91,57 +134,83 @@ static int max17040_write_reg(struct i2c_client *client, int reg, u8 value) return ret; } -static int max17040_read_reg(struct i2c_client *client, int reg) +static int max17040_read_reg(struct i2c_client *client, int reg, u16 *val) { int ret; - ret = i2c_smbus_read_byte_data(client, reg); + ret = i2c_smbus_read_word_data(client, reg); - if (ret < 0) + if (ret < 0) { dev_err(&client->dev, "%s: err %d\n", __func__, ret); + *val = 0; + return ret; + } - return ret; + *val = be16_to_cpu(ret); + return 0; } static void max17040_reset(struct i2c_client *client) { - max17040_write_reg(client, MAX17040_CMD_MSB, 0x54); - max17040_write_reg(client, MAX17040_CMD_LSB, 0x00); + max17040_write_reg(client, MAX17040_CMD_MSB, 0x5400); + + msleep(125); + + max17040_write_reg(client, MAX17040_MODE_MSB, 0x4000); } static void max17040_get_vcell(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - u8 msb; - u8 lsb; + u16 val; - msb = max17040_read_reg(client, MAX17040_VCELL_MSB); - lsb = max17040_read_reg(client, MAX17040_VCELL_LSB); - - chip->vcell = (msb << 4) + (lsb >> 4); + if (!max17040_read_reg(client, MAX17040_VCELL_MSB, &val)) + chip->vcell = (val >> 4) * 1250; + else + dev_warn(&client->dev, "i2c error, not updating vcell\n"); } +#define TO_FIXED(a,b) (((a) << 8) + (b)) +#define FIXED_TO_INT(x) ((int)((x) >> 8)) +#define FIXED_MULT(x,y) ((((u32)(x) * (u32)(y)) + (1 << 7)) >> 8) +#define FIXED_DIV(x,y) ((((u32)(x) << 8) + ((u32)(y) >> 1)) / (u32)(y)) + static void max17040_get_soc(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - u8 msb; - u8 lsb; + u32 val; + u32 fmin_cap = TO_FIXED(chip->pdata->min_capacity, 0); + u16 regval; - msb = max17040_read_reg(client, MAX17040_SOC_MSB); - lsb = max17040_read_reg(client, MAX17040_SOC_LSB); + if (max17040_read_reg(client, MAX17040_SOC_MSB, ®val)) { + dev_warn(&client->dev, "i2c error, not updating soc\n"); + return; + } - chip->soc = msb; + /* convert msb.lsb to Q8.8 */ + val = TO_FIXED(regval >> 8, regval & 0xff); + if (val <= fmin_cap) { + chip->soc = 0; + return; + } + + val = FIXED_MULT(TO_FIXED(100, 0), val - fmin_cap); + val = FIXED_DIV(val, TO_FIXED(100, 0) - fmin_cap); + chip->soc = clamp(FIXED_TO_INT(val), 0, 100); } static void max17040_get_version(struct i2c_client *client) { - u8 msb; - u8 lsb; - - msb = max17040_read_reg(client, MAX17040_VER_MSB); - lsb = max17040_read_reg(client, MAX17040_VER_LSB); + struct max17040_chip *chip = i2c_get_clientdata(client); + u16 val; - dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb); + if (!max17040_read_reg(client, MAX17040_VER_MSB, &val)) { + chip->ver = val; + dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d\n", val); + } else { + dev_err(&client->dev, + "Error reading version, some features disabled\n"); + } } static void max17040_get_online(struct i2c_client *client) @@ -164,45 +233,276 @@ static void max17040_get_status(struct i2c_client *client) } if (chip->pdata->charger_online()) { - if (chip->pdata->charger_enable()) + if (chip->pdata->charger_enable()) { chip->status = POWER_SUPPLY_STATUS_CHARGING; - else - chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + chip->status = + chip->charger_status == STATUS_CHARGE_FULL ? + POWER_SUPPLY_STATUS_FULL : + POWER_SUPPLY_STATUS_NOT_CHARGING; + } } else { chip->status = POWER_SUPPLY_STATUS_DISCHARGING; + chip->chg_limit_time = 0; + chip->charger_status = STATUS_CHARGABLE; } +} + +static void max17040_get_temp_status(struct max17040_chip *chip) +{ + int r; + int t; + + if (!chip->pdata->get_bat_temp) + return; - if (chip->soc > MAX17040_BATTERY_FULL) - chip->status = POWER_SUPPLY_STATUS_FULL; + r = chip->pdata->get_bat_temp(&t); + + if (r < 0) { + dev_err(&chip->client->dev, + "error %d reading battery temperature\n", r); + chip->bat_health = POWER_SUPPLY_HEALTH_UNKNOWN; + return; + } + + chip->bat_temp = t; + + if (chip->bat_temp >= chip->pdata->high_block_temp) { + chip->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; + } else if (chip->bat_temp <= chip->pdata->high_recover_temp && + chip->bat_temp >= chip->pdata->low_recover_temp) { + chip->bat_health = POWER_SUPPLY_HEALTH_GOOD; + } else if (chip->bat_temp <= chip->pdata->low_block_temp) { + chip->bat_health = POWER_SUPPLY_HEALTH_COLD; + } } -static void max17040_work(struct work_struct *work) +static void max17040_charger_update(struct max17040_chip *chip) { - struct max17040_chip *chip; + ktime_t ktime; + struct timespec cur_time; - chip = container_of(work, struct max17040_chip, work.work); + if (!chip->pdata->is_full_charge || !chip->pdata->allow_charging) + return; + + ktime = alarm_get_elapsed_realtime(); + cur_time = ktime_to_timespec(ktime); + + switch (chip->charger_status) { + case STATUS_CHARGABLE: + if (chip->pdata->is_full_charge() && + chip->soc >= MAX17040_BATTERY_FULL && + chip->vcell > chip->pdata->fully_charged_vol) { + chip->charger_status = STATUS_CHARGE_FULL; + chip->is_timer_flag = true; + chip->chg_limit_time = 0; + chip->pdata->allow_charging(0); + } else if (chip->chg_limit_time && + cur_time.tv_sec > chip->chg_limit_time) { + chip->charger_status = STATUS_CHARGE_TIMEOVER; + chip->is_timer_flag = true; + chip->chg_limit_time = 0; + chip->pdata->allow_charging(0); + } else if (chip->bat_health == POWER_SUPPLY_HEALTH_OVERHEAT || + chip->bat_health == POWER_SUPPLY_HEALTH_COLD) { + chip->charger_status = STATUS_ABNORMAL_TEMP; + chip->chg_limit_time = 0; + chip->pdata->allow_charging(0); + } + break; + + case STATUS_CHARGE_FULL: + if (chip->vcell <= chip->pdata->recharge_vol) { + chip->charger_status = STATUS_CHARGABLE; + chip->pdata->allow_charging(1); + } + break; + + case STATUS_ABNORMAL_TEMP: + if (chip->bat_temp <= chip->pdata->high_recover_temp && + chip->bat_temp >= + chip->pdata->low_recover_temp) { + chip->charger_status = STATUS_CHARGABLE; + chip->pdata->allow_charging(1); + } + break; + + case STATUS_CHARGE_TIMEOVER: + if (chip->vcell <= chip->pdata->fully_charged_vol) { + chip->charger_status = STATUS_CHARGABLE; + chip->pdata->allow_charging(1); + } + break; + + default: + dev_err(&chip->client->dev, "%s : invalid status [%d]\n", + __func__, chip->charger_status); + } + + if (!chip->chg_limit_time && + chip->charger_status == STATUS_CHARGABLE) { + chip->chg_limit_time = + chip->is_timer_flag ? + cur_time.tv_sec + chip->pdata->limit_recharging_time : + cur_time.tv_sec + chip->pdata->limit_charging_time; + } + + dev_dbg(&chip->client->dev, "%s, Charger Status : %d, Limit Time : %ld\n", + __func__, chip->charger_status, chip->chg_limit_time); +} + +static void max17040_update(struct max17040_chip *chip) +{ + int prev_status = chip->status; + int prev_soc = chip->soc; max17040_get_vcell(chip->client); max17040_get_soc(chip->client); max17040_get_online(chip->client); + max17040_get_temp_status(chip); + if (chip->pdata->charger_online()) + max17040_charger_update(chip); + else + chip->is_timer_flag = false; max17040_get_status(chip->client); + if ((chip->soc != prev_soc) || (chip->status != prev_status)) + power_supply_changed(&chip->battery); + + dev_info(&chip->client->dev, "online = %d vcell = %d soc = %d " + "status = %d health = %d temp = %d " + "charger status = %d\n", chip->online, chip->vcell, + chip->soc, chip->status, chip->bat_health, chip->bat_temp, + chip->charger_status); +} + +static void max17040_program_alarm(struct max17040_chip *chip, int seconds) +{ + ktime_t low_interval = ktime_set(seconds - 10, 0); + ktime_t slack = ktime_set(20, 0); + ktime_t next; - schedule_delayed_work(&chip->work, MAX17040_DELAY); + next = ktime_add(chip->last_poll, low_interval); + alarm_start_range(&chip->alarm, next, ktime_add(next, slack)); +} + +static void max17040_work(struct work_struct *work) +{ + unsigned long flags; + struct timespec ts; + struct max17040_chip *chip; + + chip = container_of(work, struct max17040_chip, work); + + max17040_update(chip); + + chip->last_poll = alarm_get_elapsed_realtime(); + ts = ktime_to_timespec(chip->last_poll); + + local_irq_save(flags); + wake_unlock(&chip->work_wake_lock); + if (!chip->shutdown) + max17040_program_alarm(chip, FAST_POLL); + local_irq_restore(flags); +} + +static void max17040_battery_alarm(struct alarm *alarm) +{ + struct max17040_chip *chip = + container_of(alarm, struct max17040_chip, alarm); + + wake_lock(&chip->work_wake_lock); + schedule_work(&chip->work); + +} + +static void max17040_ext_power_changed(struct power_supply *psy) +{ + struct max17040_chip *chip = container_of(psy, + struct max17040_chip, battery); + + wake_lock(&chip->work_wake_lock); + schedule_work(&chip->work); } static enum power_supply_property max17040_battery_props[] = { POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TECHNOLOGY, + /* must be last */ + POWER_SUPPLY_PROP_TEMP, }; +static int max17040_pm_notifier(struct notifier_block *notifier, + unsigned long pm_event, + void *unused) +{ + struct max17040_chip *chip = + container_of(notifier, struct max17040_chip, pm_notifier); + + switch (pm_event) { + case PM_SUSPEND_PREPARE: + if (!chip->pdata->charger_enable()) { + cancel_work_sync(&chip->work); + max17040_program_alarm(chip, SLOW_POLL); + chip->slow_poll = 1; + } + break; + + case PM_POST_SUSPEND: + /* We might be on a slow sample cycle. If we're + * resuming we should resample the battery state + * if it's been over a minute since we last did + * so, and move back to sampling every minute until + * we suspend again. + */ + if (chip->slow_poll) { + max17040_program_alarm(chip, FAST_POLL); + chip->slow_poll = 0; + } + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block max17040_pm_notifier_block = { + .notifier_call = max17040_pm_notifier, +}; + +static irqreturn_t max17040_alert(int irq, void *data) +{ + struct max17040_chip *chip = data; + struct i2c_client *client = chip->client; + + max17040_get_vcell(chip->client); + max17040_get_soc(chip->client); + + dev_info(&client->dev, "Low battery alert fired: soc=%d vcell=%d\n", + chip->soc, chip->vcell); + + if (chip->soc != 0) { + dev_err(&client->dev, "false low battery alert, ignoring\n"); + goto out; + } + + dev_info(&client->dev, "shutting down due to low battery...\n"); + kernel_power_off(); + +out: + return IRQ_HANDLED; +} + static int __devinit max17040_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); struct max17040_chip *chip; int ret; + u16 val; + u16 athd; + int num_props = ARRAY_SIZE(max17040_battery_props); if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) return -EIO; @@ -214,66 +514,117 @@ static int __devinit max17040_probe(struct i2c_client *client, chip->client = client; chip->pdata = client->dev.platform_data; + if (!chip->pdata->get_bat_temp) + num_props--; + i2c_set_clientdata(client, chip); chip->battery.name = "battery"; chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; chip->battery.get_property = max17040_get_property; chip->battery.properties = max17040_battery_props; - chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props); + chip->battery.num_properties = num_props; + chip->battery.external_power_changed = max17040_ext_power_changed; + + chip->bat_health = POWER_SUPPLY_HEALTH_GOOD; + chip->charger_status = STATUS_CHARGABLE; + chip->is_timer_flag = false; + chip->chg_limit_time = 0; + + if (!chip->pdata->high_block_temp) + chip->pdata->high_block_temp = 500; + if (!chip->pdata->high_recover_temp) + chip->pdata->high_recover_temp = 420; + if (!chip->pdata->low_block_temp) + chip->pdata->low_block_temp = -50; + if (!chip->pdata->fully_charged_vol) + chip->pdata->fully_charged_vol = 4150000; + if (!chip->pdata->recharge_vol) + chip->pdata->recharge_vol = 4140000; + if (!chip->pdata->limit_charging_time) + chip->pdata->limit_charging_time = 21600; + if (!chip->pdata->limit_recharging_time) + chip->pdata->limit_recharging_time = 5400; + + chip->last_poll = alarm_get_elapsed_realtime(); + alarm_init(&chip->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + max17040_battery_alarm); + + wake_lock_init(&chip->work_wake_lock, WAKE_LOCK_SUSPEND, + "max17040-battery"); + + if (!chip->pdata->skip_reset) + max17040_reset(client); + + max17040_get_version(client); + INIT_WORK(&chip->work, max17040_work); ret = power_supply_register(&client->dev, &chip->battery); if (ret) { dev_err(&client->dev, "failed: power supply register\n"); - kfree(chip); - return ret; + goto err_battery_supply_register; } - max17040_reset(client); - max17040_get_version(client); - - INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work); - schedule_delayed_work(&chip->work, MAX17040_DELAY); + /* i2c-core does not support dev_pm_ops.prepare and .complete + * So, used pm_notifier for use android_alarm. + */ + chip->pm_notifier = max17040_pm_notifier_block; + ret = register_pm_notifier(&chip->pm_notifier); + if (ret) { + dev_err(&client->dev, "failed: register pm notifier\n"); + goto err_pm_notifier; + } + schedule_work(&chip->work); + + if (HAS_ALERT_INTERRUPT(chip->ver) && chip->pdata->use_fuel_alert) { + /* setting the low SOC alert threshold */ + if (!max17040_read_reg(client, MAX17040_RCOMP_MSB, &val)) { + athd = chip->pdata->min_capacity > 1 ? + chip->pdata->min_capacity - 1 : 0; + max17040_write_reg(client, MAX17040_RCOMP_MSB, + (val & ~0x1f) | (-athd & 0x1f)); + } else { + dev_err(&client->dev, + "Error setting battery alert threshold\n"); + } + + /* add alert irq handler */ + ret = request_threaded_irq(client->irq, NULL, max17040_alert, + IRQF_TRIGGER_FALLING, "fuel gauge alert", chip); + if (ret < 0) { + dev_err(&client->dev, + "request_threaded_irq() failed: %d", ret); + goto err_pm_notifier; + } + } return 0; -} - -static int __devexit max17040_remove(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); +err_pm_notifier: power_supply_unregister(&chip->battery); - cancel_delayed_work(&chip->work); +err_battery_supply_register: + wake_lock_destroy(&chip->work_wake_lock); + alarm_cancel(&chip->alarm); kfree(chip); - return 0; -} - -#ifdef CONFIG_PM -static int max17040_suspend(struct i2c_client *client, - pm_message_t state) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - - cancel_delayed_work(&chip->work); - return 0; + return ret; } -static int max17040_resume(struct i2c_client *client) +static int __devexit max17040_remove(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - - schedule_delayed_work(&chip->work, MAX17040_DELAY); + chip->shutdown = 1; + unregister_pm_notifier(&chip->pm_notifier); + power_supply_unregister(&chip->battery); + alarm_cancel(&chip->alarm); + cancel_work_sync(&chip->work); + wake_lock_destroy(&chip->work_wake_lock); + if (HAS_ALERT_INTERRUPT(chip->ver) && chip->pdata->use_fuel_alert) + free_irq(client->irq, chip); + kfree(chip); return 0; } -#else - -#define max17040_suspend NULL -#define max17040_resume NULL - -#endif /* CONFIG_PM */ - static const struct i2c_device_id max17040_id[] = { { "max17040", 0 }, { } @@ -286,8 +637,6 @@ static struct i2c_driver max17040_i2c_driver = { }, .probe = max17040_probe, .remove = __devexit_p(max17040_remove), - .suspend = max17040_suspend, - .resume = max17040_resume, .id_table = max17040_id, }; |