Root/target/linux/s3c24xx/files-2.6.30/drivers/power/bq27000_battery.c

1/*
2 * Driver for batteries with bq27000 chips inside via HDQ
3 *
4 * Copyright 2008 Openmoko, Inc
5 * Andy Green <andy@openmoko.com>
6 *
7 * based on ds2760 driver, original copyright notice for that --->
8 *
9 * Copyright © 2007 Anton Vorontsov
10 * 2004-2007 Matt Reimer
11 * 2004 Szabolcs Gyurko
12 *
13 * Use consistent with the GNU GPL is permitted,
14 * provided that this copyright notice is
15 * preserved in its entirety in all copies and derived works.
16 *
17 * Author: Anton Vorontsov <cbou@mail.ru>
18 * February 2007
19 *
20 * Matt Reimer <mreimer@vpop.net>
21 * April 2004, 2005, 2007
22 *
23 * Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>
24 * September 2004
25 */
26
27#include <linux/module.h>
28#include <linux/param.h>
29#include <linux/jiffies.h>
30#include <linux/delay.h>
31#include <linux/pm.h>
32#include <linux/workqueue.h>
33#include <linux/platform_device.h>
34#include <linux/power_supply.h>
35#include <linux/bq27000_battery.h>
36
37enum bq27000_regs {
38    /* RAM regs */
39        /* read-write after this */
40    BQ27000_CTRL = 0, /* Device Control Register */
41    BQ27000_MODE, /* Device Mode Register */
42    BQ27000_AR_L, /* At-Rate H L */
43    BQ27000_AR_H,
44        /* read-only after this */
45    BQ27000_ARTTE_L, /* At-Rate Time To Empty H L */
46    BQ27000_ARTTE_H,
47    BQ27000_TEMP_L, /* Reported Temperature H L */
48    BQ27000_TEMP_H,
49    BQ27000_VOLT_L, /* Reported Voltage H L */
50    BQ27000_VOLT_H,
51    BQ27000_FLAGS, /* Status Flags */
52    BQ27000_RSOC, /* Relative State of Charge */
53    BQ27000_NAC_L, /* Nominal Available Capacity H L */
54    BQ27000_NAC_H,
55    BQ27000_CACD_L, /* Discharge Compensated H L */
56    BQ27000_CACD_H,
57    BQ27000_CACT_L, /* Temperature Compensated H L */
58    BQ27000_CACT_H,
59    BQ27000_LMD_L, /* Last measured discharge H L */
60    BQ27000_LMD_H,
61    BQ27000_AI_L, /* Average Current H L */
62    BQ27000_AI_H,
63    BQ27000_TTE_L, /* Time to Empty H L */
64    BQ27000_TTE_H,
65    BQ27000_TTF_L, /* Time to Full H L */
66    BQ27000_TTF_H,
67    BQ27000_SI_L, /* Standby Current H L */
68    BQ27000_SI_H,
69    BQ27000_STTE_L, /* Standby Time To Empty H L */
70    BQ27000_STTE_H,
71    BQ27000_MLI_L, /* Max Load Current H L */
72    BQ27000_MLI_H,
73    BQ27000_MLTTE_L, /* Max Load Time To Empty H L */
74    BQ27000_MLTTE_H,
75    BQ27000_SAE_L, /* Available Energy H L */
76    BQ27000_SAE_H,
77    BQ27000_AP_L, /* Available Power H L */
78    BQ27000_AP_H,
79    BQ27000_TTECP_L, /* Time to Empty at Constant Power H L */
80    BQ27000_TTECP_H,
81    BQ27000_CYCL_L, /* Cycle count since learning cycle H L */
82    BQ27000_CYCL_H,
83    BQ27000_CYCT_L, /* Cycle Count Total H L */
84    BQ27000_CYCT_H,
85    BQ27000_CSOC, /* Compensated State Of Charge */
86    /* EEPROM regs */
87        /* read-write after this */
88    BQ27000_EE_EE_EN = 0x6e, /* EEPROM Program Enable */
89    BQ27000_EE_ILMD = 0x76, /* Initial Last Measured Discharge High Byte */
90    BQ27000_EE_SEDVF, /* Scaled EDVF Threshold */
91    BQ27000_EE_SEDV1, /* Scaled EDV1 Threshold */
92    BQ27000_EE_ISLC, /* Initial Standby Load Current */
93    BQ27000_EE_DMFSD, /* Digital Magnitude Filter and Self Discharge */
94    BQ27000_EE_TAPER, /* Aging Estimate Enable, Charge Termination Taper */
95    BQ27000_EE_PKCFG, /* Pack Configuration Values */
96    BQ27000_EE_IMLC, /* Initial Max Load Current or ID #3 */
97    BQ27000_EE_DCOMP, /* Discharge rate compensation constants or ID #2 */
98    BQ27000_EE_TCOMP, /* Temperature Compensation constants or ID #1 */
99};
100
101enum bq27000_status_flags {
102    BQ27000_STATUS_CHGS = 0x80, /* 1 = being charged */
103    BQ27000_STATUS_NOACT = 0x40, /* 1 = no activity */
104    BQ27000_STATUS_IMIN = 0x20, /* 1 = Lion taper current mode */
105    BQ27000_STATUS_CI = 0x10, /* 1 = capacity likely innacurate */
106    BQ27000_STATUS_CALIP = 0x08, /* 1 = calibration in progress */
107    BQ27000_STATUS_VDQ = 0x04, /* 1 = capacity should be accurate */
108    BQ27000_STATUS_EDV1 = 0x02, /* 1 = end of discharge.. <6% left */
109    BQ27000_STATUS_EDVF = 0x01, /* 1 = no, it's really empty now */
110};
111
112#define NANOVOLTS_UNIT 3750
113
114struct bq27000_bat_regs {
115    int ai;
116    int flags;
117    int lmd;
118    int rsoc;
119    int temp;
120    int tte;
121    int ttf;
122    int volt;
123};
124
125struct bq27000_device_info {
126    struct device *dev;
127    struct power_supply bat;
128    struct power_supply ac;
129    struct power_supply usb;
130    struct delayed_work work;
131    struct bq27000_platform_data *pdata;
132
133    struct bq27000_bat_regs regs;
134};
135
136static unsigned int cache_time = 5000;
137module_param(cache_time, uint, 0644);
138MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
139
140/*
141 * reading 16 bit values over HDQ has a special hazard where the
142 * hdq device firmware can update the 16-bit register during the time we
143 * read the two halves. TI document SLUS556D recommends the algorithm here
144 * to avoid trouble
145 */
146
147static int hdq_read16(struct bq27000_device_info *di, int address)
148{
149    int acc;
150    int high;
151    int retries = 3;
152
153    while (retries--) {
154
155        high = (di->pdata->hdq_read)(address + 1); /* high part */
156
157        if (high < 0)
158            return high;
159        acc = (di->pdata->hdq_read)(address);
160        if (acc < 0)
161            return acc;
162
163        /* confirm high didn't change between reading it and low */
164        if (high == (di->pdata->hdq_read)(address + 1))
165            return (high << 8) | acc;
166    }
167
168    return -ETIME;
169}
170
171static void bq27000_battery_external_power_changed(struct power_supply *psy)
172{
173    struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat);
174
175    dev_dbg(di->dev, "%s\n", __FUNCTION__);
176    schedule_delayed_work(&di->work, 0);
177}
178
179static int bq27000_battery_get_property(struct power_supply *psy,
180                       enum power_supply_property psp,
181                       union power_supply_propval *val)
182{
183    int n;
184    struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat);
185
186    if (di->regs.rsoc < 0 && psp != POWER_SUPPLY_PROP_PRESENT)
187        return -ENODEV;
188
189    switch (psp) {
190    case POWER_SUPPLY_PROP_STATUS:
191        val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
192
193        if (!di->pdata->get_charger_online_status)
194            goto use_bat;
195        if ((di->pdata->get_charger_online_status)()) {
196            /*
197             * charger is definitively present
198             * we report our state in terms of what it says it
199             * is doing
200             */
201            if (!di->pdata->get_charger_active_status)
202                goto use_bat;
203
204            if ((di->pdata->get_charger_active_status)()) {
205                val->intval = POWER_SUPPLY_STATUS_CHARGING;
206                break;
207            }
208            val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
209            break;
210        }
211
212        /*
213         * platform provided definite indication of charger presence,
214         * and it is telling us it isn't there... but we are on so we
215         * must be running from battery --->
216         */
217
218        val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
219        break;
220
221use_bat:
222        /*
223         * either the charger is not connected, or the
224         * platform doesn't give info about charger, use battery state
225         * but... battery state can be out of date by 4 seconds or
226         * so... use the platform callbacks if possible.
227         */
228
229        /* no real activity on the battery */
230        if (di->regs.ai < 2) {
231            if (!di->regs.ttf)
232                val->intval = POWER_SUPPLY_STATUS_FULL;
233            else
234                val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
235            break;
236        }
237        /* power is actually going in or out... */
238        if (di->regs.flags < 0)
239            return di->regs.flags;
240        if (di->regs.flags & BQ27000_STATUS_CHGS)
241            val->intval = POWER_SUPPLY_STATUS_CHARGING;
242        else
243            val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
244        break;
245    case POWER_SUPPLY_PROP_HEALTH:
246        val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
247        /* Do we have accurate readings... */
248        if (di->regs.flags < 0)
249            return di->regs.flags;
250        if (di->regs.flags & BQ27000_STATUS_VDQ)
251            val->intval = POWER_SUPPLY_HEALTH_GOOD;
252        break;
253    case POWER_SUPPLY_PROP_VOLTAGE_NOW:
254        if (di->regs.volt < 0)
255            return di->regs.volt;
256        /* mV -> uV */
257        val->intval = di->regs.volt * 1000;
258        break;
259    case POWER_SUPPLY_PROP_CURRENT_NOW:
260        if (di->regs.flags < 0)
261            return di->regs.flags;
262        if (di->regs.flags & BQ27000_STATUS_CHGS)
263            n = -NANOVOLTS_UNIT;
264        else
265            n = NANOVOLTS_UNIT;
266        if (di->regs.ai < 0)
267            return di->regs.ai;
268        val->intval = (di->regs.ai * n) / di->pdata->rsense_mohms;
269        break;
270    case POWER_SUPPLY_PROP_CHARGE_FULL:
271        if (di->regs.lmd < 0)
272            return di->regs.lmd;
273        val->intval = (di->regs.lmd * 3570) / di->pdata->rsense_mohms;
274        break;
275    case POWER_SUPPLY_PROP_TEMP:
276        if (di->regs.temp < 0)
277            return di->regs.temp;
278        /* K (in 0.25K units) is 273.15 up from C (in 0.1C)*/
279        /* 10926 = 27315 * 4 / 10 */
280        val->intval = (((long)di->regs.temp * 10l) - 10926) / 4;
281        break;
282    case POWER_SUPPLY_PROP_TECHNOLOGY:
283        val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
284        break;
285    case POWER_SUPPLY_PROP_CAPACITY:
286        val->intval = di->regs.rsoc;
287        if (val->intval < 0)
288            return val->intval;
289        break;
290    case POWER_SUPPLY_PROP_PRESENT:
291        val->intval = !(di->regs.rsoc < 0);
292        break;
293    case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
294        if (di->regs.tte < 0)
295            return di->regs.tte;
296        val->intval = 60 * di->regs.tte;
297        break;
298    case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
299        if (di->regs.ttf < 0)
300            return di->regs.ttf;
301        val->intval = 60 * di->regs.ttf;
302        break;
303    case POWER_SUPPLY_PROP_ONLINE:
304        if (di->pdata->get_charger_online_status)
305            val->intval = (di->pdata->get_charger_online_status)();
306        else
307            return -EINVAL;
308        break;
309    default:
310        return -EINVAL;
311    }
312
313    return 0;
314}
315
316static void bq27000_battery_work(struct work_struct *work)
317{
318    struct bq27000_device_info *di =
319        container_of(work, struct bq27000_device_info, work.work);
320
321    if ((di->pdata->hdq_initialized)()) {
322        struct bq27000_bat_regs regs;
323
324        regs.ai = hdq_read16(di, BQ27000_AI_L);
325        regs.flags = (di->pdata->hdq_read)(BQ27000_FLAGS);
326        regs.lmd = hdq_read16(di, BQ27000_LMD_L);
327        regs.rsoc = (di->pdata->hdq_read)(BQ27000_RSOC);
328        regs.temp = hdq_read16(di, BQ27000_TEMP_L);
329        regs.tte = hdq_read16(di, BQ27000_TTE_L);
330        regs.ttf = hdq_read16(di, BQ27000_TTF_L);
331        regs.volt = hdq_read16(di, BQ27000_VOLT_L);
332
333        if (memcmp (&regs, &di->regs, sizeof(regs)) != 0) {
334            di->regs = regs;
335            power_supply_changed(&di->bat);
336        }
337    }
338
339    if (!schedule_delayed_work(&di->work, cache_time))
340        dev_err(di->dev, "battery service reschedule failed\n");
341}
342
343static enum power_supply_property bq27000_battery_props[] = {
344    POWER_SUPPLY_PROP_STATUS,
345    POWER_SUPPLY_PROP_HEALTH,
346    POWER_SUPPLY_PROP_VOLTAGE_NOW,
347    POWER_SUPPLY_PROP_CURRENT_NOW,
348    POWER_SUPPLY_PROP_CHARGE_FULL,
349    POWER_SUPPLY_PROP_TEMP,
350    POWER_SUPPLY_PROP_TECHNOLOGY,
351    POWER_SUPPLY_PROP_PRESENT,
352    POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
353    POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
354    POWER_SUPPLY_PROP_CAPACITY,
355    POWER_SUPPLY_PROP_ONLINE
356};
357
358static int bq27000_battery_probe(struct platform_device *pdev)
359{
360    int retval = 0;
361    struct bq27000_device_info *di;
362    struct bq27000_platform_data *pdata;
363
364    dev_info(&pdev->dev, "BQ27000 Battery Driver (C) 2008 Openmoko, Inc\n");
365
366    di = kzalloc(sizeof(*di), GFP_KERNEL);
367    if (!di) {
368        retval = -ENOMEM;
369        goto di_alloc_failed;
370    }
371
372    platform_set_drvdata(pdev, di);
373
374    pdata = pdev->dev.platform_data;
375    di->dev = &pdev->dev;
376    /* di->w1_dev = pdev->dev.parent; */
377    di->bat.name = pdata->name;
378    di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
379    di->bat.properties = bq27000_battery_props;
380    di->bat.num_properties = ARRAY_SIZE(bq27000_battery_props);
381    di->bat.get_property = bq27000_battery_get_property;
382    di->bat.external_power_changed =
383                  bq27000_battery_external_power_changed;
384    di->bat.use_for_apm = 1;
385    di->pdata = pdata;
386
387    retval = power_supply_register(&pdev->dev, &di->bat);
388    if (retval) {
389        dev_err(di->dev, "failed to register battery\n");
390        goto batt_failed;
391    }
392
393    INIT_DELAYED_WORK(&di->work, bq27000_battery_work);
394
395    if (!schedule_delayed_work(&di->work, 0))
396        dev_err(di->dev, "failed to schedule bq27000_battery_work\n");
397
398    return 0;
399
400batt_failed:
401    kfree(di);
402di_alloc_failed:
403    return retval;
404}
405
406static int bq27000_battery_remove(struct platform_device *pdev)
407{
408    struct bq27000_device_info *di = platform_get_drvdata(pdev);
409
410    cancel_delayed_work(&di->work);
411
412    power_supply_unregister(&di->bat);
413
414    return 0;
415}
416
417void bq27000_charging_state_change(struct platform_device *pdev)
418{
419    struct bq27000_device_info *di = platform_get_drvdata(pdev);
420
421    if (!di)
422        return;
423}
424EXPORT_SYMBOL_GPL(bq27000_charging_state_change);
425
426#ifdef CONFIG_PM
427
428static int bq27000_battery_suspend(struct platform_device *pdev,
429                  pm_message_t state)
430{
431    struct bq27000_device_info *di = platform_get_drvdata(pdev);
432
433    cancel_delayed_work(&di->work);
434    return 0;
435}
436
437static int bq27000_battery_resume(struct platform_device *pdev)
438{
439    struct bq27000_device_info *di = platform_get_drvdata(pdev);
440
441    schedule_delayed_work(&di->work, 0);
442    return 0;
443}
444
445#else
446
447#define bq27000_battery_suspend NULL
448#define bq27000_battery_resume NULL
449
450#endif /* CONFIG_PM */
451
452static struct platform_driver bq27000_battery_driver = {
453    .driver = {
454        .name = "bq27000-battery",
455    },
456    .probe = bq27000_battery_probe,
457    .remove = bq27000_battery_remove,
458    .suspend = bq27000_battery_suspend,
459    .resume = bq27000_battery_resume,
460};
461
462static int __init bq27000_battery_init(void)
463{
464    return platform_driver_register(&bq27000_battery_driver);
465}
466
467static void __exit bq27000_battery_exit(void)
468{
469    platform_driver_unregister(&bq27000_battery_driver);
470}
471
472module_init(bq27000_battery_init);
473module_exit(bq27000_battery_exit);
474
475MODULE_LICENSE("GPL");
476MODULE_AUTHOR("Andy Green <andy@openmoko.com>");
477MODULE_DESCRIPTION("bq27000 battery driver");
478

Archive Download this file



interactive