summaryrefslogtreecommitdiff
path: root/target/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch')
-rwxr-xr-xtarget/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch287
1 files changed, 287 insertions, 0 deletions
diff --git a/target/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch b/target/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch
new file mode 100755
index 0000000..8f5550f
--- /dev/null
+++ b/target/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch
@@ -0,0 +1,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
+