iio: staging: adc: palmas: add support for auto conversion during sleep
Laxman Dewangan [Sun, 21 Jul 2013 07:56:22 +0000 (12:56 +0530)]
Add support of continuous ADC conversion and threshold configuration for
ADC channel so that if the channel data crosses the threshold value, it
can wakeup system from suspend.

bug 1301017
bug 1366274

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: http://git-master/r/243914
(cherry picked from commit 8cf130a684131e528734c435ed837a297ffb3908)

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: http://git-master/r/251674
(cherry picked from commit 16c693bf69b1ea0a09731caf0b175b4cb9ddc5e5)

Change-Id: If1ab0753431761918e71623d19aadfd2bb55db08
Signed-off-by: Jinyoung Park <jinyoungp@nvidia.com>
Reviewed-on: http://git-master/r/336845
Reviewed-by: Harry Hong <hhong@nvidia.com>
Tested-by: Harry Hong <hhong@nvidia.com>

drivers/staging/iio/adc/palmas_gpadc.c
include/linux/mfd/palmas.h

index 1f375ca..08771ac 100644 (file)
@@ -88,10 +88,75 @@ struct palmas_gpadc {
        bool                            ch3_dual_current;
        bool                            extended_delay;
        int                             irq;
+       int                             irq_auto_0;
+       int                             irq_auto_1;
        struct palmas_gpadc_info        *adc_info;
        struct completion               conv_completion;
+       struct palmas_adc_wakeup_property wakeup1_data;
+       struct palmas_adc_wakeup_property wakeup2_data;
+       bool                            wakeup1_enable;
+       bool                            wakeup2_enable;
+       int                             auto_conversion_period;
 };
 
+/*
+ * GPADC lock issue in AUTO mode.
+ * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
+ *        mode feature.
+ * Details:
+ *     When the AUTO mode is the only conversion mode enabled, if the AUTO
+ *     mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
+ *     or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
+ *     conversion mechanism can be seen as locked meaning that all following
+ *     conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
+ *     will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
+ *     the GPADC.
+ *
+ * Workaround(s):
+ *     To avoid the lock mechanism, the workaround to follow before any stop
+ *     conversion request is:
+ *     Force the GPADC state machine to be ON by using the GPADC_CTRL1.
+ *             GPADC_FORCE bit = 1
+ *     Shutdown the GPADC AUTO conversion using
+ *             GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
+ *     After 100us, force the GPADC state machine to be OFF by using the
+ *             GPADC_CTRL1.  GPADC_FORCE bit = 0
+ */
+static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
+{
+       int ret;
+
+       ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+                       PALMAS_GPADC_CTRL1,
+                       PALMAS_GPADC_CTRL1_GPADC_FORCE,
+                       PALMAS_GPADC_CTRL1_GPADC_FORCE);
+       if (ret < 0) {
+               dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+               return ret;
+       }
+
+       ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+                       PALMAS_GPADC_AUTO_CTRL,
+                       PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
+                       PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
+                       0);
+       if (ret < 0) {
+               dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
+               return ret;
+       }
+
+       udelay(100);
+
+       ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+                       PALMAS_GPADC_CTRL1,
+                       PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
+       if (ret < 0) {
+               dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+               return ret;
+       }
+       return 0;
+}
+
 static irqreturn_t palmas_gpadc_irq(int irq, void *data)
 {
        struct palmas_gpadc *adc = data;
@@ -100,6 +165,15 @@ static irqreturn_t palmas_gpadc_irq(int irq, void *data)
        return IRQ_HANDLED;
 }
 
