change prefix for kernelpatchbase 2.6.26
[openwrt/staging/dedeckeh.git] / target / linux / s3c24xx / patches-2.6.26 / 1243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch
1 From b4976aa135000ce1d4804bf9fbfa92280cd357e7 Mon Sep 17 00:00:00 2001
2 From: Mike Westerhof <mwester@dls.net>
3 Date: Sun, 10 Aug 2008 09:13:31 +0100
4 Subject: [PATCH] gta01-pcf50606-disable-irq-from-suspend-until-resume.patch
5
6 This patch is the pcf50606 equivalent of the pcf50633 patch that
7 disables interrupts from the chip until after resume is complete.
8 In order to ensure no data is lost, the work function is called
9 post-resume to process any pending interrupts.
10
11 Most of the code was quite literally re-used from Andy Green's
12 original patch.
13
14 Signed-off-by: Mike Westerhof <mwester@dls.net>
15 ---
16 drivers/i2c/chips/pcf50606.c | 148 ++++++++++++++++++++++++++++++++++++++++--
17 1 files changed, 142 insertions(+), 6 deletions(-)
18
19 diff --git a/drivers/i2c/chips/pcf50606.c b/drivers/i2c/chips/pcf50606.c
20 index c97cad3..19a9829 100644
21 --- a/drivers/i2c/chips/pcf50606.c
22 +++ b/drivers/i2c/chips/pcf50606.c
23 @@ -38,6 +38,7 @@
24 #include <linux/interrupt.h>
25 #include <linux/irq.h>
26 #include <linux/workqueue.h>
27 +#include <linux/delay.h>
28 #include <linux/rtc.h>
29 #include <linux/bcd.h>
30 #include <linux/watchdog.h>
31 @@ -95,6 +96,15 @@ enum close_state {
32 CLOSE_STATE_ALLOW = 0x2342,
33 };
34
35 +enum pcf50606_suspend_states {
36 + PCF50606_SS_RUNNING,
37 + PCF50606_SS_STARTING_SUSPEND,
38 + PCF50606_SS_COMPLETED_SUSPEND,
39 + PCF50606_SS_RESUMING_BUT_NOT_US_YET,
40 + PCF50606_SS_STARTING_RESUME,
41 + PCF50606_SS_COMPLETED_RESUME,
42 +};
43 +
44 struct pcf50606_data {
45 struct i2c_client client;
46 struct pcf50606_platform_data *pdata;
47 @@ -110,6 +120,7 @@ struct pcf50606_data {
48 int onkey_seconds;
49 int irq;
50 int coldplug_done;
51 + enum pcf50606_suspend_states suspend_state;
52 #ifdef CONFIG_PM
53 struct {
54 u_int8_t dcdc1, dcdc2;
55 @@ -160,6 +171,10 @@ static const u_int16_t ntc_table_10k_3370B[] = {
56 static inline int __reg_write(struct pcf50606_data *pcf, u_int8_t reg,
57 u_int8_t val)
58 {
59 + if (pcf->suspend_state == PCF50606_SS_COMPLETED_SUSPEND) {
60 + dev_err(&pcf->client.dev, "__reg_write while suspended.\n");
61 + dump_stack();
62 + }
63 return i2c_smbus_write_byte_data(&pcf->client, reg, val);
64 }
65
66 @@ -178,6 +193,10 @@ static inline int32_t __reg_read(struct pcf50606_data *pcf, u_int8_t reg)
67 {
68 int32_t ret;
69
70 + if (pcf->suspend_state == PCF50606_SS_COMPLETED_SUSPEND) {
71 + dev_err(&pcf->client.dev, "__reg_read while suspended.\n");
72 + dump_stack();
73 + }
74 ret = i2c_smbus_read_byte_data(&pcf->client, reg);
75
76 return ret;
77 @@ -569,6 +588,48 @@ static void pcf50606_work(struct work_struct *work)
78
79 mutex_lock(&pcf->working_lock);
80 pcf->working = 1;
81 +
82 + /* sanity */
83 + if (!&pcf->client.dev)
84 + goto bail;
85 +
86 + /*
87 + * if we are presently suspending, we are not in a position to deal
88 + * with pcf50606 interrupts at all.
89 + *
90 + * Because we didn't clear the int pending registers, there will be
91 + * no edge / interrupt waiting for us when we wake. But it is OK
92 + * because at the end of our resume, we call this workqueue function
93 + * gratuitously, clearing the pending register and re-enabling
94 + * servicing this interrupt.
95 + */
96 +
97 + if ((pcf->suspend_state == PCF50606_SS_STARTING_SUSPEND) ||
98 + (pcf->suspend_state == PCF50606_SS_COMPLETED_SUSPEND))
99 + goto bail;
100 +
101 + /*
102 + * If we are inside suspend -> resume completion time we don't attempt
103 + * service until we have fully resumed. Although we could talk to the
104 + * device as soon as I2C is up, the regs in the device which we might
105 + * choose to modify as part of the service action have not been
106 + * reloaded with their pre-suspend states yet. Therefore we will
107 + * defer our service if we are called like that until our resume has
108 + * completed.
109 + *
110 + * This shouldn't happen any more because we disable servicing this
111 + * interrupt in suspend and don't re-enable it until resume is
112 + * completed.
113 + */
114 +
115 + if (pcf->suspend_state &&
116 + (pcf->suspend_state != PCF50606_SS_COMPLETED_RESUME))
117 + goto reschedule;
118 +
119 + /* this is the case early in resume! Sanity check! */
120 + if (i2c_get_clientdata(&pcf->client) == NULL)
121 + goto reschedule;
122 +
123 /*
124 * p35 pcf50606 datasheet rev 2.2:
125 * ''The system controller shall read all interrupt registers in
126 @@ -576,10 +637,27 @@ static void pcf50606_work(struct work_struct *work)
127 * because if you don't INT# gets stuck asserted forever after a
128 * while
129 */
130 - ret = i2c_smbus_read_i2c_block_data(&pcf->client, PCF50606_REG_INT1, 3,
131 - pcfirq);
132 - if (ret != 3)
133 + ret = i2c_smbus_read_i2c_block_data(&pcf->client, PCF50606_REG_INT1,
134 + sizeof(pcfirq), pcfirq);
135 + if (ret != sizeof(pcfirq)) {
136 DEBUGPC("Oh crap PMU IRQ register read failed %d\n", ret);
137 + /*
138 + * it shouldn't fail, we no longer attempt to use
139 + * I2C while it can be suspended. But we don't have
140 + * much option but to retry if if it ever did fail,
141 + * because if we don't service the interrupt to clear
142 + * it, we will never see another PMU interrupt edge.
143 + */
144 + goto reschedule;
145 + }
146 +
147 + /* hey did we just resume? (because we don't get here unless we are
148 + * running normally or the first call after resumption)
149 + *
150 + * pcf50606 resume is really really over now then.
151 + */
152 + if (pcf->suspend_state != PCF50606_SS_RUNNING)
153 + pcf->suspend_state = PCF50606_SS_RUNNING;
154
155 if (!pcf->coldplug_done) {
156 DEBUGPC("PMU Coldplug init\n");
157 @@ -814,10 +892,26 @@ static void pcf50606_work(struct work_struct *work)
158
159 DEBUGPC("\n");
160
161 +bail:
162 pcf->working = 0;
163 input_sync(pcf->input_dev);
164 put_device(&pcf->client.dev);
165 mutex_unlock(&pcf->working_lock);
166 +
167 + return;
168 +
169 +reschedule:
170 +
171 + if ((pcf->suspend_state != PCF50606_SS_STARTING_SUSPEND) &&
172 + (pcf->suspend_state != PCF50606_SS_COMPLETED_SUSPEND)) {
173 + msleep(10);
174 + dev_info(&pcf->client.dev, "rescheduling interrupt service\n");
175 + }
176 + if (!schedule_work(&pcf->work))
177 + dev_err(&pcf->client.dev, "int service reschedule failed\n");
178 +
179 + /* we don't put the device here, hold it for next time */
180 + mutex_unlock(&pcf->working_lock);
181 }
182
183 static irqreturn_t pcf50606_irq(int irq, void *_pcf)
184 @@ -828,7 +922,7 @@ static irqreturn_t pcf50606_irq(int irq, void *_pcf)
185 irq, _pcf);
186 get_device(&pcf->client.dev);
187 if (!schedule_work(&pcf->work) && !pcf->working)
188 - dev_dbg(&pcf->client.dev, "work item may be lost\n");
189 + dev_err(&pcf->client.dev, "pcf irq work already queued.\n");
190
191 return IRQ_HANDLED;
192 }
193 @@ -1881,12 +1975,27 @@ static int pcf50606_suspend(struct device *dev, pm_message_t state)
194 struct pcf50606_data *pcf = i2c_get_clientdata(client);
195 int i;
196
197 + /* we suspend once (!) as late as possible in the suspend sequencing */
198 +
199 + if ((state.event != PM_EVENT_SUSPEND) ||
200 + (pcf->suspend_state != PCF50606_SS_RUNNING))
201 + return -EBUSY;
202 +
203 /* The general idea is to power down all unused power supplies,
204 * and then mask all PCF50606 interrup sources but EXTONR, ONKEYF
205 * and ALARM */
206
207 mutex_lock(&pcf->lock);
208
209 + pcf->suspend_state = PCF50606_SS_STARTING_SUSPEND;
210 +
211 + /* we are not going to service any further interrupts until we
212 + * resume. If the IRQ workqueue is still pending in the background,
213 + * it will bail when it sees we set suspend state above.
214 + */
215 +
216 + disable_irq(pcf->irq);
217 +
218 /* Save all registers that don't "survive" standby state */
219 pcf->standby_regs.dcdc1 = __reg_read(pcf, PCF50606_REG_DCDC1);
220 pcf->standby_regs.dcdc2 = __reg_read(pcf, PCF50606_REG_DCDC2);
221 @@ -1927,6 +2036,8 @@ static int pcf50606_suspend(struct device *dev, pm_message_t state)
222 __reg_write(pcf, PCF50606_REG_INT2M, ~INT2M_RESUMERS & 0xff);
223 __reg_write(pcf, PCF50606_REG_INT3M, ~INT3M_RESUMERS & 0xff);
224
225 + pcf->suspend_state = PCF50606_SS_COMPLETED_SUSPEND;
226 +
227 mutex_unlock(&pcf->lock);
228
229 return 0;
230 @@ -1939,6 +2050,8 @@ static int pcf50606_resume(struct device *dev)
231
232 mutex_lock(&pcf->lock);
233
234 + pcf->suspend_state = PCF50606_SS_STARTING_RESUME;
235 +
236 /* Resume all saved registers that don't "survive" standby state */
237 __reg_write(pcf, PCF50606_REG_INT1M, pcf->standby_regs.int1m);
238 __reg_write(pcf, PCF50606_REG_INT2M, pcf->standby_regs.int2m);
239 @@ -1957,10 +2070,17 @@ static int pcf50606_resume(struct device *dev)
240 __reg_write(pcf, PCF50606_REG_ADCC2, pcf->standby_regs.adcc2);
241 __reg_write(pcf, PCF50606_REG_PWMC1, pcf->standby_regs.pwmc1);
242
243 + pcf->suspend_state = PCF50606_SS_COMPLETED_RESUME;
244 +
245 + enable_irq(pcf->irq);
246 +
247 mutex_unlock(&pcf->lock);
248
249 - /* Hack to fix the gta01 power button problem on resume */
250 - pcf50606_irq(0, pcf);
251 + /* Call PCF work function; this fixes an issue on the gta01 where
252 + * the power button "goes away" if it is used to wake the device.
253 + */
254 + get_device(&pcf->client.dev);
255 + pcf50606_work(&pcf->work);
256
257 return 0;
258 }
259 @@ -1998,9 +2118,25 @@ static int pcf50606_plat_remove(struct platform_device *pdev)
260 return 0;
261 }
262
263 +/* We have this purely to capture an early indication that we are coming out
264 + * of suspend, before our device resume got called; async interrupt service is
265 + * interested in this.
266 + */
267 +
268 +static int pcf50606_plat_resume(struct platform_device *pdev)
269 +{
270 + /* i2c_get_clientdata(to_i2c_client(&pdev->dev)) returns NULL at this
271 + * early resume time so we have to use pcf50606_global
272 + */
273 + pcf50606_global->suspend_state = PCF50606_SS_RESUMING_BUT_NOT_US_YET;
274 +
275 + return 0;
276 +}
277 +
278 static struct platform_driver pcf50606_plat_driver = {
279 .probe = pcf50606_plat_probe,
280 .remove = pcf50606_plat_remove,
281 + .resume_early = pcf50606_plat_resume,
282 .driver = {
283 .owner = THIS_MODULE,
284 .name = "pcf50606",
285 --
286 1.5.6.3
287