aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/s3c24xx/patches-2.6.26/0153-introduce-charging-led-behaviour.patch.patch
blob: 6f510ed0cca5d45f55d208e4fd12f66113256f8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
From 86bec3382cd1694a3b5cd6d8796cfcbdbe5ca5ff Mon Sep 17 00:00:00 2001
From: Andy Green <andy@openmoko.com>
Date: Fri, 25 Jul 2008 23:06:11 +0100
Subject: [PATCH] introduce-charging-led-behaviour.patch

Creates a new behaviour requested by Will that the red LED on GTA02
is lit during battery charging.and goes out when the battery is full.

This is done by leveraging the PMU interrupts, but in one scenario
there is no interrupt that occurs, when the battery is replaced after
being removed with the USB power in all the while.  So a sleepy work
function is started under those circumstances to watch for battery
reinsertion or USB cable pull.

100mA limit was not being observed under some conditions so this was
fixed and tested with a USB cable with D+/D- disconnected.  1A
charger behaviour was also tested.

Showing the charging action exposes some inconsistency in pcf50633
charging action.  If your battery is nearly full, it will keep
charging it at decreasing current even after it thinks it is at
100% capacity for a long while.  But if you pull that same battery
and re-insert it, the charger state machine in pcf50633 believe it is
full and won't charge it.

Signed-off-by: Andy Green <andy@openmoko.com>
---
 arch/arm/mach-s3c2440/mach-gta02.c |    8 ++
 drivers/i2c/chips/pcf50633.c       |  212 ++++++++++++++++++++++++++++++++++--
 include/linux/pcf506xx.h           |    2 +
 3 files changed, 214 insertions(+), 8 deletions(-)

diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
index 601f7bc..d7882ea 100644
--- a/arch/arm/mach-s3c2440/mach-gta02.c
+++ b/arch/arm/mach-s3c2440/mach-gta02.c
@@ -437,6 +437,14 @@ static int pmu_callback(struct device *dev, unsigned int feature,
 		case PMU_EVT_USB_REMOVE:
 			pcf50633_charge_enable(pcf50633_global, 0);
 			break;
+		case PMU_EVT_CHARGER_IDLE:
+			/* printk(KERN_ERR"PMU_EVT_CHARGER_IDLE\n"); */
+			neo1973_gpb_setpin(GTA02_GPIO_AUX_LED, 0);
+			break;
+		case PMU_EVT_CHARGER_ACTIVE:
+			/* printk(KERN_ERR"PMU_EVT_CHARGER_ACTIVE\n"); */
+			neo1973_gpb_setpin(GTA02_GPIO_AUX_LED, 1);
+			break;
 		default:
 			break;
 		}
diff --git a/drivers/i2c/chips/pcf50633.c b/drivers/i2c/chips/pcf50633.c
index 8ba81d2..38cabd2 100644
--- a/drivers/i2c/chips/pcf50633.c
+++ b/drivers/i2c/chips/pcf50633.c
@@ -47,6 +47,7 @@
 #include <linux/platform_device.h>
 #include <linux/pcf50633.h>
 #include <linux/apm-emulation.h>
+#include <linux/jiffies.h>
 
 #include <asm/mach-types.h>
 #include <asm/arch/gta02.h>
@@ -121,8 +122,23 @@ struct pcf50633_data {
 	int onkey_seconds;
 	int irq;
 	int have_been_suspended;
+	int usb_removal_count;
 	unsigned char pcfirq_resume[5];
 
+	/* if he pulls battery while charging, we notice that and correctly
+	 * report that the charger is idle.  But there is no interrupt that
+	 * fires if he puts a battery back in and charging resumes.  So when
+	 * the battery is pulled, we run this work function looking for
+	 * either charger resumption or USB cable pull
+	 */
+	struct mutex working_lock_nobat;
+	struct work_struct work_nobat;
+	int working_nobat;
+	int usb_removal_count_nobat;
+	int jiffies_last_bat_ins;
+
+	int last_curlim_set;
+
 	int coldplug_done; /* cleared by probe, set by first work service */
 	int flag_bat_voltage_read; /* ipc to /sys batt voltage read func */
 
@@ -259,6 +275,8 @@ static u_int16_t async_adc_complete(struct pcf50633_data *pcf)
 			(__reg_read(pcf, PCF50633_REG_ADCS3) &
 						  PCF50633_ADCS3_ADCDAT1L_MASK);
 
+	DEBUGPC("adc result = %d\n", ret);
+
 	return ret;
 }
 