+static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
+{
+       struct palmas_gpadc *adc = data;
+
+       dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);
+       palmas_disable_auto_conversion(adc);
+       return IRQ_HANDLED;
+}
+
 static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, int mask)
 {
        int ret;
@@ -438,6 +512,7 @@ static int __devinit palmas_gpadc_probe(struct platform_device *pdev)
        init_completion(&adc->conv_completion);
        dev_set_drvdata(&pdev->dev, iodev);
 
+       adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
        adc->irq = platform_get_irq(pdev, 0);
        ret = request_threaded_irq(adc->irq, NULL,
                palmas_gpadc_irq,
@@ -449,6 +524,38 @@ static int __devinit palmas_gpadc_probe(struct platform_device *pdev)
                goto out_unregister_map;
        }
 
+       if (adc_pdata->adc_wakeup1_data) {
+               memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
+                       sizeof(adc->wakeup1_data));
+               adc->wakeup1_enable = true;
+               adc->irq_auto_0 =  platform_get_irq(pdev, 1);
+               ret = request_threaded_irq(adc->irq_auto_0, NULL,
+                               palmas_gpadc_irq_auto,
+                               IRQF_ONESHOT | IRQF_EARLY_RESUME,
+                               "palmas-adc-auto-0", adc);
+               if (ret < 0) {
+                       dev_err(adc->dev, "request auto0 irq %d failed: %dn",
+                               adc->irq_auto_0, ret);
+                       goto out_irq_free;
+               }
+       }
+
+       if (adc_pdata->adc_wakeup2_data) {
+               memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
+                               sizeof(adc->wakeup2_data));
+               adc->wakeup2_enable = true;
+               adc->irq_auto_1 =  platform_get_irq(pdev, 2);
+               ret = request_threaded_irq(adc->irq_auto_1, NULL,
+                               palmas_gpadc_irq_auto,
+                               IRQF_ONESHOT | IRQF_EARLY_RESUME,
+                               "palmas-adc-auto-1", adc);
+               if (ret < 0) {
+                       dev_err(adc->dev, "request auto1 irq %d failed: %dn",
+                               adc->irq_auto_1, ret);
+                       goto out_irq_auto0_free;
+               }
+       }
+
        if (adc_pdata->ch0_current_uA == 0)
                adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
        else if (adc_pdata->ch0_current_uA <= 5)
@@ -489,7 +596,7 @@ static int __devinit palmas_gpadc_probe(struct platform_device *pdev)
        ret = iio_device_register(iodev);
        if (ret < 0) {
                dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
-               goto out_irq_free;
+               goto out_irq_auto1_free;
        }
 
        device_set_wakeup_capable(&pdev->dev, 1);
@@ -498,8 +605,16 @@ static int __devinit palmas_gpadc_probe(struct platform_device *pdev)
                        palmas_gpadc_calibrate(adc, i);
        }
 
+       if (adc->wakeup1_enable || adc->wakeup2_enable)
+               device_wakeup_enable(&pdev->dev);
        return 0;
 
+out_irq_auto1_free:
+       if (adc_pdata->adc_wakeup2_data)
+               free_irq(adc->irq_auto_1, adc);
+out_irq_auto0_free:
+       if (adc_pdata->adc_wakeup1_data)
+               free_irq(adc->irq_auto_0, adc);
 out_irq_free:
        free_irq(adc->irq, adc);
 out_unregister_map:
@@ -520,18 +635,162 @@ static int __devexit palmas_gpadc_remove(struct platform_device *pdev)
                iio_map_array_unregister(iodev, pdata->adc_pdata->iio_maps);
        iio_device_unregister(iodev);
        free_irq(adc->irq, adc);
+       if (adc->wakeup1_enable)
+               free_irq(adc->irq_auto_0, adc);
+       if (adc->wakeup2_enable)
+               free_irq(adc->irq_auto_1, adc);
        iio_free_device(iodev);
        return 0;
 }
 
 #ifdef CONFIG_PM_SLEEP
