summaryrefslogtreecommitdiff
path: root/target/linux/s3c24xx/patches-2.6.24/1188-fix-pcf50633-usb-curlim-workqueue-migration.patch.patch
blob: 72acf66f92f5a09c8c3e84311d4c1a21cc2c5fd0 (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
From 553cc686ad357fc169a95733c51f7ae3466bb248 Mon Sep 17 00:00:00 2001
From: Andy Green <andy@openmoko.com>
Date: Wed, 2 Jul 2008 22:40:21 +0100
Subject: [PATCH] fix-pcf50633-usb-curlim-workqueue-migration.patch

pcf50633 needs to take responsibility for managing current limit
changes asycnhrnously, ie, from USB stack enumeration.  It's a feature of
pcf50633 not mach-gta02.c, and we can do better with taking care about
keeping it from firing at a bad time in there too.

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

diff --git a/arch/arm/mach-s3c2440/mach-gta02.c b/arch/arm/mach-s3c2440/mach-gta02.c
index 64d275d..accdbc5 100644
--- a/arch/arm/mach-s3c2440/mach-gta02.c
+++ b/arch/arm/mach-s3c2440/mach-gta02.c
@@ -813,29 +813,11 @@ static void gta02_udc_command(enum s3c2410_udc_cmd_e cmd)
 	}
 }
 
