summaryrefslogtreecommitdiff
path: root/target/linux/s3c24xx/patches-2.6.26/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch
blob: 8f5550f62b888e321a9908344bd45f7430350e45 (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
From b4976aa135000ce1d4804bf9fbfa92280cd357e7 Mon Sep 17 00:00:00 2001
From: Mike Westerhof <mwester@dls.net>
Date: Sun, 10 Aug 2008 09:13:31 +0100
Subject: [PATCH] gta01-pcf50606-disable-irq-from-suspend-until-resume.patch

    This patch is the pcf50606 equivalent of the pcf50633 patch that
    disables interrupts from the chip until after resume is complete.
    In order to ensure no data is lost, the work function is called
    post-resume to process any pending interrupts.

    Most of the code was quite literally re-used from Andy Green's
    original patch.

Signed-off-by: Mike Westerhof <mwester@dls.net>
---
 drivers/i2c/chips/pcf50606.c |  148 ++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 142 insertions(+), 6 deletions(-)

diff --git a/drivers/i2c/chips/pcf50606.c b/drivers/i2c/chips/pcf50606.c
index c97cad3..19a9829 100644
--- a/drivers/i2c/chips/pcf50606.c
+++ b/drivers/i2c/chips/pcf50606.c
@@ -38,6 +38,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/workqueue.h>
+#include <linux/delay.h>
 #include <linux/rtc.h>
 #include <linux/bcd.h>
 #include <linux/watchdog.h>
@@ -95,6 +96,15 @@ enum close_state {
 	CLOSE_STATE_ALLOW = 0x2342,
 };
 
+enum pcf50606_suspend_states {
+	PCF50606_SS_RUNNING,
+	PCF50606_SS_STARTING_SUSPEND,
+	PCF50606_SS_COMPLETED_SUSPEND,
+	PCF50606_SS_RESUMING_BUT_NOT_US_YET,
+	PCF50606_SS_STARTING_RESUME,
+	PCF50606_SS_COMPLETED_RESUME,
+};
+
 struct pcf50606_data {
 	struct i2c_client client;
 	struct pcf50606_platform_data *pdata;
@@ -110,6 +120,7 @@ struct pcf50606_data {
 	int onkey_seconds;
 	int irq;
 	int coldplug_done;
+	enum pcf50606_suspend_states suspend_state;
 #ifdef CONFIG_PM
 	struct {
 		u_int8_t dcdc1, dcdc2;
@@ -160,6 +171,10 @@ static const u_int16_t ntc_table_10k_3370B[] = {
 static inline int __reg_write(struct pcf50606_data *pcf, u_int8_t reg,
 			      u_int8_t val)
 {
+	if (pcf->suspend_state == PCF50606_SS_COMPLETED_SUSPEND) {
+		dev_err(&pcf->client.dev, "__reg_write while suspended.\n");
+		dump_stack();
+	}
 	return i2c_smbus_write_byte_data(&pcf->client, reg, val);
 }
 
@@ -178,6 +193,10 @@ static inline int32_t __reg_read(struct pcf50606_data *pcf, u_int8_t reg)
 {
 	int32_t ret;
 
+	if (pcf->suspend_state == PCF50606_SS_COMPLETED_SUSPEND) {
+		dev_err(&pcf->client.dev, "__reg_read while suspended.\n");
+		dump_stack();
+	}
 	ret = i2c_smbus_read_byte_data(&pcf->client, reg);
 
 	return ret;
@@ -569,6 +588,48 @@ static void pcf50606_work(struct work_struct *work)
 
 	mutex_lock(&pcf->working_lock);
 	pcf->working = 1;
+
+	/* sanity */
+	if (!&pcf->client.dev)
+		goto bail;
+
+	/*
+	 * if we are presently suspending, we are not in a position to deal
+	 * with pcf50606 interrupts at all.
+	 *
+	 * Because we didn't clear the int pending registers, there will be
+	 * no edge / interrupt waiting for us when we wake.  But it is OK
+	 * because at the end of our resume, we call this workqueue function
+	 * gratuitously, clearing the pending register and re-enabling
+	 * servicing this interrupt.
+	 */
+
+	if ((pcf->suspend_state == PCF50606_SS_STARTING_SUSPEND) ||
+	    (pcf->suspend_state == PCF50606_SS_COMPLETED_SUSPEND))
+		goto bail;
+
+	/*
+	 * If we are inside suspend -> resume completion time we don't attempt
+	 * service until we have fully resumed.  Although we could talk to the
+	 * device as soon as I2C is up, the regs in the device which we might
+	 * choose to modify as part of the service action have not been
+	 * reloaded with their pre-suspend states yet.  Therefore we will
+	 * defer our service if we are called like that until our resume has
+	 * completed.
+	 *
+	 * This shouldn't happen any more because we disable servicing this
+	 * interrupt in suspend and don't re-enable it until resume is
+	 * completed.
+	 */
+
+	if (pcf->suspend_state &&
+		(pcf->suspend_state != PCF50606_SS_COMPLETED_RESUME))
+		goto reschedule;
+
+	/* this is the case early in resume! Sanity check! */
+	if (i2c_get_clientdata(&pcf->client) == NULL)
+		goto reschedule;
+
 	/*
 	 * p35 pcf50606 datasheet rev 2.2:
 	 * ''The system controller shall read all interrupt registers in
@@ -576,10 +637,27 @@ static void pcf50606_work(struct work_struct *work)
 	 * because if you don't INT# gets stuck asserted forever after a
 	 * while
 	 */
-	ret = i2c_smbus_read_i2c_block_data(&pcf->client, PCF50606_REG_INT1, 3,
-					    pcfirq);
-	if (ret != 3)
+	ret = i2c_smbus_read_i2c_block_data(&pcf->client, PCF50606_REG_INT1,
+					    sizeof(pcfirq), pcfirq);
+	if (ret != sizeof(pcfirq)) {
 		DEBUGPC("Oh crap PMU IRQ register read failed %d\n", ret);
+		/*
+		 * it shouldn't fail, we no longer attempt to use
+		 * I2C while it can be suspended.  But we don't have
+		 * much option but to retry if if it ever did fail,
+		 * because if we don't service the interrupt to clear
+		 * it, we will never see another PMU interrupt edge.
+		 */
+		goto reschedule;
+	}
+
+	/* hey did we just resume? (because we don't get here unless we are
+	 * running normally or the first call after resumption)
+	 *
+	 * pcf50606 resume is really really over now then.
+	 */
+	if (pcf->suspend_state != PCF50606_SS_RUNNING)
+		pcf->suspend_state = PCF50606_SS_RUNNING;
 
 	if (!pcf->coldplug_done) {
 		DEBUGPC("PMU Coldplug init\n");
@@ -814,10 +892,26 @@ static void pcf50606_work(struct work_struct *work)
 
 	DEBUGPC("\n");
 
+bail:
 	pcf->working = 0;
 	input_sync(pcf->input_dev);
 	put_device(&pcf->client.dev);
 	mutex_unlock(&pcf->working_lock);
+
+	return;
+
+reschedule:
+
+	if ((pcf->suspend_state != PCF50606_SS_STARTING_SUSPEND) &&
+	    (pcf->suspend_state != PCF50606_SS_COMPLETED_SUSPEND)) {
+		msleep(10);
+		dev_info(&pcf->client.dev, "rescheduling interrupt service\n");
+	}
+	if (!schedule_work(&pcf->work))
+		dev_err(&pcf->client.dev, "int service reschedule failed\n");
+
+	/* we don't put the device here, hold it for next time */
+	mutex_unlock(&pcf->working_lock);
 }
 
 static irqreturn_t pcf50606_irq(int irq, void *_pcf)
@@ -828,7 +922,7 @@ static irqreturn_t pcf50606_irq(int irq, void *_pcf)
 		irq, _pcf);
 	get_device(&pcf->client.dev);
 	if (!schedule_work(&pcf->work) && !pcf->working)
-		dev_dbg(&pcf->client.dev, "work item may be lost\n");
+		dev_err(&pcf->client.dev, "pcf irq work already queued.\n");
 
 	return IRQ_HANDLED;
 }
@@ -1881,12 +1975,27 @@ static int pcf50606_suspend(struct device *dev, pm_message_t state)
 	struct pcf50606_data *pcf = i2c_get_clientdata(client);
 	int i;
 
+	/* we suspend once (!) as late as possible in the suspend sequencing */
+
+	if ((state.event != PM_EVENT_SUSPEND) ||
+	    (pcf->suspend_state != PCF50606_SS_RUNNING))
+		return -EBUSY;
+
 	/* The general idea is to power down all unused power supplies,
 	 * and then mask all PCF50606 interrup sources but EXTONR, ONKEYF
 	 * and ALARM */
 
 	mutex_lock(&pcf->lock);
 
+	pcf->suspend_state = PCF50606_SS_STARTING_SUSPEND;
+
+	/* we are not going to service any further interrupts until we
+	 * resume.  If the IRQ workqueue is still pending in the background,
+	 * it will bail when it sees we set suspend state above.
+	 */
+
+	disable_irq(pcf->irq);
+
 	/* Save all registers that don't "survive" standby state */
 	pcf->standby_regs.dcdc1 = __reg_read(pcf, PCF50606_REG_DCDC1);
 	pcf->standby_regs.dcdc2 = __reg_read(pcf, PCF50606_REG_DCDC2);
@@ -1927,6 +2036,8 @@ static int pcf50606_suspend(struct device *dev, pm_message_t state)
 	__reg_write(pcf, PCF50606_REG_INT2M, ~INT2M_RESUMERS & 0xff);
 	__reg_write(pcf, PCF50606_REG_INT3M, ~INT3M_RESUMERS & 0xff);
 
+	pcf->suspend_state = PCF50606_SS_COMPLETED_SUSPEND;
+
 	mutex_unlock(&pcf->lock);
 
 	return 0;
@@ -1939,6 +2050,8 @@ static int pcf50606_resume(struct device *dev)
 
 	mutex_lock(&pcf->lock);
 
+	pcf->suspend_state = PCF50606_SS_STARTING_RESUME;
+
 	/* Resume all saved registers that don't "survive" standby state */
 	__reg_write(pcf, PCF50606_REG_INT1M, pcf->standby_regs.int1m);
 	__reg_write(pcf, PCF50606_REG_INT2M, pcf->standby_regs.int2m);
@@ -1957,10 +2070,17 @@ static int pcf50606_resume(struct device *dev)
 	__reg_write(pcf, PCF50606_REG_ADCC2, pcf->standby_regs.adcc2);
 	__reg_write(pcf, PCF50606_REG_PWMC1, pcf->standby_regs.pwmc1);
 
+	pcf->suspend_state = PCF50606_SS_COMPLETED_RESUME;
+
+	enable_irq(pcf->irq);
+
 	mutex_unlock(&pcf->lock);
 
-	/* Hack to fix the gta01 power button problem on resume */
-	pcf50606_irq(0, pcf);
+	/* Call PCF work function; this fixes an issue on the gta01 where
+	 * the power button "goes away" if it is used to wake the device.
+	 */
+	get_device(&pcf->client.dev);
+	pcf50606_work(&pcf->work);
 
 	return 0;
 }
@@ -1998,9 +2118,25 @@ static int pcf50606_plat_remove(struct platform_device *pdev)
 	return 0;
 }
 
+/* We have this purely to capture an early indication that we are coming out
+ * of suspend, before our device resume got called; async interrupt service is
+ * interested in this.
+ */
+
+static int pcf50606_plat_resume(struct platform_device *pdev)
+{
+	/* i2c_get_clientdata(to_i2c_client(&pdev->dev)) returns NULL at this
+	 * early resume time so we have to use pcf50606_global
+	 */
+	pcf50606_global->suspend_state = PCF50606_SS_RESUMING_BUT_NOT_US_YET;
+
+	return 0;
+}
+
 static struct platform_driver pcf50606_plat_driver = {
 	.probe	= pcf50606_plat_probe,
 	.remove	= pcf50606_plat_remove,
+	.resume_early = pcf50606_plat_resume,
 	.driver = {
 		.owner	= THIS_MODULE,
 		.name 	= "pcf50606",
-- 
1.5.6.3