+static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
+{
+       int adc_period, conv;
+       int i;
+       int ch0 = 0, ch1 = 0;
+       int thres;
+       int ret;
+
+       adc_period = adc->auto_conversion_period;
+       for (i = 0; i < 16; ++i) {
+               if (((1000 * (1 << i))/32) < adc_period)
+                       continue;
+       }
+       if (i > 0)
+               i--;
+       adc_period = i;
+       ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+                       PALMAS_GPADC_AUTO_CTRL,
+                       PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
+                       adc_period);
+       if (ret < 0) {
+               dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+               return ret;
+       }
+
+       conv = 0;
+       if (adc->wakeup1_enable) {
+               int is_high;
+
+               ch0 = adc->wakeup1_data.adc_channel_number;
+               conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
+               if (adc->wakeup1_data.adc_high_threshold > 0) {
+                       thres = adc->wakeup1_data.adc_high_threshold;
+                       is_high = 0;
+               } else {
+                       thres = adc->wakeup1_data.adc_low_threshold;
+                       is_high = BIT(7);
+               }
+
+               ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+                               PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
+               if (ret < 0) {
+                       dev_err(adc->dev,
+                               "THRES_CONV0_LSB write failed: %d\n", ret);
+                       return ret;
+               }
+
+               ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+                               PALMAS_GPADC_THRES_CONV0_MSB,
+                               ((thres >> 8) & 0xF) | is_high);
+               if (ret < 0) {
+                       dev_err(adc->dev,
+                               "THRES_CONV0_MSB write failed: %d\n", ret);
+                       return ret;
+               }
+       }
+
+       if (adc->wakeup2_enable) {
+               int is_high;
+
+               ch1 = adc->wakeup2_data.adc_channel_number;
+               conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
+               if (adc->wakeup2_data.adc_high_threshold > 0) {
+                       thres = adc->wakeup2_data.adc_high_threshold;
+                       is_high = 0;
+               } else {
+                       thres = adc->wakeup2_data.adc_low_threshold;
+                       is_high = BIT(7);
+               }
+
+               ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+                               PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
+               if (ret < 0) {
+                       dev_err(adc->dev,
+                               "THRES_CONV1_LSB write failed: %d\n", ret);
+                       return ret;
+               }
+
+               ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+                               PALMAS_GPADC_THRES_CONV1_MSB,
+                               ((thres >> 8) & 0xF) | is_high);
+               if (ret < 0) {
+                       dev_err(adc->dev,
+                               "THRES_CONV1_MSB write failed: %d\n", ret);
+                       return ret;
+               }
+       }
+
+       ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+                       PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
+       if (ret < 0) {
+               dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+               return ret;
+       }
+
+       ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+                       PALMAS_GPADC_AUTO_CTRL,
+                       PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
+                       PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
+       if (ret < 0) {
+               dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
+{
+       int ret;
+
+       ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+                       PALMAS_GPADC_AUTO_SELECT, 0);
+       if (ret < 0) {
+               dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+               return ret;
+       }
+
+       ret = palmas_disable_auto_conversion(adc);
+       if (ret < 0) {
+               dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
 static int palmas_gpadc_suspend(struct device *dev)
 {
        struct iio_dev *iodev = dev_get_drvdata(dev);
        struct palmas_gpadc *adc = iio_priv(iodev);
+       int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+       int ret;
+
+       if (!device_may_wakeup(dev) || !wakeup)
+               return 0;
+
+       ret = palmas_adc_wakeup_configure(adc);
+       if (ret < 0)
+               return ret;
+
+       if (adc->wakeup1_enable)
+               enable_irq_wake(adc->irq_auto_0);
+
+       if (adc->wakeup2_enable)
+               enable_irq_wake(adc->irq_auto_1);
 
-       if (device_may_wakeup(dev))
-               enable_irq_wake(adc->irq);
        return 0;
 }
 
@@ -539,9 +798,22 @@ static int palmas_gpadc_resume(struct device *dev)
 {
        struct iio_dev *iodev = dev_get_drvdata(dev);
        struct palmas_gpadc *adc = iio_priv(iodev);
+       int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+       int ret;
+
+       if (!device_may_wakeup(dev) || !wakeup)
+               return 0;
+
+       ret = palmas_adc_wakeup_reset(adc);
+       if (ret < 0)
+               return ret;
+
+       if (adc->wakeup1_enable)
+               disable_irq_wake(adc->irq_auto_0);
+
+       if (adc->wakeup2_enable)
+               disable_irq_wake(adc->irq_auto_1);
 
-       if (device_may_wakeup(dev))
-               disable_irq_wake(adc->irq);
        return 0;
 };
 #endif
index c7cc54b..9d294ca 100644 (file)
@@ -207,6 +207,18 @@ struct palmas_rtc_platform_data {
        unsigned charging_current_ua;
 };
 
+/*
+ * ADC wakeup property: Wakup the system from suspend when threshold crossed.
+ * @adc_channel_number: ADC channel number for monitoring.
+ * @adc_high_threshold: ADC High raw data for upper threshold to generate int.
+ * @adc_low_threshold: ADC low raw data for lower threshold to generate int.
+ */
+struct palmas_adc_wakeup_property {
+       int adc_channel_number;
+       int adc_high_threshold;
+       int adc_low_threshold;
+};
+
 struct palmas_gpadc_platform_data {
        int ch0_current_uA;     /* 0uA, 5uA, 15uA, 20uA */
        int ch3_current_uA;     /* 0uA, 10uA, 400uA, 800uA */
@@ -214,6 +226,9 @@ struct palmas_gpadc_platform_data {
        bool extended_delay;
 
        struct iio_map *iio_maps;
+       int auto_conversion_period_ms;
+       struct palmas_adc_wakeup_property *adc_wakeup1_data;
+       struct palmas_adc_wakeup_property *adc_wakeup2_data;
 };
 
 struct palmas_pinctrl_config {