From 0c136b451381e760c056a7683b7fc28901f54da4 Mon Sep 17 00:00:00 2001 From: Mike Westerhof Date: Fri, 8 Aug 2008 13:10:59 +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 --- 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 ddba1c7..18263fb 100644 --- a/drivers/i2c/chips/pcf50606.c +++ b/drivers/i2c/chips/pcf50606.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -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; } @@ -1882,12 +1976,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); @@ -1928,6 +2037,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; @@ -1940,6 +2051,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); @@ -1958,10 +2071,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; } @@ -1999,9 +2119,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.5