aboutsummaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/bq27000_battery.c236
1 files changed, 185 insertions, 51 deletions
diff --git a/drivers/power/bq27000_battery.c b/drivers/power/bq27000_battery.c
index cd4968edad9..ccd56ea137c 100644
--- a/drivers/power/bq27000_battery.c
+++ b/drivers/power/bq27000_battery.c
@@ -29,6 +29,7 @@
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/pm.h>
+#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/bq27000_battery.h>
@@ -110,12 +111,32 @@ enum bq27000_status_flags {
#define NANOVOLTS_UNIT 3750
+struct bq27000_bat_regs {
+ int ai;
+ int flags;
+ int lmd;
+ int rsoc;
+ int temp;
+ int tte;
+ int ttf;
+ int volt;
+};
+
struct bq27000_device_info {
struct device *dev;
struct power_supply bat;
+ struct power_supply ac;
+ struct power_supply usb;
+ struct delayed_work work;
struct bq27000_platform_data *pdata;
+
+ struct bq27000_bat_regs regs;
};
+static unsigned int cache_time = 10000;
+module_param(cache_time, uint, 0644);
+MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
+
/*
* reading 16 bit values over HDQ has a special hazard where the
* hdq device firmware can update the 16-bit register during the time we
@@ -147,13 +168,9 @@ static int hdq_read16(struct bq27000_device_info *di, int address)
return -ETIME;
}
-#define to_bq27000_device_info(x) container_of((x), \
- struct bq27000_device_info, \
- bat);
-
static void bq27000_battery_external_power_changed(struct power_supply *psy)
{
- struct bq27000_device_info *di = to_bq27000_device_info(psy);
+ struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat);
dev_dbg(di->dev, "%s\n", __FUNCTION__);
}
@@ -162,11 +179,8 @@ static int bq27000_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
- int v, n;
- struct bq27000_device_info *di = to_bq27000_device_info(psy);
-
- if (!(di->pdata->hdq_initialized)())
- return -EINVAL;
+ int n;
+ struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
@@ -207,84 +221,80 @@ use_bat:
* but... battery state can be out of date by 4 seconds or
* so... use the platform callbacks if possible.
*/
- v = hdq_read16(di, BQ27000_AI_L);
- if (v < 0)
- return v;
/* no real activity on the battery */
- if (v < 2) {
- if (!hdq_read16(di, BQ27000_TTF_L))
+ if (di->regs.ai < 2) {
+ if (!di->regs.ttf)
val->intval = POWER_SUPPLY_STATUS_FULL;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
}
/* power is actually going in or out... */
- v = (di->pdata->hdq_read)(BQ27000_FLAGS);
- if (v < 0)
- return v;
- if (v & BQ27000_STATUS_CHGS)
+ if (di->regs.flags < 0)
+ return di->regs.flags;
+ if (di->regs.flags & BQ27000_STATUS_CHGS)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ /* Do we have accurate readings... */
+ if (di->regs.flags < 0)
+ return di->regs.flags;
+ if (di->regs.flags & BQ27000_STATUS_VDQ)
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- v = hdq_read16(di, BQ27000_VOLT_L);
- if (v < 0)
- return v;
+ if (di->regs.volt < 0)
+ return di->regs.volt;
/* mV -> uV */
- val->intval = v * 1000;
+ val->intval = di->regs.volt * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
- v = (di->pdata->hdq_read)(BQ27000_FLAGS);
- if (v < 0)
- return v;
- if (v & BQ27000_STATUS_CHGS)
+ if (di->regs.flags < 0)
+ return di->regs.flags;
+ if (di->regs.flags & BQ27000_STATUS_CHGS)
n = -NANOVOLTS_UNIT;
else
n = NANOVOLTS_UNIT;
- v = hdq_read16(di, BQ27000_AI_L);
- if (v < 0)
- return v;
- val->intval = (v * n) / di->pdata->rsense_mohms;
+ if (di->regs.ai < 0)
+ return di->regs.ai;
+ val->intval = (di->regs.ai * n) / di->pdata->rsense_mohms;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
- v = hdq_read16(di, BQ27000_LMD_L);
- if (v < 0)
- return v;
- val->intval = (v * 3570) / di->pdata->rsense_mohms;
+ if (di->regs.lmd < 0)
+ return di->regs.lmd;
+ val->intval = (di->regs.lmd * 3570) / di->pdata->rsense_mohms;
break;
case POWER_SUPPLY_PROP_TEMP:
- v = hdq_read16(di, BQ27000_TEMP_L);
- if (v < 0)
- return v;
+ if (di->regs.temp < 0)
+ return di->regs.temp;
/* K (in 0.25K units) is 273.15 up from C (in 0.1C)*/
/* 10926 = 27315 * 4 / 10 */
- val->intval = (((long)v * 10l) - 10926) / 4;
+ val->intval = (((long)di->regs.temp * 10l) - 10926) / 4;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CAPACITY:
- val->intval = (di->pdata->hdq_read)(BQ27000_RSOC);
+ val->intval = di->regs.rsoc;
if (val->intval < 0)
return val->intval;
break;
case POWER_SUPPLY_PROP_PRESENT:
- v = (di->pdata->hdq_read)(BQ27000_RSOC);
- val->intval = !(v < 0);
+ val->intval = !(di->regs.rsoc < 0);
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
- v = hdq_read16(di, BQ27000_TTE_L);
- if (v < 0)
- return v;
- val->intval = 60 * v;
+ if (di->regs.tte < 0)
+ return di->regs.tte;
+ val->intval = 60 * di->regs.tte;
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
- v = hdq_read16(di, BQ27000_TTF_L);
- if (v < 0)
- return v;
- val->intval = 60 * v;
+ if (di->regs.ttf < 0)
+ return di->regs.ttf;
+ val->intval = 60 * di->regs.ttf;
break;
case POWER_SUPPLY_PROP_ONLINE:
if (di->pdata->get_charger_online_status)
@@ -299,8 +309,84 @@ use_bat:
return 0;
}
+static void bq27000_battery_work(struct work_struct *work)
+{
+ struct bq27000_device_info *di =
+ container_of(work, struct bq27000_device_info, work.work);
+
+ if ((di->pdata->hdq_initialized)()) {
+ struct bq27000_bat_regs regs;
+
+ regs.ai = hdq_read16(di, BQ27000_AI_L);
+ regs.flags = (di->pdata->hdq_read)(BQ27000_FLAGS);
+ regs.lmd = hdq_read16(di, BQ27000_LMD_L);
+ regs.rsoc = (di->pdata->hdq_read)(BQ27000_RSOC);
+ regs.temp = hdq_read16(di, BQ27000_TEMP_L);
+ regs.tte = hdq_read16(di, BQ27000_TTE_L);
+ regs.ttf = hdq_read16(di, BQ27000_TTF_L);
+ regs.volt = hdq_read16(di, BQ27000_VOLT_L);
+
+ if (memcmp (&regs, &di->regs, sizeof(regs)) != 0) {
+ di->regs = regs;
+ power_supply_changed(&di->bat);
+ }
+ }
+
+ if (!schedule_delayed_work(&di->work, cache_time))
+ dev_err(di->dev, "battery service reschedule failed\n");
+}
+
+static int ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, ac);
+
+ if (!(di->pdata->hdq_initialized)())
+ return -EINVAL;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (di->pdata->get_charger_online_status)
+ val->intval = (di->pdata->get_charger_online_status)();
+ else
+ return -EINVAL;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, usb);
+
+ if (!(di->pdata->hdq_initialized)())
+ return -EINVAL;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (di->pdata->get_charger_online_status)
+ val->intval = (di->pdata->get_charger_online_status)();
+ else
+ return -EINVAL;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
static enum power_supply_property bq27000_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL,
@@ -313,6 +399,10 @@ static enum power_supply_property bq27000_battery_props[] = {
POWER_SUPPLY_PROP_ONLINE
};
+static enum power_supply_property power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
static int bq27000_battery_probe(struct platform_device *pdev)
{
int retval = 0;
@@ -342,14 +432,47 @@ static int bq27000_battery_probe(struct platform_device *pdev)
di->bat.use_for_apm = 1;
di->pdata = pdata;
+ di->ac.name = "ac";
+ di->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ di->ac.properties = power_props;
+ di->ac.num_properties = ARRAY_SIZE(power_props);
+ di->ac.get_property = ac_get_property;
+
+ di->usb.name = "usb";
+ di->usb.type = POWER_SUPPLY_TYPE_USB;
+ di->usb.properties = power_props;
+ di->usb.num_properties = ARRAY_SIZE(power_props);
+ di->usb.get_property = usb_get_property;
+
retval = power_supply_register(&pdev->dev, &di->bat);
if (retval) {
dev_err(di->dev, "failed to register battery\n");
goto batt_failed;
}
+ retval = power_supply_register(&pdev->dev, &di->ac);
+ if (retval) {
+ dev_err(di->dev, "failed to register ac\n");
+ goto ac_failed;
+ }
+
+ retval = power_supply_register(&pdev->dev, &di->usb);
+ if (retval) {
+ dev_err(di->dev, "failed to register usb\n");
+ goto usb_failed;
+ }
+
+ INIT_DELAYED_WORK(&di->work, bq27000_battery_work);
+
+ if (!schedule_delayed_work(&di->work, 0))
+ dev_err(di->dev, "failed to schedule bq27000_battery_work\n");
+
return 0;
+usb_failed:
+ power_supply_unregister(&di->ac);
+ac_failed:
+ power_supply_unregister(&di->bat);
batt_failed:
kfree(di);
di_alloc_failed:
@@ -360,7 +483,11 @@ static int bq27000_battery_remove(struct platform_device *pdev)
{
struct bq27000_device_info *di = platform_get_drvdata(pdev);
+ cancel_delayed_work(&di->work);
+
power_supply_unregister(&di->bat);
+ power_supply_unregister(&di->ac);
+ power_supply_unregister(&di->usb);
return 0;
}
@@ -372,7 +499,8 @@ void bq27000_charging_state_change(struct platform_device *pdev)
if (!di)
return;
- power_supply_changed(&di->bat);
+ power_supply_changed(&di->ac);
+ power_supply_changed(&di->usb);
}
EXPORT_SYMBOL_GPL(bq27000_charging_state_change);
@@ -381,11 +509,17 @@ EXPORT_SYMBOL_GPL(bq27000_charging_state_change);
static int bq27000_battery_suspend(struct platform_device *pdev,
pm_message_t state)
{
+ struct bq27000_device_info *di = platform_get_drvdata(pdev);
+
+ cancel_delayed_work(&di->work);
return 0;
}
static int bq27000_battery_resume(struct platform_device *pdev)
{
+ struct bq27000_device_info *di = platform_get_drvdata(pdev);
+
+ schedule_delayed_work(&di->work, 0);
return 0;
}