summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/staging/iio/adc/palmas_gpadc.c282
-rw-r--r--include/linux/mfd/palmas.h15
2 files changed, 292 insertions, 5 deletions
diff --git a/drivers/staging/iio/adc/palmas_gpadc.c b/drivers/staging/iio/adc/palmas_gpadc.c
index 1f375ca431d8..08771ac90d4f 100644
--- a/drivers/staging/iio/adc/palmas_gpadc.c
+++ b/drivers/staging/iio/adc/palmas_gpadc.c
@@ -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
diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
index c7cc54bbbf75..9d294ca573ec 100644
--- a/include/linux/mfd/palmas.h
+++ b/include/linux/mfd/palmas.h
@@ -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 {