[NEW DRIVER V4 2/7] DA9058 ADC driver

From: Anthony Olech
Date: Fri Apr 12 2013 - 09:21:50 EST


This is the ADC component driver of the Dialog DA9058 PMIC.
This driver is just one component of the whole DA9058 PMIC
driver. It depends on the DA9058 CORE component driver.
The HWMON component driver depends on this ADC component driver.

This component driver recieves the actual platform data from
the DA9058 CORE driver, whose settings may be overridden from
the platform data supplied from the machine driver.

Signed-off-by: Anthony Olech <anthony.olech.opensource@xxxxxxxxxxx>
Signed-off-by: David Dajun Chen <david.chen@xxxxxxxxxxx>
---
drivers/iio/adc/Kconfig | 14 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/da9058-adc.c | 397 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 412 insertions(+)
create mode 100644 drivers/iio/adc/da9058-adc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index e372257..da286ec 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,20 @@ config AT91_ADC
help
Say yes here to build support for Atmel AT91 ADC.

+config DA9058_ADC
+ tristate "Dialog DA9058 PMIC ADC"
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
+ select IIO_TRIGGER
+ select SYSFS
+ help
+ Say yes here to build support for the ADC component of
+ the DAILOG DA9058 PMIC.
+ If unsure, say N (but it's safe to say "Y").
+
+ To compile this driver as a module, choose M here: the
+ module will be called da9058-adc.
+
config LP8788_ADC
bool "LP8788 ADC driver"
depends on MFD_LP8788
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 2d5f100..5c79583 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_AD7791) += ad7791.o
obj-$(CONFIG_AD7793) += ad7793.o
obj-$(CONFIG_AD7887) += ad7887.o
obj-$(CONFIG_AT91_ADC) += at91_adc.o
+obj-$(CONFIG_DA9058_ADC) += da9058-adc.o
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
obj-$(CONFIG_MAX1363) += max1363.o
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
diff --git a/drivers/iio/adc/da9058-adc.c b/drivers/iio/adc/da9058-adc.c
new file mode 100644
index 0000000..8894a25
--- /dev/null
+++ b/drivers/iio/adc/da9058-adc.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2012 Dialog Semiconductor Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/iio/iio.h>
+#include <linux/regmap.h>
+#include <linux/mfd/core.h>
+
+#include <linux/mfd/da9058/version.h>
+#include <linux/mfd/da9058/core.h>
+#include <linux/mfd/da9058/registers.h>
+#include <linux/mfd/da9058/irq.h>
+#include <linux/mfd/da9058/pdata.h>
+#include <linux/mfd/da9058/adc.h>
+#include <linux/mfd/da9058/conf.h>
+
+struct da9058_adc {
+ struct da9058 *da9058;
+ struct platform_device *pdev;
+ struct rtc_device *rtc_dev;
+ int use_automatic_adc;
+ int irq;
+};
+
+/*
+ * if the PMIC is in automatic ADC consersion mode we have the choice
+ * of just getting the last (automatic) conversion or doing a manual
+ * conversion anyway.
+ *
+ * if the PMIC is not in automatic ADC consersion mode we have no choice
+ * we just have to ignore the requested mode and just do a manual
+ * ADC conversion.
+ */
+static int da9058_automatic_adc_conversion(struct da9058 *da9058,
+ const int channel, int *value)
+{
+ unsigned int adc_msh, adc_lsh;
+ int ret;
+
+ switch (channel) {
+ case DA9058_ADCMAN_MUXSEL_VBAT:
+ ret = da9058_reg_read(da9058, DA9058_VBATRES_REG_MSB,
+ &adc_msh);
+ if (ret)
+ return ret;
+
+ ret = da9058_reg_read(da9058, DA9058_AUTORES_REG_1,
+ &adc_lsh);
+ if (ret)
+ return ret;
+
+ *value = (adc_lsh & 0x0F) | (adc_msh << 4);
+
+ return 0;
+ case DA9058_ADCMAN_MUXSEL_TEMP:
+ ret = da9058_reg_read(da9058, DA9058_TEMPRES_REG_MSB,
+ &adc_msh);
+ if (ret)
+ return ret;
+
+ ret = da9058_reg_read(da9058, DA9058_AUTORES_REG_1,
+ &adc_lsh);
+ if (ret)
+ return ret;
+
+ *value = (adc_lsh >> 4) | (adc_msh << 4);
+
+ return 0;
+ case DA9058_ADCMAN_MUXSEL_VF:
+ ret = da9058_reg_read(da9058, DA9058_VREF_REG,
+ &adc_msh);
+ if (ret)
+ return ret;
+
+ ret = da9058_reg_read(da9058, DA9058_AUTORES_REG_2,
+ &adc_lsh);
+ if (ret)
+ return ret;
+
+ *value = (adc_lsh & 0x0F) | (adc_msh << 4);
+
+ return 0;
+ case DA9058_ADCMAN_MUXSEL_ADCIN:
+ ret = da9058_reg_read(da9058, DA9058_ADCINRES_REG_MSB,
+ &adc_msh);
+ if (ret)
+ return ret;
+
+ ret = da9058_reg_read(da9058, DA9058_AUTORES_REG_2,
+ &adc_lsh);
+ if (ret)
+ return ret;
+
+ *value = (adc_lsh >> 4) | (adc_msh << 4);
+
+ return 0;
+ case DA9058_ADCMAN_MUXSEL_TJUNC:
+ ret = da9058_reg_read(da9058, DA9058_TJUNCRES_REG,
+ &adc_msh);
+ if (ret)
+ return ret;
+
+ ret = da9058_reg_read(da9058, DA9058_AUTORES_REG_3,
+ &adc_lsh);
+ if (ret)
+ return ret;
+
+ *value = (adc_lsh >> 4) | (adc_msh << 4);
+
+ return 0;
+ default:
+ dev_err(da9058->dev, "ADC Channel %d is reserved\n", channel);
+ return -EIO;
+ }
+}
+
+static int da9058_manual_adc_conversion(struct da9058 *da9058,
+ const int channel, int *value)
+{
+ unsigned int adc_msh, adc_lsh;
+ int ret;
+
+ mutex_lock(&da9058->adc_mutex);
+
+ ret = da9058_reg_write(da9058, DA9058_ADCMAN_REG,
+ DA9058_ADCMAN_MANCONV | channel);
+ if (ret < 0)
+ goto err;
+
+ if (!wait_for_completion_timeout(&da9058->adc_read_done,
+ msecs_to_jiffies(500))) {
+ dev_err(da9058->dev,
+ "timeout waiting for ADC conversion interrupt\n");
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ ret = da9058_reg_read(da9058, DA9058_ADCRESH_REG, &adc_msh);
+ if (ret < 0)
+ goto err;
+
+ ret = da9058_reg_read(da9058, DA9058_ADCRESL_REG, &adc_lsh);
+ if (ret < 0)
+ goto err;
+
+ *value = (adc_msh << 4) | (adc_lsh & 0x0F);
+
+err:
+ mutex_unlock(&da9058->adc_mutex);
+ return ret;
+}
+
+static int da9058_adc_conversion_read(struct da9058 *da9058, const int channel,
+ int automatic_mode, int *value)
+{
+ if (!value)
+ return -EINVAL;
+
+ if (automatic_mode) {
+ unsigned int adc_ctrl;
+ int ret;
+
+ ret = da9058_reg_read(da9058, DA9058_ADCCONT_REG, &adc_ctrl);
+ if (ret)
+ return ret;
+
+ if (adc_ctrl & DA9058_ADCCONT_AUTOADCEN)
+ return da9058_automatic_adc_conversion(da9058,
+ channel, value);
+ else
+ return da9058_manual_adc_conversion(da9058,
+ channel, value);
+ } else {
+ return da9058_manual_adc_conversion(da9058, channel, value);
+ }
+}
+
+static irqreturn_t da9058_adc_interrupt(int irq, void *data)
+{
+ struct da9058 *da9058 = data;
+
+ complete(&da9058->adc_read_done);
+
+ return IRQ_HANDLED;
+}
+static int da9058_adc_read_raw(struct iio_dev *idev,
+ struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+ struct da9058_adc *adc = iio_priv(idev);
+ struct da9058 *da9058 = adc->da9058;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = da9058_adc_conversion_read(da9058, chan->channel,
+ adc->use_automatic_adc, val);
+ if (ret)
+ return ret;
+ else
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ };
+}
+
+static const struct iio_info da9058_adc_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = &da9058_adc_read_raw,
+};
+
+struct iio_chan_spec da9058_adc_channels[] = {
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 0,
+ .scan_index = 0,
+ .scan_type.sign = 'u',
+ .scan_type.realbits = 12,
+ .scan_type.storagebits = 16,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT,
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 1,
+ .scan_index = 1,
+ .scan_type.sign = 'u',
+ .scan_type.realbits = 12,
+ .scan_type.storagebits = 16,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT,
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 2,
+ .scan_index = 2,
+ .scan_type.sign = 'u',
+ .scan_type.realbits = 12,
+ .scan_type.storagebits = 16,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT,
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 3,
+ .scan_index = 3,
+ .scan_type.sign = 'u',
+ .scan_type.realbits = 12,
+ .scan_type.storagebits = 16,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT,
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 4,
+ .scan_index = 4,
+ .scan_type.sign = 'u',
+ .scan_type.realbits = 12,
+ .scan_type.storagebits = 16,
+ .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT,
+ },
+
+};
+unsigned long da9058_scan_masks[] = {
+
+ (1 << ARRAY_SIZE(da9058_adc_channels)) - 1,
+};
+
+static int da9058_adc_probe(struct platform_device *pdev)
+{
+ struct da9058 *da9058 = dev_get_drvdata(pdev->dev.parent);
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct da9058_adc_pdata *adc_pdata;
+ struct da9058_adc *adc;
+ struct iio_dev *idev;
+ int ret;
+
+ if (cell == NULL) {
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ if (da9058 == NULL) {
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ if (da9058->adc_read) {
+ ret = -EEXIST;
+ goto exit;
+ }
+
+ adc_pdata = cell->platform_data;
+
+ if (adc_pdata == NULL) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ idev = iio_device_alloc(sizeof(struct da9058_adc));
+ if (idev == NULL) {
+ ret = -ENOMEM;
+ goto error_ret;
+ }
+ adc = iio_priv(idev);
+
+ platform_set_drvdata(pdev, idev);
+
+ idev->dev.parent = &pdev->dev;
+ idev->name = dev_name(&pdev->dev);
+ idev->modes = INDIO_DIRECT_MODE;
+ idev->info = &da9058_adc_info;
+
+ adc->irq = platform_get_irq(pdev, 0);
+ if (adc->irq < 0) {
+ dev_err(&pdev->dev, "can not get ADC IRQ error=%d\n",
+ adc->irq);
+ ret = -ENODEV;
+ goto error_free_device;
+ }
+
+ idev->channels = da9058_adc_channels;
+ idev->num_channels = ARRAY_SIZE(da9058_adc_channels);
+ idev->available_scan_masks = da9058_scan_masks;
+ idev->masklength = ARRAY_SIZE(da9058_adc_channels);
+
+ platform_set_drvdata(pdev, adc);
+ adc->da9058 = da9058;
+ adc->pdev = pdev;
+ adc->use_automatic_adc = adc_pdata->use_automatic_adc;
+ da9058->adc_read = da9058_adc_conversion_read;
+
+ ret = iio_device_register(idev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Couldn't register the device.\n");
+ goto error_free_device;
+ }
+
+ ret = request_threaded_irq(da9058_to_virt_irq_num(da9058, adc->irq),
+ NULL, da9058_adc_interrupt,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "DA9058 ADC EOM", da9058);
+ if (ret)
+ goto failed_to_get_adc_interrupt;
+
+ goto exit;
+
+failed_to_get_adc_interrupt:
+ iio_device_unregister(idev);
+error_free_device:
+ platform_set_drvdata(pdev, NULL);
+ iio_device_free(idev);
+exit:
+error_ret:
+ return ret;
+}
+
+static int da9058_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *idev = platform_get_drvdata(pdev);
+ struct da9058_adc *adc = iio_priv(idev);
+
+ iio_device_unregister(idev);
+ free_irq(adc->irq, adc);
+ iio_device_free(idev);
+
+ return 0;
+}
+
+static struct platform_driver da9058_adc_driver = {
+ .probe = da9058_adc_probe,
+ .remove = da9058_adc_remove,
+ .driver = {
+ .name = "da9058-adc",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(da9058_adc_driver);
+
+MODULE_DESCRIPTION("Dialog DA9058 PMIC Analogue to Digial Converter Driver");
+MODULE_AUTHOR("Anthony Olech <Anthony.Olech@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:da9058-adc");
--
end-of-patch for NEW DRIVER V4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/