@@ -512,8 +530,7 @@ static void configure_pmu_for_charger(struct pcf50633_data *pcf,
 {
 	switch (type) {
 	case CHARGER_TYPE_NONE:
-		__reg_write(pcf, PCF50633_REG_MBCC7,
-						    PCF50633_MBCC7_USB_SUSPEND);
+		pcf50633_usb_curlim_set(pcf, 0);
 		break;
 	/*
 	 * the PCF50633 has a feature that it will supply only excess current
@@ -521,10 +538,19 @@ static void configure_pmu_for_charger(struct pcf50633_data *pcf,
 	 * 500mA setting is "up to 500mA" according to that.
 	 */
 	case CHARGER_TYPE_HOSTUSB:
-		__reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_500mA);
+		/* USB subsystem should call pcf50633_usb_curlim_set to set
+		 * what was negotiated with the host when it is enumerated
+		 * successfully.  If we get called again after a good
+		 * negotiation, we keep what was negotiated.  (Removal of
+		 * USB plug destroys pcf->last_curlim_set to 0)
+		 */
+		if (pcf->last_curlim_set > 100)
+			pcf50633_usb_curlim_set(pcf, pcf->last_curlim_set);
+		else
+			pcf50633_usb_curlim_set(pcf, 100);
 		break;
 	case CHARGER_TYPE_1A:
-		__reg_write(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_1000mA);
+		pcf50633_usb_curlim_set(pcf, 1000);
 		/*
 		 * stop GPO / EN_HOSTUSB power driving out on the same
 		 * USB power pins we have a 1A charger on right now!
@@ -536,6 +562,12 @@ static void configure_pmu_for_charger(struct pcf50633_data *pcf,
 						 PCF50633_REG_GPIO1CFG) & 0xf0);
 		break;
 	}
+
+	/* max out USB fast charge current -- actual current drawn is
+	 * additionally limited by USB limit so no worries
+	 */
+	__reg_write(pcf, PCF50633_REG_MBCC5, 0xff);
+
 }
 
 static void trigger_next_adc_job_if_any(struct pcf50633_data *pcf)
@@ -562,6 +594,49 @@ static void add_request_to_adc_queue(struct pcf50633_data *pcf,
 		trigger_next_adc_job_if_any(pcf);
 }
 
+/* we are run when we see a NOBAT situation, because there is no interrupt
+ * source in pcf50633 that triggers on resuming charging.  It watches to see
+ * if charging resumes, it reassesses the charging source if it does.  If the
+ * USB power disappears, it is also a sign there must be a battery and it is
+ * NOT being charged, so it exits since the next move must be USB insertion for
+ * change of charger state
+ */
+
+static void pcf50633_work_nobat(struct work_struct *work)
+{
+	struct pcf50633_data *pcf =
+			container_of(work, struct pcf50633_data, work_nobat);
+
+	mutex_lock(&pcf->working_lock_nobat);
+	pcf->working_nobat = 1;
+	mutex_unlock(&pcf->working_lock_nobat);
+
+	while (1) {
+		msleep(1000);
+
+		/* there's a battery in there now? */
+		if (reg_read(pcf, PCF50633_REG_MBCS3) & 0x40) {
+
+			pcf->jiffies_last_bat_ins = jiffies;
+
+			/* figure out our charging stance */
+			add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
+						     PCF50633_ADCC1_AVERAGE_16);
+			goto bail;
+		}
+
+		/* he pulled USB cable since we were started?  exit then */
+		if (pcf->usb_removal_count_nobat != pcf->usb_removal_count)
+			goto bail;
+	}
+
+bail:
+	mutex_lock(&pcf->working_lock_nobat);
+	pcf->working_nobat = 0;
+	mutex_unlock(&pcf->working_lock_nobat);
+}
+
+
 static void pcf50633_work(struct work_struct *work)
 {
 	struct pcf50633_data *pcf =
@@ -674,20 +749,29 @@ static void pcf50633_work(struct work_struct *work)
 		if (pcf->pdata->cb)
 			pcf->pdata->cb(&pcf->client.dev,
 				       PCF50633_FEAT_MBC, PMU_EVT_USB_INSERT);
+		msleep(500); /* debounce, allow to see any ID resistor */
 		/* completion irq will figure out our charging stance */
 		add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
 				     PCF50633_ADCC1_AVERAGE_16);
 	}
 	if (pcfirq[0] & PCF50633_INT1_USBREM) {
 		DEBUGPC("USBREM ");
+
+		pcf->usb_removal_count++;
+
 		/* only deal if we had understood it was in */
 		if (pcf->flags & PCF50633_F_USB_PRESENT) {
 			input_report_key(pcf->input_dev, KEY_POWER2, 0);
 			apm_queue_event(APM_POWER_STATUS_CHANGE);
 			pcf->flags &= ~PCF50633_F_USB_PRESENT;
+
 			if (pcf->pdata->cb)
 				pcf->pdata->cb(&pcf->client.dev,
 					PCF50633_FEAT_MBC, PMU_EVT_USB_REMOVE);
+
+			/* destroy any memory of grant of power from host */
+			pcf->last_curlim_set = 0;
+
 			/* completion irq will figure out our charging stance */
 			add_request_to_adc_queue(pcf, PCF50633_ADCC1_MUX_ADCIN1,
 					PCF50633_ADCC1_AVERAGE_16);
@@ -759,6 +843,33 @@ static void pcf50633_work(struct work_struct *work)
 
 	if (pcfirq[2] & PCF50633_INT3_BATFULL) {
 		DEBUGPC("BATFULL ");
+
+		/* the problem is, we get a false BATFULL if we inserted battery
+		 * while USB powered.  Defeat BATFULL if we recently inserted
+		 * battery
+		 */
+
+		if ((jiffies - pcf->jiffies_last_bat_ins) < (HZ * 2)) {
+
+			DEBUGPC("*** Ignoring BATFULL ***\n");
+
+			ret = reg_read(pcf, PCF50633_REG_MBCC7) &
+					PCF56033_MBCC7_USB_MASK;
+
+
+			reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
+					 PCF56033_MBCC7_USB_MASK,
+					 PCF50633_MBCC7_USB_SUSPEND);
+
+			reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
+					 PCF56033_MBCC7_USB_MASK,
+					 ret);
+		} else {
+			if (pcf->pdata->cb)
+				pcf->pdata->cb(&pcf->client.dev,
+				       PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE);
+		}
+
 		/* FIXME: signal this to userspace */
 	}
 	if (pcfirq[2] & PCF50633_INT3_CHGHALT) {
@@ -796,8 +907,7 @@ static void pcf50633_work(struct work_struct *work)
 
 		switch (pcf->adc_queue_mux[tail]) {
 		case PCF50633_ADCC1_MUX_BATSNS_RES: /* battery voltage */
-			pcf->flag_bat_voltage_read =
-						  async_adc_complete(pcf);
+			pcf->flag_bat_voltage_read = async_adc_complete(pcf);
 			break;
 		case PCF50633_ADCC1_MUX_ADCIN1: /* charger type */
 			pcf->charger_adc_result_raw = async_adc_complete(pcf);
@@ -829,8 +939,37 @@ static void pcf50633_work(struct work_struct *work)
 		    (PCF50633_MBCS1_USBPRES | PCF50633_MBCS1_USBOK)) {
 			/*
 			 * hey no need to freak out, we have some kind of
-			 * valid charger power
+			 * valid charger power to keep us going -- but note that
+			 * we are not actually charging anything
+			 */
+			if (pcf->pdata->cb)
+				pcf->pdata->cb(&pcf->client.dev,
+				       PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE);
+
+			reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+					PCF50633_MBCC1_RESUME,
+					PCF50633_MBCC1_RESUME);
+	
+			/*
+			 * Well, we are not charging anything right this second
+			 * ... however in the next ~30s before we get the next
+			 * NOBAT, he might insert a battery.  So we schedule a
+			 * work function checking to see if
+			 * we started charging something during that time.
+			 * USB removal as well as charging terminates the work
+			 * function so we can't get terminally confused
 			 */
+			mutex_lock(&pcf->working_lock_nobat);
+			if (!pcf->working_nobat) {
+				pcf->usb_removal_count_nobat =
+							pcf->usb_removal_count;
+
+				if (!schedule_work(&pcf->work_nobat))
+					DEBUGPC("failed to schedule nobat\n");
+			}
+			mutex_unlock(&pcf->working_lock_nobat);
+
+
 			DEBUGPC("(NO)BAT ");
 		} else {
 			/* Really low battery voltage, we have 8 seconds left */
@@ -1063,10 +1202,13 @@ void pcf50633_usb_curlim_set(struct pcf50633_data *pcf, int ma)
 {
 	u_int8_t bits;
 
+	pcf->last_curlim_set = ma;
+
 	dev_dbg(&pcf->client.dev, "setting usb current limit to %d ma", ma);
 
-	if (ma >= 1000)
+	if (ma >= 1000) {
 		bits = PCF50633_MBCC7_USB_1000mA;
+	}
 	else if (ma >= 500)
 		bits = PCF50633_MBCC7_USB_500mA;
 	else if (ma >= 100)
@@ -1074,8 +1216,40 @@ void pcf50633_usb_curlim_set(struct pcf50633_data *pcf, int ma)
 	else
 		bits = PCF50633_MBCC7_USB_SUSPEND;
 
+	DEBUGPC("pcf50633_usb_curlim_set -> %dmA\n", ma);
+
+	if (!pcf->pdata->cb)
+		goto set_it;
+
+	switch (bits) {
+	case PCF50633_MBCC7_USB_100mA:
+	case PCF50633_MBCC7_USB_SUSPEND:
+		 /* no charging is gonna be happening */
+		pcf->pdata->cb(&pcf->client.dev,
+			       PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE);
+		break;
+	default: /* right charging context that if there is power, we charge */
+		if (pcf->flags & PCF50633_F_USB_PRESENT)
+			pcf->pdata->cb(&pcf->client.dev,
+			       PCF50633_FEAT_MBC, PMU_EVT_CHARGER_ACTIVE);
+		break;
+	}
+
+set_it:
 	reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, PCF56033_MBCC7_USB_MASK,
 			 bits);
+
+	/* clear batfull */
+	reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+				PCF50633_MBCC1_AUTORES,
+				0);
+	reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+				PCF50633_MBCC1_RESUME,
+				PCF50633_MBCC1_RESUME);
+	reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+				PCF50633_MBCC1_AUTORES,
+				PCF50633_MBCC1_AUTORES);
+
 }
 EXPORT_SYMBOL_GPL(pcf50633_usb_curlim_set);
 
