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