diff options
Diffstat (limited to 'target/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch')
-rwxr-xr-x | target/linux/s3c24xx/patches/0243-gta01-pcf50606-disable-irq-from-suspend-until-resume.patch | 287 |
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 0000000000..8f5550f62b --- /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 + |