@@ -1105,16 +1279,36 @@ static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, NULL);
 void pcf50633_charge_enable(struct pcf50633_data *pcf, int on)
 {
 	u_int8_t bits;
+	u_int8_t usblim;
 
 	if (!(pcf->pdata->used_features & PCF50633_FEAT_MBC))
 		return;
 
+	DEBUGPC("pcf50633_charge_enable %d\n", on);
+
 	if (on) {
 		pcf->flags |= PCF50633_F_CHG_ENABLED;
 		bits = PCF50633_MBCC1_CHGENA;
+		usblim = reg_read(pcf, PCF50633_REG_MBCC7) &
+							PCF56033_MBCC7_USB_MASK;
+		switch (usblim) {
+		case PCF50633_MBCC7_USB_1000mA:
+		case PCF50633_MBCC7_USB_500mA:
+			if (pcf->flags & PCF50633_F_USB_PRESENT)
+				if (pcf->pdata->cb)
+					pcf->pdata->cb(&pcf->client.dev,
+						       PCF50633_FEAT_MBC,
+						       PMU_EVT_CHARGER_ACTIVE);
+			break;
+		default:
+			break;
+		}
 	} else {
 		pcf->flags &= ~PCF50633_F_CHG_ENABLED;
 		bits = 0;
+		if (pcf->pdata->cb)
+			pcf->pdata->cb(&pcf->client.dev,
+				       PCF50633_FEAT_MBC, PMU_EVT_CHARGER_IDLE);
 	}
 	reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, PCF50633_MBCC1_CHGENA,
 			 bits);
@@ -1703,7 +1897,9 @@ static int pcf50633_detect(struct i2c_adapter *adapter, int address, int kind)
 
 	mutex_init(&data->lock);
 	mutex_init(&data->working_lock);
+	mutex_init(&data->working_lock_nobat);
 	INIT_WORK(&data->work, pcf50633_work);
+	INIT_WORK(&data->work_nobat, pcf50633_work_nobat);
 	data->irq = irq;
 	data->working = 0;
 	data->onkey_seconds = -1;
diff --git a/include/linux/pcf506xx.h b/include/linux/pcf506xx.h
index 33be73e..9069bd4 100644
--- a/include/linux/pcf506xx.h
+++ b/include/linux/pcf506xx.h
@@ -21,6 +21,8 @@ enum pmu_event {
 	PMU_EVT_USB_INSERT,
 	PMU_EVT_USB_REMOVE,
 #endif
+	PMU_EVT_CHARGER_ACTIVE,
+	PMU_EVT_CHARGER_IDLE,
 	__NUM_PMU_EVTS
 };
 
-- 
1.5.6.3