-/* use a work queue, since I2C API inherently schedules
- * and we get called in hardirq context from UDC driver */
-
-struct vbus_draw {
-	struct work_struct work;
-	int ma;
-};
-static struct vbus_draw gta02_udc_vbus_drawer;
-
-static void __gta02_udc_vbus_draw(struct work_struct *work)
-{
-	if (!pcf50633_global) {
-		printk(KERN_ERR  "pcf50633 not initialized yet, can't change "
-		       "vbus_draw\n");
-		return;
-	}
-	pcf50633_usb_curlim_set(pcf50633_global, gta02_udc_vbus_drawer.ma);
-}
+/* get PMU to set USB current limit accordingly */
 
 static void gta02_udc_vbus_draw(unsigned int ma)
 {
-	gta02_udc_vbus_drawer.ma = ma;
-	schedule_work(&gta02_udc_vbus_drawer.work);
+	pcf50633_notify_usb_current_limit_change(pcf50633_global, ma);
 }
 
 static struct s3c2410_udc_mach_info gta02_udc_cfg = {
@@ -1478,7 +1460,6 @@ static void __init gta02_machine_init(void)
 	s3c2410_gpio_setpin(S3C2410_GPD13, 1);
 	s3c2410_gpio_cfgpin(S3C2410_GPD13, S3C2410_GPIO_OUTPUT);
 
-	INIT_WORK(&gta02_udc_vbus_drawer.work, __gta02_udc_vbus_draw);
 	s3c24xx_udc_set_platdata(&gta02_udc_cfg);
 	set_s3c2410ts_info(&gta02_ts_cfg);
 
diff --git a/drivers/i2c/chips/pcf50633.c b/drivers/i2c/chips/pcf50633.c
index 96857a3..e53bec0 100644
--- a/drivers/i2c/chips/pcf50633.c
+++ b/drivers/i2c/chips/pcf50633.c
@@ -125,6 +125,7 @@ struct pcf50633_data {
 	int have_been_suspended;
 	int usb_removal_count;
 	unsigned char pcfirq_resume[5];
+	int probe_completed;
 
 	/* if he pulls battery while charging, we notice that and correctly
 	 * report that the charger is idle.  But there is no interrupt that
@@ -138,6 +139,12 @@ struct pcf50633_data {
 	int usb_removal_count_nobat;
 	int jiffies_last_bat_ins;
 
+	/* current limit notification handler stuff */
+	struct mutex working_lock_usb_curlimit;
+	struct work_struct work_usb_curlimit;
+	int pending_curlimit;
+	int usb_removal_count_usb_curlimit;
+
 	int last_curlim_set;
 
 	int coldplug_done; /* cleared by probe, set by first work service */
@@ -603,6 +610,92 @@ static void add_request_to_adc_queue(struct pcf50633_data *pcf,
 		trigger_next_adc_job_if_any(pcf);
 }
 
+/*
+ * we get run to handle servicing the async notification from USB stack that
+ * we got enumerated and allowed to draw a particular amount of current
+ */
+
+static void pcf50633_work_usbcurlim(struct work_struct *work)
+{
+	struct pcf50633_data *pcf =
+		    container_of(work, struct pcf50633_data, work_usb_curlimit);
+
+	mutex_lock(&pcf->working_lock_usb_curlimit);
+
+	dev_info(&pcf->client.dev, "pcf50633_work_usbcurlim\n");
+
+	if (!pcf->probe_completed)
+		goto reschedule;
+
+	/* we got a notification from USB stack before we completed resume...
+	 * that can only make trouble, reschedule for a retry
+	 */
+	if (pcf->have_been_suspended && (pcf->have_been_suspended < 3))
+		goto reschedule;
+
+	/*
+	 * did he pull USB before we managed to set the limit?
+	 */
+	if (pcf->usb_removal_count_usb_curlimit != pcf->usb_removal_count)
+		goto bail;
+
+	/* OK let's set the requested limit and finish */
+
+	dev_info(&pcf->client.dev, "pcf50633_work_usbcurlim setting %dmA\n",
+							 pcf->pending_curlimit);
+	pcf50633_usb_curlim_set(pcf, pcf->pending_curlimit);
+
+bail:
+	mutex_unlock(&pcf->working_lock_usb_curlimit);
+	return;
+
+reschedule:
+	dev_info(&pcf->client.dev, "pcf50633_work_usbcurlim rescheduling\n");
+	if (!schedule_work(&pcf->work_usb_curlimit))
+		dev_err(&pcf->client.dev, "work item may be lost\n");
+
+	mutex_unlock(&pcf->working_lock_usb_curlimit);
+	/* don't spew, delaying whatever else is happening */
+	msleep(1);
+}
+
+
+/* this is an export to allow machine to set USB current limit according to
+ * notifications of USB stack about enumeration state.  We spawn a work
+ * function to handle the actual setting, because suspend / resume and such
+ * can be in a bad state since this gets called externally asychronous to
+ * anything else going on in pcf50633.
+ */
+
+int pcf50633_notify_usb_current_limit_change(struct pcf50633_data *pcf,
+								unsigned int ma)
+{
+	/* can happen if he calls with pcf50633_global before probe
+	 * have to bail with error since we can't even schedule the work
+	 */
+	if (!pcf) {
+		dev_err(&pcf->client.dev,
+			"pcf50633_notify_usb_current_limit_change "
+			"called with NULL pcf\n");
+		return -EBUSY;
+	}
+
+	dev_info(&pcf->client.dev,
+		 "pcf50633_notify_usb_current_limit_change %dmA\n", ma);
+
+	/* prepare to detect USB power removal before we complete */
+	pcf->usb_removal_count_usb_curlimit = pcf->usb_removal_count;
+
+	pcf->pending_curlimit = ma;
+
+	if (!schedule_work(&pcf->work_usb_curlimit))
+		dev_err(&pcf->client.dev, "work item may be lost\n");
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50633_notify_usb_current_limit_change);
+
+
 /* 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
@@ -1967,8 +2060,10 @@ 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);
+	mutex_init(&data->working_lock_usb_curlimit);
 	INIT_WORK(&data->work, pcf50633_work);
 	INIT_WORK(&data->work_nobat, pcf50633_work_nobat);
+	INIT_WORK(&data->work_usb_curlimit, pcf50633_work_usbcurlim);
 	data->irq = irq;
 	data->working = 0;
 	data->onkey_seconds = -1;
@@ -2068,15 +2163,15 @@ static int pcf50633_detect(struct i2c_adapter *adapter, int address, int kind)
 		backlight_update_status(data->backlight);
 	}
 
+	data->probe_completed = 1;
 
 	if (machine_is_neo1973_gta02()) {
 		gta01_pm_gps_dev.dev.parent = &new_client->dev;
 		gta01_pm_bt_dev.dev.parent = &new_client->dev;
 		platform_device_register(&gta01_pm_bt_dev);
 		platform_device_register(&gta01_pm_gps_dev);
-	} else {
+	} else
 		apm_get_power_status = pcf50633_get_power_status;
-	}
 
 	return 0;
 exit_rtc:
diff --git a/include/linux/pcf50633.h b/include/linux/pcf50633.h
index 0522d92..fa1c7e8 100644
--- a/include/linux/pcf50633.h
+++ b/include/linux/pcf50633.h
@@ -129,6 +129,11 @@ extern void
 pcf50633_register_resume_dependency(struct pcf50633_data *pcf,
 					struct resume_dependency *dep);
 
+extern int
+pcf50633_notify_usb_current_limit_change(struct pcf50633_data *pcf,
+							       unsigned int ma);
+
+
 /* 0 = initialized and resumed and ready to roll, !=0 = either not
  * initialized or not resumed yet
  */
-- 
1.5.6.5