pm8xxx-adc: fix missing interrupts
There exists situations when an adc conversion is requested very
early in the resume sequence. These requests fail to complete
because of missing interrupts and eventually cause lockups.
When an interrupt is enabled during system resume the kernel rejects
enabling these interrupts. This keeps the interrupt disabled and causes
missed interrupt. Get around this problem by not needing to enable the
interrupt. IOW, do not call disable_irq and enable_irq, only request
the irq which leaves it in enabled state.
There is a bug in the pmic where if an interrupt is masked/unmasked
it clears the pending status. This causes a nonwakeup interrupt to
be missed as it is unmasked while resuming. Mostly this is okay but
if a adc request is made early in the suspend, it causes it to miss
interrupts. Make the adc interrupt a wakeup interrupt.
The driver initializes its completion only at probe time. This may cause
incorrect end of conversions with out of sync (spurious) interrupts.
Update the code to init a completion just before issuing a conversion.
Another issue was observed where the interrupt's unmask_irq was delayed,
causing another cpu to initiate the next conversion as soon as complete was
called. This lead to a situation where the interrupt for the next request
was triggered and the unmask happened later. Call complete within a
workqueue which guarantees that unmask would have completed and
it is safe to start the next conversion.
CRs-Fixed: 350768
Change-Id: I4c619c9c274a0cfd6a3e04b2b71ec885968d7375
Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
diff --git a/drivers/hwmon/pm8xxx-adc.c b/drivers/hwmon/pm8xxx-adc.c
index 2f59235..6bef3d3 100644
--- a/drivers/hwmon/pm8xxx-adc.c
+++ b/drivers/hwmon/pm8xxx-adc.c
@@ -239,6 +239,7 @@
if (arb_cntrl) {
data_arb_cntrl |= PM8XXX_ADC_ARB_USRP_CNTRL1_REQ;
+ INIT_COMPLETION(adc_pmic->adc_rslt_completion);
rc = pm8xxx_writeb(adc_pmic->dev->parent,
PM8XXX_ADC_ARB_USRP_CNTRL1, data_arb_cntrl);
} else
@@ -336,7 +337,6 @@
static int32_t pm8xxx_adc_configure(
struct pm8xxx_adc_amux_properties *chan_prop)
{
- struct pm8xxx_adc *adc_pmic = pmic_adc;
u8 data_amux_chan = 0, data_arb_rsv = 0, data_dig_param = 0;
int rc;
@@ -395,9 +395,6 @@
if (rc < 0)
return rc;
- if (!pm8xxx_adc_calib_first_adc)
- enable_irq(adc_pmic->adc_irq);
-
rc = pm8xxx_adc_arb_cntrl(1, data_amux_chan);
if (rc < 0) {
pr_err("Configuring ADC Arbiter"
@@ -476,16 +473,21 @@
spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
}
+void trigger_completion(struct work_struct *work)
+{
+ struct pm8xxx_adc *adc_8xxx = pmic_adc;
+
+ complete(&adc_8xxx->adc_rslt_completion);
+}
+DECLARE_WORK(trigger_completion_work, trigger_completion);
+
static irqreturn_t pm8xxx_adc_isr(int irq, void *dev_id)
{
- struct pm8xxx_adc *adc_8xxx = dev_id;
-
- disable_irq_nosync(adc_8xxx->adc_irq);
if (pm8xxx_adc_calib_first_adc)
return IRQ_HANDLED;
- /* TODO Handle spurius interrupt condition */
- complete(&adc_8xxx->adc_rslt_completion);
+
+ schedule_work(&trigger_completion_work);
return IRQ_HANDLED;
}
@@ -1214,10 +1216,10 @@
if (rc) {
dev_err(&pdev->dev, "failed to request adc irq "
"with error %d\n", rc);
+ } else {
+ enable_irq_wake(adc_pmic->adc_irq);
}
- disable_irq_nosync(adc_pmic->adc_irq);
-
adc_pmic->btm_warm_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_1);
if (adc_pmic->btm_warm_irq < 0)
return adc_pmic->btm_warm_irq;