summaryrefslogtreecommitdiffstats
path: root/drivers/mfd/ab8500-gpadc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/ab8500-gpadc.c')
-rw-r--r--drivers/mfd/ab8500-gpadc.c282
1 files changed, 207 insertions, 75 deletions
diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c
index 7f39479c1afc..8673bf66f7d7 100644
--- a/drivers/mfd/ab8500-gpadc.c
+++ b/drivers/mfd/ab8500-gpadc.c
@@ -55,13 +55,18 @@
#define EN_VTVOUT 0x02
#define EN_GPADC 0x01
#define DIS_GPADC 0x00
-#define SW_AVG_16 0x60
+#define AVG_1 0x00
+#define AVG_4 0x20
+#define AVG_8 0x40
+#define AVG_16 0x60
#define ADC_SW_CONV 0x04
#define EN_ICHAR 0x80
#define BTEMP_PULL_UP 0x08
#define EN_BUF 0x40
#define DIS_ZERO 0x00
#define GPADC_BUSY 0x01
+#define EN_FALLING 0x10
+#define EN_TRIG_EDGE 0x02
/* GPADC constants from AB8500 spec, UM0836 */
#define ADC_RESOLUTION 1024
@@ -116,7 +121,10 @@ struct adc_cal_data {
* the completion of gpadc conversion
* @ab8500_gpadc_lock: structure of type mutex
* @regu: pointer to the struct regulator
- * @irq: interrupt number that is used by gpadc
+ * @irq_sw: interrupt number that is used by gpadc for Sw
+ * conversion
+ * @irq_hw: interrupt number that is used by gpadc for Hw
+ * conversion
* @cal_data array of ADC calibration data structs
*/
struct ab8500_gpadc {
@@ -126,7 +134,8 @@ struct ab8500_gpadc {
struct completion ab8500_gpadc_complete;
struct mutex ab8500_gpadc_lock;
struct regulator *regu;
- int irq;
+ int irq_sw;
+ int irq_hw;
struct adc_cal_data cal_data[NBR_CAL_INPUTS];
};
@@ -244,30 +253,35 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel,
EXPORT_SYMBOL(ab8500_gpadc_ad_to_voltage);
/**
- * ab8500_gpadc_convert() - gpadc conversion
+ * ab8500_gpadc_sw_hw_convert() - gpadc conversion
* @channel: analog channel to be converted to digital data
+ * @avg_sample: number of ADC sample to average
+ * @trig_egde: selected ADC trig edge
+ * @trig_timer: selected ADC trigger delay timer
+ * @conv_type: selected conversion type (HW or SW conversion)
*
* This function converts the selected analog i/p to digital
* data.
*/
-int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel)
+int ab8500_gpadc_sw_hw_convert(struct ab8500_gpadc *gpadc, u8 channel,
+ u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type)
{
int ad_value;
int voltage;
- ad_value = ab8500_gpadc_read_raw(gpadc, channel);
-
- /* On failure retry a second time */
+ ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample,
+ trig_edge, trig_timer, conv_type);
+/* On failure retry a second time */
if (ad_value < 0)
- ad_value = ab8500_gpadc_read_raw(gpadc, channel);
-
- if (ad_value < 0) {
- dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", channel);
+ ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample,
+ trig_edge, trig_timer, conv_type);
+if (ad_value < 0) {
+ dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n",
+ channel);
return ad_value;
}
voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value);
-
if (voltage < 0)
dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:"
" %d AD: 0x%x\n", channel, ad_value);
@@ -279,11 +293,16 @@ EXPORT_SYMBOL(ab8500_gpadc_convert);
/**
* ab8500_gpadc_read_raw() - gpadc read
* @channel: analog channel to be read
+ * @avg_sample: number of ADC sample to average
+ * @trig_edge: selected trig edge
+ * @trig_timer: selected ADC trigger delay timer
+ * @conv_type: selected conversion type (HW or SW conversion)
*
- * This function obtains the raw ADC value, this then needs
- * to be converted by calling ab8500_gpadc_ad_to_voltage()
+ * This function obtains the raw ADC value for an hardware conversion,
+ * this then needs to be converted by calling ab8500_gpadc_ad_to_voltage()
*/
-int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
+int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel,
+ u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type)
{
int ret;
int looplimit = 0;
@@ -293,7 +312,6 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
return -ENODEV;
mutex_lock(&gpadc->ab8500_gpadc_lock);
-
/* Enable VTVout LDO this is required for GPADC */
pm_runtime_get_sync(gpadc->dev);
@@ -321,9 +339,29 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
goto out;
}
- /* Select the channel source and set average samples to 16 */
- ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC,
- AB8500_GPADC_CTRL2_REG, (channel | SW_AVG_16));
+ /* Select the channel source and set average samples */
+ switch (avg_sample) {
+ case SAMPLE_1:
+ val = channel | AVG_1;
+ break;
+ case SAMPLE_4:
+ val = channel | AVG_4;
+ break;
+ case SAMPLE_8:
+ val = channel | AVG_8;
+ break;
+ default:
+ val = channel | AVG_16;
+ break;
+
+ }
+
+ if (conv_type == ADC_HW)
+ ret = abx500_set_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL3_REG, val);
+ else
+ ret = abx500_set_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL2_REG, val);
if (ret < 0) {
dev_err(gpadc->dev,
"gpadc_conversion: set avg samples failed\n");
@@ -335,22 +373,43 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
* charging current sense if it needed, ABB 3.0 needs some special
* treatment too.
*/
+ if ((conv_type == ADC_HW) && (trig_edge)) {
+ ret = abx500_mask_and_set_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
+ EN_FALLING, EN_FALLING);
+
+ }
switch (channel) {
case MAIN_CHARGER_C:
case USB_CHARGER_C:
- ret = abx500_mask_and_set_register_interruptible(gpadc->dev,
- AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
- EN_BUF | EN_ICHAR,
- EN_BUF | EN_ICHAR);
- break;
- case BTEMP_BALL:
- if (!is_ab8500_2p0_or_earlier(gpadc->parent)) {
- /* Turn on btemp pull-up on ABB 3.0 */
+ if (conv_type == ADC_HW)
ret = abx500_mask_and_set_register_interruptible(
gpadc->dev,
AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
- EN_BUF | BTEMP_PULL_UP,
- EN_BUF | BTEMP_PULL_UP);
+ EN_BUF | EN_ICHAR | EN_TRIG_EDGE,
+ EN_BUF | EN_ICHAR | EN_TRIG_EDGE);
+ else
+ ret = abx500_mask_and_set_register_interruptible(
+ gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
+ EN_BUF | EN_ICHAR,
+ EN_BUF | EN_ICHAR);
+ break;
+ case BTEMP_BALL:
+ if (!is_ab8500_2p0_or_earlier(gpadc->parent)) {
+ if (conv_type == ADC_HW)
+ /* Turn on btemp pull-up on ABB 3.0 */
+ ret = abx500_mask_and_set_register_interruptible
+ (gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
+ EN_BUF | BTEMP_PULL_UP | EN_TRIG_EDGE,
+ EN_BUF | BTEMP_PULL_UP | EN_TRIG_EDGE);
+ else
+ ret = abx500_mask_and_set_register_interruptible
+ (gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
+ EN_BUF | BTEMP_PULL_UP,
+ EN_BUF | BTEMP_PULL_UP);
/*
* Delay might be needed for ABB8500 cut 3.0, if not, remove
@@ -361,8 +420,17 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
}
/* Intentional fallthrough */
default:
- ret = abx500_mask_and_set_register_interruptible(gpadc->dev,
- AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF);
+ if (conv_type == ADC_HW)
+ ret = abx500_mask_and_set_register_interruptible(
+ gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
+ EN_BUF | EN_TRIG_EDGE,
+ EN_BUF | EN_TRIG_EDGE);
+ else
+ ret = abx500_mask_and_set_register_interruptible(
+ gpadc->dev,
+ AB8500_GPADC,
+ AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF);
break;
}
if (ret < 0) {
@@ -371,36 +439,83 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
goto out;
}
- ret = abx500_mask_and_set_register_interruptible(gpadc->dev,
- AB8500_GPADC, AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV);
- if (ret < 0) {
- dev_err(gpadc->dev,
- "gpadc_conversion: start s/w conversion failed\n");
- goto out;
+ /* Set trigger delay timer */
+ if (conv_type == ADC_HW) {
+ ret = abx500_set_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_AUTO_TIMER_REG, trig_timer);
+ if (ret < 0) {
+ dev_err(gpadc->dev,
+ "gpadc_conversion: trig timer failed\n");
+ goto out;
+ }
+ }
+
+ /* Start SW conversion */
+ if (conv_type == ADC_SW) {
+ ret = abx500_mask_and_set_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_CTRL1_REG,
+ ADC_SW_CONV, ADC_SW_CONV);
+ if (ret < 0) {
+ dev_err(gpadc->dev,
+ "gpadc_conversion: start s/w conv failed\n");
+ goto out;
+ }
}
+
/* wait for completion of conversion */
- if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete,
- msecs_to_jiffies(CONVERSION_TIME))) {
- dev_err(gpadc->dev,
- "timeout: didn't receive GPADC conversion interrupt\n");
- ret = -EINVAL;
- goto out;
+ if (conv_type == ADC_HW) {
+ if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete,
+ 2*HZ)) {
+ dev_err(gpadc->dev,
+ "timeout didn't receive"
+ " hw GPADC conv interrupt\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ } else {
+ if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete,
+ msecs_to_jiffies(CONVERSION_TIME))) {
+ dev_err(gpadc->dev,
+ "timeout didn't receive"
+ " sw GPADC conv interrupt\n");
+ ret = -EINVAL;
+ goto out;
+ }
}
/* Read the converted RAW data */
- ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC,
- AB8500_GPADC_MANDATAL_REG, &low_data);
- if (ret < 0) {
- dev_err(gpadc->dev, "gpadc_conversion: read low data failed\n");
- goto out;
- }
+ if (conv_type == ADC_HW) {
+ ret = abx500_get_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_AUTODATAL_REG, &low_data);
+ if (ret < 0) {
+ dev_err(gpadc->dev,
+ "gpadc_conversion: read hw low data failed\n");
+ goto out;
+ }
- ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC,
- AB8500_GPADC_MANDATAH_REG, &high_data);
- if (ret < 0) {
- dev_err(gpadc->dev,
- "gpadc_conversion: read high data failed\n");
- goto out;
+ ret = abx500_get_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_AUTODATAH_REG, &high_data);
+ if (ret < 0) {
+ dev_err(gpadc->dev,
+ "gpadc_conversion: read hw high data failed\n");
+ goto out;
+ }
+ } else {
+ ret = abx500_get_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_MANDATAL_REG, &low_data);
+ if (ret < 0) {
+ dev_err(gpadc->dev,
+ "gpadc_conversion: read sw low data failed\n");
+ goto out;
+ }
+
+ ret = abx500_get_register_interruptible(gpadc->dev,
+ AB8500_GPADC, AB8500_GPADC_MANDATAH_REG, &high_data);
+ if (ret < 0) {
+ dev_err(gpadc->dev,
+ "gpadc_conversion: read sw high data failed\n");
+ goto out;
+ }
}
/* Disable GPADC */
@@ -411,6 +526,7 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel)
goto out;
}
+ /* Disable VTVout LDO this is required for GPADC */
pm_runtime_mark_last_busy(gpadc->dev);
pm_runtime_put_autosuspend(gpadc->dev);
@@ -427,9 +543,7 @@ out:
*/
(void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC,
AB8500_GPADC_CTRL1_REG, DIS_GPADC);
-
pm_runtime_put(gpadc->dev);
-
mutex_unlock(&gpadc->ab8500_gpadc_lock);
dev_err(gpadc->dev,
"gpadc_conversion: Failed to AD convert channel %d\n", channel);
@@ -438,16 +552,16 @@ out:
EXPORT_SYMBOL(ab8500_gpadc_read_raw);
/**
- * ab8500_bm_gpswadcconvend_handler() - isr for s/w gpadc conversion completion
+ * ab8500_bm_gpadcconvend_handler() - isr for gpadc conversion completion
* @irq: irq number
* @data: pointer to the data passed during request irq
*
- * This is a interrupt service routine for s/w gpadc conversion completion.
+ * This is a interrupt service routine for gpadc conversion completion.
* Notifies the gpadc completion is completed and the converted raw value
* can be read from the registers.
* Returns IRQ status(IRQ_HANDLED)
*/
-static irqreturn_t ab8500_bm_gpswadcconvend_handler(int irq, void *_gpadc)
+static irqreturn_t ab8500_bm_gpadcconvend_handler(int irq, void *_gpadc)
{
struct ab8500_gpadc *gpadc = _gpadc;
@@ -646,11 +760,19 @@ static int ab8500_gpadc_probe(struct platform_device *pdev)
return -ENOMEM;
}
- gpadc->irq = platform_get_irq_byname(pdev, "SW_CONV_END");
- if (gpadc->irq < 0) {
- dev_err(&pdev->dev, "failed to get platform irq-%d\n",
- gpadc->irq);
- ret = gpadc->irq;
+ gpadc->irq_sw = platform_get_irq_byname(pdev, "SW_CONV_END");
+ if (gpadc->irq_sw < 0) {
+ dev_err(gpadc->dev, "failed to get platform irq-%d\n",
+ gpadc->irq_sw);
+ ret = gpadc->irq_sw;
+ goto fail;
+ }
+
+ gpadc->irq_hw = platform_get_irq_byname(pdev, "HW_CONV_END");
+ if (gpadc->irq_hw < 0) {
+ dev_err(gpadc->dev, "failed to get platform irq-%d\n",
+ gpadc->irq_hw);
+ ret = gpadc->irq_hw;
goto fail;
}
@@ -661,14 +783,21 @@ static int ab8500_gpadc_probe(struct platform_device *pdev)
/* Initialize completion used to notify completion of conversion */
init_completion(&gpadc->ab8500_gpadc_complete);
- /* Register interrupt - SwAdcComplete */
- ret = request_threaded_irq(gpadc->irq, NULL,
- ab8500_bm_gpswadcconvend_handler,
- IRQF_ONESHOT | IRQF_NO_SUSPEND | IRQF_SHARED,
- "ab8500-gpadc", gpadc);
+ /* Register interrupts */
+ ret = request_threaded_irq(gpadc->irq_sw, NULL,
+ ab8500_bm_gpadcconvend_handler,
+ IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-sw", gpadc);
+ if (ret < 0) {
+ dev_err(gpadc->dev, "Failed to register interrupt, irq: %d\n",
+ gpadc->irq_sw);
+ goto fail;
+ }
+ ret = request_threaded_irq(gpadc->irq_hw, NULL,
+ ab8500_bm_gpadcconvend_handler,
+ IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-hw", gpadc);
if (ret < 0) {
dev_err(gpadc->dev, "Failed to register interrupt, irq: %d\n",
- gpadc->irq);
+ gpadc->irq_hw);
goto fail;
}
@@ -694,7 +823,8 @@ static int ab8500_gpadc_probe(struct platform_device *pdev)
dev_dbg(gpadc->dev, "probe success\n");
return 0;
fail_irq:
- free_irq(gpadc->irq, gpadc);
+ free_irq(gpadc->irq_sw, gpadc);
+ free_irq(gpadc->irq_hw, gpadc);
fail:
kfree(gpadc);
gpadc = NULL;
@@ -708,7 +838,8 @@ static int ab8500_gpadc_remove(struct platform_device *pdev)
/* remove this gpadc entry from the list */
list_del(&gpadc->node);
/* remove interrupt - completion of Sw ADC conversion */
- free_irq(gpadc->irq, gpadc);
+ free_irq(gpadc->irq_sw, gpadc);
+ free_irq(gpadc->irq_hw, gpadc);
pm_runtime_get_sync(gpadc->dev);
pm_runtime_disable(gpadc->dev);
@@ -757,6 +888,7 @@ subsys_initcall_sync(ab8500_gpadc_init);
module_exit(ab8500_gpadc_exit);
MODULE_LICENSE("GPL v2");
-MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson");
+MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson,"
+ "M'boumba Cedric Madianga");
MODULE_ALIAS("platform:ab8500_gpadc");
MODULE_DESCRIPTION("AB8500 GPADC driver");