linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] iio: adc: add exynos5 adc driver under iio framwork
@ 2013-01-21 13:37 Naveen Krishna Chatradhi
  2013-01-22  9:44 ` Lars-Peter Clausen
                   ` (5 more replies)
  0 siblings, 6 replies; 35+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-01-21 13:37 UTC (permalink / raw)
  To: linux-iio
  Cc: linux-kernel, linux-samsung-soc, dianders, gregkh, naveenkrishna.ch

This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
Also adds the Documentation for device tree bindings.

Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
 .../bindings/arm/samsung/exynos5-adc.txt           |   20 +
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos5_adc.c                      |  412 ++++++++++++++++++++
 4 files changed, 440 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
 create mode 100644 drivers/iio/adc/exynos5_adc.c

diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
new file mode 100644
index 0000000..069639e
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
@@ -0,0 +1,20 @@
+Samsung Exynos5 Analog to Digital Converter bindings
+
+Required properties:
+- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
+- reg:			Contains ADC register address range (base address and
+			length).
+- interrupts: 		Contains the interrupt information for the timer. The
+			format is being dependent on which interrupt controller
+			the Samsung device uses.
+
+Example:
+
+adc@12D10000 {
+	compatible = "samsung,exynos5250-adc";
+	reg = <0x12D10000 0x100>;
+	interrupts = <0 106 0>;
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges;
+};
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index fe822a1..33ceabf 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,13 @@ config AT91_ADC
 	help
 	  Say yes here to build support for Atmel AT91 ADC.
 
+config EXYNOS5_ADC
+	bool "Exynos5 ADC driver support"
+	help
+	  Core support for the ADC block found in the Samsung EXYNOS5 series
+	  of SoCs for drivers such as the touchscreen and hwmon to use to share
+	  this resource.
+
 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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
new file mode 100644
index 0000000..cd33ea2
--- /dev/null
+++ b/drivers/iio/adc/exynos5_adc.c
@@ -0,0 +1,412 @@
+/*
+ *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
+ *
+ *  8-channel, 10/12-bit ADC
+ *
+ *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+/* Samsung ADC registers definitions */
+#define EXYNOS_ADC_CON(x)	((x) + 0x00)
+#define EXYNOS_ADC_DLY(x)	((x) + 0x08)
+#define EXYNOS_ADC_DATX(x)	((x) + 0x0C)
+#define EXYNOS_ADC_INTCLR(x)	((x) + 0x18)
+#define EXYNOS_ADC_MUX(x)	((x) + 0x1c)
+
+/* Bit definitions for EXYNOS_ADC_MUX: */
+#define ADC_RES		(1u << 16)
+#define ADC_ECFLG	(1u << 15)
+#define ADC_PRSCEN	(1u << 14)
+#define ADC_PRSCLV(x)	(((x) & 0xFF) << 6)
+#define ADC_PRSCVLMASK	(0xFF << 6)
+#define ADC_STANDBY	(1u << 2)
+#define ADC_READ_START	(1u << 1)
+#define ADC_EN_START	(1u << 0)
+
+#define ADC_DATX_MASK	0xFFF
+
+struct exynos5_adc {
+	void __iomem		*regs;
+	struct clk		*clk;
+	unsigned int		irq;
+	struct regulator	*vdd;
+
+	struct completion	completion;
+
+	struct iio_map		*map;
+	u32			value;
+	u32			prescale;
+};
+
+static const struct of_device_id exynos5_adc_match[] = {
+	{ .compatible = "samsung,exynos5250-adc" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos5_adc_match);
+
+/* default maps used by iio consumer (ex: ntc-thermistor driver) */
+static struct iio_map exynos5_adc_iio_maps[] = {
+	{
+		.consumer_dev_name = "0.ncp15wb473",
+		.consumer_channel = "adc_user3",
+		.adc_channel_label = "adc3",
+	},
+	{},
+};
+
+static int exynos5_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 con;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		mutex_lock(&indio_dev->mlock);
+
+		/* Select the channel to be used */
+		writel(chan->address, EXYNOS_ADC_MUX(info->regs));
+		/* Trigger conversion */
+		con = readl(EXYNOS_ADC_CON(info->regs));
+		writel(con | ADC_EN_START, EXYNOS_ADC_CON(info->regs));
+
+		wait_for_completion(&info->completion);
+		*val = info->value;
+
+		mutex_unlock(&indio_dev->mlock);
+
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
+{
+	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
+
+	/* Read value and clear irq */
+	info->value = readl(EXYNOS_ADC_DATX(info->regs)) &
+				ADC_DATX_MASK;
+	writel(0, EXYNOS_ADC_INTCLR(info->regs));
+
+	complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 ret;
+
+	mutex_lock(&indio_dev->mlock);
+
+	if (readval != NULL) {
+		if (reg == 0x08)
+			ret = readl(EXYNOS_ADC_DLY(info->regs));
+		else if (reg == 0x0C)
+			ret = readl(EXYNOS_ADC_DATX(info->regs));
+		else if (reg == 0x1C)
+			ret = readl(EXYNOS_ADC_MUX(info->regs));
+		else
+			ret = readl(EXYNOS_ADC_CON(info->regs));
+
+		readval = &ret;
+	} else
+		ret = -EINVAL;
+
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info exynos5_adc_iio_info = {
+	.read_raw = &exynos5_read_raw,
+	.debugfs_reg_access = &exynos5_adc_reg_access,
+	.driver_module = THIS_MODULE,
+};
+
+#define EXYNOS_ADC_CHANNEL(_index, _id) {		\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = _index,				\
+	.address = _index,				\
+	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	.datasheet_name = _id,				\
+}
+
+static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
+	EXYNOS_ADC_CHANNEL(0, "adc0"),
+	EXYNOS_ADC_CHANNEL(1, "adc1"),
+	EXYNOS_ADC_CHANNEL(2, "adc2"),
+	EXYNOS_ADC_CHANNEL(3, "adc3"),
+	EXYNOS_ADC_CHANNEL(4, "adc4"),
+	EXYNOS_ADC_CHANNEL(5, "adc5"),
+	EXYNOS_ADC_CHANNEL(6, "adc6"),
+	EXYNOS_ADC_CHANNEL(7, "adc7"),
+};
+
+static int exynos5_adc_iio_map_register(struct iio_dev *indio_dev,
+				struct exynos5_adc *adc)
+{
+	int ret;
+
+	adc->map = exynos5_adc_iio_maps;
+
+	ret = iio_map_array_register(indio_dev, adc->map);
+	if (ret) {
+		dev_err(&indio_dev->dev, "iio map err: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static inline void exynos5_adc_iio_map_unregister(struct iio_dev *indio_dev,
+				struct exynos5_adc *adc)
+{
+	iio_map_array_unregister(indio_dev, adc->map);
+}
+
+static int exynos5_adc_remove_devices(struct device *dev, void *c)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static int exynos5_adc_probe(struct platform_device *pdev)
+{
+	struct exynos5_adc *info = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *iodev = NULL;
+	struct resource	*mem;
+	int ret = -ENODEV;
+	int irq;
+	u32 con;
+
+	iodev = iio_device_alloc(sizeof(struct exynos5_adc));
+	if (!iodev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(iodev);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "no mem resource?\n");
+		goto err_iio;
+	}
+
+	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
+	if (!info->regs) {
+		dev_err(&pdev->dev, "cannot map EXYNOS5 ADC registers\n");
+		ret = -ENOMEM;
+		goto err_iio;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = irq;
+		goto err_iio;
+	}
+
+	info->irq = irq;
+
+	ret = devm_request_irq(&pdev->dev, info->irq, exynos5_adc_isr,
+					0, dev_name(&pdev->dev), info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "cannot request Samsung ADC IRQ %d\n",
+							info->irq);
+		goto err_iio;
+	}
+
+	info->clk = devm_clk_get(&pdev->dev, "adc");
+	if (IS_ERR(info->clk)) {
+		dev_err(&pdev->dev, "failed getting clock\n");
+		ret = PTR_ERR(info->clk);
+		goto err_iio;
+	}
+
+	clk_enable(info->clk);
+
+	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
+	if (IS_ERR(info->vdd)) {
+		dev_err(&pdev->dev, "operating without regulator \"vdd\" .\n");
+		ret = PTR_ERR(info->vdd);
+		goto err_iio;
+	}
+
+	platform_set_drvdata(pdev, iodev);
+
+	init_completion(&info->completion);
+
+	iodev->name = dev_name(&pdev->dev);
+	iodev->dev.parent = &pdev->dev;
+	iodev->dev.of_node = pdev->dev.of_node;
+	iodev->info = &exynos5_adc_iio_info;
+	iodev->modes = INDIO_DIRECT_MODE;
+	iodev->channels = exynos5_adc_iio_channels;
+	iodev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
+
+	/* Enable prescaler and set platform default */
+	info->prescale = ADC_PRSCLV(49);
+	con = info->prescale | ADC_PRSCEN;
+
+	/* Enable 12-bit ADC resolution */
+	con |= ADC_RES;
+
+	writel(con, EXYNOS_ADC_CON(info->regs));
+
+	ret = exynos5_adc_iio_map_register(iodev, info);
+	if (ret)
+		goto err_iio;
+
+	ret = iio_device_register(iodev);
+	if (ret)
+		goto err_map;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		goto err_iio_dev;
+
+	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to add child nodes\n");
+		goto err_of_populate;
+	}
+
+	dev_info(&pdev->dev, "EXYNOS5 ADC driver loaded.\n");
+
+	return 0;
+
+err_of_populate:
+	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
+err_iio_dev:
+	iio_device_unregister(iodev);
+err_map:
+	exynos5_adc_iio_map_unregister(iodev, info);
+err_iio:
+	iio_device_free(iodev);
+	return ret;
+}
+
+static int exynos5_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *iodev = platform_get_drvdata(pdev);
+	struct exynos5_adc *info = iio_priv(iodev);
+
+	platform_set_drvdata(pdev, NULL);
+	iio_device_unregister(iodev);
+	exynos5_adc_iio_map_unregister(iodev, info);
+	iio_device_free(iodev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos5_adc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = container_of(dev,
+			struct platform_device, dev);
+	struct exynos5_adc *info = platform_get_drvdata(pdev);
+	u32 con;
+
+	con = readl(EXYNOS_ADC_CON(info->regs));
+	con |= ADC_STANDBY;
+	writel(con, EXYNOS_ADC_CON(info->regs));
+
+	clk_disable(info->clk);
+	regulator_disable(info->vdd);
+
+	return 0;
+}
+
+static int exynos5_adc_resume(struct device *dev)
+{
+	struct platform_device *pdev = container_of(dev,
+			struct platform_device, dev);
+	struct exynos5_adc *info = platform_get_drvdata(pdev);
+	unsigned int con;
+	int ret;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		return ret;
+
+	clk_enable(info->clk);
+
+	/* TODO: Enable prescalar */
+	con = info->prescale | ADC_PRSCEN;
+	writel(con | ADC_RES, EXYNOS_ADC_CON(info->regs));
+
+	return 0;
+}
+
+#else
+#define s3c_adc_suspend NULL
+#define s3c_adc_resume NULL
+#endif
+
+static const struct dev_pm_ops adc_pm_ops = {
+	.suspend	= exynos5_adc_suspend,
+	.resume		= exynos5_adc_resume,
+};
+
+static struct platform_driver exynos5_adc_driver = {
+	.probe		= exynos5_adc_probe,
+	.remove		= exynos5_adc_remove,
+	.driver		= {
+		.name	= "exynos5-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(exynos5_adc_match),
+		.pm	= &adc_pm_ops,
+	},
+};
+
+module_platform_driver(exynos5_adc_driver);
+
+MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
@ 2013-01-22  9:44 ` Lars-Peter Clausen
  2013-01-22 14:03   ` Naveen Krishna Ch
  2013-01-22 14:27 ` Naveen Krishna Chatradhi
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-01-22  9:44 UTC (permalink / raw)
  To: Naveen Krishna Chatradhi
  Cc: linux-iio, linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch

Hi,

On 01/21/2013 02:37 PM, Naveen Krishna Chatradhi wrote:
> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
> Also adds the Documentation for device tree bindings.
>[...]
> diff --git a/drivers/iio/adc/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
> new file mode 100644
> index 0000000..cd33ea2
> --- /dev/null
> +++ b/drivers/iio/adc/exynos5_adc.c
> @@ -0,0 +1,412 @@
> +/*
> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
> + *
> + *  8-channel, 10/12-bit ADC
> + *
> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> + *
> + *  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.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; if not, write to the Free Software
> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +/* Samsung ADC registers definitions */
> +#define EXYNOS_ADC_CON(x)	((x) + 0x00)
> +#define EXYNOS_ADC_DLY(x)	((x) + 0x08)
> +#define EXYNOS_ADC_DATX(x)	((x) + 0x0C)
> +#define EXYNOS_ADC_INTCLR(x)	((x) + 0x18)
> +#define EXYNOS_ADC_MUX(x)	((x) + 0x1c)
> +
> +/* Bit definitions for EXYNOS_ADC_MUX: */
> +#define ADC_RES		(1u << 16)
> +#define ADC_ECFLG	(1u << 15)
> +#define ADC_PRSCEN	(1u << 14)
> +#define ADC_PRSCLV(x)	(((x) & 0xFF) << 6)
> +#define ADC_PRSCVLMASK	(0xFF << 6)
> +#define ADC_STANDBY	(1u << 2)
> +#define ADC_READ_START	(1u << 1)
> +#define ADC_EN_START	(1u << 0)

You are a bit inconsistent with your prefixes, sometimes you use EXYNOS_ADC,
sometimes just ADC, it would be better to use the same prefix all the time.

> +
> +#define ADC_DATX_MASK	0xFFF
> +
> +struct exynos5_adc {
> +	void __iomem		*regs;
> +	struct clk		*clk;
> +	unsigned int		irq;
> +	struct regulator	*vdd;
> +
> +	struct completion	completion;
> +
> +	struct iio_map		*map;
> +	u32			value;
> +	u32			prescale;
> +};
> +
> +static const struct of_device_id exynos5_adc_match[] = {
> +	{ .compatible = "samsung,exynos5250-adc" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
> +
> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
> +static struct iio_map exynos5_adc_iio_maps[] = {
> +	{
> +		.consumer_dev_name = "0.ncp15wb473",
> +		.consumer_channel = "adc_user3",
> +		.adc_channel_label = "adc3",
> +	},
> +	{},
> +};

Hm... this should not be in the driver itself.

> +
> +static int exynos5_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 con;
> +
> +	if (mask == IIO_CHAN_INFO_RAW) {
> +		mutex_lock(&indio_dev->mlock);
> +
> +		/* Select the channel to be used */
> +		writel(chan->address, EXYNOS_ADC_MUX(info->regs));
> +		/* Trigger conversion */
> +		con = readl(EXYNOS_ADC_CON(info->regs));
> +		writel(con | ADC_EN_START, EXYNOS_ADC_CON(info->regs));
> +
> +		wait_for_completion(&info->completion);
> +		*val = info->value;
> +
> +		mutex_unlock(&indio_dev->mlock);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
> +{
> +	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
> +
> +	/* Read value and clear irq */
> +	info->value = readl(EXYNOS_ADC_DATX(info->regs)) &
> +				ADC_DATX_MASK;
> +	writel(0, EXYNOS_ADC_INTCLR(info->regs));
> +
> +	complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 ret;
> +
> +	mutex_lock(&indio_dev->mlock);

Do we really need to take the lock here?

> +
> +	if (readval != NULL) {
> +		if (reg == 0x08)
> +			ret = readl(EXYNOS_ADC_DLY(info->regs));
> +		else if (reg == 0x0C)
> +			ret = readl(EXYNOS_ADC_DATX(info->regs));
> +		else if (reg == 0x1C)
> +			ret = readl(EXYNOS_ADC_MUX(info->regs));
> +		else
> +			ret = readl(EXYNOS_ADC_CON(info->regs));

How about
		ret = readl(info->regs + reg)

instead of the whole if ... else if ... else if ...

> +
> +		readval = &ret;

Uhm, I'm pretty sure that the line above does not work. At least it won't do
what you want it to do.

> +	} else
> +		ret = -EINVAL;
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info exynos5_adc_iio_info = {
> +	.read_raw = &exynos5_read_raw,
> +	.debugfs_reg_access = &exynos5_adc_reg_access,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define EXYNOS_ADC_CHANNEL(_index, _id) {		\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.channel = _index,				\
> +	.address = _index,				\
> +	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	.datasheet_name = _id,				\
> +}
> +
> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
> +	EXYNOS_ADC_CHANNEL(0, "adc0"),
> +	EXYNOS_ADC_CHANNEL(1, "adc1"),
> +	EXYNOS_ADC_CHANNEL(2, "adc2"),
> +	EXYNOS_ADC_CHANNEL(3, "adc3"),
> +	EXYNOS_ADC_CHANNEL(4, "adc4"),
> +	EXYNOS_ADC_CHANNEL(5, "adc5"),
> +	EXYNOS_ADC_CHANNEL(6, "adc6"),
> +	EXYNOS_ADC_CHANNEL(7, "adc7"),
> +};
> +
> +static int exynos5_adc_iio_map_register(struct iio_dev *indio_dev,
> +				struct exynos5_adc *adc)
> +{
> +	int ret;
> +
> +	adc->map = exynos5_adc_iio_maps;
> +
> +	ret = iio_map_array_register(indio_dev, adc->map);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "iio map err: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}

Just use iio_map_array_register instead of exynos5_adc_iio_map_register

> +
> +static inline void exynos5_adc_iio_map_unregister(struct iio_dev *indio_dev,
> +				struct exynos5_adc *adc)
> +{
> +	iio_map_array_unregister(indio_dev, adc->map);
> +}

Just use iio_map_array_unregister directly instead of
exynos5_adc_iio_map_unregister

> +
> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	platform_device_unregister(pdev);
> +
> +	return 0;
> +}

Again, a single line wrapper function...

> +
> +static int exynos5_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos5_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *iodev = NULL;

this is usually called indio_dev, would be good to be consistent across dirvers.

> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +	u32 con;
> +
> +	iodev = iio_device_alloc(sizeof(struct exynos5_adc));
> +	if (!iodev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(iodev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!mem) {

devm_request_and_ioremap will check if mem is NULL, so you don't need to do
this manually.

> +		dev_err(&pdev->dev, "no mem resource?\n");
> +		goto err_iio;
> +	}
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> +	if (!info->regs) {
> +		dev_err(&pdev->dev, "cannot map EXYNOS5 ADC registers\n");

devm_request_and_ioremap already prints an error message.

> +		ret = -ENOMEM;
> +		goto err_iio;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	ret = devm_request_irq(&pdev->dev, info->irq, exynos5_adc_isr,
> +					0, dev_name(&pdev->dev), info);

Using devm_request_irq is racy in this case, since the IRQ will be freed
after the device has been freed, while it needs to be freed before the devie
has been freed.

> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "cannot request Samsung ADC IRQ %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock\n");

I think it is a good idea to include the error code in the error message.

> +		ret = PTR_ERR(info->clk);
> +		goto err_iio;
> +	}
> +
> +	clk_enable(info->clk);
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "operating without regulator \"vdd\" .\n");

Same here.

> +		ret = PTR_ERR(info->vdd);
> +		goto err_iio;
> +	}
> +
> +	platform_set_drvdata(pdev, iodev);
> +
> +	init_completion(&info->completion);
> +
> +	iodev->name = dev_name(&pdev->dev);
> +	iodev->dev.parent = &pdev->dev;
> +	iodev->dev.of_node = pdev->dev.of_node;
> +	iodev->info = &exynos5_adc_iio_info;
> +	iodev->modes = INDIO_DIRECT_MODE;
> +	iodev->channels = exynos5_adc_iio_channels;
> +	iodev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
> +
> +	/* Enable prescaler and set platform default */
> +	info->prescale = ADC_PRSCLV(49);
> +	con = info->prescale | ADC_PRSCEN;
> +
> +	/* Enable 12-bit ADC resolution */
> +	con |= ADC_RES;
> +
> +	writel(con, EXYNOS_ADC_CON(info->regs));
> +
> +	ret = exynos5_adc_iio_map_register(iodev, info);
> +	if (ret)
> +		goto err_iio;
> +
> +	ret = iio_device_register(iodev);
> +	if (ret)
> +		goto err_map;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;
> +
> +	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);

Which kind of child nodes to you expect it to have? There is nothing about
this in your dt bindings documentation.

> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to add child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	dev_info(&pdev->dev, "EXYNOS5 ADC driver loaded.\n");

That's just noise, please remove it.

> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
> +err_iio_dev:
> +	iio_device_unregister(iodev);
> +err_map:
> +	exynos5_adc_iio_map_unregister(iodev, info);
> +err_iio:
> +	iio_device_free(iodev);
> +	return ret;
> +}
> +
> +static int exynos5_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *iodev = platform_get_drvdata(pdev);
> +	struct exynos5_adc *info = iio_priv(iodev);
> +
> +	platform_set_drvdata(pdev, NULL);

The line above should not be needed

> +	iio_device_unregister(iodev);
> +	exynos5_adc_iio_map_unregister(iodev, info);
> +	iio_device_free(iodev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM

CONFIG_PM_SLEEP

> +static int exynos5_adc_suspend(struct device *dev)
> +{
> +	struct platform_device *pdev = container_of(dev,
> +			struct platform_device, dev);

to_platform_device

> +	struct exynos5_adc *info = platform_get_drvdata(pdev);

Although using dev_get_data(dev) here would also be ok I guess.

> +	u32 con;
> +
> +	con = readl(EXYNOS_ADC_CON(info->regs));
> +	con |= ADC_STANDBY;
> +	writel(con, EXYNOS_ADC_CON(info->regs));
> +
> +	clk_disable(info->clk);

clk_unprepare_disable

> +	regulator_disable(info->vdd);
> +
> +	return 0;
> +}
> +
> +static int exynos5_adc_resume(struct device *dev)
> +{
> +	struct platform_device *pdev = container_of(dev,
> +			struct platform_device, dev);

Same as above.

> +	struct exynos5_adc *info = platform_get_drvdata(pdev);
> +	unsigned int con;
> +	int ret;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		return ret;
> +
> +	clk_enable(info->clk);

clk_prepare_enable

> +
> +	/* TODO: Enable prescalar */
> +	con = info->prescale | ADC_PRSCEN;

I guess you need to enable the prescaler here ;) (or removed the TODO comment)

> +	writel(con | ADC_RES, EXYNOS_ADC_CON(info->regs));
> +
> +	return 0;
> +}
> +
> +#else
> +#define s3c_adc_suspend NULL
> +#define s3c_adc_resume NULL
> +#endif
> +
> +static const struct dev_pm_ops adc_pm_ops = {
> +	.suspend	= exynos5_adc_suspend,
> +	.resume		= exynos5_adc_resume,
> +};

static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops, exynos5_adc_suspend,
	exynos5_adc_resume);

> +
> +static struct platform_driver exynos5_adc_driver = {
> +	.probe		= exynos5_adc_probe,
> +	.remove		= exynos5_adc_remove,
> +	.driver		= {
> +		.name	= "exynos5-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = of_match_ptr(exynos5_adc_match),
> +		.pm	= &adc_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(exynos5_adc_driver);
> +
> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> +MODULE_LICENSE("GPL");


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-22  9:44 ` Lars-Peter Clausen
@ 2013-01-22 14:03   ` Naveen Krishna Ch
  0 siblings, 0 replies; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-01-22 14:03 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh

Hello All,

On 22 January 2013 15:14, Lars-Peter Clausen <lars@metafoo.de> wrote:
>
> Hi,
>
> On 01/21/2013 02:37 PM, Naveen Krishna Chatradhi wrote:
> > This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
> > Also adds the Documentation for device tree bindings.
> >[...]
> > diff --git a/drivers/iio/adc/exynos5_adc.c
> > b/drivers/iio/adc/exynos5_adc.c
> > new file mode 100644
> > index 0000000..cd33ea2
> > --- /dev/null
> > +++ b/drivers/iio/adc/exynos5_adc.c
> > @@ -0,0 +1,412 @@
> > +/*
> > + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
> > + *
> > + *  8-channel, 10/12-bit ADC
> > + *
> > + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> > + *
> > + *  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.
> > + *
> > + *  This program is distributed in the hope that it will be useful,
> > + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + *  GNU General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > + *  along with this program; if not, write to the Free Software
> > + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/delay.h>
> > +#include <linux/kernel.h>
> > +#include <linux/slab.h>
> > +#include <linux/io.h>
> > +#include <linux/clk.h>
> > +#include <linux/completion.h>
> > +#include <linux/of.h>
> > +#include <linux/of_irq.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/of_platform.h>
> > +
> > +#include <linux/iio/iio.h>
> > +#include <linux/iio/machine.h>
> > +#include <linux/iio/driver.h>
> > +
> > +/* Samsung ADC registers definitions */
> > +#define EXYNOS_ADC_CON(x)    ((x) + 0x00)
> > +#define EXYNOS_ADC_DLY(x)    ((x) + 0x08)
> > +#define EXYNOS_ADC_DATX(x)   ((x) + 0x0C)
> > +#define EXYNOS_ADC_INTCLR(x) ((x) + 0x18)
> > +#define EXYNOS_ADC_MUX(x)    ((x) + 0x1c)
> > +
> > +/* Bit definitions for EXYNOS_ADC_MUX: */
> > +#define ADC_RES              (1u << 16)
> > +#define ADC_ECFLG    (1u << 15)
> > +#define ADC_PRSCEN   (1u << 14)
> > +#define ADC_PRSCLV(x)        (((x) & 0xFF) << 6)
> > +#define ADC_PRSCVLMASK       (0xFF << 6)
> > +#define ADC_STANDBY  (1u << 2)
> > +#define ADC_READ_START       (1u << 1)
> > +#define ADC_EN_START (1u << 0)
>
> You are a bit inconsistent with your prefixes, sometimes you use
> EXYNOS_ADC,
> sometimes just ADC, it would be better to use the same prefix all the
> time.
Will correct that.
>
> > +
> > +#define ADC_DATX_MASK        0xFFF
> > +
> > +struct exynos5_adc {
> > +     void __iomem            *regs;
> > +     struct clk              *clk;
> > +     unsigned int            irq;
> > +     struct regulator        *vdd;
> > +
> > +     struct completion       completion;
> > +
> > +     struct iio_map          *map;
> > +     u32                     value;
> > +     u32                     prescale;
> > +};
> > +
> > +static const struct of_device_id exynos5_adc_match[] = {
> > +     { .compatible = "samsung,exynos5250-adc" },
> > +     {},
> > +};
> > +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
> > +
> > +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
> > +static struct iio_map exynos5_adc_iio_maps[] = {
> > +     {
> > +             .consumer_dev_name = "0.ncp15wb473",
> > +             .consumer_channel = "adc_user3",
> > +             .adc_channel_label = "adc3",
> > +     },
> > +     {},
> > +};
>
> Hm... this should not be in the driver itself.
I've taken the example of "drivers/iio/adc/lp8788_adc.c"
I think this one is a tightly coupled driver.

Kindly, suggest me a better example.

>
> > +
> > +static int exynos5_read_raw(struct iio_dev *indio_dev,
> > +                             struct iio_chan_spec const *chan,
> > +                             int *val,
> > +                             int *val2,
> > +                             long mask)
> > +{
> > +     struct exynos5_adc *info = iio_priv(indio_dev);
> > +     u32 con;
> > +
> > +     if (mask == IIO_CHAN_INFO_RAW) {
> > +             mutex_lock(&indio_dev->mlock);
> > +
> > +             /* Select the channel to be used */
> > +             writel(chan->address, EXYNOS_ADC_MUX(info->regs));
> > +             /* Trigger conversion */
> > +             con = readl(EXYNOS_ADC_CON(info->regs));
> > +             writel(con | ADC_EN_START, EXYNOS_ADC_CON(info->regs));
> > +
> > +             wait_for_completion(&info->completion);
> > +             *val = info->value;
> > +
> > +             mutex_unlock(&indio_dev->mlock);
> > +
> > +             return IIO_VAL_INT;
> > +     }
> > +
> > +     return -EINVAL;
> > +}
> > +
> > +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
> > +{
> > +     struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
> > +
> > +     /* Read value and clear irq */
> > +     info->value = readl(EXYNOS_ADC_DATX(info->regs)) &
> > +                             ADC_DATX_MASK;
> > +     writel(0, EXYNOS_ADC_INTCLR(info->regs));
> > +
> > +     complete(&info->completion);
> > +
> > +     return IRQ_HANDLED;
> > +}
> > +
> > +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
> > +                           unsigned reg, unsigned writeval,
> > +                           unsigned *readval)
> > +{
> > +     struct exynos5_adc *info = iio_priv(indio_dev);
> > +     u32 ret;
> > +
> > +     mutex_lock(&indio_dev->mlock);
>
> Do we really need to take the lock here?
Took from the reference
>
> > +
> > +     if (readval != NULL) {
> > +             if (reg == 0x08)
> > +                     ret = readl(EXYNOS_ADC_DLY(info->regs));
> > +             else if (reg == 0x0C)
> > +                     ret = readl(EXYNOS_ADC_DATX(info->regs));
> > +             else if (reg == 0x1C)
> > +                     ret = readl(EXYNOS_ADC_MUX(info->regs));
> > +             else
> > +                     ret = readl(EXYNOS_ADC_CON(info->regs));
>
> How about
>                 ret = readl(info->regs + reg)
>
> instead of the whole if ... else if ... else if ...
Sure
>
> > +
> > +             readval = &ret;
>
> Uhm, I'm pretty sure that the line above does not work. At least it won't
> do
> what you want it to do.
Right, sorry
>
> > +     } else
> > +             ret = -EINVAL;
> > +
> > +     mutex_unlock(&indio_dev->mlock);
> > +
> > +     return ret;
> > +}
> > +
> > +static const struct iio_info exynos5_adc_iio_info = {
> > +     .read_raw = &exynos5_read_raw,
> > +     .debugfs_reg_access = &exynos5_adc_reg_access,
> > +     .driver_module = THIS_MODULE,
> > +};
> > +
> > +#define EXYNOS_ADC_CHANNEL(_index, _id) {            \
> > +     .type = IIO_VOLTAGE,                            \
> > +     .indexed = 1,                                   \
> > +     .channel = _index,                              \
> > +     .address = _index,                              \
> > +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
> > +     .datasheet_name = _id,                          \
> > +}
> > +
> > +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
> > +     EXYNOS_ADC_CHANNEL(0, "adc0"),
> > +     EXYNOS_ADC_CHANNEL(1, "adc1"),
> > +     EXYNOS_ADC_CHANNEL(2, "adc2"),
> > +     EXYNOS_ADC_CHANNEL(3, "adc3"),
> > +     EXYNOS_ADC_CHANNEL(4, "adc4"),
> > +     EXYNOS_ADC_CHANNEL(5, "adc5"),
> > +     EXYNOS_ADC_CHANNEL(6, "adc6"),
> > +     EXYNOS_ADC_CHANNEL(7, "adc7"),
The next version has 10 channels and future version are gonna support
more that that. May be my earlier doubt will fix this as well.
> > +};
> > +
> > +static int exynos5_adc_iio_map_register(struct iio_dev *indio_dev,
> > +                             struct exynos5_adc *adc)
> > +{
> > +     int ret;
> > +
> > +     adc->map = exynos5_adc_iio_maps;
> > +
> > +     ret = iio_map_array_register(indio_dev, adc->map);
> > +     if (ret) {
> > +             dev_err(&indio_dev->dev, "iio map err: %d\n", ret);
> > +             return ret;
> > +     }
> > +
> > +     return 0;
> > +}
>
> Just use iio_map_array_register instead of exynos5_adc_iio_map_register
>
> > +
> > +static inline void exynos5_adc_iio_map_unregister(struct iio_dev
> > *indio_dev,
> > +                             struct exynos5_adc *adc)
> > +{
> > +     iio_map_array_unregister(indio_dev, adc->map);
> > +}
>
> Just use iio_map_array_unregister directly instead of
> exynos5_adc_iio_map_unregister
>
> > +
> > +static int exynos5_adc_remove_devices(struct device *dev, void *c)
> > +{
> > +     struct platform_device *pdev = to_platform_device(dev);
> > +
> > +     platform_device_unregister(pdev);
> > +
> > +     return 0;
> > +}
>
> Again, a single line wrapper function...
This is a call back. So..
>
> > +
> > +static int exynos5_adc_probe(struct platform_device *pdev)
> > +{
> > +     struct exynos5_adc *info = NULL;
> > +     struct device_node *np = pdev->dev.of_node;
> > +     struct iio_dev *iodev = NULL;
>
> this is usually called indio_dev, would be good to be consistent across
> dirvers.
Sure.
>
> > +     struct resource *mem;
> > +     int ret = -ENODEV;
> > +     int irq;
> > +     u32 con;
> > +
> > +     iodev = iio_device_alloc(sizeof(struct exynos5_adc));
> > +     if (!iodev) {
> > +             dev_err(&pdev->dev, "failed allocating iio device\n");
> > +             return -ENOMEM;
> > +     }
> > +
> > +     info = iio_priv(iodev);
> > +
> > +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +     if (!mem) {
>
> devm_request_and_ioremap will check if mem is NULL, so you don't need to
> do
> this manually.
>
> > +             dev_err(&pdev->dev, "no mem resource?\n");
> > +             goto err_iio;
> > +     }
> > +
> > +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> > +     if (!info->regs) {
> > +             dev_err(&pdev->dev, "cannot map EXYNOS5 ADC registers\n");
>
> devm_request_and_ioremap already prints an error message.
>
> > +             ret = -ENOMEM;
> > +             goto err_iio;
> > +     }
> > +
> > +     irq = platform_get_irq(pdev, 0);
> > +     if (irq < 0) {
> > +             dev_err(&pdev->dev, "no irq resource?\n");
> > +             ret = irq;
> > +             goto err_iio;
> > +     }
> > +
> > +     info->irq = irq;
> > +
> > +     ret = devm_request_irq(&pdev->dev, info->irq, exynos5_adc_isr,
> > +                                     0, dev_name(&pdev->dev), info);
>
> Using devm_request_irq is racy in this case, since the IRQ will be freed
> after the device has been freed, while it needs to be freed before the
> devie
> has been freed.
>
> > +     if (ret < 0) {
> > +             dev_err(&pdev->dev, "cannot request Samsung ADC IRQ %d\n",
> > +                                                     info->irq);
> > +             goto err_iio;
> > +     }
> > +
> > +     info->clk = devm_clk_get(&pdev->dev, "adc");
> > +     if (IS_ERR(info->clk)) {
> > +             dev_err(&pdev->dev, "failed getting clock\n");
>
> I think it is a good idea to include the error code in the error message.
>
> > +             ret = PTR_ERR(info->clk);
> > +             goto err_iio;
> > +     }
> > +
> > +     clk_enable(info->clk);
> > +
> > +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> > +     if (IS_ERR(info->vdd)) {
> > +             dev_err(&pdev->dev, "operating without regulator \"vdd\"
> > .\n");
>
> Same here.
Sure
>
> > +             ret = PTR_ERR(info->vdd);
> > +             goto err_iio;
> > +     }
> > +
> > +     platform_set_drvdata(pdev, iodev);
> > +
> > +     init_completion(&info->completion);
> > +
> > +     iodev->name = dev_name(&pdev->dev);
> > +     iodev->dev.parent = &pdev->dev;
> > +     iodev->dev.of_node = pdev->dev.of_node;
> > +     iodev->info = &exynos5_adc_iio_info;
> > +     iodev->modes = INDIO_DIRECT_MODE;
> > +     iodev->channels = exynos5_adc_iio_channels;
> > +     iodev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
> > +
> > +     /* Enable prescaler and set platform default */
> > +     info->prescale = ADC_PRSCLV(49);
> > +     con = info->prescale | ADC_PRSCEN;
> > +
> > +     /* Enable 12-bit ADC resolution */
> > +     con |= ADC_RES;
> > +
> > +     writel(con, EXYNOS_ADC_CON(info->regs));
> > +
> > +     ret = exynos5_adc_iio_map_register(iodev, info);
> > +     if (ret)
> > +             goto err_iio;
> > +
> > +     ret = iio_device_register(iodev);
> > +     if (ret)
> > +             goto err_map;
> > +
> > +     ret = regulator_enable(info->vdd);
> > +     if (ret)
> > +             goto err_iio_dev;
> > +
> > +     ret = of_platform_populate(np, exynos5_adc_match, NULL,
> > &pdev->dev);
>
> Which kind of child nodes to you expect it to have? There is nothing about
> this in your dt bindings documentation.
I've tested with ntc thermistors (hwmon device) on a chromebook.
>
> > +     if (ret < 0) {
> > +             dev_err(&pdev->dev, "failed to add child nodes\n");
> > +             goto err_of_populate;
> > +     }
> > +
> > +     dev_info(&pdev->dev, "EXYNOS5 ADC driver loaded.\n");
>
> That's just noise, please remove it.
>
> > +
> > +     return 0;
> > +
> > +err_of_populate:
> > +     device_for_each_child(&pdev->dev, NULL,
> > exynos5_adc_remove_devices);
> > +err_iio_dev:
> > +     iio_device_unregister(iodev);
> > +err_map:
> > +     exynos5_adc_iio_map_unregister(iodev, info);
> > +err_iio:
> > +     iio_device_free(iodev);
> > +     return ret;
> > +}
> > +
> > +static int exynos5_adc_remove(struct platform_device *pdev)
> > +{
> > +     struct iio_dev *iodev = platform_get_drvdata(pdev);
> > +     struct exynos5_adc *info = iio_priv(iodev);
> > +
> > +     platform_set_drvdata(pdev, NULL);
>
> The line above should not be needed
>
> > +     iio_device_unregister(iodev);
> > +     exynos5_adc_iio_map_unregister(iodev, info);
> > +     iio_device_free(iodev);
> > +
> > +     return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM
>
> CONFIG_PM_SLEEP
>
> > +static int exynos5_adc_suspend(struct device *dev)
> > +{
> > +     struct platform_device *pdev = container_of(dev,
> > +                     struct platform_device, dev);
>
> to_platform_device
>
> > +     struct exynos5_adc *info = platform_get_drvdata(pdev);
>
> Although using dev_get_data(dev) here would also be ok I guess.
>
> > +     u32 con;
> > +
> > +     con = readl(EXYNOS_ADC_CON(info->regs));
> > +     con |= ADC_STANDBY;
> > +     writel(con, EXYNOS_ADC_CON(info->regs));
> > +
> > +     clk_disable(info->clk);
>
> clk_unprepare_disable
Right, sure
>
> > +     regulator_disable(info->vdd);
> > +
> > +     return 0;
> > +}
> > +
> > +static int exynos5_adc_resume(struct device *dev)
> > +{
> > +     struct platform_device *pdev = container_of(dev,
> > +                     struct platform_device, dev);
>
> Same as above.
>
> > +     struct exynos5_adc *info = platform_get_drvdata(pdev);
> > +     unsigned int con;
> > +     int ret;
> > +
> > +     ret = regulator_enable(info->vdd);
> > +     if (ret)
> > +             return ret;
> > +
> > +     clk_enable(info->clk);
>
> clk_prepare_enable
>
> > +
> > +     /* TODO: Enable prescalar */
> > +     con = info->prescale | ADC_PRSCEN;
>
> I guess you need to enable the prescaler here ;) (or removed the TODO
> comment)
Sorry, missed to remove the comment
>
> > +     writel(con | ADC_RES, EXYNOS_ADC_CON(info->regs));
> > +
> > +     return 0;
> > +}
> > +
> > +#else
> > +#define s3c_adc_suspend NULL
> > +#define s3c_adc_resume NULL
> > +#endif
> > +
> > +static const struct dev_pm_ops adc_pm_ops = {
> > +     .suspend        = exynos5_adc_suspend,
> > +     .resume         = exynos5_adc_resume,
> > +};
>
> static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops, exynos5_adc_suspend,
>         exynos5_adc_resume);
>
> > +
> > +static struct platform_driver exynos5_adc_driver = {
> > +     .probe          = exynos5_adc_probe,
> > +     .remove         = exynos5_adc_remove,
> > +     .driver         = {
> > +             .name   = "exynos5-adc",
> > +             .owner  = THIS_MODULE,
> > +             .of_match_table = of_match_ptr(exynos5_adc_match),
> > +             .pm     = &adc_pm_ops,
> > +     },
> > +};
> > +
> > +module_platform_driver(exynos5_adc_driver);
> > +
> > +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
> > +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> > +MODULE_LICENSE("GPL");
>

Lars, Thanks for your comments.
Will submit a V2 soon..

--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
  2013-01-22  9:44 ` Lars-Peter Clausen
@ 2013-01-22 14:27 ` Naveen Krishna Chatradhi
  2013-01-23  4:58 ` Naveen Krishna Chatradhi
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 35+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-01-22 14:27 UTC (permalink / raw)
  To: linux-iio
  Cc: linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
Also adds the Documentation for device tree bindings.

Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v1:

1. Fixed comments from Lars
2. Added support for ADC on EXYNOS5410

Few doubts regarding the mappings.
Kindly, suggest me better methods.

 .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos5_adc.c                      |  461 ++++++++++++++++++++
 4 files changed, 506 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
 create mode 100644 drivers/iio/adc/exynos5_adc.c

diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
new file mode 100644
index 0000000..9a5b515
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
@@ -0,0 +1,37 @@
+Samsung Exynos5 Analog to Digital Converter bindings
+
+Required properties:
+- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
+- reg:			Contains ADC register address range (base address and
+			length).
+- interrupts: 		Contains the interrupt information for the timer. The
+			format is being dependent on which interrupt controller
+			the Samsung device uses.
+
+Note: child nodes can be added for auto probing from device tree.
+
+Example: adding device info in dtsi file
+
+adc@12D10000 {
+	compatible = "samsung,exynos5250-adc";
+	reg = <0x12D10000 0x100>;
+	interrupts = <0 106 0>;
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges;
+};
+
+
+Example: Adding child nodes in dts file
+
+adc@12D10000 {
+
+	/* NTC thermistor is a hwmon device */
+	ncp15wb473@0 {
+		compatible = "ntc,ncp15wb473";
+		reg = <0x0>;
+		pullup-uV = <1800000>;
+		pullup-ohm = <47000>;
+		pulldown-ohm = <0>;
+	};
+};
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index fe822a1..33ceabf 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,13 @@ config AT91_ADC
 	help
 	  Say yes here to build support for Atmel AT91 ADC.
 
+config EXYNOS5_ADC
+	bool "Exynos5 ADC driver support"
+	help
+	  Core support for the ADC block found in the Samsung EXYNOS5 series
+	  of SoCs for drivers such as the touchscreen and hwmon to use to share
+	  this resource.
+
 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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
new file mode 100644
index 0000000..62643f5
--- /dev/null
+++ b/drivers/iio/adc/exynos5_adc.c
@@ -0,0 +1,461 @@
+/*
+ *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
+ *
+ *  8 ~ 10 channel, 10/12-bit ADC
+ *
+ *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+enum adc_version {
+	ADC_V1,
+	ADC_V2
+};
+
+/* EXYNOS5250 ADC_V1 registers definitions */
+#define ADC_V1_CON(x)		((x) + 0x00)
+#define ADC_V1_DLY(x)		((x) + 0x08)
+#define ADC_V1_DATX(x)		((x) + 0x0C)
+#define ADC_V1_INTCLR(x)	((x) + 0x18)
+#define ADC_V1_MUX(x)		((x) + 0x1c)
+
+/* EXYNOS5410 ADC_V2 registers definitions */
+#define ADC_V2_CON1(x)		((x) + 0x00)
+#define ADC_V2_CON2(x)		((x) + 0x04)
+#define ADC_V2_STAT(x)		((x) + 0x08)
+#define ADC_V2_INT_EN(x)	((x) + 0x10)
+#define ADC_V2_INT_ST(x)	((x) + 0x14)
+#define ADC_V2_VER(x)		((x) + 0x20)
+
+/* Bit definitions for ADC_V1 */
+#define ADC_V1_CON_RES		(1u << 16)
+#define ADC_V1_CON_PRSCEN	(1u << 14)
+#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
+#define ADC_V1_CON_STANDBY	(1u << 2)
+
+/* Bit definitions for ADC_V2 */
+#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
+
+#define ADC_V2_CON2_OSEL	(1u << 10)
+#define ADC_V2_CON2_ESEL	(1u << 9)
+#define ADC_V2_CON2_HIGHF	(1u << 8)
+#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
+#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
+#define ADC_V2_CON2_ACH_MASK	0xF
+
+/* Bit definitions common for ADC_V1 and ADC_V2 */
+#define ADC_V1_CON_EN_START		(1u << 0)
+#define ADC_V1_DATX_MASK	0xFFF
+
+struct exynos5_adc {
+	void __iomem		*regs;
+	struct clk		*clk;
+	unsigned int		irq;
+	struct regulator	*vdd;
+
+	struct completion	completion;
+
+	struct iio_map		*map;
+	u32			value;
+	unsigned int            version;
+};
+
+static const struct of_device_id exynos5_adc_match[] = {
+	{ .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
+	{ .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos5_adc_match);
+
+static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
+	return (unsigned int)match->data;
+}
+
+/* default maps used by iio consumer (ex: ntc-thermistor driver) */
+static struct iio_map exynos5_adc_iio_maps[] = {
+	{
+		.consumer_dev_name = "0.ncp15wb473",
+		.consumer_channel = "adc3",
+		.adc_channel_label = "adc3",
+	},
+	{
+		.consumer_dev_name = "1.ncp15wb473",
+		.consumer_channel = "adc4",
+		.adc_channel_label = "adc4",
+	},
+	{
+		.consumer_dev_name = "2.ncp15wb473",
+		.consumer_channel = "adc5",
+		.adc_channel_label = "adc5",
+	},
+	{
+		.consumer_dev_name = "3.ncp15wb473",
+		.consumer_channel = "adc6",
+		.adc_channel_label = "adc6",
+	},
+	{},
+};
+
+static int exynos5_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 con1, con2;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		mutex_lock(&indio_dev->mlock);
+
+		/* Select the channel to be used and Trigger conversion */
+		if (info->version == ADC_V2) {
+			con2 = readl(ADC_V2_CON2(info->regs));
+			con2 &= ~ADC_V2_CON2_ACH_MASK;
+			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
+			writel(con2, ADC_V2_CON2(info->regs));
+
+			con1 = readl(ADC_V2_CON1(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V2_CON1(info->regs));
+		} else {
+			writel(chan->address, ADC_V1_MUX(info->regs));
+
+			con1 = readl(ADC_V1_CON(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V1_CON(info->regs));
+		}
+
+		wait_for_completion(&info->completion);
+		*val = info->value;
+
+		mutex_unlock(&indio_dev->mlock);
+
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
+{
+	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
+
+	/* Read value */
+	info->value = readl(ADC_V1_DATX(info->regs)) &
+						ADC_V1_DATX_MASK;
+	/* clear irq */
+	if (info->version == ADC_V2)
+		writel(1, ADC_V2_INT_ST(info->regs));
+	else
+		writel(1, ADC_V1_INTCLR(info->regs));
+
+	complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 ret;
+
+	mutex_lock(&indio_dev->mlock);
+
+	if (readval != NULL) {
+		ret = readl(info->regs + reg);
+		*readval = ret;
+	} else
+		ret = -EINVAL;
+
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info exynos5_adc_iio_info = {
+	.read_raw = &exynos5_read_raw,
+	.debugfs_reg_access = &exynos5_adc_reg_access,
+	.driver_module = THIS_MODULE,
+};
+
+#define ADC_V1_CHANNEL(_index, _id) {		\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = _index,				\
+	.address = _index,				\
+	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	.datasheet_name = _id,				\
+}
+
+/** ADC core in EXYNOS5410 has 10 channels,
+ * ADC core in EXYNOS5250 has 8 channels
+*/
+static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
+	ADC_V1_CHANNEL(0, "adc0"),
+	ADC_V1_CHANNEL(1, "adc1"),
+	ADC_V1_CHANNEL(2, "adc2"),
+	ADC_V1_CHANNEL(3, "adc3"),
+	ADC_V1_CHANNEL(4, "adc4"),
+	ADC_V1_CHANNEL(5, "adc5"),
+	ADC_V1_CHANNEL(6, "adc6"),
+	ADC_V1_CHANNEL(7, "adc7"),
+	ADC_V1_CHANNEL(8, "adc8"),
+	ADC_V1_CHANNEL(9, "adc9"),
+};
+
+static int exynos5_adc_remove_devices(struct device *dev, void *c)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static void exynos5_adc_hw_init(struct exynos5_adc *info)
+{
+	u32 con1, con2;
+
+	if (info->version == ADC_V2) {
+		con1 = ADC_V2_CON1_SOFT_RESET;
+		writel(con1, ADC_V2_CON1(info->regs));
+
+		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
+			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
+		writel(con2, ADC_V2_CON2(info->regs));
+
+		/* Enable interrupts */
+		writel(1, ADC_V2_INT_EN(info->regs));
+	} else {
+		/* set default prescaler values and Enable prescaler */
+		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
+
+		/* Enable 12-bit ADC resolution */
+		con1 |= ADC_V1_CON_RES;
+		writel(con1, ADC_V1_CON(info->regs));
+	}
+}
+
+static int exynos5_adc_probe(struct platform_device *pdev)
+{
+	struct exynos5_adc *info = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *iodev = NULL;
+	struct resource	*mem;
+	int ret = -ENODEV;
+	int irq;
+
+	if (!np)
+		return ret;
+
+	iodev = iio_device_alloc(sizeof(struct exynos5_adc));
+	if (!iodev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(iodev);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = irq;
+		goto err_iio;
+	}
+
+	info->irq = irq;
+
+	ret = devm_request_irq(&pdev->dev, info->irq, exynos5_adc_isr,
+					0, dev_name(&pdev->dev), info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+							info->irq);
+		goto err_iio;
+	}
+
+	info->clk = devm_clk_get(&pdev->dev, "adc");
+	if (IS_ERR(info->clk)) {
+		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+							PTR_ERR(info->clk));
+		ret = PTR_ERR(info->clk);
+		goto err_iio;
+	}
+
+	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
+	if (IS_ERR(info->vdd)) {
+		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
+							PTR_ERR(info->vdd));
+		ret = PTR_ERR(info->vdd);
+		goto err_iio;
+	}
+
+	info->version = exynos5_adc_get_version(pdev);
+
+	platform_set_drvdata(pdev, iodev);
+
+	init_completion(&info->completion);
+
+	iodev->name = dev_name(&pdev->dev);
+	iodev->dev.parent = &pdev->dev;
+	iodev->dev.of_node = pdev->dev.of_node;
+	iodev->info = &exynos5_adc_iio_info;
+	iodev->modes = INDIO_DIRECT_MODE;
+	iodev->channels = exynos5_adc_iio_channels;
+	iodev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
+
+	info->map = exynos5_adc_iio_maps;
+
+	ret = iio_map_array_register(iodev, info->map);
+	if (ret) {
+		dev_err(&iodev->dev, "failed mapping iio map err: %d\n", ret);
+		goto err_iio;
+	}
+
+	ret = iio_device_register(iodev);
+	if (ret)
+		goto err_map;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		goto err_iio_dev;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed adding child nodes\n");
+		goto err_of_populate;
+	}
+
+	return 0;
+
+err_of_populate:
+	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
+err_iio_dev:
+	iio_device_unregister(iodev);
+err_map:
+	iio_map_array_unregister(iodev, info->map);
+err_iio:
+	iio_device_free(iodev);
+	return ret;
+}
+
+static int exynos5_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *iodev = platform_get_drvdata(pdev);
+	struct exynos5_adc *info = iio_priv(iodev);
+
+	iio_device_unregister(iodev);
+	iio_map_array_unregister(iodev, info->map);
+	iio_device_free(iodev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos5_adc_suspend(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	u32 con;
+
+	if (info->version == ADC_V2) {
+		con = readl(ADC_V2_CON1(info->regs));
+		con &= ~ADC_V1_CON_EN_START;
+		writel(con, ADC_V2_CON1(info->regs));
+	} else {
+		con = readl(ADC_V1_CON(info->regs));
+		con |= ADC_V1_CON_STANDBY;
+		writel(con, ADC_V1_CON(info->regs));
+	}
+
+	clk_unprepare_disable(info->clk);
+	regulator_disable(info->vdd);
+
+	return 0;
+}
+
+static int exynos5_adc_resume(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	int ret;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		return ret;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	return 0;
+}
+
+#else
+#define exynos5_adc_suspend NULL
+#define exynos5_adc_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
+			exynos5_adc_suspend,
+			exynos5_adc_resume);
+
+static struct platform_driver exynos5_adc_driver = {
+	.probe		= exynos5_adc_probe,
+	.remove		= exynos5_adc_remove,
+	.driver		= {
+		.name	= "exynos5-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(exynos5_adc_match),
+		.pm	= &exynos5_adc_pm_ops,
+	},
+};
+
+module_platform_driver(exynos5_adc_driver);
+
+MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
  2013-01-22  9:44 ` Lars-Peter Clausen
  2013-01-22 14:27 ` Naveen Krishna Chatradhi
@ 2013-01-23  4:58 ` Naveen Krishna Chatradhi
  2013-01-23 12:52   ` Lars-Peter Clausen
  2013-02-12 21:07   ` Guenter Roeck
  2013-01-24  4:58 ` [PATCH] " Naveen Krishna Chatradhi
                   ` (2 subsequent siblings)
  5 siblings, 2 replies; 35+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-01-23  4:58 UTC (permalink / raw)
  To: linux-iio
  Cc: linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
Also adds the Documentation for device tree bindings.

Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v1:

1. Fixed comments from Lars
2. Added support for ADC on EXYNOS5410

Changes since v2:

1. Changed the instance name for (struct iio_dev *) to indio_dev
2. Changed devm_request_irq to request_irq

Few doubts regarding the mappings and child device handling.
Kindly, suggest me better methods.

 .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos5_adc.c                      |  464 ++++++++++++++++++++
 4 files changed, 509 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
 create mode 100644 drivers/iio/adc/exynos5_adc.c

diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
new file mode 100644
index 0000000..9a5b515
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
@@ -0,0 +1,37 @@
+Samsung Exynos5 Analog to Digital Converter bindings
+
+Required properties:
+- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
+- reg:			Contains ADC register address range (base address and
+			length).
+- interrupts: 		Contains the interrupt information for the timer. The
+			format is being dependent on which interrupt controller
+			the Samsung device uses.
+
+Note: child nodes can be added for auto probing from device tree.
+
+Example: adding device info in dtsi file
+
+adc@12D10000 {
+	compatible = "samsung,exynos5250-adc";
+	reg = <0x12D10000 0x100>;
+	interrupts = <0 106 0>;
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges;
+};
+
+
+Example: Adding child nodes in dts file
+
+adc@12D10000 {
+
+	/* NTC thermistor is a hwmon device */
+	ncp15wb473@0 {
+		compatible = "ntc,ncp15wb473";
+		reg = <0x0>;
+		pullup-uV = <1800000>;
+		pullup-ohm = <47000>;
+		pulldown-ohm = <0>;
+	};
+};
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index fe822a1..33ceabf 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,13 @@ config AT91_ADC
 	help
 	  Say yes here to build support for Atmel AT91 ADC.
 
+config EXYNOS5_ADC
+	bool "Exynos5 ADC driver support"
+	help
+	  Core support for the ADC block found in the Samsung EXYNOS5 series
+	  of SoCs for drivers such as the touchscreen and hwmon to use to share
+	  this resource.
+
 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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
new file mode 100644
index 0000000..8982675
--- /dev/null
+++ b/drivers/iio/adc/exynos5_adc.c
@@ -0,0 +1,464 @@
+/*
+ *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
+ *
+ *  8 ~ 10 channel, 10/12-bit ADC
+ *
+ *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+enum adc_version {
+	ADC_V1,
+	ADC_V2
+};
+
+/* EXYNOS5250 ADC_V1 registers definitions */
+#define ADC_V1_CON(x)		((x) + 0x00)
+#define ADC_V1_DLY(x)		((x) + 0x08)
+#define ADC_V1_DATX(x)		((x) + 0x0C)
+#define ADC_V1_INTCLR(x)	((x) + 0x18)
+#define ADC_V1_MUX(x)		((x) + 0x1c)
+
+/* EXYNOS5410 ADC_V2 registers definitions */
+#define ADC_V2_CON1(x)		((x) + 0x00)
+#define ADC_V2_CON2(x)		((x) + 0x04)
+#define ADC_V2_STAT(x)		((x) + 0x08)
+#define ADC_V2_INT_EN(x)	((x) + 0x10)
+#define ADC_V2_INT_ST(x)	((x) + 0x14)
+#define ADC_V2_VER(x)		((x) + 0x20)
+
+/* Bit definitions for ADC_V1 */
+#define ADC_V1_CON_RES		(1u << 16)
+#define ADC_V1_CON_PRSCEN	(1u << 14)
+#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
+#define ADC_V1_CON_STANDBY	(1u << 2)
+
+/* Bit definitions for ADC_V2 */
+#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
+
+#define ADC_V2_CON2_OSEL	(1u << 10)
+#define ADC_V2_CON2_ESEL	(1u << 9)
+#define ADC_V2_CON2_HIGHF	(1u << 8)
+#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
+#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
+#define ADC_V2_CON2_ACH_MASK	0xF
+
+/* Bit definitions common for ADC_V1 and ADC_V2 */
+#define ADC_V1_CON_EN_START		(1u << 0)
+#define ADC_V1_DATX_MASK	0xFFF
+
+struct exynos5_adc {
+	void __iomem		*regs;
+	struct clk		*clk;
+	unsigned int		irq;
+	struct regulator	*vdd;
+
+	struct completion	completion;
+
+	struct iio_map		*map;
+	u32			value;
+	unsigned int            version;
+};
+
+static const struct of_device_id exynos5_adc_match[] = {
+	{ .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
+	{ .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos5_adc_match);
+
+static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
+	return (unsigned int)match->data;
+}
+
+/* default maps used by iio consumer (ex: ntc-thermistor driver) */
+static struct iio_map exynos5_adc_iio_maps[] = {
+	{
+		.consumer_dev_name = "0.ncp15wb473",
+		.consumer_channel = "adc3",
+		.adc_channel_label = "adc3",
+	},
+	{
+		.consumer_dev_name = "1.ncp15wb473",
+		.consumer_channel = "adc4",
+		.adc_channel_label = "adc4",
+	},
+	{
+		.consumer_dev_name = "2.ncp15wb473",
+		.consumer_channel = "adc5",
+		.adc_channel_label = "adc5",
+	},
+	{
+		.consumer_dev_name = "3.ncp15wb473",
+		.consumer_channel = "adc6",
+		.adc_channel_label = "adc6",
+	},
+	{},
+};
+
+static int exynos5_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 con1, con2;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		mutex_lock(&indio_dev->mlock);
+
+		/* Select the channel to be used and Trigger conversion */
+		if (info->version == ADC_V2) {
+			con2 = readl(ADC_V2_CON2(info->regs));
+			con2 &= ~ADC_V2_CON2_ACH_MASK;
+			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
+			writel(con2, ADC_V2_CON2(info->regs));
+
+			con1 = readl(ADC_V2_CON1(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V2_CON1(info->regs));
+		} else {
+			writel(chan->address, ADC_V1_MUX(info->regs));
+
+			con1 = readl(ADC_V1_CON(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V1_CON(info->regs));
+		}
+
+		wait_for_completion(&info->completion);
+		*val = info->value;
+
+		mutex_unlock(&indio_dev->mlock);
+
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
+{
+	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
+
+	/* Read value */
+	info->value = readl(ADC_V1_DATX(info->regs)) &
+						ADC_V1_DATX_MASK;
+	/* clear irq */
+	if (info->version == ADC_V2)
+		writel(1, ADC_V2_INT_ST(info->regs));
+	else
+		writel(1, ADC_V1_INTCLR(info->regs));
+
+	complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 ret;
+
+	mutex_lock(&indio_dev->mlock);
+
+	if (readval != NULL) {
+		ret = readl(info->regs + reg);
+		*readval = ret;
+	} else
+		ret = -EINVAL;
+
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info exynos5_adc_iio_info = {
+	.read_raw = &exynos5_read_raw,
+	.debugfs_reg_access = &exynos5_adc_reg_access,
+	.driver_module = THIS_MODULE,
+};
+
+#define ADC_V1_CHANNEL(_index, _id) {		\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = _index,				\
+	.address = _index,				\
+	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	.datasheet_name = _id,				\
+}
+
+/** ADC core in EXYNOS5410 has 10 channels,
+ * ADC core in EXYNOS5250 has 8 channels
+*/
+static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
+	ADC_V1_CHANNEL(0, "adc0"),
+	ADC_V1_CHANNEL(1, "adc1"),
+	ADC_V1_CHANNEL(2, "adc2"),
+	ADC_V1_CHANNEL(3, "adc3"),
+	ADC_V1_CHANNEL(4, "adc4"),
+	ADC_V1_CHANNEL(5, "adc5"),
+	ADC_V1_CHANNEL(6, "adc6"),
+	ADC_V1_CHANNEL(7, "adc7"),
+	ADC_V1_CHANNEL(8, "adc8"),
+	ADC_V1_CHANNEL(9, "adc9"),
+};
+
+static int exynos5_adc_remove_devices(struct device *dev, void *c)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static void exynos5_adc_hw_init(struct exynos5_adc *info)
+{
+	u32 con1, con2;
+
+	if (info->version == ADC_V2) {
+		con1 = ADC_V2_CON1_SOFT_RESET;
+		writel(con1, ADC_V2_CON1(info->regs));
+
+		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
+			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
+		writel(con2, ADC_V2_CON2(info->regs));
+
+		/* Enable interrupts */
+		writel(1, ADC_V2_INT_EN(info->regs));
+	} else {
+		/* set default prescaler values and Enable prescaler */
+		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
+
+		/* Enable 12-bit ADC resolution */
+		con1 |= ADC_V1_CON_RES;
+		writel(con1, ADC_V1_CON(info->regs));
+	}
+}
+
+static int exynos5_adc_probe(struct platform_device *pdev)
+{
+	struct exynos5_adc *info = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *indio_dev = NULL;
+	struct resource	*mem;
+	int ret = -ENODEV;
+	int irq;
+
+	if (!np)
+		return ret;
+
+	indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(indio_dev);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = irq;
+		goto err_iio;
+	}
+
+	info->irq = irq;
+
+	ret = request_irq(info->irq, exynos5_adc_isr,
+					0, dev_name(&pdev->dev), info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+							info->irq);
+		goto err_iio;
+	}
+
+	info->clk = devm_clk_get(&pdev->dev, "adc");
+	if (IS_ERR(info->clk)) {
+		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+							PTR_ERR(info->clk));
+		ret = PTR_ERR(info->clk);
+		goto err_irq;
+	}
+
+	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
+	if (IS_ERR(info->vdd)) {
+		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
+							PTR_ERR(info->vdd));
+		ret = PTR_ERR(info->vdd);
+		goto err_irq;
+	}
+
+	info->version = exynos5_adc_get_version(pdev);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	init_completion(&info->completion);
+
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &exynos5_adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = exynos5_adc_iio_channels;
+	indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
+
+	info->map = exynos5_adc_iio_maps;
+
+	ret = iio_map_array_register(indio_dev, info->map);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
+		goto err_irq;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_map;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		goto err_iio_dev;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed adding child nodes\n");
+		goto err_of_populate;
+	}
+
+	return 0;
+
+err_of_populate:
+	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
+err_iio_dev:
+	iio_device_unregister(indio_dev);
+err_map:
+	iio_map_array_unregister(indio_dev, info->map);
+err_irq:
+	free_irq(info->irq, info);
+err_iio:
+	iio_device_free(indio_dev);
+	return ret;
+}
+
+static int exynos5_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct exynos5_adc *info = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	iio_map_array_unregister(indio_dev, info->map);
+	free_irq(info->irq, info);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos5_adc_suspend(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	u32 con;
+
+	if (info->version == ADC_V2) {
+		con = readl(ADC_V2_CON1(info->regs));
+		con &= ~ADC_V1_CON_EN_START;
+		writel(con, ADC_V2_CON1(info->regs));
+	} else {
+		con = readl(ADC_V1_CON(info->regs));
+		con |= ADC_V1_CON_STANDBY;
+		writel(con, ADC_V1_CON(info->regs));
+	}
+
+	clk_unprepare_disable(info->clk);
+	regulator_disable(info->vdd);
+
+	return 0;
+}
+
+static int exynos5_adc_resume(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	int ret;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		return ret;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	return 0;
+}
+
+#else
+#define exynos5_adc_suspend NULL
+#define exynos5_adc_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
+			exynos5_adc_suspend,
+			exynos5_adc_resume);
+
+static struct platform_driver exynos5_adc_driver = {
+	.probe		= exynos5_adc_probe,
+	.remove		= exynos5_adc_remove,
+	.driver		= {
+		.name	= "exynos5-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(exynos5_adc_match),
+		.pm	= &exynos5_adc_pm_ops,
+	},
+};
+
+module_platform_driver(exynos5_adc_driver);
+
+MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-23  4:58 ` Naveen Krishna Chatradhi
@ 2013-01-23 12:52   ` Lars-Peter Clausen
  2013-01-24  0:42     ` Doug Anderson
  2013-02-12 21:07   ` Guenter Roeck
  1 sibling, 1 reply; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-01-23 12:52 UTC (permalink / raw)
  To: Naveen Krishna Chatradhi
  Cc: linux-iio, linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch

On 01/23/2013 05:58 AM, Naveen Krishna Chatradhi wrote:
> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
> Also adds the Documentation for device tree bindings.
> 
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Changes since v1:
> 
> 1. Fixed comments from Lars
> 2. Added support for ADC on EXYNOS5410
> 
> Changes since v2:
> 
> 1. Changed the instance name for (struct iio_dev *) to indio_dev
> 2. Changed devm_request_irq to request_irq
> 
> Few doubts regarding the mappings and child device handling.
> Kindly, suggest me better methods.
> 

Hi,

The patch looks mostly good now. As for the mappings, the problem is that we
currently do not have any device tree bindings for IIO. So a proper solution
would be to add dt bindings for IIO.

[...]
> diff --git a/drivers/iio/adc/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
> new file mode 100644
> index 0000000..8982675
> --- /dev/null
> +++ b/drivers/iio/adc/exynos5_adc.c
[...]
> +
> +/** ADC core in EXYNOS5410 has 10 channels,
> + * ADC core in EXYNOS5250 has 8 channels
> +*/

Minor nitpick, according to Documentation/Codingsytle multi-line comments
should look like this:

/*
 * Text
 * Text
 */

[...]
> +static int exynos5_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos5_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *indio_dev = NULL;
> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +
> +	if (!np)
> +		return ret;
> +
> +	indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
> +	if (!indio_dev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(indio_dev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);

While devm_request_and_ioremap takes care of the printing and error you
still need to check whether regs is non NULL.

> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	ret = request_irq(info->irq, exynos5_adc_isr,
> +					0, dev_name(&pdev->dev), info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
> +							PTR_ERR(info->clk));
> +		ret = PTR_ERR(info->clk);
> +		goto err_irq;
> +	}
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
> +							PTR_ERR(info->vdd));
> +		ret = PTR_ERR(info->vdd);
> +		goto err_irq;
> +	}
> +
> +	info->version = exynos5_adc_get_version(pdev);
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	init_completion(&info->completion);

Since the completion is used in the IRQ handler it should be initialized
before the IRQ is requested.

> +
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &exynos5_adc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = exynos5_adc_iio_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);

Shouldn't the number of channels depend on the ADC version? E.g. 8 for v1
and 10 for v2?

> +
> +	info->map = exynos5_adc_iio_maps;
> +
> +	ret = iio_map_array_register(indio_dev, info->map);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
> +		goto err_irq;
> +	}
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto err_map;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;

You never disable the regulator again. I think you need to do that before it
is freed.

> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos5_adc_hw_init(info);
> +
> +	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed adding child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
> +err_iio_dev:
> +	iio_device_unregister(indio_dev);
> +err_map:
> +	iio_map_array_unregister(indio_dev, info->map);
> +err_irq:
> +	free_irq(info->irq, info);
> +err_iio:
> +	iio_device_free(indio_dev);
> +	return ret;
> +}
> +
[..]

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-23 12:52   ` Lars-Peter Clausen
@ 2013-01-24  0:42     ` Doug Anderson
  2013-01-24  9:54       ` Lars-Peter Clausen
  0 siblings, 1 reply; 35+ messages in thread
From: Doug Anderson @ 2013-01-24  0:42 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh, Naveen Krishna

Lars,

On Wed, Jan 23, 2013 at 4:52 AM, Lars-Peter Clausen <lars@metafoo.de> wrote:
>> Few doubts regarding the mappings and child device handling.
>> Kindly, suggest me better methods.
>
> The patch looks mostly good now. As for the mappings, the problem is that we
> currently do not have any device tree bindings for IIO. So a proper solution
> would be to add dt bindings for IIO.

Can you explain more how you think this would work?  It seems like
just having child nodes as platform drivers would be OK (to me) and
having them instantiated with of_platform_populate() seems reasonable.

...but the one thing that is missing is a way for children to get
access to the channel properly.

The children have access to the ADC "struct device" (it is their
parent device) and have a channel number (their "reg" field), but
there is no API call to map this to a "struct iio_channel".  Is that
all that's needed in this case?  ...or are you envisioning something
more?


Thanks!  :)


-Doug

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
                   ` (2 preceding siblings ...)
  2013-01-23  4:58 ` Naveen Krishna Chatradhi
@ 2013-01-24  4:58 ` Naveen Krishna Chatradhi
  2013-01-26 10:57   ` Jonathan Cameron
  2013-01-24  5:05 ` Naveen Krishna Chatradhi
  2013-02-14 12:11 ` [PATCH v6] iio: adc: add exynos " Naveen Krishna Chatradhi
  5 siblings, 1 reply; 35+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-01-24  4:58 UTC (permalink / raw)
  To: linux-iio
  Cc: linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

This patch adds driver for ADC IP found on EXYNOS5250 and EXYNOS5410
from Samsung. Also adds the Documentation for device tree bindings.

Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v1:

1. Fixed comments from Lars
2. Added support for ADC on EXYNOS5410

Changes since v2:

1. Changed the instance name for (struct iio_dev *) to indio_dev
2. Changed devm_request_irq to request_irq

Few doubts regarding the mappings and child device handling.
Kindly, suggest me better methods.

Changes since v3:

1. Added clk_prepare_disable and regulator_disable calls in _remove()
2. Moved init_completion before irq_request
3. Added NULL pointer check for devm_request_and_ioremap() return value.
4. Use number of channels as per the ADC version 
5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
6. Update the Documentation to include EXYNOS5410 compatible

Doug, i've used
	chan = iio_channel_get(dev_name(&pdev->dev), "adc3");
in ntc thermistor driver during probe and
	iio_read_channel_raw(chan, &val);
for read.

But, then the drivers become kind of coupled. Right.

Lars, Is there an other way.

Thanks for the comments

 .../bindings/arm/samsung/exynos5-adc.txt           |   38 ++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos5_adc.c                      |  475 ++++++++++++++++++++
 4 files changed, 521 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
 create mode 100644 drivers/iio/adc/exynos5_adc.c

diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
new file mode 100644
index 0000000..0f281d9
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
@@ -0,0 +1,38 @@
+Samsung Exynos5 Analog to Digital Converter bindings
+
+Required properties:
+- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
+			Must be "samsung,exynos5410-adc" for exynos5410 controllers.
+- reg:			Contains ADC register address range (base address and
+			length).
+- interrupts: 		Contains the interrupt information for the timer. The
+			format is being dependent on which interrupt controller
+			the Samsung device uses.
+
+Note: child nodes can be added for auto probing from device tree.
+
+Example: adding device info in dtsi file
+
+adc@12D10000 {
+	compatible = "samsung,exynos5250-adc";
+	reg = <0x12D10000 0x100>;
+	interrupts = <0 106 0>;
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges;
+};
+
+
+Example: Adding child nodes in dts file
+
+adc@12D10000 {
+
+	/* NTC thermistor is a hwmon device */
+	ncp15wb473@0 {
+		compatible = "ntc,ncp15wb473";
+		reg = <0x0>;
+		pullup-uV = <1800000>;
+		pullup-ohm = <47000>;
+		pulldown-ohm = <0>;
+	};
+};
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index fe822a1..33ceabf 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,13 @@ config AT91_ADC
 	help
 	  Say yes here to build support for Atmel AT91 ADC.
 
+config EXYNOS5_ADC
+	bool "Exynos5 ADC driver support"
+	help
+	  Core support for the ADC block found in the Samsung EXYNOS5 series
+	  of SoCs for drivers such as the touchscreen and hwmon to use to share
+	  this resource.
+
 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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
new file mode 100644
index 0000000..4963649
--- /dev/null
+++ b/drivers/iio/adc/exynos5_adc.c
@@ -0,0 +1,475 @@
+/*
+ *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
+ *
+ *  8 ~ 10 channel, 10/12-bit ADC
+ *
+ *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+enum adc_version {
+	ADC_V1,
+	ADC_V2
+};
+
+/* EXYNOS5250 ADC_V1 registers definitions */
+#define ADC_V1_CON(x)		((x) + 0x00)
+#define ADC_V1_DLY(x)		((x) + 0x08)
+#define ADC_V1_DATX(x)		((x) + 0x0C)
+#define ADC_V1_INTCLR(x)	((x) + 0x18)
+#define ADC_V1_MUX(x)		((x) + 0x1c)
+
+/* EXYNOS5410 ADC_V2 registers definitions */
+#define ADC_V2_CON1(x)		((x) + 0x00)
+#define ADC_V2_CON2(x)		((x) + 0x04)
+#define ADC_V2_STAT(x)		((x) + 0x08)
+#define ADC_V2_INT_EN(x)	((x) + 0x10)
+#define ADC_V2_INT_ST(x)	((x) + 0x14)
+#define ADC_V2_VER(x)		((x) + 0x20)
+
+/* Bit definitions for ADC_V1 */
+#define ADC_V1_CON_RES		(1u << 16)
+#define ADC_V1_CON_PRSCEN	(1u << 14)
+#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
+#define ADC_V1_CON_STANDBY	(1u << 2)
+
+/* Bit definitions for ADC_V2 */
+#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
+
+#define ADC_V2_CON2_OSEL	(1u << 10)
+#define ADC_V2_CON2_ESEL	(1u << 9)
+#define ADC_V2_CON2_HIGHF	(1u << 8)
+#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
+#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
+#define ADC_V2_CON2_ACH_MASK	0xF
+
+/* Bit definitions common for ADC_V1 and ADC_V2 */
+#define ADC_V1_CON_EN_START		(1u << 0)
+#define ADC_V1_DATX_MASK	0xFFF
+
+struct exynos5_adc {
+	void __iomem		*regs;
+	struct clk		*clk;
+	unsigned int		irq;
+	struct regulator	*vdd;
+
+	struct completion	completion;
+
+	struct iio_map		*map;
+	u32			value;
+	unsigned int            version;
+};
+
+static const struct of_device_id exynos5_adc_match[] = {
+	{ .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
+	{ .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos5_adc_match);
+
+static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
+	return (unsigned int)match->data;
+}
+
+/* default maps used by iio consumer (ex: ntc-thermistor driver) */
+static struct iio_map exynos5_adc_iio_map[] = {
+	{
+		.consumer_dev_name = "0.ncp15wb473",
+		.consumer_channel = "adc3",
+		.adc_channel_label = "adc3",
+	},
+	{
+		.consumer_dev_name = "1.ncp15wb473",
+		.consumer_channel = "adc4",
+		.adc_channel_label = "adc4",
+	},
+	{
+		.consumer_dev_name = "2.ncp15wb473",
+		.consumer_channel = "adc5",
+		.adc_channel_label = "adc5",
+	},
+	{
+		.consumer_dev_name = "3.ncp15wb473",
+		.consumer_channel = "adc6",
+		.adc_channel_label = "adc6",
+	},
+	{},
+};
+
+static int exynos5_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 con1, con2;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		mutex_lock(&indio_dev->mlock);
+
+		/* Select the channel to be used and Trigger conversion */
+		if (info->version == ADC_V2) {
+			con2 = readl(ADC_V2_CON2(info->regs));
+			con2 &= ~ADC_V2_CON2_ACH_MASK;
+			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
+			writel(con2, ADC_V2_CON2(info->regs));
+
+			con1 = readl(ADC_V2_CON1(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V2_CON1(info->regs));
+		} else {
+			writel(chan->address, ADC_V1_MUX(info->regs));
+
+			con1 = readl(ADC_V1_CON(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V1_CON(info->regs));
+		}
+
+		wait_for_completion(&info->completion);
+		*val = info->value;
+
+		mutex_unlock(&indio_dev->mlock);
+
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
+{
+	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
+
+	/* Read value */
+	info->value = readl(ADC_V1_DATX(info->regs)) &
+						ADC_V1_DATX_MASK;
+	/* clear irq */
+	if (info->version == ADC_V2)
+		writel(1, ADC_V2_INT_ST(info->regs));
+	else
+		writel(1, ADC_V1_INTCLR(info->regs));
+
+	complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 ret;
+
+	mutex_lock(&indio_dev->mlock);
+
+	if (readval != NULL) {
+		ret = readl(info->regs + reg);
+		*readval = ret;
+	} else
+		ret = -EINVAL;
+
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info exynos5_adc_iio_info = {
+	.read_raw = &exynos5_read_raw,
+	.debugfs_reg_access = &exynos5_adc_reg_access,
+	.driver_module = THIS_MODULE,
+};
+
+#define ADC_CHANNEL(_index, _id) {			\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = _index,				\
+	.address = _index,				\
+	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	.datasheet_name = _id,				\
+}
+
+static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
+	ADC_CHANNEL(0, "adc0"),
+	ADC_CHANNEL(1, "adc1"),
+	ADC_CHANNEL(2, "adc2"),
+	ADC_CHANNEL(3, "adc3"),
+	ADC_CHANNEL(4, "adc4"),
+	ADC_CHANNEL(5, "adc5"),
+	ADC_CHANNEL(6, "adc6"),
+	ADC_CHANNEL(7, "adc7"),
+	ADC_CHANNEL(8, "adc8"),
+	ADC_CHANNEL(9, "adc9"),
+};
+
+static int exynos5_adc_remove_devices(struct device *dev, void *c)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static void exynos5_adc_hw_init(struct exynos5_adc *info)
+{
+	u32 con1, con2;
+
+	if (info->version == ADC_V2) {
+		con1 = ADC_V2_CON1_SOFT_RESET;
+		writel(con1, ADC_V2_CON1(info->regs));
+
+		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
+			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
+		writel(con2, ADC_V2_CON2(info->regs));
+
+		/* Enable interrupts */
+		writel(1, ADC_V2_INT_EN(info->regs));
+	} else {
+		/* set default prescaler values and Enable prescaler */
+		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
+
+		/* Enable 12-bit ADC resolution */
+		con1 |= ADC_V1_CON_RES;
+		writel(con1, ADC_V1_CON(info->regs));
+	}
+}
+
+static int exynos5_adc_probe(struct platform_device *pdev)
+{
+	struct exynos5_adc *info = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *indio_dev = NULL;
+	struct resource	*mem;
+	int ret = -ENODEV;
+	int irq;
+
+	if (!np)
+		return ret;
+
+	indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(indio_dev);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
+	if (!info->regs)
+		return -ENOMEM;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = irq;
+		goto err_iio;
+	}
+
+	info->irq = irq;
+
+	init_completion(&info->completion);
+
+	ret = request_irq(info->irq, exynos5_adc_isr,
+					0, dev_name(&pdev->dev), info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+							info->irq);
+		goto err_iio;
+	}
+
+	info->clk = devm_clk_get(&pdev->dev, "adc");
+	if (IS_ERR(info->clk)) {
+		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+							PTR_ERR(info->clk));
+		ret = PTR_ERR(info->clk);
+		goto err_irq;
+	}
+
+	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
+	if (IS_ERR(info->vdd)) {
+		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
+							PTR_ERR(info->vdd));
+		ret = PTR_ERR(info->vdd);
+		goto err_irq;
+	}
+
+	info->version = exynos5_adc_get_version(pdev);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &exynos5_adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = exynos5_adc_iio_channels;
+
+	if (info->version == ADC_V1)
+		/* ADC core in EXYNOS5250 has 8 channels */
+		indio_dev->num_channels =
+				ARRAY_SIZE(exynos5_adc_iio_channels) - 2;
+	else
+		/* ADC core in EXYNOS5410 has 10 channels */
+		indio_dev->num_channels =
+				ARRAY_SIZE(exynos5_adc_iio_channels);
+
+	info->map = exynos5_adc_iio_map;
+
+	ret = iio_map_array_register(indio_dev, info->map);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
+		goto err_irq;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_map;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		goto err_iio_dev;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed adding child nodes\n");
+		goto err_of_populate;
+	}
+
+	return 0;
+
+err_of_populate:
+	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
+	clk_disable_unprepare(info->clk);
+	regulator_disable(info->vdd);
+err_iio_dev:
+	iio_device_unregister(indio_dev);
+err_map:
+	iio_map_array_unregister(indio_dev, info->map);
+err_irq:
+	free_irq(info->irq, info);
+err_iio:
+	iio_device_free(indio_dev);
+	return ret;
+}
+
+static int exynos5_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct exynos5_adc *info = iio_priv(indio_dev);
+
+	clk_disable_unprepare(info->clk);
+	regulator_disable(info->vdd);
+	iio_device_unregister(indio_dev);
+	iio_map_array_unregister(indio_dev, info->map);
+	free_irq(info->irq, info);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos5_adc_suspend(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	u32 con;
+
+	if (info->version == ADC_V2) {
+		con = readl(ADC_V2_CON1(info->regs));
+		con &= ~ADC_V1_CON_EN_START;
+		writel(con, ADC_V2_CON1(info->regs));
+	} else {
+		con = readl(ADC_V1_CON(info->regs));
+		con |= ADC_V1_CON_STANDBY;
+		writel(con, ADC_V1_CON(info->regs));
+	}
+
+	clk_disable_unprepare(info->clk);
+	regulator_disable(info->vdd);
+
+	return 0;
+}
+
+static int exynos5_adc_resume(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	int ret;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		return ret;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	return 0;
+}
+
+#else
+#define exynos5_adc_suspend NULL
+#define exynos5_adc_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
+			exynos5_adc_suspend,
+			exynos5_adc_resume);
+
+static struct platform_driver exynos5_adc_driver = {
+	.probe		= exynos5_adc_probe,
+	.remove		= exynos5_adc_remove,
+	.driver		= {
+		.name	= "exynos5-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(exynos5_adc_match),
+		.pm	= &exynos5_adc_pm_ops,
+	},
+};
+
+module_platform_driver(exynos5_adc_driver);
+
+MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
                   ` (3 preceding siblings ...)
  2013-01-24  4:58 ` [PATCH] " Naveen Krishna Chatradhi
@ 2013-01-24  5:05 ` Naveen Krishna Chatradhi
  2013-02-12  1:22   ` Olof Johansson
  2013-02-14 12:11 ` [PATCH v6] iio: adc: add exynos " Naveen Krishna Chatradhi
  5 siblings, 1 reply; 35+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-01-24  5:05 UTC (permalink / raw)
  To: linux-iio
  Cc: linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

This patch add an ADC IP found on EXYNOS5250 and EXYNOS5410 SoCs
from Samsung. Also adds the Documentation for device tree bindings.

Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v1:

1. Fixed comments from Lars
2. Added support for ADC on EXYNOS5410

Changes since v2:

1. Changed the instance name for (struct iio_dev *) to indio_dev
2. Changed devm_request_irq to request_irq

Few doubts regarding the mappings and child device handling.
Kindly, suggest me better methods.

Changes since v3:

1. Added clk_prepare_disable and regulator_disable calls in _remove()
2. Moved init_completion before irq_request
3. Added NULL pointer check for devm_request_and_ioremap() return value.
4. Use number of channels as per the ADC version 
5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
6. Update the Documentation to include EXYNOS5410 compatible

Changes since v4:

1. if devm_request_and_ioremap() failes, free iio_device before returning 

Doug, i've used
	chan = iio_channel_get(dev_name(&pdev->dev), "adc3");
in ntc thermistor driver during probe and
	iio_read_channel_raw(chan, &val);
for read.

But, then the drivers become kind of coupled. Right.

Lars, Is there an other way.

Thanks for the comments

 .../bindings/arm/samsung/exynos5-adc.txt           |   38 ++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos5_adc.c                      |  477 ++++++++++++++++++++
 4 files changed, 523 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
 create mode 100644 drivers/iio/adc/exynos5_adc.c

diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
new file mode 100644
index 0000000..0f281d9
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
@@ -0,0 +1,38 @@
+Samsung Exynos5 Analog to Digital Converter bindings
+
+Required properties:
+- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
+			Must be "samsung,exynos5410-adc" for exynos5410 controllers.
+- reg:			Contains ADC register address range (base address and
+			length).
+- interrupts: 		Contains the interrupt information for the timer. The
+			format is being dependent on which interrupt controller
+			the Samsung device uses.
+
+Note: child nodes can be added for auto probing from device tree.
+
+Example: adding device info in dtsi file
+
+adc@12D10000 {
+	compatible = "samsung,exynos5250-adc";
+	reg = <0x12D10000 0x100>;
+	interrupts = <0 106 0>;
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges;
+};
+
+
+Example: Adding child nodes in dts file
+
+adc@12D10000 {
+
+	/* NTC thermistor is a hwmon device */
+	ncp15wb473@0 {
+		compatible = "ntc,ncp15wb473";
+		reg = <0x0>;
+		pullup-uV = <1800000>;
+		pullup-ohm = <47000>;
+		pulldown-ohm = <0>;
+	};
+};
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index fe822a1..33ceabf 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,13 @@ config AT91_ADC
 	help
 	  Say yes here to build support for Atmel AT91 ADC.
 
+config EXYNOS5_ADC
+	bool "Exynos5 ADC driver support"
+	help
+	  Core support for the ADC block found in the Samsung EXYNOS5 series
+	  of SoCs for drivers such as the touchscreen and hwmon to use to share
+	  this resource.
+
 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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
new file mode 100644
index 0000000..197d622
--- /dev/null
+++ b/drivers/iio/adc/exynos5_adc.c
@@ -0,0 +1,477 @@
+/*
+ *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
+ *
+ *  8 ~ 10 channel, 10/12-bit ADC
+ *
+ *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+enum adc_version {
+	ADC_V1,
+	ADC_V2
+};
+
+/* EXYNOS5250 ADC_V1 registers definitions */
+#define ADC_V1_CON(x)		((x) + 0x00)
+#define ADC_V1_DLY(x)		((x) + 0x08)
+#define ADC_V1_DATX(x)		((x) + 0x0C)
+#define ADC_V1_INTCLR(x)	((x) + 0x18)
+#define ADC_V1_MUX(x)		((x) + 0x1c)
+
+/* EXYNOS5410 ADC_V2 registers definitions */
+#define ADC_V2_CON1(x)		((x) + 0x00)
+#define ADC_V2_CON2(x)		((x) + 0x04)
+#define ADC_V2_STAT(x)		((x) + 0x08)
+#define ADC_V2_INT_EN(x)	((x) + 0x10)
+#define ADC_V2_INT_ST(x)	((x) + 0x14)
+#define ADC_V2_VER(x)		((x) + 0x20)
+
+/* Bit definitions for ADC_V1 */
+#define ADC_V1_CON_RES		(1u << 16)
+#define ADC_V1_CON_PRSCEN	(1u << 14)
+#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
+#define ADC_V1_CON_STANDBY	(1u << 2)
+
+/* Bit definitions for ADC_V2 */
+#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
+
+#define ADC_V2_CON2_OSEL	(1u << 10)
+#define ADC_V2_CON2_ESEL	(1u << 9)
+#define ADC_V2_CON2_HIGHF	(1u << 8)
+#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
+#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
+#define ADC_V2_CON2_ACH_MASK	0xF
+
+/* Bit definitions common for ADC_V1 and ADC_V2 */
+#define ADC_V1_CON_EN_START		(1u << 0)
+#define ADC_V1_DATX_MASK	0xFFF
+
+struct exynos5_adc {
+	void __iomem		*regs;
+	struct clk		*clk;
+	unsigned int		irq;
+	struct regulator	*vdd;
+
+	struct completion	completion;
+
+	struct iio_map		*map;
+	u32			value;
+	unsigned int            version;
+};
+
+static const struct of_device_id exynos5_adc_match[] = {
+	{ .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
+	{ .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos5_adc_match);
+
+static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
+	return (unsigned int)match->data;
+}
+
+/* default maps used by iio consumer (ex: ntc-thermistor driver) */
+static struct iio_map exynos5_adc_iio_map[] = {
+	{
+		.consumer_dev_name = "0.ncp15wb473",
+		.consumer_channel = "adc3",
+		.adc_channel_label = "adc3",
+	},
+	{
+		.consumer_dev_name = "1.ncp15wb473",
+		.consumer_channel = "adc4",
+		.adc_channel_label = "adc4",
+	},
+	{
+		.consumer_dev_name = "2.ncp15wb473",
+		.consumer_channel = "adc5",
+		.adc_channel_label = "adc5",
+	},
+	{
+		.consumer_dev_name = "3.ncp15wb473",
+		.consumer_channel = "adc6",
+		.adc_channel_label = "adc6",
+	},
+	{},
+};
+
+static int exynos5_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 con1, con2;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		mutex_lock(&indio_dev->mlock);
+
+		/* Select the channel to be used and Trigger conversion */
+		if (info->version == ADC_V2) {
+			con2 = readl(ADC_V2_CON2(info->regs));
+			con2 &= ~ADC_V2_CON2_ACH_MASK;
+			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
+			writel(con2, ADC_V2_CON2(info->regs));
+
+			con1 = readl(ADC_V2_CON1(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V2_CON1(info->regs));
+		} else {
+			writel(chan->address, ADC_V1_MUX(info->regs));
+
+			con1 = readl(ADC_V1_CON(info->regs));
+			writel(con1 | ADC_V1_CON_EN_START,
+					ADC_V1_CON(info->regs));
+		}
+
+		wait_for_completion(&info->completion);
+		*val = info->value;
+
+		mutex_unlock(&indio_dev->mlock);
+
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
+{
+	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
+
+	/* Read value */
+	info->value = readl(ADC_V1_DATX(info->regs)) &
+						ADC_V1_DATX_MASK;
+	/* clear irq */
+	if (info->version == ADC_V2)
+		writel(1, ADC_V2_INT_ST(info->regs));
+	else
+		writel(1, ADC_V1_INTCLR(info->regs));
+
+	complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct exynos5_adc *info = iio_priv(indio_dev);
+	u32 ret;
+
+	mutex_lock(&indio_dev->mlock);
+
+	if (readval != NULL) {
+		ret = readl(info->regs + reg);
+		*readval = ret;
+	} else
+		ret = -EINVAL;
+
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info exynos5_adc_iio_info = {
+	.read_raw = &exynos5_read_raw,
+	.debugfs_reg_access = &exynos5_adc_reg_access,
+	.driver_module = THIS_MODULE,
+};
+
+#define ADC_CHANNEL(_index, _id) {			\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = _index,				\
+	.address = _index,				\
+	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	.datasheet_name = _id,				\
+}
+
+static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
+	ADC_CHANNEL(0, "adc0"),
+	ADC_CHANNEL(1, "adc1"),
+	ADC_CHANNEL(2, "adc2"),
+	ADC_CHANNEL(3, "adc3"),
+	ADC_CHANNEL(4, "adc4"),
+	ADC_CHANNEL(5, "adc5"),
+	ADC_CHANNEL(6, "adc6"),
+	ADC_CHANNEL(7, "adc7"),
+	ADC_CHANNEL(8, "adc8"),
+	ADC_CHANNEL(9, "adc9"),
+};
+
+static int exynos5_adc_remove_devices(struct device *dev, void *c)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static void exynos5_adc_hw_init(struct exynos5_adc *info)
+{
+	u32 con1, con2;
+
+	if (info->version == ADC_V2) {
+		con1 = ADC_V2_CON1_SOFT_RESET;
+		writel(con1, ADC_V2_CON1(info->regs));
+
+		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
+			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
+		writel(con2, ADC_V2_CON2(info->regs));
+
+		/* Enable interrupts */
+		writel(1, ADC_V2_INT_EN(info->regs));
+	} else {
+		/* set default prescaler values and Enable prescaler */
+		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
+
+		/* Enable 12-bit ADC resolution */
+		con1 |= ADC_V1_CON_RES;
+		writel(con1, ADC_V1_CON(info->regs));
+	}
+}
+
+static int exynos5_adc_probe(struct platform_device *pdev)
+{
+	struct exynos5_adc *info = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *indio_dev = NULL;
+	struct resource	*mem;
+	int ret = -ENODEV;
+	int irq;
+
+	if (!np)
+		return ret;
+
+	indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(indio_dev);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
+	if (!info->regs) {
+		ret = -ENOMEM;
+		goto err_iio;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = irq;
+		goto err_iio;
+	}
+
+	info->irq = irq;
+
+	init_completion(&info->completion);
+
+	ret = request_irq(info->irq, exynos5_adc_isr,
+					0, dev_name(&pdev->dev), info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+							info->irq);
+		goto err_iio;
+	}
+
+	info->clk = devm_clk_get(&pdev->dev, "adc");
+	if (IS_ERR(info->clk)) {
+		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+							PTR_ERR(info->clk));
+		ret = PTR_ERR(info->clk);
+		goto err_irq;
+	}
+
+	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
+	if (IS_ERR(info->vdd)) {
+		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
+							PTR_ERR(info->vdd));
+		ret = PTR_ERR(info->vdd);
+		goto err_irq;
+	}
+
+	info->version = exynos5_adc_get_version(pdev);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &exynos5_adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = exynos5_adc_iio_channels;
+
+	if (info->version == ADC_V1)
+		/* ADC core in EXYNOS5250 has 8 channels */
+		indio_dev->num_channels =
+				ARRAY_SIZE(exynos5_adc_iio_channels) - 2;
+	else
+		/* ADC core in EXYNOS5410 has 10 channels */
+		indio_dev->num_channels =
+				ARRAY_SIZE(exynos5_adc_iio_channels);
+
+	info->map = exynos5_adc_iio_map;
+
+	ret = iio_map_array_register(indio_dev, info->map);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
+		goto err_irq;
+	}
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_map;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		goto err_iio_dev;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed adding child nodes\n");
+		goto err_of_populate;
+	}
+
+	return 0;
+
+err_of_populate:
+	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
+	regulator_disable(info->vdd);
+	clk_disable_unprepare(info->clk);
+err_iio_dev:
+	iio_device_unregister(indio_dev);
+err_map:
+	iio_map_array_unregister(indio_dev, info->map);
+err_irq:
+	free_irq(info->irq, info);
+err_iio:
+	iio_device_free(indio_dev);
+	return ret;
+}
+
+static int exynos5_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct exynos5_adc *info = iio_priv(indio_dev);
+
+	regulator_disable(info->vdd);
+	clk_disable_unprepare(info->clk);
+	iio_device_unregister(indio_dev);
+	iio_map_array_unregister(indio_dev, info->map);
+	free_irq(info->irq, info);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos5_adc_suspend(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	u32 con;
+
+	if (info->version == ADC_V2) {
+		con = readl(ADC_V2_CON1(info->regs));
+		con &= ~ADC_V1_CON_EN_START;
+		writel(con, ADC_V2_CON1(info->regs));
+	} else {
+		con = readl(ADC_V1_CON(info->regs));
+		con |= ADC_V1_CON_STANDBY;
+		writel(con, ADC_V1_CON(info->regs));
+	}
+
+	clk_disable_unprepare(info->clk);
+	regulator_disable(info->vdd);
+
+	return 0;
+}
+
+static int exynos5_adc_resume(struct device *dev)
+{
+	struct exynos5_adc *info = dev_get_data(dev);
+	int ret;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		return ret;
+
+	clk_prepare_enable(info->clk);
+
+	exynos5_adc_hw_init(info);
+
+	return 0;
+}
+
+#else
+#define exynos5_adc_suspend NULL
+#define exynos5_adc_resume NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
+			exynos5_adc_suspend,
+			exynos5_adc_resume);
+
+static struct platform_driver exynos5_adc_driver = {
+	.probe		= exynos5_adc_probe,
+	.remove		= exynos5_adc_remove,
+	.driver		= {
+		.name	= "exynos5-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(exynos5_adc_match),
+		.pm	= &exynos5_adc_pm_ops,
+	},
+};
+
+module_platform_driver(exynos5_adc_driver);
+
+MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24  0:42     ` Doug Anderson
@ 2013-01-24  9:54       ` Lars-Peter Clausen
  2013-01-24 14:20         ` Naveen Krishna Ch
  2013-01-24 16:12         ` Doug Anderson
  0 siblings, 2 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-01-24  9:54 UTC (permalink / raw)
  To: Doug Anderson
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh, Naveen Krishna

On 01/24/2013 01:42 AM, Doug Anderson wrote:
> Lars,
> 
> On Wed, Jan 23, 2013 at 4:52 AM, Lars-Peter Clausen <lars@metafoo.de> wrote:
>>> Few doubts regarding the mappings and child device handling.
>>> Kindly, suggest me better methods.
>>
>> The patch looks mostly good now. As for the mappings, the problem is that we
>> currently do not have any device tree bindings for IIO. So a proper solution
>> would be to add dt bindings for IIO.
> 
> Can you explain more how you think this would work?  It seems like
> just having child nodes as platform drivers would be OK (to me) and
> having them instantiated with of_platform_populate() seems reasonable.
> 
> ...but the one thing that is missing is a way for children to get
> access to the channel properly.
> 
> The children have access to the ADC "struct device" (it is their
> parent device) and have a channel number (their "reg" field), but
> there is no API call to map this to a "struct iio_channel".  Is that
> all that's needed in this case?  ...or are you envisioning something
> more?

Well, the idea is that the consumer doesn't need to know the channel number.
That's what the mapping takes care of. It basically creates a consumer alias
for the channel. When requesting the channel the consumer always uses the
same name.

iio_channel_get(dev_name(&pdev->dev), "voltage");

And the mapping contains an entry which maps the tuple of device name and
channel name to a real IIO channel which as been registered by a IIO device.
Note if there is only one channel you can also just use NULL for the channel
name. This is similar to how clocks are managed with the clk framework.

Now for the dt bindings I think we should stick to something similar to what
the clk framework does.

E.g.

adc: adc@12D10000 {

	#io-channel-cells = <1>;
	io-channel-output-names = "adc1", "adc2", ...;

	ncp15wb473@0 {
		compatible = "ntc,ncp15wb473";
		...
		io-channels = <&adc 0>; // First ADC channel
		io-channel-names = "voltage";
	};

	ncp15wb473@0 {
		compatible = "ntc,ncp15wb473";
		...
		io-channels = <&adc 1>; // Second ADC channel
		io-channel-names = "voltage";
	}
};

io-channel-output-names and io-channel-names can be optional. In the case
where there is only one channel it's not really necessary to use
io-channel-names.

- Lars

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24  9:54       ` Lars-Peter Clausen
@ 2013-01-24 14:20         ` Naveen Krishna Ch
  2013-01-24 18:11           ` Lars-Peter Clausen
  2013-01-24 16:12         ` Doug Anderson
  1 sibling, 1 reply; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-01-24 14:20 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Doug Anderson, Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh

On 24 January 2013 15:24, Lars-Peter Clausen <lars@metafoo.de> wrote:
>
> On 01/24/2013 01:42 AM, Doug Anderson wrote:
> > Lars,
> >
> > On Wed, Jan 23, 2013 at 4:52 AM, Lars-Peter Clausen <lars@metafoo.de> wrote:
> >>> Few doubts regarding the mappings and child device handling.
> >>> Kindly, suggest me better methods.
> >>
> >> The patch looks mostly good now. As for the mappings, the problem is that we
> >> currently do not have any device tree bindings for IIO. So a proper solution
> >> would be to add dt bindings for IIO.
> >
> > Can you explain more how you think this would work?  It seems like
> > just having child nodes as platform drivers would be OK (to me) and
> > having them instantiated with of_platform_populate() seems reasonable.
> >
> > ...but the one thing that is missing is a way for children to get
> > access to the channel properly.
> >
> > The children have access to the ADC "struct device" (it is their
> > parent device) and have a channel number (their "reg" field), but
> > there is no API call to map this to a "struct iio_channel".  Is that
> > all that's needed in this case?  ...or are you envisioning something
> > more?
>
> Well, the idea is that the consumer doesn't need to know the channel number.
> That's what the mapping takes care of. It basically creates a consumer alias
> for the channel. When requesting the channel the consumer always uses the
> same name.
>
> iio_channel_get(dev_name(&pdev->dev), "voltage");
>
> And the mapping contains an entry which maps the tuple of device name and
> channel name to a real IIO channel which as been registered by a IIO device.
> Note if there is only one channel you can also just use NULL for the channel
> name. This is similar to how clocks are managed with the clk framework.
>
> Now for the dt bindings I think we should stick to something similar to what
> the clk framework does.
>
> E.g.
>
> adc: adc@12D10000 {
>
>         #io-channel-cells = <1>;
>         io-channel-output-names = "adc1", "adc2", ...;
>
>         ncp15wb473@0 {
>                 compatible = "ntc,ncp15wb473";
>                 ...
>                 io-channels = <&adc 0>; // First ADC channel
>                 io-channel-names = "voltage";
>         };
>
>         ncp15wb473@0 {
>                 compatible = "ntc,ncp15wb473";
>                 ...
>                 io-channels = <&adc 1>; // Second ADC channel
>                 io-channel-names = "voltage";
>         }
> };
>
Hello Lars,

I've a doubt here

How do i find the child dev_name for iio_map_array_register();

cause the child devices are probed during of_platform_populate();

and during the probe the child calls
iio_channel_get(dev_name(&pdev->dev), "voltage");

The child device nodes are ncp15wb473.0 and ncp15wb473.1 in this case.
But, this may change.

Kindly, help.

Assume we have a device tree like this

        adc@12D10000 {
                #io-channel-cells = <1>;
                io-channel-output-names = "adc0", "adc1", "adc2",
                                        "adc3", "adc4", "adc5",
                                "adc6", "adc7", "adc8", "adc9";

                ncp15wb473@0 {
                        compatible = "ntc,ncp15wb473";
                        reg = <0x0>;
                        io-channel-names = "voltage";
                        pullup-uV = <1800000>;
                        pullup-ohm = <47000>;
                        pulldown-ohm = <0>;
                };
                ncp15wb473@1 {
                        compatible = "ntc,ncp15wb473";
                        reg = <0x1>;
                        io-channel-names = "voltage";
                        pullup-uV = <1800000>;
                        pullup-ohm = <47000>;
                        pulldown-ohm = <0>;
                };

      };


> io-channel-output-names and io-channel-names can be optional. In the case
> where there is only one channel it's not really necessary to use
> io-channel-names.
>
> - Lars




--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24  9:54       ` Lars-Peter Clausen
  2013-01-24 14:20         ` Naveen Krishna Ch
@ 2013-01-24 16:12         ` Doug Anderson
  2013-01-24 18:19           ` Lars-Peter Clausen
  1 sibling, 1 reply; 35+ messages in thread
From: Doug Anderson @ 2013-01-24 16:12 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh, Naveen Krishna

Lars,

Thank you for your comments / thoughts...


On Thu, Jan 24, 2013 at 1:54 AM, Lars-Peter Clausen <lars@metafoo.de> wrote:
> adc: adc@12D10000 {
>
>         #io-channel-cells = <1>;
>         io-channel-output-names = "adc1", "adc2", ...;
>
>         ncp15wb473@0 {
>                 compatible = "ntc,ncp15wb473";
>                 ...
>                 io-channels = <&adc 0>; // First ADC channel

I'm not an expert, but I think the typical way is:
* No need to include a handle to &adc.  It's logically our parent.  In
a similar way i2c devices don't specify their parent bus--they are
just listed under it.
* The "0" should be specified with reg = <0>

Everything else about this syntax looks good.

To implement this I'd imagine that we'll need a new API call, right?
In this case the thermistor driver won't know the name of the channel.
 It can find the ADC (the struct device and probably other things) and
knows a channel index.  Am I understanding properly?

I think Naveen expressed the same question, though he said it a bit
differently than me.

-Doug

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24 14:20         ` Naveen Krishna Ch
@ 2013-01-24 18:11           ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-01-24 18:11 UTC (permalink / raw)
  To: Naveen Krishna Ch
  Cc: Doug Anderson, Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh, Jonathan Cameron

On 01/24/2013 03:20 PM, Naveen Krishna Ch wrote:
> On 24 January 2013 15:24, Lars-Peter Clausen <lars@metafoo.de> wrote:
>>
>> On 01/24/2013 01:42 AM, Doug Anderson wrote:
>>> Lars,
>>>
>>> On Wed, Jan 23, 2013 at 4:52 AM, Lars-Peter Clausen <lars@metafoo.de> wrote:
>>>>> Few doubts regarding the mappings and child device handling.
>>>>> Kindly, suggest me better methods.
>>>>
>>>> The patch looks mostly good now. As for the mappings, the problem is that we
>>>> currently do not have any device tree bindings for IIO. So a proper solution
>>>> would be to add dt bindings for IIO.
>>>
>>> Can you explain more how you think this would work?  It seems like
>>> just having child nodes as platform drivers would be OK (to me) and
>>> having them instantiated with of_platform_populate() seems reasonable.
>>>
>>> ...but the one thing that is missing is a way for children to get
>>> access to the channel properly.
>>>
>>> The children have access to the ADC "struct device" (it is their
>>> parent device) and have a channel number (their "reg" field), but
>>> there is no API call to map this to a "struct iio_channel".  Is that
>>> all that's needed in this case?  ...or are you envisioning something
>>> more?
>>
>> Well, the idea is that the consumer doesn't need to know the channel number.
>> That's what the mapping takes care of. It basically creates a consumer alias
>> for the channel. When requesting the channel the consumer always uses the
>> same name.
>>
>> iio_channel_get(dev_name(&pdev->dev), "voltage");
>>
>> And the mapping contains an entry which maps the tuple of device name and
>> channel name to a real IIO channel which as been registered by a IIO device.
>> Note if there is only one channel you can also just use NULL for the channel
>> name. This is similar to how clocks are managed with the clk framework.
>>
>> Now for the dt bindings I think we should stick to something similar to what
>> the clk framework does.
>>
>> E.g.
>>
>> adc: adc@12D10000 {
>>
>>         #io-channel-cells = <1>;
>>         io-channel-output-names = "adc1", "adc2", ...;
>>
>>         ncp15wb473@0 {
>>                 compatible = "ntc,ncp15wb473";
>>                 ...
>>                 io-channels = <&adc 0>; // First ADC channel
>>                 io-channel-names = "voltage";
>>         };
>>
>>         ncp15wb473@0 {
>>                 compatible = "ntc,ncp15wb473";
>>                 ...
>>                 io-channels = <&adc 1>; // Second ADC channel
>>                 io-channel-names = "voltage";
>>         }
>> };
>>
> Hello Lars,
> 
> I've a doubt here
> 
> How do i find the child dev_name for iio_map_array_register();
> 
> cause the child devices are probed during of_platform_populate();
> 
> and during the probe the child calls
> iio_channel_get(dev_name(&pdev->dev), "voltage");
> 
> The child device nodes are ncp15wb473.0 and ncp15wb473.1 in this case.
> But, this may change.


Hi,

I think we should handle the devicetree channel mapping in the IIO core and
not in the individual drivers. If we handle it in the core we do not need to
create a iio_map and won't need to know the name of the consumer.

You'd basically need to check whether the device requesting the IIO channel
has a of_node. If it has, check if it has an io-channels attribute. If it
also has an io-channels attribute lookup the IIO device by the phandle and
create a iio_channel for the nth channel of that device.

If either the device has no of_node or no io-channels attribute fallback to
using the iio_map based lookup method.

This would require one API change though, iio_channel_get would need to take
a device instead of the device name so it has access to both the device name
and the device node. Jonathan: any particular reason why you choose to let
iio_channel_get the device name instead of the device itself?

- Lars

> 
> Kindly, help.
> 
> Assume we have a device tree like this
> 
>         adc@12D10000 {
>                 #io-channel-cells = <1>;
>                 io-channel-output-names = "adc0", "adc1", "adc2",
>                                         "adc3", "adc4", "adc5",
>                                 "adc6", "adc7", "adc8", "adc9";
> 
>                 ncp15wb473@0 {
>                         compatible = "ntc,ncp15wb473";
>                         reg = <0x0>;
>                         io-channel-names = "voltage";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                 };
>                 ncp15wb473@1 {
>                         compatible = "ntc,ncp15wb473";
>                         reg = <0x1>;
>                         io-channel-names = "voltage";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                 };
> 
>       };
> 
> 
>> io-channel-output-names and io-channel-names can be optional. In the case
>> where there is only one channel it's not really necessary to use
>> io-channel-names.
>>


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24 16:12         ` Doug Anderson
@ 2013-01-24 18:19           ` Lars-Peter Clausen
  2013-01-24 19:15             ` Tomasz Figa
  0 siblings, 1 reply; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-01-24 18:19 UTC (permalink / raw)
  To: Doug Anderson
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh, Naveen Krishna

On 01/24/2013 05:12 PM, Doug Anderson wrote:
> Lars,
> 
> Thank you for your comments / thoughts...
> 

Hi,

> 
> On Thu, Jan 24, 2013 at 1:54 AM, Lars-Peter Clausen <lars@metafoo.de> wrote:
>> adc: adc@12D10000 {
>>
>>         #io-channel-cells = <1>;
>>         io-channel-output-names = "adc1", "adc2", ...;
>>
>>         ncp15wb473@0 {
>>                 compatible = "ntc,ncp15wb473";
>>                 ...
>>                 io-channels = <&adc 0>; // First ADC channel
> 
> I'm not an expert, but I think the typical way is:
> * No need to include a handle to &adc.  It's logically our parent.  In
> a similar way i2c devices don't specify their parent bus--they are
> just listed under it.
> * The "0" should be specified with reg = <0>

The relationship between the IIO sensor device and the consumer device is
not always a parent child relationship. In this case it makes sense to have
the ADC as the parent for the thermistors. But for other cases this may not
be true. E.g. take a touchscreen or power monitoring platform device which
uses the IIO device to do measurements.

> 
> To implement this I'd imagine that we'll need a new API call, right?
> In this case the thermistor driver won't know the name of the channel.
>  It can find the ADC (the struct device and probably other things) and
> knows a channel index.  Am I understanding properly?

This can be done by adding a new api call, but it would be best if both dt
and non-dt based consumers can use the same function. I outlined one
possible solution how this can be done in the previous mail to Naveen.

- Lars


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24 18:19           ` Lars-Peter Clausen
@ 2013-01-24 19:15             ` Tomasz Figa
  2013-01-24 19:30               ` Lars-Peter Clausen
  0 siblings, 1 reply; 35+ messages in thread
From: Tomasz Figa @ 2013-01-24 19:15 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Doug Anderson, Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh, Naveen Krishna

Hi,

On Thursday 24 of January 2013 19:19:57 Lars-Peter Clausen wrote:
> On 01/24/2013 05:12 PM, Doug Anderson wrote:
> > Lars,
> > 
> > Thank you for your comments / thoughts...
> 
> Hi,
> 
> > On Thu, Jan 24, 2013 at 1:54 AM, Lars-Peter Clausen <lars@metafoo.de> 
wrote:
> >> adc: adc@12D10000 {
> >> 
> >>         #io-channel-cells = <1>;
> >>         io-channel-output-names = "adc1", "adc2", ...;
> >>         
> >>         ncp15wb473@0 {
> >>         
> >>                 compatible = "ntc,ncp15wb473";
> >>                 ...
> >>                 io-channels = <&adc 0>; // First ADC channel
> > 
> > I'm not an expert, but I think the typical way is:
> > * No need to include a handle to &adc.  It's logically our parent.  In
> > a similar way i2c devices don't specify their parent bus--they are
> > just listed under it.
> > * The "0" should be specified with reg = <0>
> 
> The relationship between the IIO sensor device and the consumer device
> is not always a parent child relationship. In this case it makes sense
> to have the ADC as the parent for the thermistors. But for other cases
> this may not be true. E.g. take a touchscreen or power monitoring
> platform device which uses the IIO device to do measurements.

The policy is to use children with reg property only inside a node 
representing a bus controller through which the child device is being 
accessed (like I2C, SPI).

I would see IIO bindings similar to what we have with GPIOs, interrupts or 
regulators, so io-channels = <&iio-controller channel> seems fine (or 
rather iio-channels) with the node under appropriate parent.

> > To implement this I'd imagine that we'll need a new API call, right?
> > In this case the thermistor driver won't know the name of the channel.
> > 
> >  It can find the ADC (the struct device and probably other things) and
> > 
> > knows a channel index.  Am I understanding properly?
> 
> This can be done by adding a new api call, but it would be best if both
> dt and non-dt based consumers can use the same function. I outlined one
> possible solution how this can be done in the previous mail to Naveen.

In case of the solution I mentioned, implementation would be almost 
identical to what is done with GPIOs (see drivers/gpio/gpiolib-of.c).

Best regards,
Tomasz


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24 19:15             ` Tomasz Figa
@ 2013-01-24 19:30               ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-01-24 19:30 UTC (permalink / raw)
  To: Tomasz Figa
  Cc: Doug Anderson, Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, gregkh, Naveen Krishna

On 01/24/2013 08:15 PM, Tomasz Figa wrote:
> Hi,
> 
> On Thursday 24 of January 2013 19:19:57 Lars-Peter Clausen wrote:
>> On 01/24/2013 05:12 PM, Doug Anderson wrote:
>>> Lars,
>>>
>>> Thank you for your comments / thoughts...
>>
>> Hi,
>>
>>> On Thu, Jan 24, 2013 at 1:54 AM, Lars-Peter Clausen <lars@metafoo.de> 
> wrote:
>>>> adc: adc@12D10000 {
>>>>
>>>>         #io-channel-cells = <1>;
>>>>         io-channel-output-names = "adc1", "adc2", ...;
>>>>         
>>>>         ncp15wb473@0 {
>>>>         
>>>>                 compatible = "ntc,ncp15wb473";
>>>>                 ...
>>>>                 io-channels = <&adc 0>; // First ADC channel
>>>
>>> I'm not an expert, but I think the typical way is:
>>> * No need to include a handle to &adc.  It's logically our parent.  In
>>> a similar way i2c devices don't specify their parent bus--they are
>>> just listed under it.
>>> * The "0" should be specified with reg = <0>
>>
>> The relationship between the IIO sensor device and the consumer device
>> is not always a parent child relationship. In this case it makes sense
>> to have the ADC as the parent for the thermistors. But for other cases
>> this may not be true. E.g. take a touchscreen or power monitoring
>> platform device which uses the IIO device to do measurements.
> 
> The policy is to use children with reg property only inside a node 
> representing a bus controller through which the child device is being 
> accessed (like I2C, SPI).
> 
> I would see IIO bindings similar to what we have with GPIOs, interrupts or 
> regulators, so io-channels = <&iio-controller channel> seems fine (or 
> rather iio-channels) with the node under appropriate parent.

IIO is a very Linux specific term, the device tree bindings should be as OS
agnostic as possible, so io-channels is probably the better term.


> 
>>> To implement this I'd imagine that we'll need a new API call, right?
>>> In this case the thermistor driver won't know the name of the channel.
>>>
>>>  It can find the ADC (the struct device and probably other things) and
>>>
>>> knows a channel index.  Am I understanding properly?
>>
>> This can be done by adding a new api call, but it would be best if both
>> dt and non-dt based consumers can use the same function. I outlined one
>> possible solution how this can be done in the previous mail to Naveen.
> 
> In case of the solution I mentioned, implementation would be almost 
> identical to what is done with GPIOs (see drivers/gpio/gpiolib-of.c).

Although similar to the GPIO bindings, the clk bindings are in my opinion an
even better example.

- Lars

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24  4:58 ` [PATCH] " Naveen Krishna Chatradhi
@ 2013-01-26 10:57   ` Jonathan Cameron
  2013-01-30  6:02     ` Naveen Krishna Ch
  0 siblings, 1 reply; 35+ messages in thread
From: Jonathan Cameron @ 2013-01-26 10:57 UTC (permalink / raw)
  To: Naveen Krishna Chatradhi
  Cc: linux-iio, linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

On 01/24/2013 04:58 AM, Naveen Krishna Chatradhi wrote:
> This patch adds driver for ADC IP found on EXYNOS5250 and EXYNOS5410
> from Samsung. Also adds the Documentation for device tree bindings.
> 
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
Just a quick general comment on patch formatting. For later versions give
a title of
[PATCH V5]...
to the email as then it's easy for those of us who have been sitting
back and quitely not reading the thread to figure out which the latest
patch is.

Thanks

Jonathan

> ---
> Changes since v1:
> 
> 1. Fixed comments from Lars
> 2. Added support for ADC on EXYNOS5410
> 
> Changes since v2:
> 
> 1. Changed the instance name for (struct iio_dev *) to indio_dev
> 2. Changed devm_request_irq to request_irq
> 
> Few doubts regarding the mappings and child device handling.
> Kindly, suggest me better methods.
> 
> Changes since v3:
> 
> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
> 2. Moved init_completion before irq_request
> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
> 4. Use number of channels as per the ADC version 
> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
> 6. Update the Documentation to include EXYNOS5410 compatible
> 
> Doug, i've used
> 	chan = iio_channel_get(dev_name(&pdev->dev), "adc3");
> in ntc thermistor driver during probe and
> 	iio_read_channel_raw(chan, &val);
> for read.
> 
> But, then the drivers become kind of coupled. Right.
> 
> Lars, Is there an other way.
> 
> Thanks for the comments
> 
>  .../bindings/arm/samsung/exynos5-adc.txt           |   38 ++
>  drivers/iio/adc/Kconfig                            |    7 +
>  drivers/iio/adc/Makefile                           |    1 +
>  drivers/iio/adc/exynos5_adc.c                      |  475 ++++++++++++++++++++
>  4 files changed, 521 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>  create mode 100644 drivers/iio/adc/exynos5_adc.c
> 
> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
> new file mode 100644
> index 0000000..0f281d9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
> @@ -0,0 +1,38 @@
> +Samsung Exynos5 Analog to Digital Converter bindings
> +
> +Required properties:
> +- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
> +			Must be "samsung,exynos5410-adc" for exynos5410 controllers.
> +- reg:			Contains ADC register address range (base address and
> +			length).
> +- interrupts: 		Contains the interrupt information for the timer. The
> +			format is being dependent on which interrupt controller
> +			the Samsung device uses.
> +
> +Note: child nodes can be added for auto probing from device tree.
> +
> +Example: adding device info in dtsi file
> +
> +adc@12D10000 {
> +	compatible = "samsung,exynos5250-adc";
> +	reg = <0x12D10000 0x100>;
> +	interrupts = <0 106 0>;
> +	#address-cells = <1>;
> +	#size-cells = <1>;
> +	ranges;
> +};
> +
> +
> +Example: Adding child nodes in dts file
> +
> +adc@12D10000 {
> +
> +	/* NTC thermistor is a hwmon device */
> +	ncp15wb473@0 {
> +		compatible = "ntc,ncp15wb473";
> +		reg = <0x0>;
> +		pullup-uV = <1800000>;
> +		pullup-ohm = <47000>;
> +		pulldown-ohm = <0>;
> +	};
> +};
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index fe822a1..33ceabf 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -91,6 +91,13 @@ config AT91_ADC
>  	help
>  	  Say yes here to build support for Atmel AT91 ADC.
>  
> +config EXYNOS5_ADC
> +	bool "Exynos5 ADC driver support"
> +	help
> +	  Core support for the ADC block found in the Samsung EXYNOS5 series
> +	  of SoCs for drivers such as the touchscreen and hwmon to use to share
> +	  this resource.
> +
>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
> new file mode 100644
> index 0000000..4963649
> --- /dev/null
> +++ b/drivers/iio/adc/exynos5_adc.c
> @@ -0,0 +1,475 @@
> +/*
> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
> + *
> + *  8 ~ 10 channel, 10/12-bit ADC
> + *
> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> + *
> + *  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.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; if not, write to the Free Software
> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +enum adc_version {
> +	ADC_V1,
> +	ADC_V2
> +};
> +
> +/* EXYNOS5250 ADC_V1 registers definitions */
> +#define ADC_V1_CON(x)		((x) + 0x00)
> +#define ADC_V1_DLY(x)		((x) + 0x08)
> +#define ADC_V1_DATX(x)		((x) + 0x0C)
> +#define ADC_V1_INTCLR(x)	((x) + 0x18)
> +#define ADC_V1_MUX(x)		((x) + 0x1c)
> +
> +/* EXYNOS5410 ADC_V2 registers definitions */
> +#define ADC_V2_CON1(x)		((x) + 0x00)
> +#define ADC_V2_CON2(x)		((x) + 0x04)
> +#define ADC_V2_STAT(x)		((x) + 0x08)
> +#define ADC_V2_INT_EN(x)	((x) + 0x10)
> +#define ADC_V2_INT_ST(x)	((x) + 0x14)
> +#define ADC_V2_VER(x)		((x) + 0x20)
> +
> +/* Bit definitions for ADC_V1 */
> +#define ADC_V1_CON_RES		(1u << 16)
> +#define ADC_V1_CON_PRSCEN	(1u << 14)
> +#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
> +#define ADC_V1_CON_STANDBY	(1u << 2)
> +
> +/* Bit definitions for ADC_V2 */
> +#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
> +
> +#define ADC_V2_CON2_OSEL	(1u << 10)
> +#define ADC_V2_CON2_ESEL	(1u << 9)
> +#define ADC_V2_CON2_HIGHF	(1u << 8)
> +#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
> +#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
> +#define ADC_V2_CON2_ACH_MASK	0xF
> +
> +/* Bit definitions common for ADC_V1 and ADC_V2 */
> +#define ADC_V1_CON_EN_START		(1u << 0)
> +#define ADC_V1_DATX_MASK	0xFFF
> +
> +struct exynos5_adc {
> +	void __iomem		*regs;
> +	struct clk		*clk;
> +	unsigned int		irq;
> +	struct regulator	*vdd;
> +
> +	struct completion	completion;
> +
> +	struct iio_map		*map;
> +	u32			value;
> +	unsigned int            version;
> +};
> +
> +static const struct of_device_id exynos5_adc_match[] = {
> +	{ .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
> +	{ .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
> +
> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
> +{
> +	const struct of_device_id *match;
> +
> +	match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
> +	return (unsigned int)match->data;
> +}
> +
> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
> +static struct iio_map exynos5_adc_iio_map[] = {
> +	{
> +		.consumer_dev_name = "0.ncp15wb473",
> +		.consumer_channel = "adc3",
> +		.adc_channel_label = "adc3",
> +	},
> +	{
> +		.consumer_dev_name = "1.ncp15wb473",
> +		.consumer_channel = "adc4",
> +		.adc_channel_label = "adc4",
> +	},
> +	{
> +		.consumer_dev_name = "2.ncp15wb473",
> +		.consumer_channel = "adc5",
> +		.adc_channel_label = "adc5",
> +	},
> +	{
> +		.consumer_dev_name = "3.ncp15wb473",
> +		.consumer_channel = "adc6",
> +		.adc_channel_label = "adc6",
> +	},
> +	{},
> +};
> +
> +static int exynos5_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 con1, con2;
> +
> +	if (mask == IIO_CHAN_INFO_RAW) {
> +		mutex_lock(&indio_dev->mlock);
> +
> +		/* Select the channel to be used and Trigger conversion */
> +		if (info->version == ADC_V2) {
> +			con2 = readl(ADC_V2_CON2(info->regs));
> +			con2 &= ~ADC_V2_CON2_ACH_MASK;
> +			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
> +			writel(con2, ADC_V2_CON2(info->regs));
> +
> +			con1 = readl(ADC_V2_CON1(info->regs));
> +			writel(con1 | ADC_V1_CON_EN_START,
> +					ADC_V2_CON1(info->regs));
> +		} else {
> +			writel(chan->address, ADC_V1_MUX(info->regs));
> +
> +			con1 = readl(ADC_V1_CON(info->regs));
> +			writel(con1 | ADC_V1_CON_EN_START,
> +					ADC_V1_CON(info->regs));
> +		}
> +
> +		wait_for_completion(&info->completion);
> +		*val = info->value;
> +
> +		mutex_unlock(&indio_dev->mlock);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
> +{
> +	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
> +
> +	/* Read value */
> +	info->value = readl(ADC_V1_DATX(info->regs)) &
> +						ADC_V1_DATX_MASK;
> +	/* clear irq */
> +	if (info->version == ADC_V2)
> +		writel(1, ADC_V2_INT_ST(info->regs));
> +	else
> +		writel(1, ADC_V1_INTCLR(info->regs));
> +
> +	complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 ret;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	if (readval != NULL) {
> +		ret = readl(info->regs + reg);
> +		*readval = ret;
> +	} else
> +		ret = -EINVAL;
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info exynos5_adc_iio_info = {
> +	.read_raw = &exynos5_read_raw,
> +	.debugfs_reg_access = &exynos5_adc_reg_access,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define ADC_CHANNEL(_index, _id) {			\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.channel = _index,				\
> +	.address = _index,				\
> +	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	.datasheet_name = _id,				\
> +}
> +
> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
> +	ADC_CHANNEL(0, "adc0"),
> +	ADC_CHANNEL(1, "adc1"),
> +	ADC_CHANNEL(2, "adc2"),
> +	ADC_CHANNEL(3, "adc3"),
> +	ADC_CHANNEL(4, "adc4"),
> +	ADC_CHANNEL(5, "adc5"),
> +	ADC_CHANNEL(6, "adc6"),
> +	ADC_CHANNEL(7, "adc7"),
> +	ADC_CHANNEL(8, "adc8"),
> +	ADC_CHANNEL(9, "adc9"),
> +};
> +
> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	platform_device_unregister(pdev);
> +
> +	return 0;
> +}
> +
> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
> +{
> +	u32 con1, con2;
> +
> +	if (info->version == ADC_V2) {
> +		con1 = ADC_V2_CON1_SOFT_RESET;
> +		writel(con1, ADC_V2_CON1(info->regs));
> +
> +		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
> +			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
> +		writel(con2, ADC_V2_CON2(info->regs));
> +
> +		/* Enable interrupts */
> +		writel(1, ADC_V2_INT_EN(info->regs));
> +	} else {
> +		/* set default prescaler values and Enable prescaler */
> +		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
> +
> +		/* Enable 12-bit ADC resolution */
> +		con1 |= ADC_V1_CON_RES;
> +		writel(con1, ADC_V1_CON(info->regs));
> +	}
> +}
> +
> +static int exynos5_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos5_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *indio_dev = NULL;
> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +
> +	if (!np)
> +		return ret;
> +
> +	indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
> +	if (!indio_dev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(indio_dev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> +	if (!info->regs)
> +		return -ENOMEM;
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	init_completion(&info->completion);
> +
> +	ret = request_irq(info->irq, exynos5_adc_isr,
> +					0, dev_name(&pdev->dev), info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
> +							PTR_ERR(info->clk));
> +		ret = PTR_ERR(info->clk);
> +		goto err_irq;
> +	}
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
> +							PTR_ERR(info->vdd));
> +		ret = PTR_ERR(info->vdd);
> +		goto err_irq;
> +	}
> +
> +	info->version = exynos5_adc_get_version(pdev);
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &exynos5_adc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = exynos5_adc_iio_channels;
> +
> +	if (info->version == ADC_V1)
> +		/* ADC core in EXYNOS5250 has 8 channels */
> +		indio_dev->num_channels =
> +				ARRAY_SIZE(exynos5_adc_iio_channels) - 2;
> +	else
> +		/* ADC core in EXYNOS5410 has 10 channels */
> +		indio_dev->num_channels =
> +				ARRAY_SIZE(exynos5_adc_iio_channels);
> +
> +	info->map = exynos5_adc_iio_map;
> +
> +	ret = iio_map_array_register(indio_dev, info->map);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
> +		goto err_irq;
> +	}
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto err_map;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos5_adc_hw_init(info);
> +
> +	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed adding child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
> +	clk_disable_unprepare(info->clk);
> +	regulator_disable(info->vdd);
> +err_iio_dev:
> +	iio_device_unregister(indio_dev);
> +err_map:
> +	iio_map_array_unregister(indio_dev, info->map);
> +err_irq:
> +	free_irq(info->irq, info);
> +err_iio:
> +	iio_device_free(indio_dev);
> +	return ret;
> +}
> +
> +static int exynos5_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +
> +	clk_disable_unprepare(info->clk);
> +	regulator_disable(info->vdd);
> +	iio_device_unregister(indio_dev);
> +	iio_map_array_unregister(indio_dev, info->map);
> +	free_irq(info->irq, info);
> +	iio_device_free(indio_dev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int exynos5_adc_suspend(struct device *dev)
> +{
> +	struct exynos5_adc *info = dev_get_data(dev);
> +	u32 con;
> +
> +	if (info->version == ADC_V2) {
> +		con = readl(ADC_V2_CON1(info->regs));
> +		con &= ~ADC_V1_CON_EN_START;
> +		writel(con, ADC_V2_CON1(info->regs));
> +	} else {
> +		con = readl(ADC_V1_CON(info->regs));
> +		con |= ADC_V1_CON_STANDBY;
> +		writel(con, ADC_V1_CON(info->regs));
> +	}
> +
> +	clk_disable_unprepare(info->clk);
> +	regulator_disable(info->vdd);
> +
> +	return 0;
> +}
> +
> +static int exynos5_adc_resume(struct device *dev)
> +{
> +	struct exynos5_adc *info = dev_get_data(dev);
> +	int ret;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		return ret;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos5_adc_hw_init(info);
> +
> +	return 0;
> +}
> +
> +#else
> +#define exynos5_adc_suspend NULL
> +#define exynos5_adc_resume NULL
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
> +			exynos5_adc_suspend,
> +			exynos5_adc_resume);
> +
> +static struct platform_driver exynos5_adc_driver = {
> +	.probe		= exynos5_adc_probe,
> +	.remove		= exynos5_adc_remove,
> +	.driver		= {
> +		.name	= "exynos5-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = of_match_ptr(exynos5_adc_match),
> +		.pm	= &exynos5_adc_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(exynos5_adc_driver);
> +
> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> +MODULE_LICENSE("GPL");
> 

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-26 10:57   ` Jonathan Cameron
@ 2013-01-30  6:02     ` Naveen Krishna Ch
  0 siblings, 0 replies; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-01-30  6:02 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh, lars

On 26 January 2013 16:27, Jonathan Cameron <jic23@kernel.org> wrote:
> On 01/24/2013 04:58 AM, Naveen Krishna Chatradhi wrote:
>> This patch adds driver for ADC IP found on EXYNOS5250 and EXYNOS5410
>> from Samsung. Also adds the Documentation for device tree bindings.
>>
>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> Just a quick general comment on patch formatting. For later versions give
> a title of
> [PATCH V5]...
> to the email as then it's easy for those of us who have been sitting
> back and quitely not reading the thread to figure out which the latest
> patch is.
>
> Thanks
>
> Jonathan
Sure Jonathan (I was assuming using message id would be enough), i
will correct it.
>
>> ---
>> Changes since v1:
>>
>> 1. Fixed comments from Lars
>> 2. Added support for ADC on EXYNOS5410
>>
>> Changes since v2:
>>
>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>> 2. Changed devm_request_irq to request_irq
>>
>> Few doubts regarding the mappings and child device handling.
>> Kindly, suggest me better methods.
>>
>> Changes since v3:
>>
>> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
>> 2. Moved init_completion before irq_request
>> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
>> 4. Use number of channels as per the ADC version
>> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
>> 6. Update the Documentation to include EXYNOS5410 compatible
>>
>> Doug, i've used
>>       chan = iio_channel_get(dev_name(&pdev->dev), "adc3");
>> in ntc thermistor driver during probe and
>>       iio_read_channel_raw(chan, &val);
>> for read.
>>
>> But, then the drivers become kind of coupled. Right.
>>
>> Lars, Is there an other way.
>>
>> Thanks for the comments
>>
>>  .../bindings/arm/samsung/exynos5-adc.txt           |   38 ++
>>  drivers/iio/adc/Kconfig                            |    7 +
>>  drivers/iio/adc/Makefile                           |    1 +
>>  drivers/iio/adc/exynos5_adc.c                      |  475 ++++++++++++++++++++
>>  4 files changed, 521 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>  create mode 100644 drivers/iio/adc/exynos5_adc.c
>>
>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>> new file mode 100644
>> index 0000000..0f281d9
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>> @@ -0,0 +1,38 @@
>> +Samsung Exynos5 Analog to Digital Converter bindings
>> +
>> +Required properties:
>> +- compatible:                Must be "samsung,exynos5250-adc" for exynos5250 controllers.
>> +                     Must be "samsung,exynos5410-adc" for exynos5410 controllers.
>> +- reg:                       Contains ADC register address range (base address and
>> +                     length).
>> +- interrupts:                Contains the interrupt information for the timer. The
>> +                     format is being dependent on which interrupt controller
>> +                     the Samsung device uses.
>> +
>> +Note: child nodes can be added for auto probing from device tree.
>> +
>> +Example: adding device info in dtsi file
>> +
>> +adc@12D10000 {
>> +     compatible = "samsung,exynos5250-adc";
>> +     reg = <0x12D10000 0x100>;
>> +     interrupts = <0 106 0>;
>> +     #address-cells = <1>;
>> +     #size-cells = <1>;
>> +     ranges;
>> +};
>> +
>> +
>> +Example: Adding child nodes in dts file
>> +
>> +adc@12D10000 {
>> +
>> +     /* NTC thermistor is a hwmon device */
>> +     ncp15wb473@0 {
>> +             compatible = "ntc,ncp15wb473";
>> +             reg = <0x0>;
>> +             pullup-uV = <1800000>;
>> +             pullup-ohm = <47000>;
>> +             pulldown-ohm = <0>;
>> +     };
>> +};
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index fe822a1..33ceabf 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -91,6 +91,13 @@ config AT91_ADC
>>       help
>>         Say yes here to build support for Atmel AT91 ADC.
>>
>> +config EXYNOS5_ADC
>> +     bool "Exynos5 ADC driver support"
>> +     help
>> +       Core support for the ADC block found in the Samsung EXYNOS5 series
>> +       of SoCs for drivers such as the touchscreen and hwmon to use to share
>> +       this resource.
>> +
>>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
>> new file mode 100644
>> index 0000000..4963649
>> --- /dev/null
>> +++ b/drivers/iio/adc/exynos5_adc.c
>> @@ -0,0 +1,475 @@
>> +/*
>> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
>> + *
>> + *  8 ~ 10 channel, 10/12-bit ADC
>> + *
>> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>> + *
>> + *  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.
>> + *
>> + *  This program is distributed in the hope that it will be useful,
>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + *  GNU General Public License for more details.
>> + *
>> + *  You should have received a copy of the GNU General Public License
>> + *  along with this program; if not, write to the Free Software
>> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/kernel.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/of_platform.h>
>> +
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/iio/driver.h>
>> +
>> +enum adc_version {
>> +     ADC_V1,
>> +     ADC_V2
>> +};
>> +
>> +/* EXYNOS5250 ADC_V1 registers definitions */
>> +#define ADC_V1_CON(x)                ((x) + 0x00)
>> +#define ADC_V1_DLY(x)                ((x) + 0x08)
>> +#define ADC_V1_DATX(x)               ((x) + 0x0C)
>> +#define ADC_V1_INTCLR(x)     ((x) + 0x18)
>> +#define ADC_V1_MUX(x)                ((x) + 0x1c)
>> +
>> +/* EXYNOS5410 ADC_V2 registers definitions */
>> +#define ADC_V2_CON1(x)               ((x) + 0x00)
>> +#define ADC_V2_CON2(x)               ((x) + 0x04)
>> +#define ADC_V2_STAT(x)               ((x) + 0x08)
>> +#define ADC_V2_INT_EN(x)     ((x) + 0x10)
>> +#define ADC_V2_INT_ST(x)     ((x) + 0x14)
>> +#define ADC_V2_VER(x)                ((x) + 0x20)
>> +
>> +/* Bit definitions for ADC_V1 */
>> +#define ADC_V1_CON_RES               (1u << 16)
>> +#define ADC_V1_CON_PRSCEN    (1u << 14)
>> +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
>> +#define ADC_V1_CON_STANDBY   (1u << 2)
>> +
>> +/* Bit definitions for ADC_V2 */
>> +#define ADC_V2_CON1_SOFT_RESET       (1u << 2)
>> +
>> +#define ADC_V2_CON2_OSEL     (1u << 10)
>> +#define ADC_V2_CON2_ESEL     (1u << 9)
>> +#define ADC_V2_CON2_HIGHF    (1u << 8)
>> +#define ADC_V2_CON2_C_TIME(x)        (((x) & 7) << 4)
>> +#define ADC_V2_CON2_ACH_SEL(x)       (((x) & 0xF) << 0)
>> +#define ADC_V2_CON2_ACH_MASK 0xF
>> +
>> +/* Bit definitions common for ADC_V1 and ADC_V2 */
>> +#define ADC_V1_CON_EN_START          (1u << 0)
>> +#define ADC_V1_DATX_MASK     0xFFF
>> +
>> +struct exynos5_adc {
>> +     void __iomem            *regs;
>> +     struct clk              *clk;
>> +     unsigned int            irq;
>> +     struct regulator        *vdd;
>> +
>> +     struct completion       completion;
>> +
>> +     struct iio_map          *map;
>> +     u32                     value;
>> +     unsigned int            version;
>> +};
>> +
>> +static const struct of_device_id exynos5_adc_match[] = {
>> +     { .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
>> +     { .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
>> +     {},
>> +};
>> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
>> +
>> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
>> +{
>> +     const struct of_device_id *match;
>> +
>> +     match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
>> +     return (unsigned int)match->data;
>> +}
>> +
>> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
>> +static struct iio_map exynos5_adc_iio_map[] = {
>> +     {
>> +             .consumer_dev_name = "0.ncp15wb473",
>> +             .consumer_channel = "adc3",
>> +             .adc_channel_label = "adc3",
>> +     },
>> +     {
>> +             .consumer_dev_name = "1.ncp15wb473",
>> +             .consumer_channel = "adc4",
>> +             .adc_channel_label = "adc4",
>> +     },
>> +     {
>> +             .consumer_dev_name = "2.ncp15wb473",
>> +             .consumer_channel = "adc5",
>> +             .adc_channel_label = "adc5",
>> +     },
>> +     {
>> +             .consumer_dev_name = "3.ncp15wb473",
>> +             .consumer_channel = "adc6",
>> +             .adc_channel_label = "adc6",
>> +     },
>> +     {},
>> +};
>> +
>> +static int exynos5_read_raw(struct iio_dev *indio_dev,
>> +                             struct iio_chan_spec const *chan,
>> +                             int *val,
>> +                             int *val2,
>> +                             long mask)
>> +{
>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>> +     u32 con1, con2;
>> +
>> +     if (mask == IIO_CHAN_INFO_RAW) {
>> +             mutex_lock(&indio_dev->mlock);
>> +
>> +             /* Select the channel to be used and Trigger conversion */
>> +             if (info->version == ADC_V2) {
>> +                     con2 = readl(ADC_V2_CON2(info->regs));
>> +                     con2 &= ~ADC_V2_CON2_ACH_MASK;
>> +                     con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
>> +                     writel(con2, ADC_V2_CON2(info->regs));
>> +
>> +                     con1 = readl(ADC_V2_CON1(info->regs));
>> +                     writel(con1 | ADC_V1_CON_EN_START,
>> +                                     ADC_V2_CON1(info->regs));
>> +             } else {
>> +                     writel(chan->address, ADC_V1_MUX(info->regs));
>> +
>> +                     con1 = readl(ADC_V1_CON(info->regs));
>> +                     writel(con1 | ADC_V1_CON_EN_START,
>> +                                     ADC_V1_CON(info->regs));
>> +             }
>> +
>> +             wait_for_completion(&info->completion);
>> +             *val = info->value;
>> +
>> +             mutex_unlock(&indio_dev->mlock);
>> +
>> +             return IIO_VAL_INT;
>> +     }
>> +
>> +     return -EINVAL;
>> +}
>> +
>> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
>> +{
>> +     struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
>> +
>> +     /* Read value */
>> +     info->value = readl(ADC_V1_DATX(info->regs)) &
>> +                                             ADC_V1_DATX_MASK;
>> +     /* clear irq */
>> +     if (info->version == ADC_V2)
>> +             writel(1, ADC_V2_INT_ST(info->regs));
>> +     else
>> +             writel(1, ADC_V1_INTCLR(info->regs));
>> +
>> +     complete(&info->completion);
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
>> +                           unsigned reg, unsigned writeval,
>> +                           unsigned *readval)
>> +{
>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>> +     u32 ret;
>> +
>> +     mutex_lock(&indio_dev->mlock);
>> +
>> +     if (readval != NULL) {
>> +             ret = readl(info->regs + reg);
>> +             *readval = ret;
>> +     } else
>> +             ret = -EINVAL;
>> +
>> +     mutex_unlock(&indio_dev->mlock);
>> +
>> +     return ret;
>> +}
>> +
>> +static const struct iio_info exynos5_adc_iio_info = {
>> +     .read_raw = &exynos5_read_raw,
>> +     .debugfs_reg_access = &exynos5_adc_reg_access,
>> +     .driver_module = THIS_MODULE,
>> +};
>> +
>> +#define ADC_CHANNEL(_index, _id) {                   \
>> +     .type = IIO_VOLTAGE,                            \
>> +     .indexed = 1,                                   \
>> +     .channel = _index,                              \
>> +     .address = _index,                              \
>> +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
>> +     .datasheet_name = _id,                          \
>> +}
>> +
>> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
>> +     ADC_CHANNEL(0, "adc0"),
>> +     ADC_CHANNEL(1, "adc1"),
>> +     ADC_CHANNEL(2, "adc2"),
>> +     ADC_CHANNEL(3, "adc3"),
>> +     ADC_CHANNEL(4, "adc4"),
>> +     ADC_CHANNEL(5, "adc5"),
>> +     ADC_CHANNEL(6, "adc6"),
>> +     ADC_CHANNEL(7, "adc7"),
>> +     ADC_CHANNEL(8, "adc8"),
>> +     ADC_CHANNEL(9, "adc9"),
>> +};
>> +
>> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
>> +{
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +
>> +     platform_device_unregister(pdev);
>> +
>> +     return 0;
>> +}
>> +
>> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
>> +{
>> +     u32 con1, con2;
>> +
>> +     if (info->version == ADC_V2) {
>> +             con1 = ADC_V2_CON1_SOFT_RESET;
>> +             writel(con1, ADC_V2_CON1(info->regs));
>> +
>> +             con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
>> +                     ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
>> +             writel(con2, ADC_V2_CON2(info->regs));
>> +
>> +             /* Enable interrupts */
>> +             writel(1, ADC_V2_INT_EN(info->regs));
>> +     } else {
>> +             /* set default prescaler values and Enable prescaler */
>> +             con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
>> +
>> +             /* Enable 12-bit ADC resolution */
>> +             con1 |= ADC_V1_CON_RES;
>> +             writel(con1, ADC_V1_CON(info->regs));
>> +     }
>> +}
>> +
>> +static int exynos5_adc_probe(struct platform_device *pdev)
>> +{
>> +     struct exynos5_adc *info = NULL;
>> +     struct device_node *np = pdev->dev.of_node;
>> +     struct iio_dev *indio_dev = NULL;
>> +     struct resource *mem;
>> +     int ret = -ENODEV;
>> +     int irq;
>> +
>> +     if (!np)
>> +             return ret;
>> +
>> +     indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
>> +     if (!indio_dev) {
>> +             dev_err(&pdev->dev, "failed allocating iio device\n");
>> +             return -ENOMEM;
>> +     }
>> +
>> +     info = iio_priv(indio_dev);
>> +
>> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +
>> +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
>> +     if (!info->regs)
>> +             return -ENOMEM;
>> +
>> +     irq = platform_get_irq(pdev, 0);
>> +     if (irq < 0) {
>> +             dev_err(&pdev->dev, "no irq resource?\n");
>> +             ret = irq;
>> +             goto err_iio;
>> +     }
>> +
>> +     info->irq = irq;
>> +
>> +     init_completion(&info->completion);
>> +
>> +     ret = request_irq(info->irq, exynos5_adc_isr,
>> +                                     0, dev_name(&pdev->dev), info);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>> +                                                     info->irq);
>> +             goto err_iio;
>> +     }
>> +
>> +     info->clk = devm_clk_get(&pdev->dev, "adc");
>> +     if (IS_ERR(info->clk)) {
>> +             dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>> +                                                     PTR_ERR(info->clk));
>> +             ret = PTR_ERR(info->clk);
>> +             goto err_irq;
>> +     }
>> +
>> +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
>> +     if (IS_ERR(info->vdd)) {
>> +             dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
>> +                                                     PTR_ERR(info->vdd));
>> +             ret = PTR_ERR(info->vdd);
>> +             goto err_irq;
>> +     }
>> +
>> +     info->version = exynos5_adc_get_version(pdev);
>> +
>> +     platform_set_drvdata(pdev, indio_dev);
>> +
>> +     indio_dev->name = dev_name(&pdev->dev);
>> +     indio_dev->dev.parent = &pdev->dev;
>> +     indio_dev->dev.of_node = pdev->dev.of_node;
>> +     indio_dev->info = &exynos5_adc_iio_info;
>> +     indio_dev->modes = INDIO_DIRECT_MODE;
>> +     indio_dev->channels = exynos5_adc_iio_channels;
>> +
>> +     if (info->version == ADC_V1)
>> +             /* ADC core in EXYNOS5250 has 8 channels */
>> +             indio_dev->num_channels =
>> +                             ARRAY_SIZE(exynos5_adc_iio_channels) - 2;
>> +     else
>> +             /* ADC core in EXYNOS5410 has 10 channels */
>> +             indio_dev->num_channels =
>> +                             ARRAY_SIZE(exynos5_adc_iio_channels);
>> +
>> +     info->map = exynos5_adc_iio_map;
>> +
>> +     ret = iio_map_array_register(indio_dev, info->map);
>> +     if (ret) {
>> +             dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
>> +             goto err_irq;
>> +     }
>> +
>> +     ret = iio_device_register(indio_dev);
>> +     if (ret)
>> +             goto err_map;
>> +
>> +     ret = regulator_enable(info->vdd);
>> +     if (ret)
>> +             goto err_iio_dev;
>> +
>> +     clk_prepare_enable(info->clk);
>> +
>> +     exynos5_adc_hw_init(info);
>> +
>> +     ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "failed adding child nodes\n");
>> +             goto err_of_populate;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_of_populate:
>> +     device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
>> +     clk_disable_unprepare(info->clk);
>> +     regulator_disable(info->vdd);
>> +err_iio_dev:
>> +     iio_device_unregister(indio_dev);
>> +err_map:
>> +     iio_map_array_unregister(indio_dev, info->map);
>> +err_irq:
>> +     free_irq(info->irq, info);
>> +err_iio:
>> +     iio_device_free(indio_dev);
>> +     return ret;
>> +}
>> +
>> +static int exynos5_adc_remove(struct platform_device *pdev)
>> +{
>> +     struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>> +
>> +     clk_disable_unprepare(info->clk);
>> +     regulator_disable(info->vdd);
>> +     iio_device_unregister(indio_dev);
>> +     iio_map_array_unregister(indio_dev, info->map);
>> +     free_irq(info->irq, info);
>> +     iio_device_free(indio_dev);
>> +
>> +     return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int exynos5_adc_suspend(struct device *dev)
>> +{
>> +     struct exynos5_adc *info = dev_get_data(dev);
>> +     u32 con;
>> +
>> +     if (info->version == ADC_V2) {
>> +             con = readl(ADC_V2_CON1(info->regs));
>> +             con &= ~ADC_V1_CON_EN_START;
>> +             writel(con, ADC_V2_CON1(info->regs));
>> +     } else {
>> +             con = readl(ADC_V1_CON(info->regs));
>> +             con |= ADC_V1_CON_STANDBY;
>> +             writel(con, ADC_V1_CON(info->regs));
>> +     }
>> +
>> +     clk_disable_unprepare(info->clk);
>> +     regulator_disable(info->vdd);
>> +
>> +     return 0;
>> +}
>> +
>> +static int exynos5_adc_resume(struct device *dev)
>> +{
>> +     struct exynos5_adc *info = dev_get_data(dev);
>> +     int ret;
>> +
>> +     ret = regulator_enable(info->vdd);
>> +     if (ret)
>> +             return ret;
>> +
>> +     clk_prepare_enable(info->clk);
>> +
>> +     exynos5_adc_hw_init(info);
>> +
>> +     return 0;
>> +}
>> +
>> +#else
>> +#define exynos5_adc_suspend NULL
>> +#define exynos5_adc_resume NULL
>> +#endif
>> +
>> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
>> +                     exynos5_adc_suspend,
>> +                     exynos5_adc_resume);
>> +
>> +static struct platform_driver exynos5_adc_driver = {
>> +     .probe          = exynos5_adc_probe,
>> +     .remove         = exynos5_adc_remove,
>> +     .driver         = {
>> +             .name   = "exynos5-adc",
>> +             .owner  = THIS_MODULE,
>> +             .of_match_table = of_match_ptr(exynos5_adc_match),
>> +             .pm     = &exynos5_adc_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(exynos5_adc_driver);
>> +
>> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
>> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
>> +MODULE_LICENSE("GPL");
>>



--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] iio: adc: add exynos5 adc driver under iio framwork
  2013-01-24  5:05 ` Naveen Krishna Chatradhi
@ 2013-02-12  1:22   ` Olof Johansson
  0 siblings, 0 replies; 35+ messages in thread
From: Olof Johansson @ 2013-02-12  1:22 UTC (permalink / raw)
  To: Naveen Krishna Chatradhi
  Cc: linux-iio, linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

Hi,

It looks like the bindings here are still completely unsettled, or at least
unimplemented -- there seems to be a preference for an encoding similar to how
interrupts/gpios/clks work. I think that wouldn't be a bad idea.

For the case of this particular driver and connections, it'd just mean you
would need to do the binding slightly differently. You can still have some of
the sensors under the ADC node if they're not sitting on another bus where it
makes sense to locate them.


Some further comments below.


On Thu, Jan 24, 2013 at 10:35:32AM +0530, Naveen Krishna Chatradhi wrote:
> This patch add an ADC IP found on EXYNOS5250 and EXYNOS5410 SoCs
> from Samsung. Also adds the Documentation for device tree bindings.
> 
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> ---
> Changes since v1:
> 
> 1. Fixed comments from Lars
> 2. Added support for ADC on EXYNOS5410
> 
> Changes since v2:
> 
> 1. Changed the instance name for (struct iio_dev *) to indio_dev
> 2. Changed devm_request_irq to request_irq
> 
> Few doubts regarding the mappings and child device handling.
> Kindly, suggest me better methods.
> 
> Changes since v3:
> 
> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
> 2. Moved init_completion before irq_request
> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
> 4. Use number of channels as per the ADC version 
> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
> 6. Update the Documentation to include EXYNOS5410 compatible
> 
> Changes since v4:
> 
> 1. if devm_request_and_ioremap() failes, free iio_device before returning 
> 
> Doug, i've used
> 	chan = iio_channel_get(dev_name(&pdev->dev), "adc3");
> in ntc thermistor driver during probe and
> 	iio_read_channel_raw(chan, &val);
> for read.
> 
> But, then the drivers become kind of coupled. Right.
> 
> Lars, Is there an other way.
> 
> Thanks for the comments
> 
>  .../bindings/arm/samsung/exynos5-adc.txt           |   38 ++
>  drivers/iio/adc/Kconfig                            |    7 +
>  drivers/iio/adc/Makefile                           |    1 +
>  drivers/iio/adc/exynos5_adc.c                      |  477 ++++++++++++++++++++
>  4 files changed, 523 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>  create mode 100644 drivers/iio/adc/exynos5_adc.c
> 
> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
> new file mode 100644
> index 0000000..0f281d9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
> @@ -0,0 +1,38 @@
> +Samsung Exynos5 Analog to Digital Converter bindings
> +
> +Required properties:
> +- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
> +			Must be "samsung,exynos5410-adc" for exynos5410 controllers.

Looking at the out-of-tree patch we have that adds exynos5 support to
arch/arm/plat-samsung/adc.c, this looks like it should be:

samsung,exynos4412-adc should be the base case (this was the first chip with
the new "version 4" ADC hardware). Then for 5250 the chip-specific
prefixes should also be there for compatible in case there are SoC-specific
bugs or quirks or features that the driver in the future might need to care
about.

Based on the code below, it looks like 5410 isn't compatible with 4412, since
it needs special init sequences. So that means 5410 shouldn't be claimed to be
compatible.


> +- reg:			Contains ADC register address range (base address and
> +			length).
> +- interrupts: 		Contains the interrupt information for the timer. The
> +			format is being dependent on which interrupt controller
> +			the Samsung device uses.
> +
> +Note: child nodes can be added for auto probing from device tree.
> +
> +Example: adding device info in dtsi file
> +
> +adc@12D10000 {
> +	compatible = "samsung,exynos5250-adc";
> +	reg = <0x12D10000 0x100>;
> +	interrupts = <0 106 0>;
> +	#address-cells = <1>;
> +	#size-cells = <1>;

This is where something like #channel-cells or something else would be needed
-- not address cells and size cells. And then (see below)...

> +	ranges;
> +};
> +
> +
> +Example: Adding child nodes in dts file
> +
> +adc@12D10000 {
> +
> +	/* NTC thermistor is a hwmon device */
> +	ncp15wb473@0 {
> +		compatible = "ntc,ncp15wb473";
> +		reg = <0x0>;
> +		pullup-uV = <1800000>;
> +		pullup-ohm = <47000>;
> +		pulldown-ohm = <0>;

So, here is where the reference to channel should be specified. Even for those
who are located as children to the adc node.

Something like (naming is up in the air):
io-channel = <&adc 0>;

> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index fe822a1..33ceabf 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -91,6 +91,13 @@ config AT91_ADC
>  	help
>  	  Say yes here to build support for Atmel AT91 ADC.
>  
> +config EXYNOS5_ADC
> +	bool "Exynos5 ADC driver support"
> +	help
> +	  Core support for the ADC block found in the Samsung EXYNOS5 series
> +	  of SoCs for drivers such as the touchscreen and hwmon to use to share
> +	  this resource.

Given the out-of-tree patch that enables EXYNOS5250 (and 4412) support to
arch/arm/plat-samsung/adc.c, it seems shortsighted to name this driver exynos5.
The driver should be able to handle older versions of the Samsung chipsets
without too much work.

Initial versions could be contained to only support EXYNOS5 and 4412, that'd be
fine. But to avoid future renames, keep the name generic (exynos_adc)

> diff --git a/drivers/iio/adc/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
> new file mode 100644
> index 0000000..197d622
> --- /dev/null
> +++ b/drivers/iio/adc/exynos5_adc.c
> @@ -0,0 +1,477 @@
> +/*
> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs

So all these kind of references would need to be changed too.

> + *
> + *  8 ~ 10 channel, 10/12-bit ADC
> + *
> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> + *
> + *  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.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; if not, write to the Free Software
> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +enum adc_version {
> +	ADC_V1,
> +	ADC_V2
> +};
> +
> +/* EXYNOS5250 ADC_V1 registers definitions */

Given that the arch/arm/plat-samsung/adc.c driver is already in the tree, and
it uses ADCV1..3 already for even older platforms, it seems awkward to reuse
the same numbering scheme but for other chips here. Maybe use 4412 as prefix
here, and 5410 for V2? Where does 5440 fall into this, is it compatible with
5250 or with 5410?

> +#define ADC_V1_CON(x)		((x) + 0x00)
> +#define ADC_V1_DLY(x)		((x) + 0x08)
> +#define ADC_V1_DATX(x)		((x) + 0x0C)
> +#define ADC_V1_INTCLR(x)	((x) + 0x18)
> +#define ADC_V1_MUX(x)		((x) + 0x1c)
> +
> +/* EXYNOS5410 ADC_V2 registers definitions */
> +#define ADC_V2_CON1(x)		((x) + 0x00)
> +#define ADC_V2_CON2(x)		((x) + 0x04)
> +#define ADC_V2_STAT(x)		((x) + 0x08)
> +#define ADC_V2_INT_EN(x)	((x) + 0x10)
> +#define ADC_V2_INT_ST(x)	((x) + 0x14)
> +#define ADC_V2_VER(x)		((x) + 0x20)
> +
> +/* Bit definitions for ADC_V1 */
> +#define ADC_V1_CON_RES		(1u << 16)
> +#define ADC_V1_CON_PRSCEN	(1u << 14)
> +#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
> +#define ADC_V1_CON_STANDBY	(1u << 2)
> +
> +/* Bit definitions for ADC_V2 */
> +#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
> +
> +#define ADC_V2_CON2_OSEL	(1u << 10)
> +#define ADC_V2_CON2_ESEL	(1u << 9)
> +#define ADC_V2_CON2_HIGHF	(1u << 8)
> +#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
> +#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
> +#define ADC_V2_CON2_ACH_MASK	0xF
> +
> +/* Bit definitions common for ADC_V1 and ADC_V2 */
> +#define ADC_V1_CON_EN_START		(1u << 0)
> +#define ADC_V1_DATX_MASK	0xFFF
> +
> +struct exynos5_adc {
> +	void __iomem		*regs;
> +	struct clk		*clk;
> +	unsigned int		irq;
> +	struct regulator	*vdd;
> +
> +	struct completion	completion;
> +
> +	struct iio_map		*map;
> +	u32			value;
> +	unsigned int            version;
> +};
> +
> +static const struct of_device_id exynos5_adc_match[] = {
> +	{ .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
> +	{ .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
> +
> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
> +{
> +	const struct of_device_id *match;
> +
> +	match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
> +	return (unsigned int)match->data;
> +}
> +
> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
> +static struct iio_map exynos5_adc_iio_map[] = {
> +	{
> +		.consumer_dev_name = "0.ncp15wb473",
> +		.consumer_channel = "adc3",
> +		.adc_channel_label = "adc3",
> +	},
> +	{
> +		.consumer_dev_name = "1.ncp15wb473",
> +		.consumer_channel = "adc4",
> +		.adc_channel_label = "adc4",
> +	},
> +	{
> +		.consumer_dev_name = "2.ncp15wb473",
> +		.consumer_channel = "adc5",
> +		.adc_channel_label = "adc5",
> +	},
> +	{
> +		.consumer_dev_name = "3.ncp15wb473",
> +		.consumer_channel = "adc6",
> +		.adc_channel_label = "adc6",
> +	},
> +	{},
> +};

Does it really make sense to have these default sensors here? How many boards
will have these vs those who will need something else? What happens if someone
enables the driver without specifying correctly what sensors they have on their
hardware? Seems suboptimal to provide a default that doesn't always match
reality.

> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
> +{
> +	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
> +
> +	/* Read value */
> +	info->value = readl(ADC_V1_DATX(info->regs)) &
> +						ADC_V1_DATX_MASK;

You read _V1 registers on both versions of hardware here (and some other
places). I guess register definitions (and offset) are compatible, but it's
a little confusing given that pretty much everything else seems to be
separate/different.

Either keep them completely separate, or rename the register something that's
not versioned.

> +	/* clear irq */
> +	if (info->version == ADC_V2)
> +		writel(1, ADC_V2_INT_ST(info->regs));
> +	else
> +		writel(1, ADC_V1_INTCLR(info->regs));
> +
> +	complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 ret;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	if (readval != NULL) {
> +		ret = readl(info->regs + reg);
> +		*readval = ret;
> +	} else
> +		ret = -EINVAL;
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info exynos5_adc_iio_info = {
> +	.read_raw = &exynos5_read_raw,
> +	.debugfs_reg_access = &exynos5_adc_reg_access,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define ADC_CHANNEL(_index, _id) {			\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.channel = _index,				\
> +	.address = _index,				\
> +	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	.datasheet_name = _id,				\
> +}
> +
> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
> +	ADC_CHANNEL(0, "adc0"),
> +	ADC_CHANNEL(1, "adc1"),
> +	ADC_CHANNEL(2, "adc2"),
> +	ADC_CHANNEL(3, "adc3"),
> +	ADC_CHANNEL(4, "adc4"),
> +	ADC_CHANNEL(5, "adc5"),
> +	ADC_CHANNEL(6, "adc6"),
> +	ADC_CHANNEL(7, "adc7"),
> +	ADC_CHANNEL(8, "adc8"),
> +	ADC_CHANNEL(9, "adc9"),
> +};
> +
> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	platform_device_unregister(pdev);
> +
> +	return 0;
> +}
> +
> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
> +{
> +	u32 con1, con2;
> +
> +	if (info->version == ADC_V2) {
> +		con1 = ADC_V2_CON1_SOFT_RESET;
> +		writel(con1, ADC_V2_CON1(info->regs));
> +
> +		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
> +			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
> +		writel(con2, ADC_V2_CON2(info->regs));
> +
> +		/* Enable interrupts */
> +		writel(1, ADC_V2_INT_EN(info->regs));
> +	} else {
> +		/* set default prescaler values and Enable prescaler */
> +		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
> +
> +		/* Enable 12-bit ADC resolution */
> +		con1 |= ADC_V1_CON_RES;
> +		writel(con1, ADC_V1_CON(info->regs));
> +	}
> +}
> +
> +static int exynos5_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos5_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *indio_dev = NULL;
> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +
> +	if (!np)
> +		return ret;
> +
> +	indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
> +	if (!indio_dev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(indio_dev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> +	if (!info->regs) {
> +		ret = -ENOMEM;
> +		goto err_iio;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	init_completion(&info->completion);
> +
> +	ret = request_irq(info->irq, exynos5_adc_isr,
> +					0, dev_name(&pdev->dev), info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
> +							PTR_ERR(info->clk));
> +		ret = PTR_ERR(info->clk);
> +		goto err_irq;
> +	}
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
> +							PTR_ERR(info->vdd));
> +		ret = PTR_ERR(info->vdd);
> +		goto err_irq;
> +	}
> +
> +	info->version = exynos5_adc_get_version(pdev);
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &exynos5_adc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = exynos5_adc_iio_channels;
> +
> +	if (info->version == ADC_V1)
> +		/* ADC core in EXYNOS5250 has 8 channels */
> +		indio_dev->num_channels =
> +				ARRAY_SIZE(exynos5_adc_iio_channels) - 2;

This is fragile code, it'll break (or need changing) if V3 has even more
channels. Just set the value instead of doing a delta. Via a define if you
prefer.

> +	else
> +		/* ADC core in EXYNOS5410 has 10 channels */
> +		indio_dev->num_channels =
> +				ARRAY_SIZE(exynos5_adc_iio_channels);
> +
> +	info->map = exynos5_adc_iio_map;
> +
> +	ret = iio_map_array_register(indio_dev, info->map);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
> +		goto err_irq;
> +	}
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto err_map;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos5_adc_hw_init(info);
> +
> +	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed adding child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
> +	regulator_disable(info->vdd);
> +	clk_disable_unprepare(info->clk);
> +err_iio_dev:
> +	iio_device_unregister(indio_dev);
> +err_map:
> +	iio_map_array_unregister(indio_dev, info->map);
> +err_irq:
> +	free_irq(info->irq, info);
> +err_iio:
> +	iio_device_free(indio_dev);
> +	return ret;
> +}
> +
> +static int exynos5_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +
> +	regulator_disable(info->vdd);
> +	clk_disable_unprepare(info->clk);
> +	iio_device_unregister(indio_dev);
> +	iio_map_array_unregister(indio_dev, info->map);
> +	free_irq(info->irq, info);
> +	iio_device_free(indio_dev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int exynos5_adc_suspend(struct device *dev)
> +{
> +	struct exynos5_adc *info = dev_get_data(dev);
> +	u32 con;
> +
> +	if (info->version == ADC_V2) {
> +		con = readl(ADC_V2_CON1(info->regs));
> +		con &= ~ADC_V1_CON_EN_START;
> +		writel(con, ADC_V2_CON1(info->regs));
> +	} else {
> +		con = readl(ADC_V1_CON(info->regs));
> +		con |= ADC_V1_CON_STANDBY;
> +		writel(con, ADC_V1_CON(info->regs));
> +	}
> +
> +	clk_disable_unprepare(info->clk);
> +	regulator_disable(info->vdd);
> +
> +	return 0;
> +}
> +
> +static int exynos5_adc_resume(struct device *dev)
> +{
> +	struct exynos5_adc *info = dev_get_data(dev);
> +	int ret;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		return ret;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos5_adc_hw_init(info);
> +
> +	return 0;
> +}
> +
> +#else
> +#define exynos5_adc_suspend NULL
> +#define exynos5_adc_resume NULL
> +#endif

with SIMPLE_DEV_PM_OPS, you don't need the else case above.

> +
> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
> +			exynos5_adc_suspend,
> +			exynos5_adc_resume);
> +
> +static struct platform_driver exynos5_adc_driver = {
> +	.probe		= exynos5_adc_probe,
> +	.remove		= exynos5_adc_remove,
> +	.driver		= {
> +		.name	= "exynos5-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = of_match_ptr(exynos5_adc_match),
> +		.pm	= &exynos5_adc_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(exynos5_adc_driver);
> +
> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> +MODULE_LICENSE("GPL");
> -- 
> 1.7.9.5
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-01-23  4:58 ` Naveen Krishna Chatradhi
  2013-01-23 12:52   ` Lars-Peter Clausen
@ 2013-02-12 21:07   ` Guenter Roeck
  2013-02-13  2:48     ` Naveen Krishna Ch
  1 sibling, 1 reply; 35+ messages in thread
From: Guenter Roeck @ 2013-02-12 21:07 UTC (permalink / raw)
  To: Naveen Krishna Chatradhi
  Cc: linux-iio, linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

On Wed, Jan 23, 2013 at 04:58:06AM -0000, Naveen Krishna Chatradhi wrote:
> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
> Also adds the Documentation for device tree bindings.
> 
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> 
> ---
> Changes since v1:
> 
> 1. Fixed comments from Lars
> 2. Added support for ADC on EXYNOS5410
> 
> Changes since v2:
> 
> 1. Changed the instance name for (struct iio_dev *) to indio_dev
> 2. Changed devm_request_irq to request_irq
> 
> Few doubts regarding the mappings and child device handling.
> Kindly, suggest me better methods.
> 
>  .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
>  drivers/iio/adc/Kconfig                            |    7 +
>  drivers/iio/adc/Makefile                           |    1 +
>  drivers/iio/adc/exynos5_adc.c                      |  464 ++++++++++++++++++++
>  4 files changed, 509 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>  create mode 100644 drivers/iio/adc/exynos5_adc.c
> 
> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
> new file mode 100644
> index 0000000..9a5b515
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
> @@ -0,0 +1,37 @@
> +Samsung Exynos5 Analog to Digital Converter bindings
> +
> +Required properties:
> +- compatible:		Must be "samsung,exynos5250-adc" for exynos5250 controllers.
> +- reg:			Contains ADC register address range (base address and
> +			length).
> +- interrupts: 		Contains the interrupt information for the timer. The
> +			format is being dependent on which interrupt controller
> +			the Samsung device uses.
> +
> +Note: child nodes can be added for auto probing from device tree.
> +
> +Example: adding device info in dtsi file
> +
> +adc@12D10000 {
> +	compatible = "samsung,exynos5250-adc";
> +	reg = <0x12D10000 0x100>;
> +	interrupts = <0 106 0>;
> +	#address-cells = <1>;
> +	#size-cells = <1>;
> +	ranges;
> +};
> +
> +
> +Example: Adding child nodes in dts file
> +
> +adc@12D10000 {
> +
> +	/* NTC thermistor is a hwmon device */
> +	ncp15wb473@0 {
> +		compatible = "ntc,ncp15wb473";
> +		reg = <0x0>;
> +		pullup-uV = <1800000>;
> +		pullup-ohm = <47000>;
> +		pulldown-ohm = <0>;
> +	};
> +};

How about:

	adc: adc@12D10000 {
		compatible = "samsung,exynos5250-adc";
		reg = <0x12D10000 0x100>;
		interrupts = <0 106 0>;
		#io-channel-cells = <1>;
	};

	...

	ncp15wb473@0 {
		compatible = "ntc,ncp15wb473";
		reg = <0x0>; /* is this needed ? */
		io-channels = <&adc 0>;
		io-channel-names = "adc";
		pullup-uV = <1800000>;	/* uV or uv ? */ 
		pullup-ohm = <47000>;
		pulldown-ohm = <0>;
	};

The ncp15wb473 driver would then use either iio_channel_get_all() to get the iio
channel list or, if it only supports one adc channel per instance, iio_channel_get().

In that context, it would probably make sense to rework the ntc_thermistor
driver to support both DT as well as direct instantiation using access functions
and platform data (as it does today).

Also see https://patchwork.kernel.org/patch/2112171/.

Thanks,
Guenter

> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index fe822a1..33ceabf 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -91,6 +91,13 @@ config AT91_ADC
>  	help
>  	  Say yes here to build support for Atmel AT91 ADC.
>  
> +config EXYNOS5_ADC
> +	bool "Exynos5 ADC driver support"
> +	help
> +	  Core support for the ADC block found in the Samsung EXYNOS5 series
> +	  of SoCs for drivers such as the touchscreen and hwmon to use to share
> +	  this resource.
> +
>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
> new file mode 100644
> index 0000000..8982675
> --- /dev/null
> +++ b/drivers/iio/adc/exynos5_adc.c
> @@ -0,0 +1,464 @@
> +/*
> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
> + *
> + *  8 ~ 10 channel, 10/12-bit ADC
> + *
> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> + *
> + *  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.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; if not, write to the Free Software
> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +enum adc_version {
> +	ADC_V1,
> +	ADC_V2
> +};
> +
> +/* EXYNOS5250 ADC_V1 registers definitions */
> +#define ADC_V1_CON(x)		((x) + 0x00)
> +#define ADC_V1_DLY(x)		((x) + 0x08)
> +#define ADC_V1_DATX(x)		((x) + 0x0C)
> +#define ADC_V1_INTCLR(x)	((x) + 0x18)
> +#define ADC_V1_MUX(x)		((x) + 0x1c)
> +
> +/* EXYNOS5410 ADC_V2 registers definitions */
> +#define ADC_V2_CON1(x)		((x) + 0x00)
> +#define ADC_V2_CON2(x)		((x) + 0x04)
> +#define ADC_V2_STAT(x)		((x) + 0x08)
> +#define ADC_V2_INT_EN(x)	((x) + 0x10)
> +#define ADC_V2_INT_ST(x)	((x) + 0x14)
> +#define ADC_V2_VER(x)		((x) + 0x20)
> +
> +/* Bit definitions for ADC_V1 */
> +#define ADC_V1_CON_RES		(1u << 16)
> +#define ADC_V1_CON_PRSCEN	(1u << 14)
> +#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
> +#define ADC_V1_CON_STANDBY	(1u << 2)
> +
> +/* Bit definitions for ADC_V2 */
> +#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
> +
> +#define ADC_V2_CON2_OSEL	(1u << 10)
> +#define ADC_V2_CON2_ESEL	(1u << 9)
> +#define ADC_V2_CON2_HIGHF	(1u << 8)
> +#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
> +#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
> +#define ADC_V2_CON2_ACH_MASK	0xF
> +
> +/* Bit definitions common for ADC_V1 and ADC_V2 */
> +#define ADC_V1_CON_EN_START		(1u << 0)
> +#define ADC_V1_DATX_MASK	0xFFF
> +
> +struct exynos5_adc {
> +	void __iomem		*regs;
> +	struct clk		*clk;
> +	unsigned int		irq;
> +	struct regulator	*vdd;
> +
> +	struct completion	completion;
> +
> +	struct iio_map		*map;
> +	u32			value;
> +	unsigned int            version;
> +};
> +
> +static const struct of_device_id exynos5_adc_match[] = {
> +	{ .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
> +	{ .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
> +
> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
> +{
> +	const struct of_device_id *match;
> +
> +	match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
> +	return (unsigned int)match->data;
> +}
> +
> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
> +static struct iio_map exynos5_adc_iio_maps[] = {
> +	{
> +		.consumer_dev_name = "0.ncp15wb473",
> +		.consumer_channel = "adc3",
> +		.adc_channel_label = "adc3",
> +	},
> +	{
> +		.consumer_dev_name = "1.ncp15wb473",
> +		.consumer_channel = "adc4",
> +		.adc_channel_label = "adc4",
> +	},
> +	{
> +		.consumer_dev_name = "2.ncp15wb473",
> +		.consumer_channel = "adc5",
> +		.adc_channel_label = "adc5",
> +	},
> +	{
> +		.consumer_dev_name = "3.ncp15wb473",
> +		.consumer_channel = "adc6",
> +		.adc_channel_label = "adc6",
> +	},
> +	{},
> +};
> +
> +static int exynos5_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 con1, con2;
> +
> +	if (mask == IIO_CHAN_INFO_RAW) {
> +		mutex_lock(&indio_dev->mlock);
> +
> +		/* Select the channel to be used and Trigger conversion */
> +		if (info->version == ADC_V2) {
> +			con2 = readl(ADC_V2_CON2(info->regs));
> +			con2 &= ~ADC_V2_CON2_ACH_MASK;
> +			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
> +			writel(con2, ADC_V2_CON2(info->regs));
> +
> +			con1 = readl(ADC_V2_CON1(info->regs));
> +			writel(con1 | ADC_V1_CON_EN_START,
> +					ADC_V2_CON1(info->regs));
> +		} else {
> +			writel(chan->address, ADC_V1_MUX(info->regs));
> +
> +			con1 = readl(ADC_V1_CON(info->regs));
> +			writel(con1 | ADC_V1_CON_EN_START,
> +					ADC_V1_CON(info->regs));
> +		}
> +
> +		wait_for_completion(&info->completion);
> +		*val = info->value;
> +
> +		mutex_unlock(&indio_dev->mlock);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
> +{
> +	struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
> +
> +	/* Read value */
> +	info->value = readl(ADC_V1_DATX(info->regs)) &
> +						ADC_V1_DATX_MASK;
> +	/* clear irq */
> +	if (info->version == ADC_V2)
> +		writel(1, ADC_V2_INT_ST(info->regs));
> +	else
> +		writel(1, ADC_V1_INTCLR(info->regs));
> +
> +	complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +	u32 ret;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	if (readval != NULL) {
> +		ret = readl(info->regs + reg);
> +		*readval = ret;
> +	} else
> +		ret = -EINVAL;
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info exynos5_adc_iio_info = {
> +	.read_raw = &exynos5_read_raw,
> +	.debugfs_reg_access = &exynos5_adc_reg_access,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define ADC_V1_CHANNEL(_index, _id) {		\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.channel = _index,				\
> +	.address = _index,				\
> +	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	.datasheet_name = _id,				\
> +}
> +
> +/** ADC core in EXYNOS5410 has 10 channels,
> + * ADC core in EXYNOS5250 has 8 channels
> +*/
> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
> +	ADC_V1_CHANNEL(0, "adc0"),
> +	ADC_V1_CHANNEL(1, "adc1"),
> +	ADC_V1_CHANNEL(2, "adc2"),
> +	ADC_V1_CHANNEL(3, "adc3"),
> +	ADC_V1_CHANNEL(4, "adc4"),
> +	ADC_V1_CHANNEL(5, "adc5"),
> +	ADC_V1_CHANNEL(6, "adc6"),
> +	ADC_V1_CHANNEL(7, "adc7"),
> +	ADC_V1_CHANNEL(8, "adc8"),
> +	ADC_V1_CHANNEL(9, "adc9"),
> +};
> +
> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	platform_device_unregister(pdev);
> +
> +	return 0;
> +}
> +
> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
> +{
> +	u32 con1, con2;
> +
> +	if (info->version == ADC_V2) {
> +		con1 = ADC_V2_CON1_SOFT_RESET;
> +		writel(con1, ADC_V2_CON1(info->regs));
> +
> +		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
> +			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
> +		writel(con2, ADC_V2_CON2(info->regs));
> +
> +		/* Enable interrupts */
> +		writel(1, ADC_V2_INT_EN(info->regs));
> +	} else {
> +		/* set default prescaler values and Enable prescaler */
> +		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
> +
> +		/* Enable 12-bit ADC resolution */
> +		con1 |= ADC_V1_CON_RES;
> +		writel(con1, ADC_V1_CON(info->regs));
> +	}
> +}
> +
> +static int exynos5_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos5_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *indio_dev = NULL;
> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +
> +	if (!np)
> +		return ret;
> +
> +	indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
> +	if (!indio_dev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(indio_dev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	ret = request_irq(info->irq, exynos5_adc_isr,
> +					0, dev_name(&pdev->dev), info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
> +							PTR_ERR(info->clk));
> +		ret = PTR_ERR(info->clk);
> +		goto err_irq;
> +	}
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
> +							PTR_ERR(info->vdd));
> +		ret = PTR_ERR(info->vdd);
> +		goto err_irq;
> +	}
> +
> +	info->version = exynos5_adc_get_version(pdev);
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	init_completion(&info->completion);
> +
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &exynos5_adc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = exynos5_adc_iio_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
> +
> +	info->map = exynos5_adc_iio_maps;
> +
> +	ret = iio_map_array_register(indio_dev, info->map);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
> +		goto err_irq;
> +	}
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto err_map;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos5_adc_hw_init(info);
> +
> +	ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed adding child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
> +err_iio_dev:
> +	iio_device_unregister(indio_dev);
> +err_map:
> +	iio_map_array_unregister(indio_dev, info->map);
> +err_irq:
> +	free_irq(info->irq, info);
> +err_iio:
> +	iio_device_free(indio_dev);
> +	return ret;
> +}
> +
> +static int exynos5_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct exynos5_adc *info = iio_priv(indio_dev);
> +
> +	iio_device_unregister(indio_dev);
> +	iio_map_array_unregister(indio_dev, info->map);
> +	free_irq(info->irq, info);
> +	iio_device_free(indio_dev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int exynos5_adc_suspend(struct device *dev)
> +{
> +	struct exynos5_adc *info = dev_get_data(dev);
> +	u32 con;
> +
> +	if (info->version == ADC_V2) {
> +		con = readl(ADC_V2_CON1(info->regs));
> +		con &= ~ADC_V1_CON_EN_START;
> +		writel(con, ADC_V2_CON1(info->regs));
> +	} else {
> +		con = readl(ADC_V1_CON(info->regs));
> +		con |= ADC_V1_CON_STANDBY;
> +		writel(con, ADC_V1_CON(info->regs));
> +	}
> +
> +	clk_unprepare_disable(info->clk);
> +	regulator_disable(info->vdd);
> +
> +	return 0;
> +}
> +
> +static int exynos5_adc_resume(struct device *dev)
> +{
> +	struct exynos5_adc *info = dev_get_data(dev);
> +	int ret;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		return ret;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos5_adc_hw_init(info);
> +
> +	return 0;
> +}
> +
> +#else
> +#define exynos5_adc_suspend NULL
> +#define exynos5_adc_resume NULL
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
> +			exynos5_adc_suspend,
> +			exynos5_adc_resume);
> +
> +static struct platform_driver exynos5_adc_driver = {
> +	.probe		= exynos5_adc_probe,
> +	.remove		= exynos5_adc_remove,
> +	.driver		= {
> +		.name	= "exynos5-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = of_match_ptr(exynos5_adc_match),
> +		.pm	= &exynos5_adc_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(exynos5_adc_driver);
> +
> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> +MODULE_LICENSE("GPL");

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-02-12 21:07   ` Guenter Roeck
@ 2013-02-13  2:48     ` Naveen Krishna Ch
  2013-02-13 11:05       ` Naveen Krishna Ch
  2013-02-13 13:16       ` Naveen Krishna Ch
  0 siblings, 2 replies; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-02-13  2:48 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh, lars

On 13 February 2013 02:37, Guenter Roeck <linux@roeck-us.net> wrote:
> On Wed, Jan 23, 2013 at 04:58:06AM -0000, Naveen Krishna Chatradhi wrote:
>> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
>> Also adds the Documentation for device tree bindings.
>>
>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>
>> ---
>> Changes since v1:
>>
>> 1. Fixed comments from Lars
>> 2. Added support for ADC on EXYNOS5410
>>
>> Changes since v2:
>>
>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>> 2. Changed devm_request_irq to request_irq
>>
>> Few doubts regarding the mappings and child device handling.
>> Kindly, suggest me better methods.
>>
>>  .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
>>  drivers/iio/adc/Kconfig                            |    7 +
>>  drivers/iio/adc/Makefile                           |    1 +
>>  drivers/iio/adc/exynos5_adc.c                      |  464 ++++++++++++++++++++
>>  4 files changed, 509 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>  create mode 100644 drivers/iio/adc/exynos5_adc.c
>>
>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>> new file mode 100644
>> index 0000000..9a5b515
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>> @@ -0,0 +1,37 @@
>> +Samsung Exynos5 Analog to Digital Converter bindings
>> +
>> +Required properties:
>> +- compatible:                Must be "samsung,exynos5250-adc" for exynos5250 controllers.
>> +- reg:                       Contains ADC register address range (base address and
>> +                     length).
>> +- interrupts:                Contains the interrupt information for the timer. The
>> +                     format is being dependent on which interrupt controller
>> +                     the Samsung device uses.
>> +
>> +Note: child nodes can be added for auto probing from device tree.
>> +
>> +Example: adding device info in dtsi file
>> +
>> +adc@12D10000 {
>> +     compatible = "samsung,exynos5250-adc";
>> +     reg = <0x12D10000 0x100>;
>> +     interrupts = <0 106 0>;
>> +     #address-cells = <1>;
>> +     #size-cells = <1>;
>> +     ranges;
>> +};
>> +
>> +
>> +Example: Adding child nodes in dts file
>> +
>> +adc@12D10000 {
>> +
>> +     /* NTC thermistor is a hwmon device */
>> +     ncp15wb473@0 {
>> +             compatible = "ntc,ncp15wb473";
>> +             reg = <0x0>;
>> +             pullup-uV = <1800000>;
>> +             pullup-ohm = <47000>;
>> +             pulldown-ohm = <0>;
>> +     };
>> +};
>
> How about:
>
>         adc: adc@12D10000 {
>                 compatible = "samsung,exynos5250-adc";
>                 reg = <0x12D10000 0x100>;
>                 interrupts = <0 106 0>;
>                 #io-channel-cells = <1>;
>         };
>
>         ...
>
>         ncp15wb473@0 {
>                 compatible = "ntc,ncp15wb473";
>                 reg = <0x0>; /* is this needed ? */
>                 io-channels = <&adc 0>;
>                 io-channel-names = "adc";
>                 pullup-uV = <1800000>;  /* uV or uv ? */
>                 pullup-ohm = <47000>;
>                 pulldown-ohm = <0>;
>         };
>
> The ncp15wb473 driver would then use either iio_channel_get_all() to get the iio
> channel list or, if it only supports one adc channel per instance, iio_channel_get().
>
> In that context, it would probably make sense to rework the ntc_thermistor
> driver to support both DT as well as direct instantiation using access functions
> and platform data (as it does today).
>
> Also see https://patchwork.kernel.org/patch/2112171/.
>
> Thanks,
> Guenter
Yes Guenter, I will rebase and submit the ADC driver based on your patch set.
>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index fe822a1..33ceabf 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -91,6 +91,13 @@ config AT91_ADC
>>       help
>>         Say yes here to build support for Atmel AT91 ADC.
>>
>> +config EXYNOS5_ADC
>> +     bool "Exynos5 ADC driver support"
>> +     help
>> +       Core support for the ADC block found in the Samsung EXYNOS5 series
>> +       of SoCs for drivers such as the touchscreen and hwmon to use to share
>> +       this resource.
>> +
>>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
>> new file mode 100644
>> index 0000000..8982675
>> --- /dev/null
>> +++ b/drivers/iio/adc/exynos5_adc.c
>> @@ -0,0 +1,464 @@
>> +/*
>> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
>> + *
>> + *  8 ~ 10 channel, 10/12-bit ADC
>> + *
>> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>> + *
>> + *  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.
>> + *
>> + *  This program is distributed in the hope that it will be useful,
>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + *  GNU General Public License for more details.
>> + *
>> + *  You should have received a copy of the GNU General Public License
>> + *  along with this program; if not, write to the Free Software
>> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/kernel.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/of_platform.h>
>> +
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/iio/driver.h>
>> +
>> +enum adc_version {
>> +     ADC_V1,
>> +     ADC_V2
>> +};
>> +
>> +/* EXYNOS5250 ADC_V1 registers definitions */
>> +#define ADC_V1_CON(x)                ((x) + 0x00)
>> +#define ADC_V1_DLY(x)                ((x) + 0x08)
>> +#define ADC_V1_DATX(x)               ((x) + 0x0C)
>> +#define ADC_V1_INTCLR(x)     ((x) + 0x18)
>> +#define ADC_V1_MUX(x)                ((x) + 0x1c)
>> +
>> +/* EXYNOS5410 ADC_V2 registers definitions */
>> +#define ADC_V2_CON1(x)               ((x) + 0x00)
>> +#define ADC_V2_CON2(x)               ((x) + 0x04)
>> +#define ADC_V2_STAT(x)               ((x) + 0x08)
>> +#define ADC_V2_INT_EN(x)     ((x) + 0x10)
>> +#define ADC_V2_INT_ST(x)     ((x) + 0x14)
>> +#define ADC_V2_VER(x)                ((x) + 0x20)
>> +
>> +/* Bit definitions for ADC_V1 */
>> +#define ADC_V1_CON_RES               (1u << 16)
>> +#define ADC_V1_CON_PRSCEN    (1u << 14)
>> +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
>> +#define ADC_V1_CON_STANDBY   (1u << 2)
>> +
>> +/* Bit definitions for ADC_V2 */
>> +#define ADC_V2_CON1_SOFT_RESET       (1u << 2)
>> +
>> +#define ADC_V2_CON2_OSEL     (1u << 10)
>> +#define ADC_V2_CON2_ESEL     (1u << 9)
>> +#define ADC_V2_CON2_HIGHF    (1u << 8)
>> +#define ADC_V2_CON2_C_TIME(x)        (((x) & 7) << 4)
>> +#define ADC_V2_CON2_ACH_SEL(x)       (((x) & 0xF) << 0)
>> +#define ADC_V2_CON2_ACH_MASK 0xF
>> +
>> +/* Bit definitions common for ADC_V1 and ADC_V2 */
>> +#define ADC_V1_CON_EN_START          (1u << 0)
>> +#define ADC_V1_DATX_MASK     0xFFF
>> +
>> +struct exynos5_adc {
>> +     void __iomem            *regs;
>> +     struct clk              *clk;
>> +     unsigned int            irq;
>> +     struct regulator        *vdd;
>> +
>> +     struct completion       completion;
>> +
>> +     struct iio_map          *map;
>> +     u32                     value;
>> +     unsigned int            version;
>> +};
>> +
>> +static const struct of_device_id exynos5_adc_match[] = {
>> +     { .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
>> +     { .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
>> +     {},
>> +};
>> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
>> +
>> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
>> +{
>> +     const struct of_device_id *match;
>> +
>> +     match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
>> +     return (unsigned int)match->data;
>> +}
>> +
>> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
>> +static struct iio_map exynos5_adc_iio_maps[] = {
>> +     {
>> +             .consumer_dev_name = "0.ncp15wb473",
>> +             .consumer_channel = "adc3",
>> +             .adc_channel_label = "adc3",
>> +     },
>> +     {
>> +             .consumer_dev_name = "1.ncp15wb473",
>> +             .consumer_channel = "adc4",
>> +             .adc_channel_label = "adc4",
>> +     },
>> +     {
>> +             .consumer_dev_name = "2.ncp15wb473",
>> +             .consumer_channel = "adc5",
>> +             .adc_channel_label = "adc5",
>> +     },
>> +     {
>> +             .consumer_dev_name = "3.ncp15wb473",
>> +             .consumer_channel = "adc6",
>> +             .adc_channel_label = "adc6",
>> +     },
>> +     {},
>> +};
>> +
>> +static int exynos5_read_raw(struct iio_dev *indio_dev,
>> +                             struct iio_chan_spec const *chan,
>> +                             int *val,
>> +                             int *val2,
>> +                             long mask)
>> +{
>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>> +     u32 con1, con2;
>> +
>> +     if (mask == IIO_CHAN_INFO_RAW) {
>> +             mutex_lock(&indio_dev->mlock);
>> +
>> +             /* Select the channel to be used and Trigger conversion */
>> +             if (info->version == ADC_V2) {
>> +                     con2 = readl(ADC_V2_CON2(info->regs));
>> +                     con2 &= ~ADC_V2_CON2_ACH_MASK;
>> +                     con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
>> +                     writel(con2, ADC_V2_CON2(info->regs));
>> +
>> +                     con1 = readl(ADC_V2_CON1(info->regs));
>> +                     writel(con1 | ADC_V1_CON_EN_START,
>> +                                     ADC_V2_CON1(info->regs));
>> +             } else {
>> +                     writel(chan->address, ADC_V1_MUX(info->regs));
>> +
>> +                     con1 = readl(ADC_V1_CON(info->regs));
>> +                     writel(con1 | ADC_V1_CON_EN_START,
>> +                                     ADC_V1_CON(info->regs));
>> +             }
>> +
>> +             wait_for_completion(&info->completion);
>> +             *val = info->value;
>> +
>> +             mutex_unlock(&indio_dev->mlock);
>> +
>> +             return IIO_VAL_INT;
>> +     }
>> +
>> +     return -EINVAL;
>> +}
>> +
>> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
>> +{
>> +     struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
>> +
>> +     /* Read value */
>> +     info->value = readl(ADC_V1_DATX(info->regs)) &
>> +                                             ADC_V1_DATX_MASK;
>> +     /* clear irq */
>> +     if (info->version == ADC_V2)
>> +             writel(1, ADC_V2_INT_ST(info->regs));
>> +     else
>> +             writel(1, ADC_V1_INTCLR(info->regs));
>> +
>> +     complete(&info->completion);
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
>> +                           unsigned reg, unsigned writeval,
>> +                           unsigned *readval)
>> +{
>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>> +     u32 ret;
>> +
>> +     mutex_lock(&indio_dev->mlock);
>> +
>> +     if (readval != NULL) {
>> +             ret = readl(info->regs + reg);
>> +             *readval = ret;
>> +     } else
>> +             ret = -EINVAL;
>> +
>> +     mutex_unlock(&indio_dev->mlock);
>> +
>> +     return ret;
>> +}
>> +
>> +static const struct iio_info exynos5_adc_iio_info = {
>> +     .read_raw = &exynos5_read_raw,
>> +     .debugfs_reg_access = &exynos5_adc_reg_access,
>> +     .driver_module = THIS_MODULE,
>> +};
>> +
>> +#define ADC_V1_CHANNEL(_index, _id) {                \
>> +     .type = IIO_VOLTAGE,                            \
>> +     .indexed = 1,                                   \
>> +     .channel = _index,                              \
>> +     .address = _index,                              \
>> +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
>> +     .datasheet_name = _id,                          \
>> +}
>> +
>> +/** ADC core in EXYNOS5410 has 10 channels,
>> + * ADC core in EXYNOS5250 has 8 channels
>> +*/
>> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
>> +     ADC_V1_CHANNEL(0, "adc0"),
>> +     ADC_V1_CHANNEL(1, "adc1"),
>> +     ADC_V1_CHANNEL(2, "adc2"),
>> +     ADC_V1_CHANNEL(3, "adc3"),
>> +     ADC_V1_CHANNEL(4, "adc4"),
>> +     ADC_V1_CHANNEL(5, "adc5"),
>> +     ADC_V1_CHANNEL(6, "adc6"),
>> +     ADC_V1_CHANNEL(7, "adc7"),
>> +     ADC_V1_CHANNEL(8, "adc8"),
>> +     ADC_V1_CHANNEL(9, "adc9"),
>> +};
>> +
>> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
>> +{
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +
>> +     platform_device_unregister(pdev);
>> +
>> +     return 0;
>> +}
>> +
>> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
>> +{
>> +     u32 con1, con2;
>> +
>> +     if (info->version == ADC_V2) {
>> +             con1 = ADC_V2_CON1_SOFT_RESET;
>> +             writel(con1, ADC_V2_CON1(info->regs));
>> +
>> +             con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
>> +                     ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
>> +             writel(con2, ADC_V2_CON2(info->regs));
>> +
>> +             /* Enable interrupts */
>> +             writel(1, ADC_V2_INT_EN(info->regs));
>> +     } else {
>> +             /* set default prescaler values and Enable prescaler */
>> +             con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
>> +
>> +             /* Enable 12-bit ADC resolution */
>> +             con1 |= ADC_V1_CON_RES;
>> +             writel(con1, ADC_V1_CON(info->regs));
>> +     }
>> +}
>> +
>> +static int exynos5_adc_probe(struct platform_device *pdev)
>> +{
>> +     struct exynos5_adc *info = NULL;
>> +     struct device_node *np = pdev->dev.of_node;
>> +     struct iio_dev *indio_dev = NULL;
>> +     struct resource *mem;
>> +     int ret = -ENODEV;
>> +     int irq;
>> +
>> +     if (!np)
>> +             return ret;
>> +
>> +     indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
>> +     if (!indio_dev) {
>> +             dev_err(&pdev->dev, "failed allocating iio device\n");
>> +             return -ENOMEM;
>> +     }
>> +
>> +     info = iio_priv(indio_dev);
>> +
>> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +
>> +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
>> +
>> +     irq = platform_get_irq(pdev, 0);
>> +     if (irq < 0) {
>> +             dev_err(&pdev->dev, "no irq resource?\n");
>> +             ret = irq;
>> +             goto err_iio;
>> +     }
>> +
>> +     info->irq = irq;
>> +
>> +     ret = request_irq(info->irq, exynos5_adc_isr,
>> +                                     0, dev_name(&pdev->dev), info);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>> +                                                     info->irq);
>> +             goto err_iio;
>> +     }
>> +
>> +     info->clk = devm_clk_get(&pdev->dev, "adc");
>> +     if (IS_ERR(info->clk)) {
>> +             dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>> +                                                     PTR_ERR(info->clk));
>> +             ret = PTR_ERR(info->clk);
>> +             goto err_irq;
>> +     }
>> +
>> +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
>> +     if (IS_ERR(info->vdd)) {
>> +             dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
>> +                                                     PTR_ERR(info->vdd));
>> +             ret = PTR_ERR(info->vdd);
>> +             goto err_irq;
>> +     }
>> +
>> +     info->version = exynos5_adc_get_version(pdev);
>> +
>> +     platform_set_drvdata(pdev, indio_dev);
>> +
>> +     init_completion(&info->completion);
>> +
>> +     indio_dev->name = dev_name(&pdev->dev);
>> +     indio_dev->dev.parent = &pdev->dev;
>> +     indio_dev->dev.of_node = pdev->dev.of_node;
>> +     indio_dev->info = &exynos5_adc_iio_info;
>> +     indio_dev->modes = INDIO_DIRECT_MODE;
>> +     indio_dev->channels = exynos5_adc_iio_channels;
>> +     indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
>> +
>> +     info->map = exynos5_adc_iio_maps;
>> +
>> +     ret = iio_map_array_register(indio_dev, info->map);
>> +     if (ret) {
>> +             dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
>> +             goto err_irq;
>> +     }
>> +
>> +     ret = iio_device_register(indio_dev);
>> +     if (ret)
>> +             goto err_map;
>> +
>> +     ret = regulator_enable(info->vdd);
>> +     if (ret)
>> +             goto err_iio_dev;
>> +
>> +     clk_prepare_enable(info->clk);
>> +
>> +     exynos5_adc_hw_init(info);
>> +
>> +     ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "failed adding child nodes\n");
>> +             goto err_of_populate;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_of_populate:
>> +     device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
>> +err_iio_dev:
>> +     iio_device_unregister(indio_dev);
>> +err_map:
>> +     iio_map_array_unregister(indio_dev, info->map);
>> +err_irq:
>> +     free_irq(info->irq, info);
>> +err_iio:
>> +     iio_device_free(indio_dev);
>> +     return ret;
>> +}
>> +
>> +static int exynos5_adc_remove(struct platform_device *pdev)
>> +{
>> +     struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>> +
>> +     iio_device_unregister(indio_dev);
>> +     iio_map_array_unregister(indio_dev, info->map);
>> +     free_irq(info->irq, info);
>> +     iio_device_free(indio_dev);
>> +
>> +     return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int exynos5_adc_suspend(struct device *dev)
>> +{
>> +     struct exynos5_adc *info = dev_get_data(dev);
>> +     u32 con;
>> +
>> +     if (info->version == ADC_V2) {
>> +             con = readl(ADC_V2_CON1(info->regs));
>> +             con &= ~ADC_V1_CON_EN_START;
>> +             writel(con, ADC_V2_CON1(info->regs));
>> +     } else {
>> +             con = readl(ADC_V1_CON(info->regs));
>> +             con |= ADC_V1_CON_STANDBY;
>> +             writel(con, ADC_V1_CON(info->regs));
>> +     }
>> +
>> +     clk_unprepare_disable(info->clk);
>> +     regulator_disable(info->vdd);
>> +
>> +     return 0;
>> +}
>> +
>> +static int exynos5_adc_resume(struct device *dev)
>> +{
>> +     struct exynos5_adc *info = dev_get_data(dev);
>> +     int ret;
>> +
>> +     ret = regulator_enable(info->vdd);
>> +     if (ret)
>> +             return ret;
>> +
>> +     clk_prepare_enable(info->clk);
>> +
>> +     exynos5_adc_hw_init(info);
>> +
>> +     return 0;
>> +}
>> +
>> +#else
>> +#define exynos5_adc_suspend NULL
>> +#define exynos5_adc_resume NULL
>> +#endif
>> +
>> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
>> +                     exynos5_adc_suspend,
>> +                     exynos5_adc_resume);
>> +
>> +static struct platform_driver exynos5_adc_driver = {
>> +     .probe          = exynos5_adc_probe,
>> +     .remove         = exynos5_adc_remove,
>> +     .driver         = {
>> +             .name   = "exynos5-adc",
>> +             .owner  = THIS_MODULE,
>> +             .of_match_table = of_match_ptr(exynos5_adc_match),
>> +             .pm     = &exynos5_adc_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(exynos5_adc_driver);
>> +
>> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
>> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
>> +MODULE_LICENSE("GPL");



--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-02-13  2:48     ` Naveen Krishna Ch
@ 2013-02-13 11:05       ` Naveen Krishna Ch
  2013-02-13 13:16       ` Naveen Krishna Ch
  1 sibling, 0 replies; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-02-13 11:05 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh, lars

Hello Guenter,

I've s setup like the below one, kindly, help me find the right device
tree not params

ADC 0
        1
        1
        1
        1

On 13 February 2013 08:18, Naveen Krishna Ch <naveenkrishna.ch@gmail.com> wrote:
> On 13 February 2013 02:37, Guenter Roeck <linux@roeck-us.net> wrote:
>> On Wed, Jan 23, 2013 at 04:58:06AM -0000, Naveen Krishna Chatradhi wrote:
>>> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
>>> Also adds the Documentation for device tree bindings.
>>>
>>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>
>>> ---
>>> Changes since v1:
>>>
>>> 1. Fixed comments from Lars
>>> 2. Added support for ADC on EXYNOS5410
>>>
>>> Changes since v2:
>>>
>>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>>> 2. Changed devm_request_irq to request_irq
>>>
>>> Few doubts regarding the mappings and child device handling.
>>> Kindly, suggest me better methods.
>>>
>>>  .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
>>>  drivers/iio/adc/Kconfig                            |    7 +
>>>  drivers/iio/adc/Makefile                           |    1 +
>>>  drivers/iio/adc/exynos5_adc.c                      |  464 ++++++++++++++++++++
>>>  4 files changed, 509 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>  create mode 100644 drivers/iio/adc/exynos5_adc.c
>>>
>>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>> new file mode 100644
>>> index 0000000..9a5b515
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>> @@ -0,0 +1,37 @@
>>> +Samsung Exynos5 Analog to Digital Converter bindings
>>> +
>>> +Required properties:
>>> +- compatible:                Must be "samsung,exynos5250-adc" for exynos5250 controllers.
>>> +- reg:                       Contains ADC register address range (base address and
>>> +                     length).
>>> +- interrupts:                Contains the interrupt information for the timer. The
>>> +                     format is being dependent on which interrupt controller
>>> +                     the Samsung device uses.
>>> +
>>> +Note: child nodes can be added for auto probing from device tree.
>>> +
>>> +Example: adding device info in dtsi file
>>> +
>>> +adc@12D10000 {
>>> +     compatible = "samsung,exynos5250-adc";
>>> +     reg = <0x12D10000 0x100>;
>>> +     interrupts = <0 106 0>;
>>> +     #address-cells = <1>;
>>> +     #size-cells = <1>;
>>> +     ranges;
>>> +};
>>> +
>>> +
>>> +Example: Adding child nodes in dts file
>>> +
>>> +adc@12D10000 {
>>> +
>>> +     /* NTC thermistor is a hwmon device */
>>> +     ncp15wb473@0 {
>>> +             compatible = "ntc,ncp15wb473";
>>> +             reg = <0x0>;
>>> +             pullup-uV = <1800000>;
>>> +             pullup-ohm = <47000>;
>>> +             pulldown-ohm = <0>;
>>> +     };
>>> +};
>>
>> How about:
>>
>>         adc: adc@12D10000 {
>>                 compatible = "samsung,exynos5250-adc";
>>                 reg = <0x12D10000 0x100>;
>>                 interrupts = <0 106 0>;
>>                 #io-channel-cells = <1>;
>>         };
>>
>>         ...
>>
>>         ncp15wb473@0 {
>>                 compatible = "ntc,ncp15wb473";
>>                 reg = <0x0>; /* is this needed ? */
>>                 io-channels = <&adc 0>;
>>                 io-channel-names = "adc";
>>                 pullup-uV = <1800000>;  /* uV or uv ? */
>>                 pullup-ohm = <47000>;
>>                 pulldown-ohm = <0>;
>>         };
>>
>> The ncp15wb473 driver would then use either iio_channel_get_all() to get the iio
>> channel list or, if it only supports one adc channel per instance, iio_channel_get().
>>
>> In that context, it would probably make sense to rework the ntc_thermistor
>> driver to support both DT as well as direct instantiation using access functions
>> and platform data (as it does today).
>>
>> Also see https://patchwork.kernel.org/patch/2112171/.
>>
>> Thanks,
>> Guenter
> Yes Guenter, I will rebase and submit the ADC driver based on your patch set.
>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index fe822a1..33ceabf 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -91,6 +91,13 @@ config AT91_ADC
>>>       help
>>>         Say yes here to build support for Atmel AT91 ADC.
>>>
>>> +config EXYNOS5_ADC
>>> +     bool "Exynos5 ADC driver support"
>>> +     help
>>> +       Core support for the ADC block found in the Samsung EXYNOS5 series
>>> +       of SoCs for drivers such as the touchscreen and hwmon to use to share
>>> +       this resource.
>>> +
>>>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
>>> new file mode 100644
>>> index 0000000..8982675
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/exynos5_adc.c
>>> @@ -0,0 +1,464 @@
>>> +/*
>>> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
>>> + *
>>> + *  8 ~ 10 channel, 10/12-bit ADC
>>> + *
>>> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>> + *
>>> + *  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.
>>> + *
>>> + *  This program is distributed in the hope that it will be useful,
>>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + *  GNU General Public License for more details.
>>> + *
>>> + *  You should have received a copy of the GNU General Public License
>>> + *  along with this program; if not, write to the Free Software
>>> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/io.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_irq.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/of_platform.h>
>>> +
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/machine.h>
>>> +#include <linux/iio/driver.h>
>>> +
>>> +enum adc_version {
>>> +     ADC_V1,
>>> +     ADC_V2
>>> +};
>>> +
>>> +/* EXYNOS5250 ADC_V1 registers definitions */
>>> +#define ADC_V1_CON(x)                ((x) + 0x00)
>>> +#define ADC_V1_DLY(x)                ((x) + 0x08)
>>> +#define ADC_V1_DATX(x)               ((x) + 0x0C)
>>> +#define ADC_V1_INTCLR(x)     ((x) + 0x18)
>>> +#define ADC_V1_MUX(x)                ((x) + 0x1c)
>>> +
>>> +/* EXYNOS5410 ADC_V2 registers definitions */
>>> +#define ADC_V2_CON1(x)               ((x) + 0x00)
>>> +#define ADC_V2_CON2(x)               ((x) + 0x04)
>>> +#define ADC_V2_STAT(x)               ((x) + 0x08)
>>> +#define ADC_V2_INT_EN(x)     ((x) + 0x10)
>>> +#define ADC_V2_INT_ST(x)     ((x) + 0x14)
>>> +#define ADC_V2_VER(x)                ((x) + 0x20)
>>> +
>>> +/* Bit definitions for ADC_V1 */
>>> +#define ADC_V1_CON_RES               (1u << 16)
>>> +#define ADC_V1_CON_PRSCEN    (1u << 14)
>>> +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
>>> +#define ADC_V1_CON_STANDBY   (1u << 2)
>>> +
>>> +/* Bit definitions for ADC_V2 */
>>> +#define ADC_V2_CON1_SOFT_RESET       (1u << 2)
>>> +
>>> +#define ADC_V2_CON2_OSEL     (1u << 10)
>>> +#define ADC_V2_CON2_ESEL     (1u << 9)
>>> +#define ADC_V2_CON2_HIGHF    (1u << 8)
>>> +#define ADC_V2_CON2_C_TIME(x)        (((x) & 7) << 4)
>>> +#define ADC_V2_CON2_ACH_SEL(x)       (((x) & 0xF) << 0)
>>> +#define ADC_V2_CON2_ACH_MASK 0xF
>>> +
>>> +/* Bit definitions common for ADC_V1 and ADC_V2 */
>>> +#define ADC_V1_CON_EN_START          (1u << 0)
>>> +#define ADC_V1_DATX_MASK     0xFFF
>>> +
>>> +struct exynos5_adc {
>>> +     void __iomem            *regs;
>>> +     struct clk              *clk;
>>> +     unsigned int            irq;
>>> +     struct regulator        *vdd;
>>> +
>>> +     struct completion       completion;
>>> +
>>> +     struct iio_map          *map;
>>> +     u32                     value;
>>> +     unsigned int            version;
>>> +};
>>> +
>>> +static const struct of_device_id exynos5_adc_match[] = {
>>> +     { .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
>>> +     { .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
>>> +     {},
>>> +};
>>> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
>>> +
>>> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
>>> +{
>>> +     const struct of_device_id *match;
>>> +
>>> +     match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
>>> +     return (unsigned int)match->data;
>>> +}
>>> +
>>> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
>>> +static struct iio_map exynos5_adc_iio_maps[] = {
>>> +     {
>>> +             .consumer_dev_name = "0.ncp15wb473",
>>> +             .consumer_channel = "adc3",
>>> +             .adc_channel_label = "adc3",
>>> +     },
>>> +     {
>>> +             .consumer_dev_name = "1.ncp15wb473",
>>> +             .consumer_channel = "adc4",
>>> +             .adc_channel_label = "adc4",
>>> +     },
>>> +     {
>>> +             .consumer_dev_name = "2.ncp15wb473",
>>> +             .consumer_channel = "adc5",
>>> +             .adc_channel_label = "adc5",
>>> +     },
>>> +     {
>>> +             .consumer_dev_name = "3.ncp15wb473",
>>> +             .consumer_channel = "adc6",
>>> +             .adc_channel_label = "adc6",
>>> +     },
>>> +     {},
>>> +};
>>> +
>>> +static int exynos5_read_raw(struct iio_dev *indio_dev,
>>> +                             struct iio_chan_spec const *chan,
>>> +                             int *val,
>>> +                             int *val2,
>>> +                             long mask)
>>> +{
>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>> +     u32 con1, con2;
>>> +
>>> +     if (mask == IIO_CHAN_INFO_RAW) {
>>> +             mutex_lock(&indio_dev->mlock);
>>> +
>>> +             /* Select the channel to be used and Trigger conversion */
>>> +             if (info->version == ADC_V2) {
>>> +                     con2 = readl(ADC_V2_CON2(info->regs));
>>> +                     con2 &= ~ADC_V2_CON2_ACH_MASK;
>>> +                     con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
>>> +                     writel(con2, ADC_V2_CON2(info->regs));
>>> +
>>> +                     con1 = readl(ADC_V2_CON1(info->regs));
>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>> +                                     ADC_V2_CON1(info->regs));
>>> +             } else {
>>> +                     writel(chan->address, ADC_V1_MUX(info->regs));
>>> +
>>> +                     con1 = readl(ADC_V1_CON(info->regs));
>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>> +                                     ADC_V1_CON(info->regs));
>>> +             }
>>> +
>>> +             wait_for_completion(&info->completion);
>>> +             *val = info->value;
>>> +
>>> +             mutex_unlock(&indio_dev->mlock);
>>> +
>>> +             return IIO_VAL_INT;
>>> +     }
>>> +
>>> +     return -EINVAL;
>>> +}
>>> +
>>> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
>>> +{
>>> +     struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
>>> +
>>> +     /* Read value */
>>> +     info->value = readl(ADC_V1_DATX(info->regs)) &
>>> +                                             ADC_V1_DATX_MASK;
>>> +     /* clear irq */
>>> +     if (info->version == ADC_V2)
>>> +             writel(1, ADC_V2_INT_ST(info->regs));
>>> +     else
>>> +             writel(1, ADC_V1_INTCLR(info->regs));
>>> +
>>> +     complete(&info->completion);
>>> +
>>> +     return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
>>> +                           unsigned reg, unsigned writeval,
>>> +                           unsigned *readval)
>>> +{
>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>> +     u32 ret;
>>> +
>>> +     mutex_lock(&indio_dev->mlock);
>>> +
>>> +     if (readval != NULL) {
>>> +             ret = readl(info->regs + reg);
>>> +             *readval = ret;
>>> +     } else
>>> +             ret = -EINVAL;
>>> +
>>> +     mutex_unlock(&indio_dev->mlock);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static const struct iio_info exynos5_adc_iio_info = {
>>> +     .read_raw = &exynos5_read_raw,
>>> +     .debugfs_reg_access = &exynos5_adc_reg_access,
>>> +     .driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +#define ADC_V1_CHANNEL(_index, _id) {                \
>>> +     .type = IIO_VOLTAGE,                            \
>>> +     .indexed = 1,                                   \
>>> +     .channel = _index,                              \
>>> +     .address = _index,                              \
>>> +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
>>> +     .datasheet_name = _id,                          \
>>> +}
>>> +
>>> +/** ADC core in EXYNOS5410 has 10 channels,
>>> + * ADC core in EXYNOS5250 has 8 channels
>>> +*/
>>> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
>>> +     ADC_V1_CHANNEL(0, "adc0"),
>>> +     ADC_V1_CHANNEL(1, "adc1"),
>>> +     ADC_V1_CHANNEL(2, "adc2"),
>>> +     ADC_V1_CHANNEL(3, "adc3"),
>>> +     ADC_V1_CHANNEL(4, "adc4"),
>>> +     ADC_V1_CHANNEL(5, "adc5"),
>>> +     ADC_V1_CHANNEL(6, "adc6"),
>>> +     ADC_V1_CHANNEL(7, "adc7"),
>>> +     ADC_V1_CHANNEL(8, "adc8"),
>>> +     ADC_V1_CHANNEL(9, "adc9"),
>>> +};
>>> +
>>> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
>>> +{
>>> +     struct platform_device *pdev = to_platform_device(dev);
>>> +
>>> +     platform_device_unregister(pdev);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
>>> +{
>>> +     u32 con1, con2;
>>> +
>>> +     if (info->version == ADC_V2) {
>>> +             con1 = ADC_V2_CON1_SOFT_RESET;
>>> +             writel(con1, ADC_V2_CON1(info->regs));
>>> +
>>> +             con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
>>> +                     ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
>>> +             writel(con2, ADC_V2_CON2(info->regs));
>>> +
>>> +             /* Enable interrupts */
>>> +             writel(1, ADC_V2_INT_EN(info->regs));
>>> +     } else {
>>> +             /* set default prescaler values and Enable prescaler */
>>> +             con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
>>> +
>>> +             /* Enable 12-bit ADC resolution */
>>> +             con1 |= ADC_V1_CON_RES;
>>> +             writel(con1, ADC_V1_CON(info->regs));
>>> +     }
>>> +}
>>> +
>>> +static int exynos5_adc_probe(struct platform_device *pdev)
>>> +{
>>> +     struct exynos5_adc *info = NULL;
>>> +     struct device_node *np = pdev->dev.of_node;
>>> +     struct iio_dev *indio_dev = NULL;
>>> +     struct resource *mem;
>>> +     int ret = -ENODEV;
>>> +     int irq;
>>> +
>>> +     if (!np)
>>> +             return ret;
>>> +
>>> +     indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
>>> +     if (!indio_dev) {
>>> +             dev_err(&pdev->dev, "failed allocating iio device\n");
>>> +             return -ENOMEM;
>>> +     }
>>> +
>>> +     info = iio_priv(indio_dev);
>>> +
>>> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> +
>>> +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
>>> +
>>> +     irq = platform_get_irq(pdev, 0);
>>> +     if (irq < 0) {
>>> +             dev_err(&pdev->dev, "no irq resource?\n");
>>> +             ret = irq;
>>> +             goto err_iio;
>>> +     }
>>> +
>>> +     info->irq = irq;
>>> +
>>> +     ret = request_irq(info->irq, exynos5_adc_isr,
>>> +                                     0, dev_name(&pdev->dev), info);
>>> +     if (ret < 0) {
>>> +             dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>>> +                                                     info->irq);
>>> +             goto err_iio;
>>> +     }
>>> +
>>> +     info->clk = devm_clk_get(&pdev->dev, "adc");
>>> +     if (IS_ERR(info->clk)) {
>>> +             dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>>> +                                                     PTR_ERR(info->clk));
>>> +             ret = PTR_ERR(info->clk);
>>> +             goto err_irq;
>>> +     }
>>> +
>>> +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
>>> +     if (IS_ERR(info->vdd)) {
>>> +             dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
>>> +                                                     PTR_ERR(info->vdd));
>>> +             ret = PTR_ERR(info->vdd);
>>> +             goto err_irq;
>>> +     }
>>> +
>>> +     info->version = exynos5_adc_get_version(pdev);
>>> +
>>> +     platform_set_drvdata(pdev, indio_dev);
>>> +
>>> +     init_completion(&info->completion);
>>> +
>>> +     indio_dev->name = dev_name(&pdev->dev);
>>> +     indio_dev->dev.parent = &pdev->dev;
>>> +     indio_dev->dev.of_node = pdev->dev.of_node;
>>> +     indio_dev->info = &exynos5_adc_iio_info;
>>> +     indio_dev->modes = INDIO_DIRECT_MODE;
>>> +     indio_dev->channels = exynos5_adc_iio_channels;
>>> +     indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
>>> +
>>> +     info->map = exynos5_adc_iio_maps;
>>> +
>>> +     ret = iio_map_array_register(indio_dev, info->map);
>>> +     if (ret) {
>>> +             dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
>>> +             goto err_irq;
>>> +     }
>>> +
>>> +     ret = iio_device_register(indio_dev);
>>> +     if (ret)
>>> +             goto err_map;
>>> +
>>> +     ret = regulator_enable(info->vdd);
>>> +     if (ret)
>>> +             goto err_iio_dev;
>>> +
>>> +     clk_prepare_enable(info->clk);
>>> +
>>> +     exynos5_adc_hw_init(info);
>>> +
>>> +     ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
>>> +     if (ret < 0) {
>>> +             dev_err(&pdev->dev, "failed adding child nodes\n");
>>> +             goto err_of_populate;
>>> +     }
>>> +
>>> +     return 0;
>>> +
>>> +err_of_populate:
>>> +     device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
>>> +err_iio_dev:
>>> +     iio_device_unregister(indio_dev);
>>> +err_map:
>>> +     iio_map_array_unregister(indio_dev, info->map);
>>> +err_irq:
>>> +     free_irq(info->irq, info);
>>> +err_iio:
>>> +     iio_device_free(indio_dev);
>>> +     return ret;
>>> +}
>>> +
>>> +static int exynos5_adc_remove(struct platform_device *pdev)
>>> +{
>>> +     struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>> +
>>> +     iio_device_unregister(indio_dev);
>>> +     iio_map_array_unregister(indio_dev, info->map);
>>> +     free_irq(info->irq, info);
>>> +     iio_device_free(indio_dev);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +#ifdef CONFIG_PM_SLEEP
>>> +static int exynos5_adc_suspend(struct device *dev)
>>> +{
>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>> +     u32 con;
>>> +
>>> +     if (info->version == ADC_V2) {
>>> +             con = readl(ADC_V2_CON1(info->regs));
>>> +             con &= ~ADC_V1_CON_EN_START;
>>> +             writel(con, ADC_V2_CON1(info->regs));
>>> +     } else {
>>> +             con = readl(ADC_V1_CON(info->regs));
>>> +             con |= ADC_V1_CON_STANDBY;
>>> +             writel(con, ADC_V1_CON(info->regs));
>>> +     }
>>> +
>>> +     clk_unprepare_disable(info->clk);
>>> +     regulator_disable(info->vdd);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int exynos5_adc_resume(struct device *dev)
>>> +{
>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>> +     int ret;
>>> +
>>> +     ret = regulator_enable(info->vdd);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     clk_prepare_enable(info->clk);
>>> +
>>> +     exynos5_adc_hw_init(info);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +#else
>>> +#define exynos5_adc_suspend NULL
>>> +#define exynos5_adc_resume NULL
>>> +#endif
>>> +
>>> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
>>> +                     exynos5_adc_suspend,
>>> +                     exynos5_adc_resume);
>>> +
>>> +static struct platform_driver exynos5_adc_driver = {
>>> +     .probe          = exynos5_adc_probe,
>>> +     .remove         = exynos5_adc_remove,
>>> +     .driver         = {
>>> +             .name   = "exynos5-adc",
>>> +             .owner  = THIS_MODULE,
>>> +             .of_match_table = of_match_ptr(exynos5_adc_match),
>>> +             .pm     = &exynos5_adc_pm_ops,
>>> +     },
>>> +};
>>> +
>>> +module_platform_driver(exynos5_adc_driver);
>>> +
>>> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
>>> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
>>> +MODULE_LICENSE("GPL");
>
>
>
> --
> Shine bright,
> (: Nav :)



--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-02-13  2:48     ` Naveen Krishna Ch
  2013-02-13 11:05       ` Naveen Krishna Ch
@ 2013-02-13 13:16       ` Naveen Krishna Ch
  2013-02-13 13:30         ` Lars-Peter Clausen
  2013-02-13 15:51         ` Guenter Roeck
  1 sibling, 2 replies; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-02-13 13:16 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh, lars

Please ignore the unfinished previous mail.



On 13 February 2013 08:18, Naveen Krishna Ch <naveenkrishna.ch@gmail.com> wrote:
> On 13 February 2013 02:37, Guenter Roeck <linux@roeck-us.net> wrote:
>> On Wed, Jan 23, 2013 at 04:58:06AM -0000, Naveen Krishna Chatradhi wrote:
>>> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
>>> Also adds the Documentation for device tree bindings.
>>>
>>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>
>>> ---
>>> Changes since v1:
>>>
>>> 1. Fixed comments from Lars
>>> 2. Added support for ADC on EXYNOS5410
>>>
>>> Changes since v2:
>>>
>>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>>> 2. Changed devm_request_irq to request_irq
>>>
>>> Few doubts regarding the mappings and child device handling.
>>> Kindly, suggest me better methods.
>>>
>>>  .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
>>>  drivers/iio/adc/Kconfig                            |    7 +
>>>  drivers/iio/adc/Makefile                           |    1 +
>>>  drivers/iio/adc/exynos5_adc.c                      |  464 ++++++++++++++++++++
>>>  4 files changed, 509 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>  create mode 100644 drivers/iio/adc/exynos5_adc.c
>>>
>>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>> new file mode 100644
>>> index 0000000..9a5b515
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>> @@ -0,0 +1,37 @@
>>> +Samsung Exynos5 Analog to Digital Converter bindings
>>> +
>>> +Required properties:
>>> +- compatible:                Must be "samsung,exynos5250-adc" for exynos5250 controllers.
>>> +- reg:                       Contains ADC register address range (base address and
>>> +                     length).
>>> +- interrupts:                Contains the interrupt information for the timer. The
>>> +                     format is being dependent on which interrupt controller
>>> +                     the Samsung device uses.
>>> +
>>> +Note: child nodes can be added for auto probing from device tree.
>>> +
>>> +Example: adding device info in dtsi file
>>> +
>>> +adc@12D10000 {
>>> +     compatible = "samsung,exynos5250-adc";
>>> +     reg = <0x12D10000 0x100>;
>>> +     interrupts = <0 106 0>;
>>> +     #address-cells = <1>;
>>> +     #size-cells = <1>;
>>> +     ranges;
>>> +};
>>> +
>>> +
>>> +Example: Adding child nodes in dts file
>>> +
>>> +adc@12D10000 {
>>> +
>>> +     /* NTC thermistor is a hwmon device */
>>> +     ncp15wb473@0 {
>>> +             compatible = "ntc,ncp15wb473";
>>> +             reg = <0x0>;
>>> +             pullup-uV = <1800000>;
>>> +             pullup-ohm = <47000>;
>>> +             pulldown-ohm = <0>;
>>> +     };
>>> +};
>>
>> How about:
>>
>>         adc: adc@12D10000 {
>>                 compatible = "samsung,exynos5250-adc";
>>                 reg = <0x12D10000 0x100>;
>>                 interrupts = <0 106 0>;
>>                 #io-channel-cells = <1>;
>>         };
>>
>>         ...
>>
>>         ncp15wb473@0 {
>>                 compatible = "ntc,ncp15wb473";
>>                 reg = <0x0>; /* is this needed ? */
>>                 io-channels = <&adc 0>;
>>                 io-channel-names = "adc";
>>                 pullup-uV = <1800000>;  /* uV or uv ? */
>>                 pullup-ohm = <47000>;
>>                 pulldown-ohm = <0>;
>>         };
>>
>> The ncp15wb473 driver would then use either iio_channel_get_all() to get the iio
>> channel list or, if it only supports one adc channel per instance, iio_channel_get().
>>
>> In that context, it would probably make sense to rework the ntc_thermistor
>> driver to support both DT as well as direct instantiation using access functions
>> and platform data (as it does today).
>>
>> Also see https://patchwork.kernel.org/patch/2112171/.

Hello Guenter,

I've rebase my adc driver on top of your (OF for IIO patch)

My setup is like the below one. kindly, help me find the right device
tree node params

One ADC controller with 8 channels,
  4 NTC thermistors are connected to channel 3, 4, 5 and 6 of ADC respectively

ADC ch - 0
ADC ch - 1
ADC ch - 2
ADC ch - 3 ------------------NTC
ADC ch - 4 ------------------NTC
ADC ch - 5 ------------------NTC
ADC ch - 6 ------------------NTC
ADC ch - 7

I've started off with something like this.

        adc0: adc@12D10000 {
                compatible = "samsung,exynos5250-adc";
                reg = <0x12D10000 0x100>;
                interrupts = <0 106 0>;
                #io-channel-cells = <1>;
        };

        adc0: adc@12D10000 {
                vdd-supply = <&buck5_reg>;

                ncp15wb473@0 {
                        compatible = "ntc,ncp15wb473";
                        io-channels = <&adc0 3>;
                        io-channel-names = "adc3";
                        pullup-uV = <1800000>;
                        pullup-ohm = <47000>;
                        pulldown-ohm = <0>;
                        id = <3>;
                };

                ncp15wb473@1 {
                        compatible = "ntc,ncp15wb473";
                        pullup-uV = <1800000>;
                        pullup-ohm = <47000>;
                        pulldown-ohm = <0>;
                        io-channels = <&adc0 4>;
                        io-channel-names = "adc4";
                        id = <4>;
                };
                ncp15wb473@2 {
                        compatible = "ntc,ncp15wb473";
                        pullup-uV = <1800000>;
                        pullup-ohm = <47000>;
                        pulldown-ohm = <0>;
                        io-channels = <&adc0 5>;
                        io-channel-names = "adc5";
                        id = <5>;
                };
                ncp15wb473@3 {
                        compatible = "ntc,ncp15wb473";
                        pullup-uV = <1800000>;
                        pullup-ohm = <47000>;
                        pulldown-ohm = <0>;
                        io-channels = <&adc0 6>;
                        io-channel-names = "adc6";
                        id = <6>;
                };
        };

ADC driver will use of_platform_populate() to populate the child nodes
(ntc thermistors in my case)

I've modified the NTC driver to support DT. in probe
chan = iio_channel_get(&pdev->dev, "adcX");
and using "id" field to use respective ADC channel to do the raw_read()

Issue:
1. I get weird device names for thermistors starting from ncp15wb473.2
to ncp15wb473.5
2. ADC doesn't looks like creating valid channels

Any clues please

>>
>> Thanks,
>> Guenter
> Yes Guenter, I will rebase and submit the ADC driver based on your patch set.
>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index fe822a1..33ceabf 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -91,6 +91,13 @@ config AT91_ADC
>>>       help
>>>         Say yes here to build support for Atmel AT91 ADC.
>>>
>>> +config EXYNOS5_ADC
>>> +     bool "Exynos5 ADC driver support"
>>> +     help
>>> +       Core support for the ADC block found in the Samsung EXYNOS5 series
>>> +       of SoCs for drivers such as the touchscreen and hwmon to use to share
>>> +       this resource.
>>> +
>>>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
>>> new file mode 100644
>>> index 0000000..8982675
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/exynos5_adc.c
>>> @@ -0,0 +1,464 @@
>>> +/*
>>> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
>>> + *
>>> + *  8 ~ 10 channel, 10/12-bit ADC
>>> + *
>>> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>> + *
>>> + *  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.
>>> + *
>>> + *  This program is distributed in the hope that it will be useful,
>>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + *  GNU General Public License for more details.
>>> + *
>>> + *  You should have received a copy of the GNU General Public License
>>> + *  along with this program; if not, write to the Free Software
>>> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/io.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_irq.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/of_platform.h>
>>> +
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/machine.h>
>>> +#include <linux/iio/driver.h>
>>> +
>>> +enum adc_version {
>>> +     ADC_V1,
>>> +     ADC_V2
>>> +};
>>> +
>>> +/* EXYNOS5250 ADC_V1 registers definitions */
>>> +#define ADC_V1_CON(x)                ((x) + 0x00)
>>> +#define ADC_V1_DLY(x)                ((x) + 0x08)
>>> +#define ADC_V1_DATX(x)               ((x) + 0x0C)
>>> +#define ADC_V1_INTCLR(x)     ((x) + 0x18)
>>> +#define ADC_V1_MUX(x)                ((x) + 0x1c)
>>> +
>>> +/* EXYNOS5410 ADC_V2 registers definitions */
>>> +#define ADC_V2_CON1(x)               ((x) + 0x00)
>>> +#define ADC_V2_CON2(x)               ((x) + 0x04)
>>> +#define ADC_V2_STAT(x)               ((x) + 0x08)
>>> +#define ADC_V2_INT_EN(x)     ((x) + 0x10)
>>> +#define ADC_V2_INT_ST(x)     ((x) + 0x14)
>>> +#define ADC_V2_VER(x)                ((x) + 0x20)
>>> +
>>> +/* Bit definitions for ADC_V1 */
>>> +#define ADC_V1_CON_RES               (1u << 16)
>>> +#define ADC_V1_CON_PRSCEN    (1u << 14)
>>> +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
>>> +#define ADC_V1_CON_STANDBY   (1u << 2)
>>> +
>>> +/* Bit definitions for ADC_V2 */
>>> +#define ADC_V2_CON1_SOFT_RESET       (1u << 2)
>>> +
>>> +#define ADC_V2_CON2_OSEL     (1u << 10)
>>> +#define ADC_V2_CON2_ESEL     (1u << 9)
>>> +#define ADC_V2_CON2_HIGHF    (1u << 8)
>>> +#define ADC_V2_CON2_C_TIME(x)        (((x) & 7) << 4)
>>> +#define ADC_V2_CON2_ACH_SEL(x)       (((x) & 0xF) << 0)
>>> +#define ADC_V2_CON2_ACH_MASK 0xF
>>> +
>>> +/* Bit definitions common for ADC_V1 and ADC_V2 */
>>> +#define ADC_V1_CON_EN_START          (1u << 0)
>>> +#define ADC_V1_DATX_MASK     0xFFF
>>> +
>>> +struct exynos5_adc {
>>> +     void __iomem            *regs;
>>> +     struct clk              *clk;
>>> +     unsigned int            irq;
>>> +     struct regulator        *vdd;
>>> +
>>> +     struct completion       completion;
>>> +
>>> +     struct iio_map          *map;
>>> +     u32                     value;
>>> +     unsigned int            version;
>>> +};
>>> +
>>> +static const struct of_device_id exynos5_adc_match[] = {
>>> +     { .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
>>> +     { .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
>>> +     {},
>>> +};
>>> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
>>> +
>>> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
>>> +{
>>> +     const struct of_device_id *match;
>>> +
>>> +     match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
>>> +     return (unsigned int)match->data;
>>> +}
>>> +
>>> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
>>> +static struct iio_map exynos5_adc_iio_maps[] = {
>>> +     {
>>> +             .consumer_dev_name = "0.ncp15wb473",
>>> +             .consumer_channel = "adc3",
>>> +             .adc_channel_label = "adc3",
>>> +     },
>>> +     {
>>> +             .consumer_dev_name = "1.ncp15wb473",
>>> +             .consumer_channel = "adc4",
>>> +             .adc_channel_label = "adc4",
>>> +     },
>>> +     {
>>> +             .consumer_dev_name = "2.ncp15wb473",
>>> +             .consumer_channel = "adc5",
>>> +             .adc_channel_label = "adc5",
>>> +     },
>>> +     {
>>> +             .consumer_dev_name = "3.ncp15wb473",
>>> +             .consumer_channel = "adc6",
>>> +             .adc_channel_label = "adc6",
>>> +     },
>>> +     {},
>>> +};
>>> +
>>> +static int exynos5_read_raw(struct iio_dev *indio_dev,
>>> +                             struct iio_chan_spec const *chan,
>>> +                             int *val,
>>> +                             int *val2,
>>> +                             long mask)
>>> +{
>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>> +     u32 con1, con2;
>>> +
>>> +     if (mask == IIO_CHAN_INFO_RAW) {
>>> +             mutex_lock(&indio_dev->mlock);
>>> +
>>> +             /* Select the channel to be used and Trigger conversion */
>>> +             if (info->version == ADC_V2) {
>>> +                     con2 = readl(ADC_V2_CON2(info->regs));
>>> +                     con2 &= ~ADC_V2_CON2_ACH_MASK;
>>> +                     con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
>>> +                     writel(con2, ADC_V2_CON2(info->regs));
>>> +
>>> +                     con1 = readl(ADC_V2_CON1(info->regs));
>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>> +                                     ADC_V2_CON1(info->regs));
>>> +             } else {
>>> +                     writel(chan->address, ADC_V1_MUX(info->regs));
>>> +
>>> +                     con1 = readl(ADC_V1_CON(info->regs));
>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>> +                                     ADC_V1_CON(info->regs));
>>> +             }
>>> +
>>> +             wait_for_completion(&info->completion);
>>> +             *val = info->value;
>>> +
>>> +             mutex_unlock(&indio_dev->mlock);
>>> +
>>> +             return IIO_VAL_INT;
>>> +     }
>>> +
>>> +     return -EINVAL;
>>> +}
>>> +
>>> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
>>> +{
>>> +     struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
>>> +
>>> +     /* Read value */
>>> +     info->value = readl(ADC_V1_DATX(info->regs)) &
>>> +                                             ADC_V1_DATX_MASK;
>>> +     /* clear irq */
>>> +     if (info->version == ADC_V2)
>>> +             writel(1, ADC_V2_INT_ST(info->regs));
>>> +     else
>>> +             writel(1, ADC_V1_INTCLR(info->regs));
>>> +
>>> +     complete(&info->completion);
>>> +
>>> +     return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
>>> +                           unsigned reg, unsigned writeval,
>>> +                           unsigned *readval)
>>> +{
>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>> +     u32 ret;
>>> +
>>> +     mutex_lock(&indio_dev->mlock);
>>> +
>>> +     if (readval != NULL) {
>>> +             ret = readl(info->regs + reg);
>>> +             *readval = ret;
>>> +     } else
>>> +             ret = -EINVAL;
>>> +
>>> +     mutex_unlock(&indio_dev->mlock);
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +static const struct iio_info exynos5_adc_iio_info = {
>>> +     .read_raw = &exynos5_read_raw,
>>> +     .debugfs_reg_access = &exynos5_adc_reg_access,
>>> +     .driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +#define ADC_V1_CHANNEL(_index, _id) {                \
>>> +     .type = IIO_VOLTAGE,                            \
>>> +     .indexed = 1,                                   \
>>> +     .channel = _index,                              \
>>> +     .address = _index,                              \
>>> +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
>>> +     .datasheet_name = _id,                          \
>>> +}
>>> +
>>> +/** ADC core in EXYNOS5410 has 10 channels,
>>> + * ADC core in EXYNOS5250 has 8 channels
>>> +*/
>>> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
>>> +     ADC_V1_CHANNEL(0, "adc0"),
>>> +     ADC_V1_CHANNEL(1, "adc1"),
>>> +     ADC_V1_CHANNEL(2, "adc2"),
>>> +     ADC_V1_CHANNEL(3, "adc3"),
>>> +     ADC_V1_CHANNEL(4, "adc4"),
>>> +     ADC_V1_CHANNEL(5, "adc5"),
>>> +     ADC_V1_CHANNEL(6, "adc6"),
>>> +     ADC_V1_CHANNEL(7, "adc7"),
>>> +     ADC_V1_CHANNEL(8, "adc8"),
>>> +     ADC_V1_CHANNEL(9, "adc9"),
>>> +};
>>> +
>>> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
>>> +{
>>> +     struct platform_device *pdev = to_platform_device(dev);
>>> +
>>> +     platform_device_unregister(pdev);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
>>> +{
>>> +     u32 con1, con2;
>>> +
>>> +     if (info->version == ADC_V2) {
>>> +             con1 = ADC_V2_CON1_SOFT_RESET;
>>> +             writel(con1, ADC_V2_CON1(info->regs));
>>> +
>>> +             con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
>>> +                     ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
>>> +             writel(con2, ADC_V2_CON2(info->regs));
>>> +
>>> +             /* Enable interrupts */
>>> +             writel(1, ADC_V2_INT_EN(info->regs));
>>> +     } else {
>>> +             /* set default prescaler values and Enable prescaler */
>>> +             con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
>>> +
>>> +             /* Enable 12-bit ADC resolution */
>>> +             con1 |= ADC_V1_CON_RES;
>>> +             writel(con1, ADC_V1_CON(info->regs));
>>> +     }
>>> +}
>>> +
>>> +static int exynos5_adc_probe(struct platform_device *pdev)
>>> +{
>>> +     struct exynos5_adc *info = NULL;
>>> +     struct device_node *np = pdev->dev.of_node;
>>> +     struct iio_dev *indio_dev = NULL;
>>> +     struct resource *mem;
>>> +     int ret = -ENODEV;
>>> +     int irq;
>>> +
>>> +     if (!np)
>>> +             return ret;
>>> +
>>> +     indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
>>> +     if (!indio_dev) {
>>> +             dev_err(&pdev->dev, "failed allocating iio device\n");
>>> +             return -ENOMEM;
>>> +     }
>>> +
>>> +     info = iio_priv(indio_dev);
>>> +
>>> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> +
>>> +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
>>> +
>>> +     irq = platform_get_irq(pdev, 0);
>>> +     if (irq < 0) {
>>> +             dev_err(&pdev->dev, "no irq resource?\n");
>>> +             ret = irq;
>>> +             goto err_iio;
>>> +     }
>>> +
>>> +     info->irq = irq;
>>> +
>>> +     ret = request_irq(info->irq, exynos5_adc_isr,
>>> +                                     0, dev_name(&pdev->dev), info);
>>> +     if (ret < 0) {
>>> +             dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>>> +                                                     info->irq);
>>> +             goto err_iio;
>>> +     }
>>> +
>>> +     info->clk = devm_clk_get(&pdev->dev, "adc");
>>> +     if (IS_ERR(info->clk)) {
>>> +             dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>>> +                                                     PTR_ERR(info->clk));
>>> +             ret = PTR_ERR(info->clk);
>>> +             goto err_irq;
>>> +     }
>>> +
>>> +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
>>> +     if (IS_ERR(info->vdd)) {
>>> +             dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
>>> +                                                     PTR_ERR(info->vdd));
>>> +             ret = PTR_ERR(info->vdd);
>>> +             goto err_irq;
>>> +     }
>>> +
>>> +     info->version = exynos5_adc_get_version(pdev);
>>> +
>>> +     platform_set_drvdata(pdev, indio_dev);
>>> +
>>> +     init_completion(&info->completion);
>>> +
>>> +     indio_dev->name = dev_name(&pdev->dev);
>>> +     indio_dev->dev.parent = &pdev->dev;
>>> +     indio_dev->dev.of_node = pdev->dev.of_node;
>>> +     indio_dev->info = &exynos5_adc_iio_info;
>>> +     indio_dev->modes = INDIO_DIRECT_MODE;
>>> +     indio_dev->channels = exynos5_adc_iio_channels;
>>> +     indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
>>> +
>>> +     info->map = exynos5_adc_iio_maps;
>>> +
>>> +     ret = iio_map_array_register(indio_dev, info->map);
>>> +     if (ret) {
>>> +             dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
>>> +             goto err_irq;
>>> +     }
>>> +
>>> +     ret = iio_device_register(indio_dev);
>>> +     if (ret)
>>> +             goto err_map;
>>> +
>>> +     ret = regulator_enable(info->vdd);
>>> +     if (ret)
>>> +             goto err_iio_dev;
>>> +
>>> +     clk_prepare_enable(info->clk);
>>> +
>>> +     exynos5_adc_hw_init(info);
>>> +
>>> +     ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
>>> +     if (ret < 0) {
>>> +             dev_err(&pdev->dev, "failed adding child nodes\n");
>>> +             goto err_of_populate;
>>> +     }
>>> +
>>> +     return 0;
>>> +
>>> +err_of_populate:
>>> +     device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
>>> +err_iio_dev:
>>> +     iio_device_unregister(indio_dev);
>>> +err_map:
>>> +     iio_map_array_unregister(indio_dev, info->map);
>>> +err_irq:
>>> +     free_irq(info->irq, info);
>>> +err_iio:
>>> +     iio_device_free(indio_dev);
>>> +     return ret;
>>> +}
>>> +
>>> +static int exynos5_adc_remove(struct platform_device *pdev)
>>> +{
>>> +     struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>> +
>>> +     iio_device_unregister(indio_dev);
>>> +     iio_map_array_unregister(indio_dev, info->map);
>>> +     free_irq(info->irq, info);
>>> +     iio_device_free(indio_dev);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +#ifdef CONFIG_PM_SLEEP
>>> +static int exynos5_adc_suspend(struct device *dev)
>>> +{
>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>> +     u32 con;
>>> +
>>> +     if (info->version == ADC_V2) {
>>> +             con = readl(ADC_V2_CON1(info->regs));
>>> +             con &= ~ADC_V1_CON_EN_START;
>>> +             writel(con, ADC_V2_CON1(info->regs));
>>> +     } else {
>>> +             con = readl(ADC_V1_CON(info->regs));
>>> +             con |= ADC_V1_CON_STANDBY;
>>> +             writel(con, ADC_V1_CON(info->regs));
>>> +     }
>>> +
>>> +     clk_unprepare_disable(info->clk);
>>> +     regulator_disable(info->vdd);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +static int exynos5_adc_resume(struct device *dev)
>>> +{
>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>> +     int ret;
>>> +
>>> +     ret = regulator_enable(info->vdd);
>>> +     if (ret)
>>> +             return ret;
>>> +
>>> +     clk_prepare_enable(info->clk);
>>> +
>>> +     exynos5_adc_hw_init(info);
>>> +
>>> +     return 0;
>>> +}
>>> +
>>> +#else
>>> +#define exynos5_adc_suspend NULL
>>> +#define exynos5_adc_resume NULL
>>> +#endif
>>> +
>>> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
>>> +                     exynos5_adc_suspend,
>>> +                     exynos5_adc_resume);
>>> +
>>> +static struct platform_driver exynos5_adc_driver = {
>>> +     .probe          = exynos5_adc_probe,
>>> +     .remove         = exynos5_adc_remove,
>>> +     .driver         = {
>>> +             .name   = "exynos5-adc",
>>> +             .owner  = THIS_MODULE,
>>> +             .of_match_table = of_match_ptr(exynos5_adc_match),
>>> +             .pm     = &exynos5_adc_pm_ops,
>>> +     },
>>> +};
>>> +
>>> +module_platform_driver(exynos5_adc_driver);
>>> +
>>> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
>>> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
>>> +MODULE_LICENSE("GPL");
>
>
>
> --
> Shine bright,
> (: Nav :)



--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-02-13 13:16       ` Naveen Krishna Ch
@ 2013-02-13 13:30         ` Lars-Peter Clausen
  2013-02-13 13:53           ` Naveen Krishna Ch
  2013-02-13 15:51         ` Guenter Roeck
  1 sibling, 1 reply; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-02-13 13:30 UTC (permalink / raw)
  To: Naveen Krishna Ch
  Cc: Guenter Roeck, Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh

On 02/13/2013 02:16 PM, Naveen Krishna Ch wrote:
> Please ignore the unfinished previous mail.
> 
> 
> 
> On 13 February 2013 08:18, Naveen Krishna Ch <naveenkrishna.ch@gmail.com> wrote:
>> On 13 February 2013 02:37, Guenter Roeck <linux@roeck-us.net> wrote:
>>> On Wed, Jan 23, 2013 at 04:58:06AM -0000, Naveen Krishna Chatradhi wrote:
>>>> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
>>>> Also adds the Documentation for device tree bindings.
>>>>
>>>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>>
>>>> ---
>>>> Changes since v1:
>>>>
>>>> 1. Fixed comments from Lars
>>>> 2. Added support for ADC on EXYNOS5410
>>>>
>>>> Changes since v2:
>>>>
>>>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>>>> 2. Changed devm_request_irq to request_irq
>>>>
>>>> Few doubts regarding the mappings and child device handling.
>>>> Kindly, suggest me better methods.
>>>>
>>>>  .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
>>>>  drivers/iio/adc/Kconfig                            |    7 +
>>>>  drivers/iio/adc/Makefile                           |    1 +
>>>>  drivers/iio/adc/exynos5_adc.c                      |  464 ++++++++++++++++++++
>>>>  4 files changed, 509 insertions(+)
>>>>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>>  create mode 100644 drivers/iio/adc/exynos5_adc.c
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>> new file mode 100644
>>>> index 0000000..9a5b515
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>> @@ -0,0 +1,37 @@
>>>> +Samsung Exynos5 Analog to Digital Converter bindings
>>>> +
>>>> +Required properties:
>>>> +- compatible:                Must be "samsung,exynos5250-adc" for exynos5250 controllers.
>>>> +- reg:                       Contains ADC register address range (base address and
>>>> +                     length).
>>>> +- interrupts:                Contains the interrupt information for the timer. The
>>>> +                     format is being dependent on which interrupt controller
>>>> +                     the Samsung device uses.
>>>> +
>>>> +Note: child nodes can be added for auto probing from device tree.
>>>> +
>>>> +Example: adding device info in dtsi file
>>>> +
>>>> +adc@12D10000 {
>>>> +     compatible = "samsung,exynos5250-adc";
>>>> +     reg = <0x12D10000 0x100>;
>>>> +     interrupts = <0 106 0>;
>>>> +     #address-cells = <1>;
>>>> +     #size-cells = <1>;
>>>> +     ranges;
>>>> +};
>>>> +
>>>> +
>>>> +Example: Adding child nodes in dts file
>>>> +
>>>> +adc@12D10000 {
>>>> +
>>>> +     /* NTC thermistor is a hwmon device */
>>>> +     ncp15wb473@0 {
>>>> +             compatible = "ntc,ncp15wb473";
>>>> +             reg = <0x0>;
>>>> +             pullup-uV = <1800000>;
>>>> +             pullup-ohm = <47000>;
>>>> +             pulldown-ohm = <0>;
>>>> +     };
>>>> +};
>>>
>>> How about:
>>>
>>>         adc: adc@12D10000 {
>>>                 compatible = "samsung,exynos5250-adc";
>>>                 reg = <0x12D10000 0x100>;
>>>                 interrupts = <0 106 0>;
>>>                 #io-channel-cells = <1>;
>>>         };
>>>
>>>         ...
>>>
>>>         ncp15wb473@0 {
>>>                 compatible = "ntc,ncp15wb473";
>>>                 reg = <0x0>; /* is this needed ? */
>>>                 io-channels = <&adc 0>;
>>>                 io-channel-names = "adc";
>>>                 pullup-uV = <1800000>;  /* uV or uv ? */
>>>                 pullup-ohm = <47000>;
>>>                 pulldown-ohm = <0>;
>>>         };
>>>
>>> The ncp15wb473 driver would then use either iio_channel_get_all() to get the iio
>>> channel list or, if it only supports one adc channel per instance, iio_channel_get().
>>>
>>> In that context, it would probably make sense to rework the ntc_thermistor
>>> driver to support both DT as well as direct instantiation using access functions
>>> and platform data (as it does today).
>>>
>>> Also see https://patchwork.kernel.org/patch/2112171/.
> 
> Hello Guenter,
> 
> I've rebase my adc driver on top of your (OF for IIO patch)
> 
> My setup is like the below one. kindly, help me find the right device
> tree node params
> 
> One ADC controller with 8 channels,
>   4 NTC thermistors are connected to channel 3, 4, 5 and 6 of ADC respectively
> 
> ADC ch - 0
> ADC ch - 1
> ADC ch - 2
> ADC ch - 3 ------------------NTC
> ADC ch - 4 ------------------NTC
> ADC ch - 5 ------------------NTC
> ADC ch - 6 ------------------NTC
> ADC ch - 7
> 
> I've started off with something like this.
> 
>         adc0: adc@12D10000 {
>                 compatible = "samsung,exynos5250-adc";
>                 reg = <0x12D10000 0x100>;
>                 interrupts = <0 106 0>;
>                 #io-channel-cells = <1>;
>         };
> 
>         adc0: adc@12D10000 {

This is a copy paste error, right? you only have one dt node for the adc?

>                 vdd-supply = <&buck5_reg>;
> 
>                 ncp15wb473@0 {
>                         compatible = "ntc,ncp15wb473";
>                         io-channels = <&adc0 3>;
>                         io-channel-names = "adc3";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         id = <3>;
>                 };
> 
>                 ncp15wb473@1 {
>                         compatible = "ntc,ncp15wb473";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         io-channels = <&adc0 4>;
>                         io-channel-names = "adc4";
>                         id = <4>;
>                 };
>                 ncp15wb473@2 {
>                         compatible = "ntc,ncp15wb473";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         io-channels = <&adc0 5>;
>                         io-channel-names = "adc5";
>                         id = <5>;
>                 };
>                 ncp15wb473@3 {
>                         compatible = "ntc,ncp15wb473";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         io-channels = <&adc0 6>;
>                         io-channel-names = "adc6";
>                         id = <6>;
>                 };
>         };
> 
> ADC driver will use of_platform_populate() to populate the child nodes
> (ntc thermistors in my case)
> 
> I've modified the NTC driver to support DT. in probe
> chan = iio_channel_get(&pdev->dev, "adcX");
> and using "id" field to use respective ADC channel to do the raw_read()

The beauty of the interface is that the consumer doesn't need to know the
number of the channel it is using. This is already fully described in the
io-channels property. Since you only have one channel per consumer just use

	iio_chanel_get(&pdev->dev, NULL)

> 
> Issue:
> 1. I get weird device names for thermistors starting from ncp15wb473.2
> to ncp15wb473.5
> 2. ADC doesn't looks like creating valid channels
> 
> Any clues please
> 
>>>
>>> Thanks,
>>> Guenter
>> Yes Guenter, I will rebase and submit the ADC driver based on your patch set.
>>>
>>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>>> index fe822a1..33ceabf 100644
>>>> --- a/drivers/iio/adc/Kconfig
>>>> +++ b/drivers/iio/adc/Kconfig
>>>> @@ -91,6 +91,13 @@ config AT91_ADC
>>>>       help
>>>>         Say yes here to build support for Atmel AT91 ADC.
>>>>
>>>> +config EXYNOS5_ADC
>>>> +     bool "Exynos5 ADC driver support"
>>>> +     help
>>>> +       Core support for the ADC block found in the Samsung EXYNOS5 series
>>>> +       of SoCs for drivers such as the touchscreen and hwmon to use to share
>>>> +       this resource.
>>>> +
>>>>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
>>>> new file mode 100644
>>>> index 0000000..8982675
>>>> --- /dev/null
>>>> +++ b/drivers/iio/adc/exynos5_adc.c
>>>> @@ -0,0 +1,464 @@
>>>> +/*
>>>> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
>>>> + *
>>>> + *  8 ~ 10 channel, 10/12-bit ADC
>>>> + *
>>>> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>> + *
>>>> + *  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.
>>>> + *
>>>> + *  This program is distributed in the hope that it will be useful,
>>>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>>> + *  GNU General Public License for more details.
>>>> + *
>>>> + *  You should have received a copy of the GNU General Public License
>>>> + *  along with this program; if not, write to the Free Software
>>>> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>>>> + */
>>>> +
>>>> +#include <linux/module.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/delay.h>
>>>> +#include <linux/kernel.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/completion.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_irq.h>
>>>> +#include <linux/regulator/consumer.h>
>>>> +#include <linux/of_platform.h>
>>>> +
>>>> +#include <linux/iio/iio.h>
>>>> +#include <linux/iio/machine.h>
>>>> +#include <linux/iio/driver.h>
>>>> +
>>>> +enum adc_version {
>>>> +     ADC_V1,
>>>> +     ADC_V2
>>>> +};
>>>> +
>>>> +/* EXYNOS5250 ADC_V1 registers definitions */
>>>> +#define ADC_V1_CON(x)                ((x) + 0x00)
>>>> +#define ADC_V1_DLY(x)                ((x) + 0x08)
>>>> +#define ADC_V1_DATX(x)               ((x) + 0x0C)
>>>> +#define ADC_V1_INTCLR(x)     ((x) + 0x18)
>>>> +#define ADC_V1_MUX(x)                ((x) + 0x1c)
>>>> +
>>>> +/* EXYNOS5410 ADC_V2 registers definitions */
>>>> +#define ADC_V2_CON1(x)               ((x) + 0x00)
>>>> +#define ADC_V2_CON2(x)               ((x) + 0x04)
>>>> +#define ADC_V2_STAT(x)               ((x) + 0x08)
>>>> +#define ADC_V2_INT_EN(x)     ((x) + 0x10)
>>>> +#define ADC_V2_INT_ST(x)     ((x) + 0x14)
>>>> +#define ADC_V2_VER(x)                ((x) + 0x20)
>>>> +
>>>> +/* Bit definitions for ADC_V1 */
>>>> +#define ADC_V1_CON_RES               (1u << 16)
>>>> +#define ADC_V1_CON_PRSCEN    (1u << 14)
>>>> +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
>>>> +#define ADC_V1_CON_STANDBY   (1u << 2)
>>>> +
>>>> +/* Bit definitions for ADC_V2 */
>>>> +#define ADC_V2_CON1_SOFT_RESET       (1u << 2)
>>>> +
>>>> +#define ADC_V2_CON2_OSEL     (1u << 10)
>>>> +#define ADC_V2_CON2_ESEL     (1u << 9)
>>>> +#define ADC_V2_CON2_HIGHF    (1u << 8)
>>>> +#define ADC_V2_CON2_C_TIME(x)        (((x) & 7) << 4)
>>>> +#define ADC_V2_CON2_ACH_SEL(x)       (((x) & 0xF) << 0)
>>>> +#define ADC_V2_CON2_ACH_MASK 0xF
>>>> +
>>>> +/* Bit definitions common for ADC_V1 and ADC_V2 */
>>>> +#define ADC_V1_CON_EN_START          (1u << 0)
>>>> +#define ADC_V1_DATX_MASK     0xFFF
>>>> +
>>>> +struct exynos5_adc {
>>>> +     void __iomem            *regs;
>>>> +     struct clk              *clk;
>>>> +     unsigned int            irq;
>>>> +     struct regulator        *vdd;
>>>> +
>>>> +     struct completion       completion;
>>>> +
>>>> +     struct iio_map          *map;
>>>> +     u32                     value;
>>>> +     unsigned int            version;
>>>> +};
>>>> +
>>>> +static const struct of_device_id exynos5_adc_match[] = {
>>>> +     { .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
>>>> +     { .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
>>>> +     {},
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
>>>> +
>>>> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
>>>> +{
>>>> +     const struct of_device_id *match;
>>>> +
>>>> +     match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
>>>> +     return (unsigned int)match->data;
>>>> +}
>>>> +
>>>> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
>>>> +static struct iio_map exynos5_adc_iio_maps[] = {
>>>> +     {
>>>> +             .consumer_dev_name = "0.ncp15wb473",
>>>> +             .consumer_channel = "adc3",
>>>> +             .adc_channel_label = "adc3",
>>>> +     },
>>>> +     {
>>>> +             .consumer_dev_name = "1.ncp15wb473",
>>>> +             .consumer_channel = "adc4",
>>>> +             .adc_channel_label = "adc4",
>>>> +     },
>>>> +     {
>>>> +             .consumer_dev_name = "2.ncp15wb473",
>>>> +             .consumer_channel = "adc5",
>>>> +             .adc_channel_label = "adc5",
>>>> +     },
>>>> +     {
>>>> +             .consumer_dev_name = "3.ncp15wb473",
>>>> +             .consumer_channel = "adc6",
>>>> +             .adc_channel_label = "adc6",
>>>> +     },
>>>> +     {},
>>>> +};
>>>> +
>>>> +static int exynos5_read_raw(struct iio_dev *indio_dev,
>>>> +                             struct iio_chan_spec const *chan,
>>>> +                             int *val,
>>>> +                             int *val2,
>>>> +                             long mask)
>>>> +{
>>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>>> +     u32 con1, con2;
>>>> +
>>>> +     if (mask == IIO_CHAN_INFO_RAW) {
>>>> +             mutex_lock(&indio_dev->mlock);
>>>> +
>>>> +             /* Select the channel to be used and Trigger conversion */
>>>> +             if (info->version == ADC_V2) {
>>>> +                     con2 = readl(ADC_V2_CON2(info->regs));
>>>> +                     con2 &= ~ADC_V2_CON2_ACH_MASK;
>>>> +                     con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
>>>> +                     writel(con2, ADC_V2_CON2(info->regs));
>>>> +
>>>> +                     con1 = readl(ADC_V2_CON1(info->regs));
>>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>>> +                                     ADC_V2_CON1(info->regs));
>>>> +             } else {
>>>> +                     writel(chan->address, ADC_V1_MUX(info->regs));
>>>> +
>>>> +                     con1 = readl(ADC_V1_CON(info->regs));
>>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>>> +                                     ADC_V1_CON(info->regs));
>>>> +             }
>>>> +
>>>> +             wait_for_completion(&info->completion);
>>>> +             *val = info->value;
>>>> +
>>>> +             mutex_unlock(&indio_dev->mlock);
>>>> +
>>>> +             return IIO_VAL_INT;
>>>> +     }
>>>> +
>>>> +     return -EINVAL;
>>>> +}
>>>> +
>>>> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
>>>> +{
>>>> +     struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
>>>> +
>>>> +     /* Read value */
>>>> +     info->value = readl(ADC_V1_DATX(info->regs)) &
>>>> +                                             ADC_V1_DATX_MASK;
>>>> +     /* clear irq */
>>>> +     if (info->version == ADC_V2)
>>>> +             writel(1, ADC_V2_INT_ST(info->regs));
>>>> +     else
>>>> +             writel(1, ADC_V1_INTCLR(info->regs));
>>>> +
>>>> +     complete(&info->completion);
>>>> +
>>>> +     return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
>>>> +                           unsigned reg, unsigned writeval,
>>>> +                           unsigned *readval)
>>>> +{
>>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>>> +     u32 ret;
>>>> +
>>>> +     mutex_lock(&indio_dev->mlock);
>>>> +
>>>> +     if (readval != NULL) {
>>>> +             ret = readl(info->regs + reg);
>>>> +             *readval = ret;
>>>> +     } else
>>>> +             ret = -EINVAL;
>>>> +
>>>> +     mutex_unlock(&indio_dev->mlock);
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static const struct iio_info exynos5_adc_iio_info = {
>>>> +     .read_raw = &exynos5_read_raw,
>>>> +     .debugfs_reg_access = &exynos5_adc_reg_access,
>>>> +     .driver_module = THIS_MODULE,
>>>> +};
>>>> +
>>>> +#define ADC_V1_CHANNEL(_index, _id) {                \
>>>> +     .type = IIO_VOLTAGE,                            \
>>>> +     .indexed = 1,                                   \
>>>> +     .channel = _index,                              \
>>>> +     .address = _index,                              \
>>>> +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
>>>> +     .datasheet_name = _id,                          \
>>>> +}
>>>> +
>>>> +/** ADC core in EXYNOS5410 has 10 channels,
>>>> + * ADC core in EXYNOS5250 has 8 channels
>>>> +*/
>>>> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
>>>> +     ADC_V1_CHANNEL(0, "adc0"),
>>>> +     ADC_V1_CHANNEL(1, "adc1"),
>>>> +     ADC_V1_CHANNEL(2, "adc2"),
>>>> +     ADC_V1_CHANNEL(3, "adc3"),
>>>> +     ADC_V1_CHANNEL(4, "adc4"),
>>>> +     ADC_V1_CHANNEL(5, "adc5"),
>>>> +     ADC_V1_CHANNEL(6, "adc6"),
>>>> +     ADC_V1_CHANNEL(7, "adc7"),
>>>> +     ADC_V1_CHANNEL(8, "adc8"),
>>>> +     ADC_V1_CHANNEL(9, "adc9"),
>>>> +};
>>>> +
>>>> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
>>>> +{
>>>> +     struct platform_device *pdev = to_platform_device(dev);
>>>> +
>>>> +     platform_device_unregister(pdev);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
>>>> +{
>>>> +     u32 con1, con2;
>>>> +
>>>> +     if (info->version == ADC_V2) {
>>>> +             con1 = ADC_V2_CON1_SOFT_RESET;
>>>> +             writel(con1, ADC_V2_CON1(info->regs));
>>>> +
>>>> +             con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
>>>> +                     ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
>>>> +             writel(con2, ADC_V2_CON2(info->regs));
>>>> +
>>>> +             /* Enable interrupts */
>>>> +             writel(1, ADC_V2_INT_EN(info->regs));
>>>> +     } else {
>>>> +             /* set default prescaler values and Enable prescaler */
>>>> +             con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
>>>> +
>>>> +             /* Enable 12-bit ADC resolution */
>>>> +             con1 |= ADC_V1_CON_RES;
>>>> +             writel(con1, ADC_V1_CON(info->regs));
>>>> +     }
>>>> +}
>>>> +
>>>> +static int exynos5_adc_probe(struct platform_device *pdev)
>>>> +{
>>>> +     struct exynos5_adc *info = NULL;
>>>> +     struct device_node *np = pdev->dev.of_node;
>>>> +     struct iio_dev *indio_dev = NULL;
>>>> +     struct resource *mem;
>>>> +     int ret = -ENODEV;
>>>> +     int irq;
>>>> +
>>>> +     if (!np)
>>>> +             return ret;
>>>> +
>>>> +     indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
>>>> +     if (!indio_dev) {
>>>> +             dev_err(&pdev->dev, "failed allocating iio device\n");
>>>> +             return -ENOMEM;
>>>> +     }
>>>> +
>>>> +     info = iio_priv(indio_dev);
>>>> +
>>>> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>> +
>>>> +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
>>>> +
>>>> +     irq = platform_get_irq(pdev, 0);
>>>> +     if (irq < 0) {
>>>> +             dev_err(&pdev->dev, "no irq resource?\n");
>>>> +             ret = irq;
>>>> +             goto err_iio;
>>>> +     }
>>>> +
>>>> +     info->irq = irq;
>>>> +
>>>> +     ret = request_irq(info->irq, exynos5_adc_isr,
>>>> +                                     0, dev_name(&pdev->dev), info);
>>>> +     if (ret < 0) {
>>>> +             dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>>>> +                                                     info->irq);
>>>> +             goto err_iio;
>>>> +     }
>>>> +
>>>> +     info->clk = devm_clk_get(&pdev->dev, "adc");
>>>> +     if (IS_ERR(info->clk)) {
>>>> +             dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>>>> +                                                     PTR_ERR(info->clk));
>>>> +             ret = PTR_ERR(info->clk);
>>>> +             goto err_irq;
>>>> +     }
>>>> +
>>>> +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
>>>> +     if (IS_ERR(info->vdd)) {
>>>> +             dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
>>>> +                                                     PTR_ERR(info->vdd));
>>>> +             ret = PTR_ERR(info->vdd);
>>>> +             goto err_irq;
>>>> +     }
>>>> +
>>>> +     info->version = exynos5_adc_get_version(pdev);
>>>> +
>>>> +     platform_set_drvdata(pdev, indio_dev);
>>>> +
>>>> +     init_completion(&info->completion);
>>>> +
>>>> +     indio_dev->name = dev_name(&pdev->dev);
>>>> +     indio_dev->dev.parent = &pdev->dev;
>>>> +     indio_dev->dev.of_node = pdev->dev.of_node;
>>>> +     indio_dev->info = &exynos5_adc_iio_info;
>>>> +     indio_dev->modes = INDIO_DIRECT_MODE;
>>>> +     indio_dev->channels = exynos5_adc_iio_channels;
>>>> +     indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
>>>> +
>>>> +     info->map = exynos5_adc_iio_maps;
>>>> +
>>>> +     ret = iio_map_array_register(indio_dev, info->map);
>>>> +     if (ret) {
>>>> +             dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
>>>> +             goto err_irq;
>>>> +     }
>>>> +
>>>> +     ret = iio_device_register(indio_dev);
>>>> +     if (ret)
>>>> +             goto err_map;
>>>> +
>>>> +     ret = regulator_enable(info->vdd);
>>>> +     if (ret)
>>>> +             goto err_iio_dev;
>>>> +
>>>> +     clk_prepare_enable(info->clk);
>>>> +
>>>> +     exynos5_adc_hw_init(info);
>>>> +
>>>> +     ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
>>>> +     if (ret < 0) {
>>>> +             dev_err(&pdev->dev, "failed adding child nodes\n");
>>>> +             goto err_of_populate;
>>>> +     }
>>>> +
>>>> +     return 0;
>>>> +
>>>> +err_of_populate:
>>>> +     device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
>>>> +err_iio_dev:
>>>> +     iio_device_unregister(indio_dev);
>>>> +err_map:
>>>> +     iio_map_array_unregister(indio_dev, info->map);
>>>> +err_irq:
>>>> +     free_irq(info->irq, info);
>>>> +err_iio:
>>>> +     iio_device_free(indio_dev);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static int exynos5_adc_remove(struct platform_device *pdev)
>>>> +{
>>>> +     struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>>> +
>>>> +     iio_device_unregister(indio_dev);
>>>> +     iio_map_array_unregister(indio_dev, info->map);
>>>> +     free_irq(info->irq, info);
>>>> +     iio_device_free(indio_dev);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +#ifdef CONFIG_PM_SLEEP
>>>> +static int exynos5_adc_suspend(struct device *dev)
>>>> +{
>>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>>> +     u32 con;
>>>> +
>>>> +     if (info->version == ADC_V2) {
>>>> +             con = readl(ADC_V2_CON1(info->regs));
>>>> +             con &= ~ADC_V1_CON_EN_START;
>>>> +             writel(con, ADC_V2_CON1(info->regs));
>>>> +     } else {
>>>> +             con = readl(ADC_V1_CON(info->regs));
>>>> +             con |= ADC_V1_CON_STANDBY;
>>>> +             writel(con, ADC_V1_CON(info->regs));
>>>> +     }
>>>> +
>>>> +     clk_unprepare_disable(info->clk);
>>>> +     regulator_disable(info->vdd);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +static int exynos5_adc_resume(struct device *dev)
>>>> +{
>>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>>> +     int ret;
>>>> +
>>>> +     ret = regulator_enable(info->vdd);
>>>> +     if (ret)
>>>> +             return ret;
>>>> +
>>>> +     clk_prepare_enable(info->clk);
>>>> +
>>>> +     exynos5_adc_hw_init(info);
>>>> +
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +#else
>>>> +#define exynos5_adc_suspend NULL
>>>> +#define exynos5_adc_resume NULL
>>>> +#endif
>>>> +
>>>> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
>>>> +                     exynos5_adc_suspend,
>>>> +                     exynos5_adc_resume);
>>>> +
>>>> +static struct platform_driver exynos5_adc_driver = {
>>>> +     .probe          = exynos5_adc_probe,
>>>> +     .remove         = exynos5_adc_remove,
>>>> +     .driver         = {
>>>> +             .name   = "exynos5-adc",
>>>> +             .owner  = THIS_MODULE,
>>>> +             .of_match_table = of_match_ptr(exynos5_adc_match),
>>>> +             .pm     = &exynos5_adc_pm_ops,
>>>> +     },
>>>> +};
>>>> +
>>>> +module_platform_driver(exynos5_adc_driver);
>>>> +
>>>> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
>>>> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
>>>> +MODULE_LICENSE("GPL");
>>
>>
>>
>> --
>> Shine bright,
>> (: Nav :)
> 
> 
> 
> --
> Shine bright,
> (: Nav :)
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-02-13 13:30         ` Lars-Peter Clausen
@ 2013-02-13 13:53           ` Naveen Krishna Ch
  2013-02-13 14:05             ` Lars-Peter Clausen
  0 siblings, 1 reply; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-02-13 13:53 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Guenter Roeck, Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh

On 13 February 2013 19:00, Lars-Peter Clausen <lars@metafoo.de> wrote:
> On 02/13/2013 02:16 PM, Naveen Krishna Ch wrote:
>> Please ignore the unfinished previous mail.
>>
>>
>>
>> On 13 February 2013 08:18, Naveen Krishna Ch <naveenkrishna.ch@gmail.com> wrote:
>>> On 13 February 2013 02:37, Guenter Roeck <linux@roeck-us.net> wrote:
>>>> On Wed, Jan 23, 2013 at 04:58:06AM -0000, Naveen Krishna Chatradhi wrote:
>>>>> This patch add an ADC IP found on EXYNOS5 series socs from Samsung.
>>>>> Also adds the Documentation for device tree bindings.
>>>>>
>>>>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>>>
>>>>> ---
>>>>> Changes since v1:
>>>>>
>>>>> 1. Fixed comments from Lars
>>>>> 2. Added support for ADC on EXYNOS5410
>>>>>
>>>>> Changes since v2:
>>>>>
>>>>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>>>>> 2. Changed devm_request_irq to request_irq
>>>>>
>>>>> Few doubts regarding the mappings and child device handling.
>>>>> Kindly, suggest me better methods.
>>>>>
>>>>>  .../bindings/arm/samsung/exynos5-adc.txt           |   37 ++
>>>>>  drivers/iio/adc/Kconfig                            |    7 +
>>>>>  drivers/iio/adc/Makefile                           |    1 +
>>>>>  drivers/iio/adc/exynos5_adc.c                      |  464 ++++++++++++++++++++
>>>>>  4 files changed, 509 insertions(+)
>>>>>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>>>  create mode 100644 drivers/iio/adc/exynos5_adc.c
>>>>>
>>>>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>>> new file mode 100644
>>>>> index 0000000..9a5b515
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos5-adc.txt
>>>>> @@ -0,0 +1,37 @@
>>>>> +Samsung Exynos5 Analog to Digital Converter bindings
>>>>> +
>>>>> +Required properties:
>>>>> +- compatible:                Must be "samsung,exynos5250-adc" for exynos5250 controllers.
>>>>> +- reg:                       Contains ADC register address range (base address and
>>>>> +                     length).
>>>>> +- interrupts:                Contains the interrupt information for the timer. The
>>>>> +                     format is being dependent on which interrupt controller
>>>>> +                     the Samsung device uses.
>>>>> +
>>>>> +Note: child nodes can be added for auto probing from device tree.
>>>>> +
>>>>> +Example: adding device info in dtsi file
>>>>> +
>>>>> +adc@12D10000 {
>>>>> +     compatible = "samsung,exynos5250-adc";
>>>>> +     reg = <0x12D10000 0x100>;
>>>>> +     interrupts = <0 106 0>;
>>>>> +     #address-cells = <1>;
>>>>> +     #size-cells = <1>;
>>>>> +     ranges;
>>>>> +};
>>>>> +
>>>>> +
>>>>> +Example: Adding child nodes in dts file
>>>>> +
>>>>> +adc@12D10000 {
>>>>> +
>>>>> +     /* NTC thermistor is a hwmon device */
>>>>> +     ncp15wb473@0 {
>>>>> +             compatible = "ntc,ncp15wb473";
>>>>> +             reg = <0x0>;
>>>>> +             pullup-uV = <1800000>;
>>>>> +             pullup-ohm = <47000>;
>>>>> +             pulldown-ohm = <0>;
>>>>> +     };
>>>>> +};
>>>>
>>>> How about:
>>>>
>>>>         adc: adc@12D10000 {
>>>>                 compatible = "samsung,exynos5250-adc";
>>>>                 reg = <0x12D10000 0x100>;
>>>>                 interrupts = <0 106 0>;
>>>>                 #io-channel-cells = <1>;
>>>>         };
>>>>
>>>>         ...
>>>>
>>>>         ncp15wb473@0 {
>>>>                 compatible = "ntc,ncp15wb473";
>>>>                 reg = <0x0>; /* is this needed ? */
>>>>                 io-channels = <&adc 0>;
>>>>                 io-channel-names = "adc";
>>>>                 pullup-uV = <1800000>;  /* uV or uv ? */
>>>>                 pullup-ohm = <47000>;
>>>>                 pulldown-ohm = <0>;
>>>>         };
>>>>
>>>> The ncp15wb473 driver would then use either iio_channel_get_all() to get the iio
>>>> channel list or, if it only supports one adc channel per instance, iio_channel_get().
>>>>
>>>> In that context, it would probably make sense to rework the ntc_thermistor
>>>> driver to support both DT as well as direct instantiation using access functions
>>>> and platform data (as it does today).
>>>>
>>>> Also see https://patchwork.kernel.org/patch/2112171/.
>>
>> Hello Guenter,
>>
>> I've rebase my adc driver on top of your (OF for IIO patch)
>>
>> My setup is like the below one. kindly, help me find the right device
>> tree node params
>>
>> One ADC controller with 8 channels,
>>   4 NTC thermistors are connected to channel 3, 4, 5 and 6 of ADC respectively
>>
>> ADC ch - 0
>> ADC ch - 1
>> ADC ch - 2
>> ADC ch - 3 ------------------NTC
>> ADC ch - 4 ------------------NTC
>> ADC ch - 5 ------------------NTC
>> ADC ch - 6 ------------------NTC
>> ADC ch - 7
>>
>> I've started off with something like this.
>>
>>         adc0: adc@12D10000 {
>>                 compatible = "samsung,exynos5250-adc";
>>                 reg = <0x12D10000 0x100>;
>>                 interrupts = <0 106 0>;
>>                 #io-channel-cells = <1>;
>>         };
This is in SOC dtsi file
>>
>>         adc0: adc@12D10000 {
>
> This is a copy paste error, right? you only have one dt node for the adc?
>
>>                 vdd-supply = <&buck5_reg>;
>>
>>                 ncp15wb473@0 {
>>                         compatible = "ntc,ncp15wb473";
>>                         io-channels = <&adc0 3>;
>>                         io-channel-names = "adc3";
>>                         pullup-uV = <1800000>;
>>                         pullup-ohm = <47000>;
>>                         pulldown-ohm = <0>;
>>                         id = <3>;
>>                 };
>>
>>                 ncp15wb473@1 {
>>                         compatible = "ntc,ncp15wb473";
>>                         pullup-uV = <1800000>;
>>                         pullup-ohm = <47000>;
>>                         pulldown-ohm = <0>;
>>                         io-channels = <&adc0 4>;
>>                         io-channel-names = "adc4";
>>                         id = <4>;
>>                 };
>>                 ncp15wb473@2 {
>>                         compatible = "ntc,ncp15wb473";
>>                         pullup-uV = <1800000>;
>>                         pullup-ohm = <47000>;
>>                         pulldown-ohm = <0>;
>>                         io-channels = <&adc0 5>;
>>                         io-channel-names = "adc5";
>>                         id = <5>;
>>                 };
>>                 ncp15wb473@3 {
>>                         compatible = "ntc,ncp15wb473";
>>                         pullup-uV = <1800000>;
>>                         pullup-ohm = <47000>;
>>                         pulldown-ohm = <0>;
>>                         io-channels = <&adc0 6>;
>>                         io-channel-names = "adc6";
>>                         id = <6>;
>>                 };
>>         };
This is in board dts file.
>>
>> ADC driver will use of_platform_populate() to populate the child nodes
>> (ntc thermistors in my case)
>>
>> I've modified the NTC driver to support DT. in probe
>> chan = iio_channel_get(&pdev->dev, "adcX");
>> and using "id" field to use respective ADC channel to do the raw_read()
>
> The beauty of the interface is that the consumer doesn't need to know the
> number of the channel it is using. This is already fully described in the
> io-channels property. Since you only have one channel per consumer just use
>
>         iio_chanel_get(&pdev->dev, NULL)
Right this helped me get the channels properly.

I've a doubt in the driver posted at https://lkml.org/lkml/2013/1/24/2
i don't need to use this anymore right use iio_map_array_register() Right.

Thats so simple then.. Thanks
>
>>
>> Issue:
>> 1. I get weird device names for thermistors starting from ncp15wb473.2
>> to ncp15wb473.5
>> 2. ADC doesn't looks like creating valid channels
>>
>> Any clues please
>>
>>>>
>>>> Thanks,
>>>> Guenter
>>> Yes Guenter, I will rebase and submit the ADC driver based on your patch set.
>>>>
>>>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>>>> index fe822a1..33ceabf 100644
>>>>> --- a/drivers/iio/adc/Kconfig
>>>>> +++ b/drivers/iio/adc/Kconfig
>>>>> @@ -91,6 +91,13 @@ config AT91_ADC
>>>>>       help
>>>>>         Say yes here to build support for Atmel AT91 ADC.
>>>>>
>>>>> +config EXYNOS5_ADC
>>>>> +     bool "Exynos5 ADC driver support"
>>>>> +     help
>>>>> +       Core support for the ADC block found in the Samsung EXYNOS5 series
>>>>> +       of SoCs for drivers such as the touchscreen and hwmon to use to share
>>>>> +       this resource.
>>>>> +
>>>>>  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..5b4a4f6 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_EXYNOS5_ADC) += exynos5_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/exynos5_adc.c b/drivers/iio/adc/exynos5_adc.c
>>>>> new file mode 100644
>>>>> index 0000000..8982675
>>>>> --- /dev/null
>>>>> +++ b/drivers/iio/adc/exynos5_adc.c
>>>>> @@ -0,0 +1,464 @@
>>>>> +/*
>>>>> + *  exynos5_adc.c - Support for ADC in EXYNOS5 SoCs
>>>>> + *
>>>>> + *  8 ~ 10 channel, 10/12-bit ADC
>>>>> + *
>>>>> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>>> + *
>>>>> + *  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.
>>>>> + *
>>>>> + *  This program is distributed in the hope that it will be useful,
>>>>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>>>> + *  GNU General Public License for more details.
>>>>> + *
>>>>> + *  You should have received a copy of the GNU General Public License
>>>>> + *  along with this program; if not, write to the Free Software
>>>>> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>>>>> + */
>>>>> +
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/interrupt.h>
>>>>> +#include <linux/delay.h>
>>>>> +#include <linux/kernel.h>
>>>>> +#include <linux/slab.h>
>>>>> +#include <linux/io.h>
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/completion.h>
>>>>> +#include <linux/of.h>
>>>>> +#include <linux/of_irq.h>
>>>>> +#include <linux/regulator/consumer.h>
>>>>> +#include <linux/of_platform.h>
>>>>> +
>>>>> +#include <linux/iio/iio.h>
>>>>> +#include <linux/iio/machine.h>
>>>>> +#include <linux/iio/driver.h>
>>>>> +
>>>>> +enum adc_version {
>>>>> +     ADC_V1,
>>>>> +     ADC_V2
>>>>> +};
>>>>> +
>>>>> +/* EXYNOS5250 ADC_V1 registers definitions */
>>>>> +#define ADC_V1_CON(x)                ((x) + 0x00)
>>>>> +#define ADC_V1_DLY(x)                ((x) + 0x08)
>>>>> +#define ADC_V1_DATX(x)               ((x) + 0x0C)
>>>>> +#define ADC_V1_INTCLR(x)     ((x) + 0x18)
>>>>> +#define ADC_V1_MUX(x)                ((x) + 0x1c)
>>>>> +
>>>>> +/* EXYNOS5410 ADC_V2 registers definitions */
>>>>> +#define ADC_V2_CON1(x)               ((x) + 0x00)
>>>>> +#define ADC_V2_CON2(x)               ((x) + 0x04)
>>>>> +#define ADC_V2_STAT(x)               ((x) + 0x08)
>>>>> +#define ADC_V2_INT_EN(x)     ((x) + 0x10)
>>>>> +#define ADC_V2_INT_ST(x)     ((x) + 0x14)
>>>>> +#define ADC_V2_VER(x)                ((x) + 0x20)
>>>>> +
>>>>> +/* Bit definitions for ADC_V1 */
>>>>> +#define ADC_V1_CON_RES               (1u << 16)
>>>>> +#define ADC_V1_CON_PRSCEN    (1u << 14)
>>>>> +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
>>>>> +#define ADC_V1_CON_STANDBY   (1u << 2)
>>>>> +
>>>>> +/* Bit definitions for ADC_V2 */
>>>>> +#define ADC_V2_CON1_SOFT_RESET       (1u << 2)
>>>>> +
>>>>> +#define ADC_V2_CON2_OSEL     (1u << 10)
>>>>> +#define ADC_V2_CON2_ESEL     (1u << 9)
>>>>> +#define ADC_V2_CON2_HIGHF    (1u << 8)
>>>>> +#define ADC_V2_CON2_C_TIME(x)        (((x) & 7) << 4)
>>>>> +#define ADC_V2_CON2_ACH_SEL(x)       (((x) & 0xF) << 0)
>>>>> +#define ADC_V2_CON2_ACH_MASK 0xF
>>>>> +
>>>>> +/* Bit definitions common for ADC_V1 and ADC_V2 */
>>>>> +#define ADC_V1_CON_EN_START          (1u << 0)
>>>>> +#define ADC_V1_DATX_MASK     0xFFF
>>>>> +
>>>>> +struct exynos5_adc {
>>>>> +     void __iomem            *regs;
>>>>> +     struct clk              *clk;
>>>>> +     unsigned int            irq;
>>>>> +     struct regulator        *vdd;
>>>>> +
>>>>> +     struct completion       completion;
>>>>> +
>>>>> +     struct iio_map          *map;
>>>>> +     u32                     value;
>>>>> +     unsigned int            version;
>>>>> +};
>>>>> +
>>>>> +static const struct of_device_id exynos5_adc_match[] = {
>>>>> +     { .compatible = "samsung,exynos5250-adc", .data = (void *)ADC_V1 },
>>>>> +     { .compatible = "samsung,exynos5410-adc", .data = (void *)ADC_V2 },
>>>>> +     {},
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, exynos5_adc_match);
>>>>> +
>>>>> +static inline unsigned int exynos5_adc_get_version(struct platform_device *pdev)
>>>>> +{
>>>>> +     const struct of_device_id *match;
>>>>> +
>>>>> +     match = of_match_node(exynos5_adc_match, pdev->dev.of_node);
>>>>> +     return (unsigned int)match->data;
>>>>> +}
>>>>> +
>>>>> +/* default maps used by iio consumer (ex: ntc-thermistor driver) */
>>>>> +static struct iio_map exynos5_adc_iio_maps[] = {
>>>>> +     {
>>>>> +             .consumer_dev_name = "0.ncp15wb473",
>>>>> +             .consumer_channel = "adc3",
>>>>> +             .adc_channel_label = "adc3",
>>>>> +     },
>>>>> +     {
>>>>> +             .consumer_dev_name = "1.ncp15wb473",
>>>>> +             .consumer_channel = "adc4",
>>>>> +             .adc_channel_label = "adc4",
>>>>> +     },
>>>>> +     {
>>>>> +             .consumer_dev_name = "2.ncp15wb473",
>>>>> +             .consumer_channel = "adc5",
>>>>> +             .adc_channel_label = "adc5",
>>>>> +     },
>>>>> +     {
>>>>> +             .consumer_dev_name = "3.ncp15wb473",
>>>>> +             .consumer_channel = "adc6",
>>>>> +             .adc_channel_label = "adc6",
>>>>> +     },
>>>>> +     {},
>>>>> +};
>>>>> +
>>>>> +static int exynos5_read_raw(struct iio_dev *indio_dev,
>>>>> +                             struct iio_chan_spec const *chan,
>>>>> +                             int *val,
>>>>> +                             int *val2,
>>>>> +                             long mask)
>>>>> +{
>>>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>>>> +     u32 con1, con2;
>>>>> +
>>>>> +     if (mask == IIO_CHAN_INFO_RAW) {
>>>>> +             mutex_lock(&indio_dev->mlock);
>>>>> +
>>>>> +             /* Select the channel to be used and Trigger conversion */
>>>>> +             if (info->version == ADC_V2) {
>>>>> +                     con2 = readl(ADC_V2_CON2(info->regs));
>>>>> +                     con2 &= ~ADC_V2_CON2_ACH_MASK;
>>>>> +                     con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
>>>>> +                     writel(con2, ADC_V2_CON2(info->regs));
>>>>> +
>>>>> +                     con1 = readl(ADC_V2_CON1(info->regs));
>>>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>>>> +                                     ADC_V2_CON1(info->regs));
>>>>> +             } else {
>>>>> +                     writel(chan->address, ADC_V1_MUX(info->regs));
>>>>> +
>>>>> +                     con1 = readl(ADC_V1_CON(info->regs));
>>>>> +                     writel(con1 | ADC_V1_CON_EN_START,
>>>>> +                                     ADC_V1_CON(info->regs));
>>>>> +             }
>>>>> +
>>>>> +             wait_for_completion(&info->completion);
>>>>> +             *val = info->value;
>>>>> +
>>>>> +             mutex_unlock(&indio_dev->mlock);
>>>>> +
>>>>> +             return IIO_VAL_INT;
>>>>> +     }
>>>>> +
>>>>> +     return -EINVAL;
>>>>> +}
>>>>> +
>>>>> +static irqreturn_t exynos5_adc_isr(int irq, void *dev_id)
>>>>> +{
>>>>> +     struct exynos5_adc *info = (struct exynos5_adc *)dev_id;
>>>>> +
>>>>> +     /* Read value */
>>>>> +     info->value = readl(ADC_V1_DATX(info->regs)) &
>>>>> +                                             ADC_V1_DATX_MASK;
>>>>> +     /* clear irq */
>>>>> +     if (info->version == ADC_V2)
>>>>> +             writel(1, ADC_V2_INT_ST(info->regs));
>>>>> +     else
>>>>> +             writel(1, ADC_V1_INTCLR(info->regs));
>>>>> +
>>>>> +     complete(&info->completion);
>>>>> +
>>>>> +     return IRQ_HANDLED;
>>>>> +}
>>>>> +
>>>>> +static int exynos5_adc_reg_access(struct iio_dev *indio_dev,
>>>>> +                           unsigned reg, unsigned writeval,
>>>>> +                           unsigned *readval)
>>>>> +{
>>>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>>>> +     u32 ret;
>>>>> +
>>>>> +     mutex_lock(&indio_dev->mlock);
>>>>> +
>>>>> +     if (readval != NULL) {
>>>>> +             ret = readl(info->regs + reg);
>>>>> +             *readval = ret;
>>>>> +     } else
>>>>> +             ret = -EINVAL;
>>>>> +
>>>>> +     mutex_unlock(&indio_dev->mlock);
>>>>> +
>>>>> +     return ret;
>>>>> +}
>>>>> +
>>>>> +static const struct iio_info exynos5_adc_iio_info = {
>>>>> +     .read_raw = &exynos5_read_raw,
>>>>> +     .debugfs_reg_access = &exynos5_adc_reg_access,
>>>>> +     .driver_module = THIS_MODULE,
>>>>> +};
>>>>> +
>>>>> +#define ADC_V1_CHANNEL(_index, _id) {                \
>>>>> +     .type = IIO_VOLTAGE,                            \
>>>>> +     .indexed = 1,                                   \
>>>>> +     .channel = _index,                              \
>>>>> +     .address = _index,                              \
>>>>> +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
>>>>> +     .datasheet_name = _id,                          \
>>>>> +}
>>>>> +
>>>>> +/** ADC core in EXYNOS5410 has 10 channels,
>>>>> + * ADC core in EXYNOS5250 has 8 channels
>>>>> +*/
>>>>> +static const struct iio_chan_spec exynos5_adc_iio_channels[] = {
>>>>> +     ADC_V1_CHANNEL(0, "adc0"),
>>>>> +     ADC_V1_CHANNEL(1, "adc1"),
>>>>> +     ADC_V1_CHANNEL(2, "adc2"),
>>>>> +     ADC_V1_CHANNEL(3, "adc3"),
>>>>> +     ADC_V1_CHANNEL(4, "adc4"),
>>>>> +     ADC_V1_CHANNEL(5, "adc5"),
>>>>> +     ADC_V1_CHANNEL(6, "adc6"),
>>>>> +     ADC_V1_CHANNEL(7, "adc7"),
>>>>> +     ADC_V1_CHANNEL(8, "adc8"),
>>>>> +     ADC_V1_CHANNEL(9, "adc9"),
>>>>> +};
>>>>> +
>>>>> +static int exynos5_adc_remove_devices(struct device *dev, void *c)
>>>>> +{
>>>>> +     struct platform_device *pdev = to_platform_device(dev);
>>>>> +
>>>>> +     platform_device_unregister(pdev);
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> +static void exynos5_adc_hw_init(struct exynos5_adc *info)
>>>>> +{
>>>>> +     u32 con1, con2;
>>>>> +
>>>>> +     if (info->version == ADC_V2) {
>>>>> +             con1 = ADC_V2_CON1_SOFT_RESET;
>>>>> +             writel(con1, ADC_V2_CON1(info->regs));
>>>>> +
>>>>> +             con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
>>>>> +                     ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
>>>>> +             writel(con2, ADC_V2_CON2(info->regs));
>>>>> +
>>>>> +             /* Enable interrupts */
>>>>> +             writel(1, ADC_V2_INT_EN(info->regs));
>>>>> +     } else {
>>>>> +             /* set default prescaler values and Enable prescaler */
>>>>> +             con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
>>>>> +
>>>>> +             /* Enable 12-bit ADC resolution */
>>>>> +             con1 |= ADC_V1_CON_RES;
>>>>> +             writel(con1, ADC_V1_CON(info->regs));
>>>>> +     }
>>>>> +}
>>>>> +
>>>>> +static int exynos5_adc_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +     struct exynos5_adc *info = NULL;
>>>>> +     struct device_node *np = pdev->dev.of_node;
>>>>> +     struct iio_dev *indio_dev = NULL;
>>>>> +     struct resource *mem;
>>>>> +     int ret = -ENODEV;
>>>>> +     int irq;
>>>>> +
>>>>> +     if (!np)
>>>>> +             return ret;
>>>>> +
>>>>> +     indio_dev = iio_device_alloc(sizeof(struct exynos5_adc));
>>>>> +     if (!indio_dev) {
>>>>> +             dev_err(&pdev->dev, "failed allocating iio device\n");
>>>>> +             return -ENOMEM;
>>>>> +     }
>>>>> +
>>>>> +     info = iio_priv(indio_dev);
>>>>> +
>>>>> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>>> +
>>>>> +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
>>>>> +
>>>>> +     irq = platform_get_irq(pdev, 0);
>>>>> +     if (irq < 0) {
>>>>> +             dev_err(&pdev->dev, "no irq resource?\n");
>>>>> +             ret = irq;
>>>>> +             goto err_iio;
>>>>> +     }
>>>>> +
>>>>> +     info->irq = irq;
>>>>> +
>>>>> +     ret = request_irq(info->irq, exynos5_adc_isr,
>>>>> +                                     0, dev_name(&pdev->dev), info);
>>>>> +     if (ret < 0) {
>>>>> +             dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>>>>> +                                                     info->irq);
>>>>> +             goto err_iio;
>>>>> +     }
>>>>> +
>>>>> +     info->clk = devm_clk_get(&pdev->dev, "adc");
>>>>> +     if (IS_ERR(info->clk)) {
>>>>> +             dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>>>>> +                                                     PTR_ERR(info->clk));
>>>>> +             ret = PTR_ERR(info->clk);
>>>>> +             goto err_irq;
>>>>> +     }
>>>>> +
>>>>> +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
>>>>> +     if (IS_ERR(info->vdd)) {
>>>>> +             dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
>>>>> +                                                     PTR_ERR(info->vdd));
>>>>> +             ret = PTR_ERR(info->vdd);
>>>>> +             goto err_irq;
>>>>> +     }
>>>>> +
>>>>> +     info->version = exynos5_adc_get_version(pdev);
>>>>> +
>>>>> +     platform_set_drvdata(pdev, indio_dev);
>>>>> +
>>>>> +     init_completion(&info->completion);
>>>>> +
>>>>> +     indio_dev->name = dev_name(&pdev->dev);
>>>>> +     indio_dev->dev.parent = &pdev->dev;
>>>>> +     indio_dev->dev.of_node = pdev->dev.of_node;
>>>>> +     indio_dev->info = &exynos5_adc_iio_info;
>>>>> +     indio_dev->modes = INDIO_DIRECT_MODE;
>>>>> +     indio_dev->channels = exynos5_adc_iio_channels;
>>>>> +     indio_dev->num_channels = ARRAY_SIZE(exynos5_adc_iio_channels);
>>>>> +
>>>>> +     info->map = exynos5_adc_iio_maps;
>>>>> +
>>>>> +     ret = iio_map_array_register(indio_dev, info->map);
>>>>> +     if (ret) {
>>>>> +             dev_err(&indio_dev->dev, "failed mapping iio, err: %d\n", ret);
>>>>> +             goto err_irq;
>>>>> +     }
>>>>> +
>>>>> +     ret = iio_device_register(indio_dev);
>>>>> +     if (ret)
>>>>> +             goto err_map;
>>>>> +
>>>>> +     ret = regulator_enable(info->vdd);
>>>>> +     if (ret)
>>>>> +             goto err_iio_dev;
>>>>> +
>>>>> +     clk_prepare_enable(info->clk);
>>>>> +
>>>>> +     exynos5_adc_hw_init(info);
>>>>> +
>>>>> +     ret = of_platform_populate(np, exynos5_adc_match, NULL, &pdev->dev);
>>>>> +     if (ret < 0) {
>>>>> +             dev_err(&pdev->dev, "failed adding child nodes\n");
>>>>> +             goto err_of_populate;
>>>>> +     }
>>>>> +
>>>>> +     return 0;
>>>>> +
>>>>> +err_of_populate:
>>>>> +     device_for_each_child(&pdev->dev, NULL, exynos5_adc_remove_devices);
>>>>> +err_iio_dev:
>>>>> +     iio_device_unregister(indio_dev);
>>>>> +err_map:
>>>>> +     iio_map_array_unregister(indio_dev, info->map);
>>>>> +err_irq:
>>>>> +     free_irq(info->irq, info);
>>>>> +err_iio:
>>>>> +     iio_device_free(indio_dev);
>>>>> +     return ret;
>>>>> +}
>>>>> +
>>>>> +static int exynos5_adc_remove(struct platform_device *pdev)
>>>>> +{
>>>>> +     struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>>>> +     struct exynos5_adc *info = iio_priv(indio_dev);
>>>>> +
>>>>> +     iio_device_unregister(indio_dev);
>>>>> +     iio_map_array_unregister(indio_dev, info->map);
>>>>> +     free_irq(info->irq, info);
>>>>> +     iio_device_free(indio_dev);
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> +#ifdef CONFIG_PM_SLEEP
>>>>> +static int exynos5_adc_suspend(struct device *dev)
>>>>> +{
>>>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>>>> +     u32 con;
>>>>> +
>>>>> +     if (info->version == ADC_V2) {
>>>>> +             con = readl(ADC_V2_CON1(info->regs));
>>>>> +             con &= ~ADC_V1_CON_EN_START;
>>>>> +             writel(con, ADC_V2_CON1(info->regs));
>>>>> +     } else {
>>>>> +             con = readl(ADC_V1_CON(info->regs));
>>>>> +             con |= ADC_V1_CON_STANDBY;
>>>>> +             writel(con, ADC_V1_CON(info->regs));
>>>>> +     }
>>>>> +
>>>>> +     clk_unprepare_disable(info->clk);
>>>>> +     regulator_disable(info->vdd);
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> +static int exynos5_adc_resume(struct device *dev)
>>>>> +{
>>>>> +     struct exynos5_adc *info = dev_get_data(dev);
>>>>> +     int ret;
>>>>> +
>>>>> +     ret = regulator_enable(info->vdd);
>>>>> +     if (ret)
>>>>> +             return ret;
>>>>> +
>>>>> +     clk_prepare_enable(info->clk);
>>>>> +
>>>>> +     exynos5_adc_hw_init(info);
>>>>> +
>>>>> +     return 0;
>>>>> +}
>>>>> +
>>>>> +#else
>>>>> +#define exynos5_adc_suspend NULL
>>>>> +#define exynos5_adc_resume NULL
>>>>> +#endif
>>>>> +
>>>>> +static SIMPLE_DEV_PM_OPS(exynos5_adc_pm_ops,
>>>>> +                     exynos5_adc_suspend,
>>>>> +                     exynos5_adc_resume);
>>>>> +
>>>>> +static struct platform_driver exynos5_adc_driver = {
>>>>> +     .probe          = exynos5_adc_probe,
>>>>> +     .remove         = exynos5_adc_remove,
>>>>> +     .driver         = {
>>>>> +             .name   = "exynos5-adc",
>>>>> +             .owner  = THIS_MODULE,
>>>>> +             .of_match_table = of_match_ptr(exynos5_adc_match),
>>>>> +             .pm     = &exynos5_adc_pm_ops,
>>>>> +     },
>>>>> +};
>>>>> +
>>>>> +module_platform_driver(exynos5_adc_driver);
>>>>> +
>>>>> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
>>>>> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
>>>>> +MODULE_LICENSE("GPL");
>>>
>>>
>>>
>>> --
>>> Shine bright,
>>> (: Nav :)
>>
>>
>>
>> --
>> Shine bright,
>> (: Nav :)
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-02-13 13:53           ` Naveen Krishna Ch
@ 2013-02-13 14:05             ` Lars-Peter Clausen
  0 siblings, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-02-13 14:05 UTC (permalink / raw)
  To: Naveen Krishna Ch
  Cc: Guenter Roeck, Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh

On 02/13/2013 02:53 PM, Naveen Krishna Ch wrote:
[...]
>>> ADC driver will use of_platform_populate() to populate the child nodes
>>> (ntc thermistors in my case)
>>>
>>> I've modified the NTC driver to support DT. in probe
>>> chan = iio_channel_get(&pdev->dev, "adcX");
>>> and using "id" field to use respective ADC channel to do the raw_read()
>>
>> The beauty of the interface is that the consumer doesn't need to know the
>> number of the channel it is using. This is already fully described in the
>> io-channels property. Since you only have one channel per consumer just use
>>
>>         iio_chanel_get(&pdev->dev, NULL)
> Right this helped me get the channels properly.
> 
> I've a doubt in the driver posted at https://lkml.org/lkml/2013/1/24/2
> i don't need to use this anymore right use iio_map_array_register() Right.
> 
> Thats so simple then.. Thanks

Yes, if you are using devicetree you don't need to use
iio_map_array_register() anymore :)

- Lars


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: iio: adc: add exynos5 adc driver under iio framwork
  2013-02-13 13:16       ` Naveen Krishna Ch
  2013-02-13 13:30         ` Lars-Peter Clausen
@ 2013-02-13 15:51         ` Guenter Roeck
  1 sibling, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2013-02-13 15:51 UTC (permalink / raw)
  To: Naveen Krishna Ch
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh, lars

On Wed, Feb 13, 2013 at 06:46:01PM +0530, Naveen Krishna Ch wrote:
[ ... ]
> 
> Hello Guenter,
> 
> I've rebase my adc driver on top of your (OF for IIO patch)
> 
> My setup is like the below one. kindly, help me find the right device
> tree node params
> 
> One ADC controller with 8 channels,
>   4 NTC thermistors are connected to channel 3, 4, 5 and 6 of ADC respectively
> 
> ADC ch - 0
> ADC ch - 1
> ADC ch - 2
> ADC ch - 3 ------------------NTC
> ADC ch - 4 ------------------NTC
> ADC ch - 5 ------------------NTC
> ADC ch - 6 ------------------NTC
> ADC ch - 7
> 
> I've started off with something like this.
> 
>         adc0: adc@12D10000 {
>                 compatible = "samsung,exynos5250-adc";
>                 reg = <0x12D10000 0x100>;
>                 interrupts = <0 106 0>;
>                 #io-channel-cells = <1>;
>         };
> 
>         adc0: adc@12D10000 {
>                 vdd-supply = <&buck5_reg>;
> 
>                 ncp15wb473@0 {
>                         compatible = "ntc,ncp15wb473";
>                         io-channels = <&adc0 3>;
>                         io-channel-names = "adc3";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         id = <3>;
>                 };
> 
>                 ncp15wb473@1 {
>                         compatible = "ntc,ncp15wb473";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         io-channels = <&adc0 4>;
>                         io-channel-names = "adc4";
>                         id = <4>;
>                 };
>                 ncp15wb473@2 {
>                         compatible = "ntc,ncp15wb473";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         io-channels = <&adc0 5>;
>                         io-channel-names = "adc5";
>                         id = <5>;
>                 };
>                 ncp15wb473@3 {
>                         compatible = "ntc,ncp15wb473";
>                         pullup-uV = <1800000>;
>                         pullup-ohm = <47000>;
>                         pulldown-ohm = <0>;
>                         io-channels = <&adc0 6>;
>                         io-channel-names = "adc6";
>                         id = <6>;
>                 };
>         };
> 
> ADC driver will use of_platform_populate() to populate the child nodes
> (ntc thermistors in my case)
> 
> I've modified the NTC driver to support DT. in probe
> chan = iio_channel_get(&pdev->dev, "adcX");
> and using "id" field to use respective ADC channel to do the raw_read()
> 
> Issue:
> 1. I get weird device names for thermistors starting from ncp15wb473.2
> to ncp15wb473.5

I noticed that device IDs and names created when OF is active are sometimes
not as one would expect. I never managed to get the device IDs I tried to
configure. Ultimately, it did not matter and I never bothered to track it down.

Guenter

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v6] iio: adc: add exynos adc driver under iio framwork
  2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
                   ` (4 preceding siblings ...)
  2013-01-24  5:05 ` Naveen Krishna Chatradhi
@ 2013-02-14 12:11 ` Naveen Krishna Chatradhi
  2013-02-14 20:55   ` Lars-Peter Clausen
  2013-02-15  6:56   ` [PATCH v7] " Naveen Krishna Chatradhi
  5 siblings, 2 replies; 35+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-02-14 12:11 UTC (permalink / raw)
  To: linux-iio
  Cc: linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

This patch adds New driver to support:
1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
   and future SoCs from Samsung
2. Add ADC driver under iio/adc framework
3. Also adds the Documentation for device tree bindings

Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v1:

1. Fixed comments from Lars
2. Added support for ADC on EXYNOS5410

Changes since v2:

1. Changed the instance name for (struct iio_dev *) to indio_dev
2. Changed devm_request_irq to request_irq

Few doubts regarding the mappings and child device handling.
Kindly, suggest me better methods.

Changes since v3:

1. Added clk_prepare_disable and regulator_disable calls in _remove()
2. Moved init_completion before irq_request
3. Added NULL pointer check for devm_request_and_ioremap() return value.
4. Use number of channels as per the ADC version
5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
6. Update the Documentation to include EXYNOS5410 compatible

Changes since v4:

1. if devm_request_and_ioremap() failes, free iio_device before returning

Changes since v5:

1. Fixed comments from Olof (ADC hardware version handling)
2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".

 .../bindings/arm/samsung/exynos5-adc.txt           |   42 ++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos_adc.c                       |  438 ++++++++++++++++++++
 4 files changed, 488 insertions(+)
 .../devicetree/bindings/arm/samsung/exynos-adc.txt |   52 +++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos_adc.c                       |  437 ++++++++++++++++++++
 4 files changed, 497 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
 create mode 100644 drivers/iio/adc/exynos_adc.c

diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
new file mode 100644
index 0000000..f686378
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
@@ -0,0 +1,52 @@
+Samsung Exynos Analog to Digital Converter bindings
+
+This devicetree binding are for the new adc driver written fori
+Exynos4 and upward SoCs from Samsung.
+
+New driver handles the following
+1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
+   and future SoCs from Samsung
+2. Add ADC driver under iio/adc framework
+3. Also adds the Documentation for device tree bindings
+
+Required properties:
+- compatible:		Must be "samsung,exynos-adc-v1"
+				for exynos4412/5250 controllers.
+			Must be "samsung,exynos-adc-v2" for
+				future controllers.
+- reg:			Contains ADC register address range (base address and
+			length).
+- interrupts: 		Contains the interrupt information for the timer. The
+			format is being dependent on which interrupt controller
+			the Samsung device uses.
+- #io-channel-cells = <1>; As ADC has multiple outputs
+
+Note: child nodes can be added for auto probing from device tree.
+
+Example: adding device info in dtsi file
+
+adc: adc@12D10000 {
+	compatible = "samsung,exynos-adc-v1";
+	reg = <0x12D10000 0x100>;
+	interrupts = <0 106 0>;
+	#io-channel-cells = <1>;
+	io-channel-ranges;
+};
+
+
+Example: Adding child nodes in dts file
+
+adc@12D10000 {
+
+	/* NTC thermistor is a hwmon device */
+	ncp15wb473@0 {
+		compatible = "ntc,ncp15wb473";
+		pullup-uV = <1800000>;
+		pullup-ohm = <47000>;
+		pulldown-ohm = <0>;
+		io-channels = <&adc 4>;
+	};
+};
+
+Note: Does not apply to ADC driver under arch/arm/plat-samsung/
+Note: The child node can be added under the adc node or seperately.
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index e372257..04311f8 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,13 @@ config AT91_ADC
 	help
 	  Say yes here to build support for Atmel AT91 ADC.
 
+config EXYNOS_ADC
+	bool "Exynos ADC driver support"
+	help
+	  Core support for the ADC block found in the Samsung EXYNOS series
+	  of SoCs for drivers such as the touchscreen and hwmon to use to share
+	  this resource.
+
 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..fabac2c 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_EXYNOS_ADC) += exynos_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/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
new file mode 100644
index 0000000..144a9e2
--- /dev/null
+++ b/drivers/iio/adc/exynos_adc.c
@@ -0,0 +1,437 @@
+/*
+ *  exynos_adc.c - Support for ADC in EXYNOS SoCs
+ *
+ *  8 ~ 10 channel, 10/12-bit ADC
+ *
+ *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+enum adc_version {
+	ADC_V1,
+	ADC_V2
+};
+
+/* EXYNOS4412/5250 ADC_V1 registers definitions */
+#define ADC_V1_CON(x)		((x) + 0x00)
+#define ADC_V1_DLY(x)		((x) + 0x08)
+#define ADC_V1_DATX(x)		((x) + 0x0C)
+#define ADC_V1_INTCLR(x)	((x) + 0x18)
+#define ADC_V1_MUX(x)		((x) + 0x1c)
+
+/* Future ADC_V2 registers definitions */
+#define ADC_V2_CON1(x)		((x) + 0x00)
+#define ADC_V2_CON2(x)		((x) + 0x04)
+#define ADC_V2_STAT(x)		((x) + 0x08)
+#define ADC_V2_INT_EN(x)	((x) + 0x10)
+#define ADC_V2_INT_ST(x)	((x) + 0x14)
+#define ADC_V2_VER(x)		((x) + 0x20)
+
+/* Bit definitions for ADC_V1 */
+#define ADC_V1_CON_RES		(1u << 16)
+#define ADC_V1_CON_PRSCEN	(1u << 14)
+#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
+#define ADC_V1_CON_STANDBY	(1u << 2)
+
+/* Bit definitions for ADC_V2 */
+#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
+
+#define ADC_V2_CON2_OSEL	(1u << 10)
+#define ADC_V2_CON2_ESEL	(1u << 9)
+#define ADC_V2_CON2_HIGHF	(1u << 8)
+#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
+#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
+#define ADC_V2_CON2_ACH_MASK	0xF
+
+#define MAX_ADC_V2_CHANNELS	10
+#define MAX_ADC_V1_CHANNELS	8
+
+/* Bit definitions common for ADC_V1 and ADC_V2 */
+#define ADC_CON_EN_START	(1u << 0)
+#define ADC_DATX_MASK		0xFFF
+
+struct exynos_adc {
+	void __iomem		*regs;
+	struct clk		*clk;
+	unsigned int		irq;
+	struct regulator	*vdd;
+
+	struct completion	completion;
+
+	u32			value;
+	unsigned int            version;
+};
+
+static const struct of_device_id exynos_adc_match[] = {
+	{ .compatible = "samsung,exynos-adc-v1", .data = (void *)ADC_V1 },
+	{ .compatible = "samsung,exynos-adc-v2", .data = (void *)ADC_V2 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos_adc_match);
+
+static inline unsigned int exynos_adc_get_version(struct platform_device *pdev)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(exynos_adc_match, pdev->dev.of_node);
+	return (unsigned int)match->data;
+}
+
+static int exynos_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct exynos_adc *info = iio_priv(indio_dev);
+	u32 con1, con2;
+
+	if (mask == IIO_CHAN_INFO_RAW) {
+		mutex_lock(&indio_dev->mlock);
+
+		/* Select the channel to be used and Trigger conversion */
+		if (info->version == ADC_V2) {
+			con2 = readl(ADC_V2_CON2(info->regs));
+			con2 &= ~ADC_V2_CON2_ACH_MASK;
+			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
+			writel(con2, ADC_V2_CON2(info->regs));
+
+			con1 = readl(ADC_V2_CON1(info->regs));
+			writel(con1 | ADC_CON_EN_START,
+					ADC_V2_CON1(info->regs));
+		} else {
+			writel(chan->address, ADC_V1_MUX(info->regs));
+
+			con1 = readl(ADC_V1_CON(info->regs));
+			writel(con1 | ADC_CON_EN_START,
+					ADC_V1_CON(info->regs));
+		}
+
+		wait_for_completion(&info->completion);
+		*val = info->value;
+
+		mutex_unlock(&indio_dev->mlock);
+
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
+{
+	struct exynos_adc *info = (struct exynos_adc *)dev_id;
+
+	/* Read value */
+	info->value = readl(ADC_V1_DATX(info->regs)) &
+						ADC_DATX_MASK;
+	/* clear irq */
+	if (info->version == ADC_V2)
+		writel(1, ADC_V2_INT_ST(info->regs));
+	else
+		writel(1, ADC_V1_INTCLR(info->regs));
+
+	complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int exynos_adc_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct exynos_adc *info = iio_priv(indio_dev);
+	u32 ret;
+
+	mutex_lock(&indio_dev->mlock);
+
+	if (readval != NULL) {
+		ret = readl(info->regs + reg);
+		*readval = ret;
+	} else
+		ret = -EINVAL;
+
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info exynos_adc_iio_info = {
+	.read_raw = &exynos_read_raw,
+	.debugfs_reg_access = &exynos_adc_reg_access,
+	.driver_module = THIS_MODULE,
+};
+
+#define ADC_CHANNEL(_index, _id) {			\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = _index,				\
+	.address = _index,				\
+	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	.datasheet_name = _id,				\
+}
+
+static const struct iio_chan_spec exynos_adc_iio_channels[] = {
+	ADC_CHANNEL(0, "adc0"),
+	ADC_CHANNEL(1, "adc1"),
+	ADC_CHANNEL(2, "adc2"),
+	ADC_CHANNEL(3, "adc3"),
+	ADC_CHANNEL(4, "adc4"),
+	ADC_CHANNEL(5, "adc5"),
+	ADC_CHANNEL(6, "adc6"),
+	ADC_CHANNEL(7, "adc7"),
+	ADC_CHANNEL(8, "adc8"),
+	ADC_CHANNEL(9, "adc9"),
+};
+
+static int exynos_adc_remove_devices(struct device *dev, void *c)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static void exynos_adc_hw_init(struct exynos_adc *info)
+{
+	u32 con1, con2;
+
+	if (info->version == ADC_V2) {
+		con1 = ADC_V2_CON1_SOFT_RESET;
+		writel(con1, ADC_V2_CON1(info->regs));
+
+		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
+			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
+		writel(con2, ADC_V2_CON2(info->regs));
+
+		/* Enable interrupts */
+		writel(1, ADC_V2_INT_EN(info->regs));
+	} else {
+		/* set default prescaler values and Enable prescaler */
+		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
+
+		/* Enable 12-bit ADC resolution */
+		con1 |= ADC_V1_CON_RES;
+		writel(con1, ADC_V1_CON(info->regs));
+	}
+}
+
+static int exynos_adc_probe(struct platform_device *pdev)
+{
+	struct exynos_adc *info = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *indio_dev = NULL;
+	struct resource	*mem;
+	int ret = -ENODEV;
+	int irq;
+
+	if (!np)
+		return ret;
+
+	indio_dev = iio_device_alloc(sizeof(struct exynos_adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(indio_dev);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
+	if (!info->regs) {
+		ret = -ENOMEM;
+		goto err_iio;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = irq;
+		goto err_iio;
+	}
+
+	info->irq = irq;
+
+	init_completion(&info->completion);
+
+	ret = request_irq(info->irq, exynos_adc_isr,
+					0, dev_name(&pdev->dev), info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+							info->irq);
+		goto err_iio;
+	}
+
+	info->clk = devm_clk_get(&pdev->dev, "adc");
+	if (IS_ERR(info->clk)) {
+		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+							PTR_ERR(info->clk));
+		ret = PTR_ERR(info->clk);
+		goto err_irq;
+	}
+
+	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
+	if (IS_ERR(info->vdd)) {
+		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
+							PTR_ERR(info->vdd));
+		ret = PTR_ERR(info->vdd);
+		goto err_irq;
+	}
+
+	info->version = exynos_adc_get_version(pdev);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &exynos_adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = exynos_adc_iio_channels;
+
+	if (info->version == ADC_V1)
+		indio_dev->num_channels = MAX_ADC_V1_CHANNELS;
+	else
+		indio_dev->num_channels = MAX_ADC_V2_CHANNELS;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_irq;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		goto err_iio_dev;
+
+	clk_prepare_enable(info->clk);
+
+	exynos_adc_hw_init(info);
+
+	ret = of_platform_populate(np, exynos_adc_match, NULL, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed adding child nodes\n");
+		goto err_of_populate;
+	}
+
+	return 0;
+
+err_of_populate:
+	device_for_each_child(&pdev->dev, NULL, exynos_adc_remove_devices);
+	regulator_disable(info->vdd);
+	clk_disable_unprepare(info->clk);
+err_iio_dev:
+	iio_device_unregister(indio_dev);
+err_irq:
+	free_irq(info->irq, info);
+err_iio:
+	iio_device_free(indio_dev);
+	return ret;
+}
+
+static int exynos_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct exynos_adc *info = iio_priv(indio_dev);
+
+	regulator_disable(info->vdd);
+	clk_disable_unprepare(info->clk);
+	iio_device_unregister(indio_dev);
+	free_irq(info->irq, info);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos_adc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct exynos_adc *info = platform_get_drvdata(pdev);
+	u32 con;
+
+	if (info->version == ADC_V2) {
+		con = readl(ADC_V2_CON1(info->regs));
+		con &= ~ADC_CON_EN_START;
+		writel(con, ADC_V2_CON1(info->regs));
+	} else {
+		con = readl(ADC_V1_CON(info->regs));
+		con |= ADC_V1_CON_STANDBY;
+		writel(con, ADC_V1_CON(info->regs));
+	}
+
+	clk_disable_unprepare(info->clk);
+	regulator_disable(info->vdd);
+
+	return 0;
+}
+
+static int exynos_adc_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct exynos_adc *info = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		return ret;
+
+	clk_prepare_enable(info->clk);
+
+	exynos_adc_hw_init(info);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(exynos_adc_pm_ops,
+			exynos_adc_suspend,
+			exynos_adc_resume);
+
+static struct platform_driver exynos_adc_driver = {
+	.probe		= exynos_adc_probe,
+	.remove		= exynos_adc_remove,
+	.driver		= {
+		.name	= "exynos-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(exynos_adc_match),
+		.pm	= &exynos_adc_pm_ops,
+	},
+};
+
+module_platform_driver(exynos_adc_driver);
+
+MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
+MODULE_LICENSE("GPL v2");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH v6] iio: adc: add exynos adc driver under iio framwork
  2013-02-14 12:11 ` [PATCH v6] iio: adc: add exynos " Naveen Krishna Chatradhi
@ 2013-02-14 20:55   ` Lars-Peter Clausen
  2013-02-15  6:56   ` [PATCH v7] " Naveen Krishna Chatradhi
  1 sibling, 0 replies; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-02-14 20:55 UTC (permalink / raw)
  To: Naveen Krishna Chatradhi
  Cc: linux-iio, linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch

On 02/14/2013 01:11 PM, Naveen Krishna Chatradhi wrote:
> This patch adds New driver to support:
> 1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
>    and future SoCs from Samsung
> 2. Add ADC driver under iio/adc framework
> 3. Also adds the Documentation for device tree bindings
> 
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>

Looks good to me. There is one bug left in debugfs_reg_access function though,
which should be fixed before the driver is merged.

> ---
> Changes since v1:
> 
> 1. Fixed comments from Lars
> 2. Added support for ADC on EXYNOS5410
> 
> Changes since v2:
> 
> 1. Changed the instance name for (struct iio_dev *) to indio_dev
> 2. Changed devm_request_irq to request_irq
> 
> Few doubts regarding the mappings and child device handling.
> Kindly, suggest me better methods.
> 
> Changes since v3:
> 
> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
> 2. Moved init_completion before irq_request
> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
> 4. Use number of channels as per the ADC version
> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
> 6. Update the Documentation to include EXYNOS5410 compatible
> 
> Changes since v4:
> 
> 1. if devm_request_and_ioremap() failes, free iio_device before returning
> 
> Changes since v5:
> 
> 1. Fixed comments from Olof (ADC hardware version handling)
> 2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".
> 
>  .../bindings/arm/samsung/exynos5-adc.txt           |   42 ++
>  drivers/iio/adc/Kconfig                            |    7 +
>  drivers/iio/adc/Makefile                           |    1 +
>  drivers/iio/adc/exynos_adc.c                       |  438 ++++++++++++++++++++
>  4 files changed, 488 insertions(+)
>  .../devicetree/bindings/arm/samsung/exynos-adc.txt |   52 +++
>  drivers/iio/adc/Kconfig                            |    7 +
>  drivers/iio/adc/Makefile                           |    1 +
>  drivers/iio/adc/exynos_adc.c                       |  437 ++++++++++++++++++++
>  4 files changed, 497 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>  create mode 100644 drivers/iio/adc/exynos_adc.c
> 
> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
> new file mode 100644
> index 0000000..f686378
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
> @@ -0,0 +1,52 @@
[...]
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 2d5f100..fabac2c 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_EXYNOS_ADC) += exynos_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/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
> new file mode 100644
> index 0000000..144a9e2
> --- /dev/null
> +++ b/drivers/iio/adc/exynos_adc.c
> @@ -0,0 +1,437 @@
> [...]
> +static int exynos_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct exynos_adc *info = iio_priv(indio_dev);
> +	u32 con1, con2;
> +
> +	if (mask == IIO_CHAN_INFO_RAW) {

How about rewriting this as:

	if (mask != IIO_CHAN_INFO_RAW)
		return -EINVAL;

	mutex_lock(...);
	...

keeps the indention level low.

> +		mutex_lock(&indio_dev->mlock);
> +
> +		/* Select the channel to be used and Trigger conversion */
> +		if (info->version == ADC_V2) {
> +			con2 = readl(ADC_V2_CON2(info->regs));
> +			con2 &= ~ADC_V2_CON2_ACH_MASK;
> +			con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
> +			writel(con2, ADC_V2_CON2(info->regs));
> +
> +			con1 = readl(ADC_V2_CON1(info->regs));
> +			writel(con1 | ADC_CON_EN_START,
> +					ADC_V2_CON1(info->regs));
> +		} else {
> +			writel(chan->address, ADC_V1_MUX(info->regs));
> +
> +			con1 = readl(ADC_V1_CON(info->regs));
> +			writel(con1 | ADC_CON_EN_START,
> +					ADC_V1_CON(info->regs));
> +		}
> +
> +		wait_for_completion(&info->completion);

I'd add at least a timeout so you don't get stuck here forever if something
goes wrong.

> +		*val = info->value;
> +
> +		mutex_unlock(&indio_dev->mlock);
> +
> +		return IIO_VAL_INT;
> +	}
> +
> +	return -EINVAL;
> +}
> +
[...]
> +static int exynos_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos_adc *info = iio_priv(indio_dev);
> +	u32 ret;
> +
> +	mutex_lock(&indio_dev->mlock);

I don't think the locking here is necessary.

> +
> +	if (readval != NULL) {
> +		ret = readl(info->regs + reg);
> +		*readval = ret;

At the end of the function you return the read value, you should return 0 on
success though.

> +	} else
> +		ret = -EINVAL;
> +
> +	mutex_unlock(&indio_dev->mlock);


How about rewriting the function as:

	if (readval == NULL)
		return -EINVAL;

	*readval = readl(info->regs + reg)

	return 0;

> +
> +	return ret;
> +}
[...]
> +
> +static int exynos_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct exynos_adc *info = iio_priv(indio_dev);
> +

Do you need to remove the child devices here as well?

> +	regulator_disable(info->vdd);
> +	clk_disable_unprepare(info->clk);
> +	iio_device_unregister(indio_dev);
> +	free_irq(info->irq, info);
> +	iio_device_free(indio_dev);
> +
> +	return 0;
> +}


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v7] iio: adc: add exynos adc driver under iio framwork
  2013-02-14 12:11 ` [PATCH v6] iio: adc: add exynos " Naveen Krishna Chatradhi
  2013-02-14 20:55   ` Lars-Peter Clausen
@ 2013-02-15  6:56   ` Naveen Krishna Chatradhi
  2013-02-15 13:13     ` Lars-Peter Clausen
  1 sibling, 1 reply; 35+ messages in thread
From: Naveen Krishna Chatradhi @ 2013-02-15  6:56 UTC (permalink / raw)
  To: linux-iio
  Cc: linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch, lars

This patch adds New driver to support:
1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
   and future SoCs from Samsung
2. Add ADC driver under iio/adc framework
3. Also adds the Documentation for device tree bindings

Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
---
Changes since v1:

1. Fixed comments from Lars
2. Added support for ADC on EXYNOS5410

Changes since v2:

1. Changed the instance name for (struct iio_dev *) to indio_dev
2. Changed devm_request_irq to request_irq

Few doubts regarding the mappings and child device handling.
Kindly, suggest me better methods.

Changes since v3:

1. Added clk_prepare_disable and regulator_disable calls in _remove()
2. Moved init_completion before irq_request
3. Added NULL pointer check for devm_request_and_ioremap() return value.
4. Use number of channels as per the ADC version
5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
6. Update the Documentation to include EXYNOS5410 compatible

Changes since v4:

1. if devm_request_and_ioremap() failes, free iio_device before returning

Changes since v5:

1. Fixed comments from Olof (ADC hardware version handling)
2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".

Changes since v6:

1. Addressed comments from Lars-Peter Clausen

 .../bindings/arm/samsung/exynos5-adc.txt           |   42 ++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos_adc.c                       |  438 ++++++++++++++++++++
 4 files changed, 488 insertions(+)
 .../devicetree/bindings/arm/samsung/exynos-adc.txt |   52 +++
 drivers/iio/adc/Kconfig                            |    7 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/exynos_adc.c                       |  440 ++++++++++++++++++++
 4 files changed, 500 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
 create mode 100644 drivers/iio/adc/exynos_adc.c

diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
new file mode 100644
index 0000000..f686378
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
@@ -0,0 +1,52 @@
+Samsung Exynos Analog to Digital Converter bindings
+
+This devicetree binding are for the new adc driver written fori
+Exynos4 and upward SoCs from Samsung.
+
+New driver handles the following
+1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
+   and future SoCs from Samsung
+2. Add ADC driver under iio/adc framework
+3. Also adds the Documentation for device tree bindings
+
+Required properties:
+- compatible:		Must be "samsung,exynos-adc-v1"
+				for exynos4412/5250 controllers.
+			Must be "samsung,exynos-adc-v2" for
+				future controllers.
+- reg:			Contains ADC register address range (base address and
+			length).
+- interrupts: 		Contains the interrupt information for the timer. The
+			format is being dependent on which interrupt controller
+			the Samsung device uses.
+- #io-channel-cells = <1>; As ADC has multiple outputs
+
+Note: child nodes can be added for auto probing from device tree.
+
+Example: adding device info in dtsi file
+
+adc: adc@12D10000 {
+	compatible = "samsung,exynos-adc-v1";
+	reg = <0x12D10000 0x100>;
+	interrupts = <0 106 0>;
+	#io-channel-cells = <1>;
+	io-channel-ranges;
+};
+
+
+Example: Adding child nodes in dts file
+
+adc@12D10000 {
+
+	/* NTC thermistor is a hwmon device */
+	ncp15wb473@0 {
+		compatible = "ntc,ncp15wb473";
+		pullup-uV = <1800000>;
+		pullup-ohm = <47000>;
+		pulldown-ohm = <0>;
+		io-channels = <&adc 4>;
+	};
+};
+
+Note: Does not apply to ADC driver under arch/arm/plat-samsung/
+Note: The child node can be added under the adc node or seperately.
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index e372257..04311f8 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -91,6 +91,13 @@ config AT91_ADC
 	help
 	  Say yes here to build support for Atmel AT91 ADC.
 
+config EXYNOS_ADC
+	bool "Exynos ADC driver support"
+	help
+	  Core support for the ADC block found in the Samsung EXYNOS series
+	  of SoCs for drivers such as the touchscreen and hwmon to use to share
+	  this resource.
+
 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..fabac2c 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_EXYNOS_ADC) += exynos_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/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
new file mode 100644
index 0000000..ed6fdd7
--- /dev/null
+++ b/drivers/iio/adc/exynos_adc.c
@@ -0,0 +1,440 @@
+/*
+ *  exynos_adc.c - Support for ADC in EXYNOS SoCs
+ *
+ *  8 ~ 10 channel, 10/12-bit ADC
+ *
+ *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
+ *
+ *  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.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_platform.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+enum adc_version {
+	ADC_V1,
+	ADC_V2
+};
+
+/* EXYNOS4412/5250 ADC_V1 registers definitions */
+#define ADC_V1_CON(x)		((x) + 0x00)
+#define ADC_V1_DLY(x)		((x) + 0x08)
+#define ADC_V1_DATX(x)		((x) + 0x0C)
+#define ADC_V1_INTCLR(x)	((x) + 0x18)
+#define ADC_V1_MUX(x)		((x) + 0x1c)
+
+/* Future ADC_V2 registers definitions */
+#define ADC_V2_CON1(x)		((x) + 0x00)
+#define ADC_V2_CON2(x)		((x) + 0x04)
+#define ADC_V2_STAT(x)		((x) + 0x08)
+#define ADC_V2_INT_EN(x)	((x) + 0x10)
+#define ADC_V2_INT_ST(x)	((x) + 0x14)
+#define ADC_V2_VER(x)		((x) + 0x20)
+
+/* Bit definitions for ADC_V1 */
+#define ADC_V1_CON_RES		(1u << 16)
+#define ADC_V1_CON_PRSCEN	(1u << 14)
+#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
+#define ADC_V1_CON_STANDBY	(1u << 2)
+
+/* Bit definitions for ADC_V2 */
+#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
+
+#define ADC_V2_CON2_OSEL	(1u << 10)
+#define ADC_V2_CON2_ESEL	(1u << 9)
+#define ADC_V2_CON2_HIGHF	(1u << 8)
+#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
+#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
+#define ADC_V2_CON2_ACH_MASK	0xF
+
+#define MAX_ADC_V2_CHANNELS	10
+#define MAX_ADC_V1_CHANNELS	8
+
+/* Bit definitions common for ADC_V1 and ADC_V2 */
+#define ADC_CON_EN_START	(1u << 0)
+#define ADC_DATX_MASK		0xFFF
+
+#define EXYNOS_ADC_TIMEOUT	(msecs_to_jiffies(1000))
+
+struct exynos_adc {
+	void __iomem		*regs;
+	struct clk		*clk;
+	unsigned int		irq;
+	struct regulator	*vdd;
+
+	struct completion	completion;
+
+	u32			value;
+	unsigned int            version;
+};
+
+static const struct of_device_id exynos_adc_match[] = {
+	{ .compatible = "samsung,exynos-adc-v1", .data = (void *)ADC_V1 },
+	{ .compatible = "samsung,exynos-adc-v2", .data = (void *)ADC_V2 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, exynos_adc_match);
+
+static inline unsigned int exynos_adc_get_version(struct platform_device *pdev)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(exynos_adc_match, pdev->dev.of_node);
+	return (unsigned int)match->data;
+}
+
+static int exynos_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val,
+				int *val2,
+				long mask)
+{
+	struct exynos_adc *info = iio_priv(indio_dev);
+	unsigned long timeout;
+	u32 con1, con2;
+
+	if (mask != IIO_CHAN_INFO_RAW)
+		return -EINVAL;
+
+	mutex_lock(&indio_dev->mlock);
+
+	/* Select the channel to be used and Trigger conversion */
+	if (info->version == ADC_V2) {
+		con2 = readl(ADC_V2_CON2(info->regs));
+		con2 &= ~ADC_V2_CON2_ACH_MASK;
+		con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
+		writel(con2, ADC_V2_CON2(info->regs));
+
+		con1 = readl(ADC_V2_CON1(info->regs));
+		writel(con1 | ADC_CON_EN_START,
+				ADC_V2_CON1(info->regs));
+	} else {
+		writel(chan->address, ADC_V1_MUX(info->regs));
+
+		con1 = readl(ADC_V1_CON(info->regs));
+		writel(con1 | ADC_CON_EN_START,
+				ADC_V1_CON(info->regs));
+	}
+
+	timeout = wait_for_completion_interruptible_timeout
+			(&info->completion, EXYNOS_ADC_TIMEOUT);
+	*val = info->value;
+
+	mutex_unlock(&indio_dev->mlock);
+
+	if (timeout == 0)
+		return -ETIMEDOUT;
+
+	return IIO_VAL_INT;
+}
+
+static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
+{
+	struct exynos_adc *info = (struct exynos_adc *)dev_id;
+
+	/* Read value */
+	info->value = readl(ADC_V1_DATX(info->regs)) &
+						ADC_DATX_MASK;
+	/* clear irq */
+	if (info->version == ADC_V2)
+		writel(1, ADC_V2_INT_ST(info->regs));
+	else
+		writel(1, ADC_V1_INTCLR(info->regs));
+
+	complete(&info->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int exynos_adc_reg_access(struct iio_dev *indio_dev,
+			      unsigned reg, unsigned writeval,
+			      unsigned *readval)
+{
+	struct exynos_adc *info = iio_priv(indio_dev);
+
+	if (readval == NULL)
+		return -EINVAL;
+
+	*readval = readl(info->regs + reg);
+
+	return 0;
+}
+
+static const struct iio_info exynos_adc_iio_info = {
+	.read_raw = &exynos_read_raw,
+	.debugfs_reg_access = &exynos_adc_reg_access,
+	.driver_module = THIS_MODULE,
+};
+
+#define ADC_CHANNEL(_index, _id) {			\
+	.type = IIO_VOLTAGE,				\
+	.indexed = 1,					\
+	.channel = _index,				\
+	.address = _index,				\
+	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	.datasheet_name = _id,				\
+}
+
+static const struct iio_chan_spec exynos_adc_iio_channels[] = {
+	ADC_CHANNEL(0, "adc0"),
+	ADC_CHANNEL(1, "adc1"),
+	ADC_CHANNEL(2, "adc2"),
+	ADC_CHANNEL(3, "adc3"),
+	ADC_CHANNEL(4, "adc4"),
+	ADC_CHANNEL(5, "adc5"),
+	ADC_CHANNEL(6, "adc6"),
+	ADC_CHANNEL(7, "adc7"),
+	ADC_CHANNEL(8, "adc8"),
+	ADC_CHANNEL(9, "adc9"),
+};
+
+static int exynos_adc_remove_devices(struct device *dev, void *c)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+
+	platform_device_unregister(pdev);
+
+	return 0;
+}
+
+static void exynos_adc_hw_init(struct exynos_adc *info)
+{
+	u32 con1, con2;
+
+	if (info->version == ADC_V2) {
+		con1 = ADC_V2_CON1_SOFT_RESET;
+		writel(con1, ADC_V2_CON1(info->regs));
+
+		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
+			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
+		writel(con2, ADC_V2_CON2(info->regs));
+
+		/* Enable interrupts */
+		writel(1, ADC_V2_INT_EN(info->regs));
+	} else {
+		/* set default prescaler values and Enable prescaler */
+		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
+
+		/* Enable 12-bit ADC resolution */
+		con1 |= ADC_V1_CON_RES;
+		writel(con1, ADC_V1_CON(info->regs));
+	}
+}
+
+static int exynos_adc_probe(struct platform_device *pdev)
+{
+	struct exynos_adc *info = NULL;
+	struct device_node *np = pdev->dev.of_node;
+	struct iio_dev *indio_dev = NULL;
+	struct resource	*mem;
+	int ret = -ENODEV;
+	int irq;
+
+	if (!np)
+		return ret;
+
+	indio_dev = iio_device_alloc(sizeof(struct exynos_adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "failed allocating iio device\n");
+		return -ENOMEM;
+	}
+
+	info = iio_priv(indio_dev);
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
+	if (!info->regs) {
+		ret = -ENOMEM;
+		goto err_iio;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq resource?\n");
+		ret = irq;
+		goto err_iio;
+	}
+
+	info->irq = irq;
+
+	init_completion(&info->completion);
+
+	ret = request_irq(info->irq, exynos_adc_isr,
+					0, dev_name(&pdev->dev), info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
+							info->irq);
+		goto err_iio;
+	}
+
+	info->clk = devm_clk_get(&pdev->dev, "adc");
+	if (IS_ERR(info->clk)) {
+		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
+							PTR_ERR(info->clk));
+		ret = PTR_ERR(info->clk);
+		goto err_irq;
+	}
+
+	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
+	if (IS_ERR(info->vdd)) {
+		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
+							PTR_ERR(info->vdd));
+		ret = PTR_ERR(info->vdd);
+		goto err_irq;
+	}
+
+	info->version = exynos_adc_get_version(pdev);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->dev.of_node = pdev->dev.of_node;
+	indio_dev->info = &exynos_adc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = exynos_adc_iio_channels;
+
+	if (info->version == ADC_V1)
+		indio_dev->num_channels = MAX_ADC_V1_CHANNELS;
+	else
+		indio_dev->num_channels = MAX_ADC_V2_CHANNELS;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto err_irq;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		goto err_iio_dev;
+
+	clk_prepare_enable(info->clk);
+
+	exynos_adc_hw_init(info);
+
+	ret = of_platform_populate(np, exynos_adc_match, NULL, &pdev->dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed adding child nodes\n");
+		goto err_of_populate;
+	}
+
+	return 0;
+
+err_of_populate:
+	device_for_each_child(&pdev->dev, NULL,
+				exynos_adc_remove_devices);
+	regulator_disable(info->vdd);
+	clk_disable_unprepare(info->clk);
+err_iio_dev:
+	iio_device_unregister(indio_dev);
+err_irq:
+	free_irq(info->irq, info);
+err_iio:
+	iio_device_free(indio_dev);
+	return ret;
+}
+
+static int exynos_adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct exynos_adc *info = iio_priv(indio_dev);
+
+	device_for_each_child(&pdev->dev, NULL,
+				exynos_adc_remove_devices);
+	regulator_disable(info->vdd);
+	clk_disable_unprepare(info->clk);
+	iio_device_unregister(indio_dev);
+	free_irq(info->irq, info);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos_adc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct exynos_adc *info = platform_get_drvdata(pdev);
+	u32 con;
+
+	if (info->version == ADC_V2) {
+		con = readl(ADC_V2_CON1(info->regs));
+		con &= ~ADC_CON_EN_START;
+		writel(con, ADC_V2_CON1(info->regs));
+	} else {
+		con = readl(ADC_V1_CON(info->regs));
+		con |= ADC_V1_CON_STANDBY;
+		writel(con, ADC_V1_CON(info->regs));
+	}
+
+	clk_disable_unprepare(info->clk);
+	regulator_disable(info->vdd);
+
+	return 0;
+}
+
+static int exynos_adc_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct exynos_adc *info = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = regulator_enable(info->vdd);
+	if (ret)
+		return ret;
+
+	clk_prepare_enable(info->clk);
+
+	exynos_adc_hw_init(info);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(exynos_adc_pm_ops,
+			exynos_adc_suspend,
+			exynos_adc_resume);
+
+static struct platform_driver exynos_adc_driver = {
+	.probe		= exynos_adc_probe,
+	.remove		= exynos_adc_remove,
+	.driver		= {
+		.name	= "exynos-adc",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(exynos_adc_match),
+		.pm	= &exynos_adc_pm_ops,
+	},
+};
+
+module_platform_driver(exynos_adc_driver);
+
+MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
+MODULE_LICENSE("GPL v2");
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH v7] iio: adc: add exynos adc driver under iio framwork
  2013-02-15  6:56   ` [PATCH v7] " Naveen Krishna Chatradhi
@ 2013-02-15 13:13     ` Lars-Peter Clausen
  2013-02-15 13:17       ` Naveen Krishna Ch
  0 siblings, 1 reply; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-02-15 13:13 UTC (permalink / raw)
  To: Naveen Krishna Chatradhi
  Cc: linux-iio, linux-kernel, linux-samsung-soc, dianders, gregkh,
	naveenkrishna.ch

On 02/15/2013 07:56 AM, Naveen Krishna Chatradhi wrote:
> This patch adds New driver to support:
> 1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
>    and future SoCs from Samsung
> 2. Add ADC driver under iio/adc framework
> 3. Also adds the Documentation for device tree bindings
> 
> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>

Looks good.

Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>

One minor thing though, there are a couple of places where you break a line
into multiple lines, even though the line fits easily inside the 80 chars
per line limit. In my opinion this doesn't help the legibility of the code.
E.g.:

+	info->value = readl(ADC_V1_DATX(info->regs)) &
+						ADC_DATX_MASK;

There is no need to respin the patch just for this, but if you happen to
make another version of the patch, that's something to consider.

> ---
> Changes since v1:
> 
> 1. Fixed comments from Lars
> 2. Added support for ADC on EXYNOS5410
> 
> Changes since v2:
> 
> 1. Changed the instance name for (struct iio_dev *) to indio_dev
> 2. Changed devm_request_irq to request_irq
> 
> Few doubts regarding the mappings and child device handling.
> Kindly, suggest me better methods.
> 
> Changes since v3:
> 
> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
> 2. Moved init_completion before irq_request
> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
> 4. Use number of channels as per the ADC version
> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
> 6. Update the Documentation to include EXYNOS5410 compatible
> 
> Changes since v4:
> 
> 1. if devm_request_and_ioremap() failes, free iio_device before returning
> 
> Changes since v5:
> 
> 1. Fixed comments from Olof (ADC hardware version handling)
> 2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".
> 
> Changes since v6:
> 
> 1. Addressed comments from Lars-Peter Clausen


btw. these kind of change logs are not really helpful, it would be better to
list the actual changes made.

> 
>  .../bindings/arm/samsung/exynos5-adc.txt           |   42 ++
>  drivers/iio/adc/Kconfig                            |    7 +
>  drivers/iio/adc/Makefile                           |    1 +
>  drivers/iio/adc/exynos_adc.c                       |  438 ++++++++++++++++++++
>  4 files changed, 488 insertions(+)
>  .../devicetree/bindings/arm/samsung/exynos-adc.txt |   52 +++
>  drivers/iio/adc/Kconfig                            |    7 +
>  drivers/iio/adc/Makefile                           |    1 +
>  drivers/iio/adc/exynos_adc.c                       |  440 ++++++++++++++++++++
>  4 files changed, 500 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>  create mode 100644 drivers/iio/adc/exynos_adc.c
> 
> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
> new file mode 100644
> index 0000000..f686378
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
> @@ -0,0 +1,52 @@
> +Samsung Exynos Analog to Digital Converter bindings
> +
> +This devicetree binding are for the new adc driver written fori
> +Exynos4 and upward SoCs from Samsung.
> +
> +New driver handles the following
> +1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
> +   and future SoCs from Samsung
> +2. Add ADC driver under iio/adc framework
> +3. Also adds the Documentation for device tree bindings
> +
> +Required properties:
> +- compatible:		Must be "samsung,exynos-adc-v1"
> +				for exynos4412/5250 controllers.
> +			Must be "samsung,exynos-adc-v2" for
> +				future controllers.
> +- reg:			Contains ADC register address range (base address and
> +			length).
> +- interrupts: 		Contains the interrupt information for the timer. The
> +			format is being dependent on which interrupt controller
> +			the Samsung device uses.
> +- #io-channel-cells = <1>; As ADC has multiple outputs
> +
> +Note: child nodes can be added for auto probing from device tree.
> +
> +Example: adding device info in dtsi file
> +
> +adc: adc@12D10000 {
> +	compatible = "samsung,exynos-adc-v1";
> +	reg = <0x12D10000 0x100>;
> +	interrupts = <0 106 0>;
> +	#io-channel-cells = <1>;
> +	io-channel-ranges;
> +};
> +
> +
> +Example: Adding child nodes in dts file
> +
> +adc@12D10000 {
> +
> +	/* NTC thermistor is a hwmon device */
> +	ncp15wb473@0 {
> +		compatible = "ntc,ncp15wb473";
> +		pullup-uV = <1800000>;
> +		pullup-ohm = <47000>;
> +		pulldown-ohm = <0>;
> +		io-channels = <&adc 4>;
> +	};
> +};
> +
> +Note: Does not apply to ADC driver under arch/arm/plat-samsung/
> +Note: The child node can be added under the adc node or seperately.
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index e372257..04311f8 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -91,6 +91,13 @@ config AT91_ADC
>  	help
>  	  Say yes here to build support for Atmel AT91 ADC.
>  
> +config EXYNOS_ADC
> +	bool "Exynos ADC driver support"
> +	help
> +	  Core support for the ADC block found in the Samsung EXYNOS series
> +	  of SoCs for drivers such as the touchscreen and hwmon to use to share
> +	  this resource.
> +
>  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..fabac2c 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_EXYNOS_ADC) += exynos_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/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
> new file mode 100644
> index 0000000..ed6fdd7
> --- /dev/null
> +++ b/drivers/iio/adc/exynos_adc.c
> @@ -0,0 +1,440 @@
> +/*
> + *  exynos_adc.c - Support for ADC in EXYNOS SoCs
> + *
> + *  8 ~ 10 channel, 10/12-bit ADC
> + *
> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
> + *
> + *  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.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; if not, write to the Free Software
> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +enum adc_version {
> +	ADC_V1,
> +	ADC_V2
> +};
> +
> +/* EXYNOS4412/5250 ADC_V1 registers definitions */
> +#define ADC_V1_CON(x)		((x) + 0x00)
> +#define ADC_V1_DLY(x)		((x) + 0x08)
> +#define ADC_V1_DATX(x)		((x) + 0x0C)
> +#define ADC_V1_INTCLR(x)	((x) + 0x18)
> +#define ADC_V1_MUX(x)		((x) + 0x1c)
> +
> +/* Future ADC_V2 registers definitions */
> +#define ADC_V2_CON1(x)		((x) + 0x00)
> +#define ADC_V2_CON2(x)		((x) + 0x04)
> +#define ADC_V2_STAT(x)		((x) + 0x08)
> +#define ADC_V2_INT_EN(x)	((x) + 0x10)
> +#define ADC_V2_INT_ST(x)	((x) + 0x14)
> +#define ADC_V2_VER(x)		((x) + 0x20)
> +
> +/* Bit definitions for ADC_V1 */
> +#define ADC_V1_CON_RES		(1u << 16)
> +#define ADC_V1_CON_PRSCEN	(1u << 14)
> +#define ADC_V1_CON_PRSCLV(x)	(((x) & 0xFF) << 6)
> +#define ADC_V1_CON_STANDBY	(1u << 2)
> +
> +/* Bit definitions for ADC_V2 */
> +#define ADC_V2_CON1_SOFT_RESET	(1u << 2)
> +
> +#define ADC_V2_CON2_OSEL	(1u << 10)
> +#define ADC_V2_CON2_ESEL	(1u << 9)
> +#define ADC_V2_CON2_HIGHF	(1u << 8)
> +#define ADC_V2_CON2_C_TIME(x)	(((x) & 7) << 4)
> +#define ADC_V2_CON2_ACH_SEL(x)	(((x) & 0xF) << 0)
> +#define ADC_V2_CON2_ACH_MASK	0xF
> +
> +#define MAX_ADC_V2_CHANNELS	10
> +#define MAX_ADC_V1_CHANNELS	8
> +
> +/* Bit definitions common for ADC_V1 and ADC_V2 */
> +#define ADC_CON_EN_START	(1u << 0)
> +#define ADC_DATX_MASK		0xFFF
> +
> +#define EXYNOS_ADC_TIMEOUT	(msecs_to_jiffies(1000))
> +
> +struct exynos_adc {
> +	void __iomem		*regs;
> +	struct clk		*clk;
> +	unsigned int		irq;
> +	struct regulator	*vdd;
> +
> +	struct completion	completion;
> +
> +	u32			value;
> +	unsigned int            version;
> +};
> +
> +static const struct of_device_id exynos_adc_match[] = {
> +	{ .compatible = "samsung,exynos-adc-v1", .data = (void *)ADC_V1 },
> +	{ .compatible = "samsung,exynos-adc-v2", .data = (void *)ADC_V2 },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, exynos_adc_match);
> +
> +static inline unsigned int exynos_adc_get_version(struct platform_device *pdev)
> +{
> +	const struct of_device_id *match;
> +
> +	match = of_match_node(exynos_adc_match, pdev->dev.of_node);
> +	return (unsigned int)match->data;
> +}
> +
> +static int exynos_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct exynos_adc *info = iio_priv(indio_dev);
> +	unsigned long timeout;
> +	u32 con1, con2;
> +
> +	if (mask != IIO_CHAN_INFO_RAW)
> +		return -EINVAL;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	/* Select the channel to be used and Trigger conversion */
> +	if (info->version == ADC_V2) {
> +		con2 = readl(ADC_V2_CON2(info->regs));
> +		con2 &= ~ADC_V2_CON2_ACH_MASK;
> +		con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
> +		writel(con2, ADC_V2_CON2(info->regs));
> +
> +		con1 = readl(ADC_V2_CON1(info->regs));
> +		writel(con1 | ADC_CON_EN_START,
> +				ADC_V2_CON1(info->regs));
> +	} else {
> +		writel(chan->address, ADC_V1_MUX(info->regs));
> +
> +		con1 = readl(ADC_V1_CON(info->regs));
> +		writel(con1 | ADC_CON_EN_START,
> +				ADC_V1_CON(info->regs));
> +	}
> +
> +	timeout = wait_for_completion_interruptible_timeout
> +			(&info->completion, EXYNOS_ADC_TIMEOUT);
> +	*val = info->value;
> +
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	if (timeout == 0)
> +		return -ETIMEDOUT;
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
> +{
> +	struct exynos_adc *info = (struct exynos_adc *)dev_id;
> +
> +	/* Read value */
> +	info->value = readl(ADC_V1_DATX(info->regs)) &
> +						ADC_DATX_MASK;
> +	/* clear irq */
> +	if (info->version == ADC_V2)
> +		writel(1, ADC_V2_INT_ST(info->regs));
> +	else
> +		writel(1, ADC_V1_INTCLR(info->regs));
> +
> +	complete(&info->completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int exynos_adc_reg_access(struct iio_dev *indio_dev,
> +			      unsigned reg, unsigned writeval,
> +			      unsigned *readval)
> +{
> +	struct exynos_adc *info = iio_priv(indio_dev);
> +
> +	if (readval == NULL)
> +		return -EINVAL;
> +
> +	*readval = readl(info->regs + reg);
> +
> +	return 0;
> +}
> +
> +static const struct iio_info exynos_adc_iio_info = {
> +	.read_raw = &exynos_read_raw,
> +	.debugfs_reg_access = &exynos_adc_reg_access,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define ADC_CHANNEL(_index, _id) {			\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.channel = _index,				\
> +	.address = _index,				\
> +	.info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	.datasheet_name = _id,				\
> +}
> +
> +static const struct iio_chan_spec exynos_adc_iio_channels[] = {
> +	ADC_CHANNEL(0, "adc0"),
> +	ADC_CHANNEL(1, "adc1"),
> +	ADC_CHANNEL(2, "adc2"),
> +	ADC_CHANNEL(3, "adc3"),
> +	ADC_CHANNEL(4, "adc4"),
> +	ADC_CHANNEL(5, "adc5"),
> +	ADC_CHANNEL(6, "adc6"),
> +	ADC_CHANNEL(7, "adc7"),
> +	ADC_CHANNEL(8, "adc8"),
> +	ADC_CHANNEL(9, "adc9"),
> +};
> +
> +static int exynos_adc_remove_devices(struct device *dev, void *c)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +
> +	platform_device_unregister(pdev);
> +
> +	return 0;
> +}
> +
> +static void exynos_adc_hw_init(struct exynos_adc *info)
> +{
> +	u32 con1, con2;
> +
> +	if (info->version == ADC_V2) {
> +		con1 = ADC_V2_CON1_SOFT_RESET;
> +		writel(con1, ADC_V2_CON1(info->regs));
> +
> +		con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
> +			ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
> +		writel(con2, ADC_V2_CON2(info->regs));
> +
> +		/* Enable interrupts */
> +		writel(1, ADC_V2_INT_EN(info->regs));
> +	} else {
> +		/* set default prescaler values and Enable prescaler */
> +		con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
> +
> +		/* Enable 12-bit ADC resolution */
> +		con1 |= ADC_V1_CON_RES;
> +		writel(con1, ADC_V1_CON(info->regs));
> +	}
> +}
> +
> +static int exynos_adc_probe(struct platform_device *pdev)
> +{
> +	struct exynos_adc *info = NULL;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *indio_dev = NULL;
> +	struct resource	*mem;
> +	int ret = -ENODEV;
> +	int irq;
> +
> +	if (!np)
> +		return ret;
> +
> +	indio_dev = iio_device_alloc(sizeof(struct exynos_adc));
> +	if (!indio_dev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	info = iio_priv(indio_dev);
> +
> +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +	info->regs = devm_request_and_ioremap(&pdev->dev, mem);
> +	if (!info->regs) {
> +		ret = -ENOMEM;
> +		goto err_iio;
> +	}
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no irq resource?\n");
> +		ret = irq;
> +		goto err_iio;
> +	}
> +
> +	info->irq = irq;
> +
> +	init_completion(&info->completion);
> +
> +	ret = request_irq(info->irq, exynos_adc_isr,
> +					0, dev_name(&pdev->dev), info);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
> +							info->irq);
> +		goto err_iio;
> +	}
> +
> +	info->clk = devm_clk_get(&pdev->dev, "adc");
> +	if (IS_ERR(info->clk)) {
> +		dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
> +							PTR_ERR(info->clk));
> +		ret = PTR_ERR(info->clk);
> +		goto err_irq;
> +	}
> +
> +	info->vdd = devm_regulator_get(&pdev->dev, "vdd");
> +	if (IS_ERR(info->vdd)) {
> +		dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
> +							PTR_ERR(info->vdd));
> +		ret = PTR_ERR(info->vdd);
> +		goto err_irq;
> +	}
> +
> +	info->version = exynos_adc_get_version(pdev);
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &exynos_adc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = exynos_adc_iio_channels;
> +
> +	if (info->version == ADC_V1)
> +		indio_dev->num_channels = MAX_ADC_V1_CHANNELS;
> +	else
> +		indio_dev->num_channels = MAX_ADC_V2_CHANNELS;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto err_irq;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		goto err_iio_dev;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos_adc_hw_init(info);
> +
> +	ret = of_platform_populate(np, exynos_adc_match, NULL, &pdev->dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed adding child nodes\n");
> +		goto err_of_populate;
> +	}
> +
> +	return 0;
> +
> +err_of_populate:
> +	device_for_each_child(&pdev->dev, NULL,
> +				exynos_adc_remove_devices);
> +	regulator_disable(info->vdd);
> +	clk_disable_unprepare(info->clk);
> +err_iio_dev:
> +	iio_device_unregister(indio_dev);
> +err_irq:
> +	free_irq(info->irq, info);
> +err_iio:
> +	iio_device_free(indio_dev);
> +	return ret;
> +}
> +
> +static int exynos_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct exynos_adc *info = iio_priv(indio_dev);
> +
> +	device_for_each_child(&pdev->dev, NULL,
> +				exynos_adc_remove_devices);
> +	regulator_disable(info->vdd);
> +	clk_disable_unprepare(info->clk);
> +	iio_device_unregister(indio_dev);
> +	free_irq(info->irq, info);
> +	iio_device_free(indio_dev);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int exynos_adc_suspend(struct device *dev)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct exynos_adc *info = platform_get_drvdata(pdev);
> +	u32 con;
> +
> +	if (info->version == ADC_V2) {
> +		con = readl(ADC_V2_CON1(info->regs));
> +		con &= ~ADC_CON_EN_START;
> +		writel(con, ADC_V2_CON1(info->regs));
> +	} else {
> +		con = readl(ADC_V1_CON(info->regs));
> +		con |= ADC_V1_CON_STANDBY;
> +		writel(con, ADC_V1_CON(info->regs));
> +	}
> +
> +	clk_disable_unprepare(info->clk);
> +	regulator_disable(info->vdd);
> +
> +	return 0;
> +}
> +
> +static int exynos_adc_resume(struct device *dev)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct exynos_adc *info = platform_get_drvdata(pdev);
> +	int ret;
> +
> +	ret = regulator_enable(info->vdd);
> +	if (ret)
> +		return ret;
> +
> +	clk_prepare_enable(info->clk);
> +
> +	exynos_adc_hw_init(info);
> +
> +	return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(exynos_adc_pm_ops,
> +			exynos_adc_suspend,
> +			exynos_adc_resume);
> +
> +static struct platform_driver exynos_adc_driver = {
> +	.probe		= exynos_adc_probe,
> +	.remove		= exynos_adc_remove,
> +	.driver		= {
> +		.name	= "exynos-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = of_match_ptr(exynos_adc_match),
> +		.pm	= &exynos_adc_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(exynos_adc_driver);
> +
> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
> +MODULE_LICENSE("GPL v2");


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v7] iio: adc: add exynos adc driver under iio framwork
  2013-02-15 13:13     ` Lars-Peter Clausen
@ 2013-02-15 13:17       ` Naveen Krishna Ch
  2013-02-15 13:26         ` Lars-Peter Clausen
  0 siblings, 1 reply; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-02-15 13:17 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh

On 15 February 2013 18:43, Lars-Peter Clausen <lars@metafoo.de> wrote:
> On 02/15/2013 07:56 AM, Naveen Krishna Chatradhi wrote:
>> This patch adds New driver to support:
>> 1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
>>    and future SoCs from Samsung
>> 2. Add ADC driver under iio/adc framework
>> 3. Also adds the Documentation for device tree bindings
>>
>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>
> Looks good.
>
> Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>
>
> One minor thing though, there are a couple of places where you break a line
> into multiple lines, even though the line fits easily inside the 80 chars
> per line limit. In my opinion this doesn't help the legibility of the code.
> E.g.:
>
> +       info->value = readl(ADC_V1_DATX(info->regs)) &
> +                                               ADC_DATX_MASK;
>
> There is no need to respin the patch just for this, but if you happen to
> make another version of the patch, that's something to consider.
>
>> ---
>> Changes since v1:
>>
>> 1. Fixed comments from Lars
>> 2. Added support for ADC on EXYNOS5410
>>
>> Changes since v2:
>>
>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>> 2. Changed devm_request_irq to request_irq
>>
>> Few doubts regarding the mappings and child device handling.
>> Kindly, suggest me better methods.
>>
>> Changes since v3:
>>
>> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
>> 2. Moved init_completion before irq_request
>> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
>> 4. Use number of channels as per the ADC version
>> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
>> 6. Update the Documentation to include EXYNOS5410 compatible
>>
>> Changes since v4:
>>
>> 1. if devm_request_and_ioremap() failes, free iio_device before returning
>>
>> Changes since v5:
>>
>> 1. Fixed comments from Olof (ADC hardware version handling)
>> 2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".
>>
>> Changes since v6:
>>
>> 1. Addressed comments from Lars-Peter Clausen
>
>
> btw. these kind of change logs are not really helpful, it would be better to
> list the actual changes made.
Hello Lars,

No other changes from my side. But, I can send another version.
Do you want me to list the latest change alone instead of the whole
change list ?

>
>>
>>  .../bindings/arm/samsung/exynos5-adc.txt           |   42 ++
>>  drivers/iio/adc/Kconfig                            |    7 +
>>  drivers/iio/adc/Makefile                           |    1 +
>>  drivers/iio/adc/exynos_adc.c                       |  438 ++++++++++++++++++++
>>  4 files changed, 488 insertions(+)
>>  .../devicetree/bindings/arm/samsung/exynos-adc.txt |   52 +++
>>  drivers/iio/adc/Kconfig                            |    7 +
>>  drivers/iio/adc/Makefile                           |    1 +
>>  drivers/iio/adc/exynos_adc.c                       |  440 ++++++++++++++++++++
>>  4 files changed, 500 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>>  create mode 100644 drivers/iio/adc/exynos_adc.c
>>
>> diff --git a/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>> new file mode 100644
>> index 0000000..f686378
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/samsung/exynos-adc.txt
>> @@ -0,0 +1,52 @@
>> +Samsung Exynos Analog to Digital Converter bindings
>> +
>> +This devicetree binding are for the new adc driver written fori
>> +Exynos4 and upward SoCs from Samsung.
>> +
>> +New driver handles the following
>> +1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
>> +   and future SoCs from Samsung
>> +2. Add ADC driver under iio/adc framework
>> +3. Also adds the Documentation for device tree bindings
>> +
>> +Required properties:
>> +- compatible:                Must be "samsung,exynos-adc-v1"
>> +                             for exynos4412/5250 controllers.
>> +                     Must be "samsung,exynos-adc-v2" for
>> +                             future controllers.
>> +- reg:                       Contains ADC register address range (base address and
>> +                     length).
>> +- interrupts:                Contains the interrupt information for the timer. The
>> +                     format is being dependent on which interrupt controller
>> +                     the Samsung device uses.
>> +- #io-channel-cells = <1>; As ADC has multiple outputs
>> +
>> +Note: child nodes can be added for auto probing from device tree.
>> +
>> +Example: adding device info in dtsi file
>> +
>> +adc: adc@12D10000 {
>> +     compatible = "samsung,exynos-adc-v1";
>> +     reg = <0x12D10000 0x100>;
>> +     interrupts = <0 106 0>;
>> +     #io-channel-cells = <1>;
>> +     io-channel-ranges;
>> +};
>> +
>> +
>> +Example: Adding child nodes in dts file
>> +
>> +adc@12D10000 {
>> +
>> +     /* NTC thermistor is a hwmon device */
>> +     ncp15wb473@0 {
>> +             compatible = "ntc,ncp15wb473";
>> +             pullup-uV = <1800000>;
>> +             pullup-ohm = <47000>;
>> +             pulldown-ohm = <0>;
>> +             io-channels = <&adc 4>;
>> +     };
>> +};
>> +
>> +Note: Does not apply to ADC driver under arch/arm/plat-samsung/
>> +Note: The child node can be added under the adc node or seperately.
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index e372257..04311f8 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -91,6 +91,13 @@ config AT91_ADC
>>       help
>>         Say yes here to build support for Atmel AT91 ADC.
>>
>> +config EXYNOS_ADC
>> +     bool "Exynos ADC driver support"
>> +     help
>> +       Core support for the ADC block found in the Samsung EXYNOS series
>> +       of SoCs for drivers such as the touchscreen and hwmon to use to share
>> +       this resource.
>> +
>>  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..fabac2c 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_EXYNOS_ADC) += exynos_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/exynos_adc.c b/drivers/iio/adc/exynos_adc.c
>> new file mode 100644
>> index 0000000..ed6fdd7
>> --- /dev/null
>> +++ b/drivers/iio/adc/exynos_adc.c
>> @@ -0,0 +1,440 @@
>> +/*
>> + *  exynos_adc.c - Support for ADC in EXYNOS SoCs
>> + *
>> + *  8 ~ 10 channel, 10/12-bit ADC
>> + *
>> + *  Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>> + *
>> + *  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.
>> + *
>> + *  This program is distributed in the hope that it will be useful,
>> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + *  GNU General Public License for more details.
>> + *
>> + *  You should have received a copy of the GNU General Public License
>> + *  along with this program; if not, write to the Free Software
>> + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/kernel.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/of_platform.h>
>> +
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/iio/driver.h>
>> +
>> +enum adc_version {
>> +     ADC_V1,
>> +     ADC_V2
>> +};
>> +
>> +/* EXYNOS4412/5250 ADC_V1 registers definitions */
>> +#define ADC_V1_CON(x)                ((x) + 0x00)
>> +#define ADC_V1_DLY(x)                ((x) + 0x08)
>> +#define ADC_V1_DATX(x)               ((x) + 0x0C)
>> +#define ADC_V1_INTCLR(x)     ((x) + 0x18)
>> +#define ADC_V1_MUX(x)                ((x) + 0x1c)
>> +
>> +/* Future ADC_V2 registers definitions */
>> +#define ADC_V2_CON1(x)               ((x) + 0x00)
>> +#define ADC_V2_CON2(x)               ((x) + 0x04)
>> +#define ADC_V2_STAT(x)               ((x) + 0x08)
>> +#define ADC_V2_INT_EN(x)     ((x) + 0x10)
>> +#define ADC_V2_INT_ST(x)     ((x) + 0x14)
>> +#define ADC_V2_VER(x)                ((x) + 0x20)
>> +
>> +/* Bit definitions for ADC_V1 */
>> +#define ADC_V1_CON_RES               (1u << 16)
>> +#define ADC_V1_CON_PRSCEN    (1u << 14)
>> +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
>> +#define ADC_V1_CON_STANDBY   (1u << 2)
>> +
>> +/* Bit definitions for ADC_V2 */
>> +#define ADC_V2_CON1_SOFT_RESET       (1u << 2)
>> +
>> +#define ADC_V2_CON2_OSEL     (1u << 10)
>> +#define ADC_V2_CON2_ESEL     (1u << 9)
>> +#define ADC_V2_CON2_HIGHF    (1u << 8)
>> +#define ADC_V2_CON2_C_TIME(x)        (((x) & 7) << 4)
>> +#define ADC_V2_CON2_ACH_SEL(x)       (((x) & 0xF) << 0)
>> +#define ADC_V2_CON2_ACH_MASK 0xF
>> +
>> +#define MAX_ADC_V2_CHANNELS  10
>> +#define MAX_ADC_V1_CHANNELS  8
>> +
>> +/* Bit definitions common for ADC_V1 and ADC_V2 */
>> +#define ADC_CON_EN_START     (1u << 0)
>> +#define ADC_DATX_MASK                0xFFF
>> +
>> +#define EXYNOS_ADC_TIMEOUT   (msecs_to_jiffies(1000))
>> +
>> +struct exynos_adc {
>> +     void __iomem            *regs;
>> +     struct clk              *clk;
>> +     unsigned int            irq;
>> +     struct regulator        *vdd;
>> +
>> +     struct completion       completion;
>> +
>> +     u32                     value;
>> +     unsigned int            version;
>> +};
>> +
>> +static const struct of_device_id exynos_adc_match[] = {
>> +     { .compatible = "samsung,exynos-adc-v1", .data = (void *)ADC_V1 },
>> +     { .compatible = "samsung,exynos-adc-v2", .data = (void *)ADC_V2 },
>> +     {},
>> +};
>> +MODULE_DEVICE_TABLE(of, exynos_adc_match);
>> +
>> +static inline unsigned int exynos_adc_get_version(struct platform_device *pdev)
>> +{
>> +     const struct of_device_id *match;
>> +
>> +     match = of_match_node(exynos_adc_match, pdev->dev.of_node);
>> +     return (unsigned int)match->data;
>> +}
>> +
>> +static int exynos_read_raw(struct iio_dev *indio_dev,
>> +                             struct iio_chan_spec const *chan,
>> +                             int *val,
>> +                             int *val2,
>> +                             long mask)
>> +{
>> +     struct exynos_adc *info = iio_priv(indio_dev);
>> +     unsigned long timeout;
>> +     u32 con1, con2;
>> +
>> +     if (mask != IIO_CHAN_INFO_RAW)
>> +             return -EINVAL;
>> +
>> +     mutex_lock(&indio_dev->mlock);
>> +
>> +     /* Select the channel to be used and Trigger conversion */
>> +     if (info->version == ADC_V2) {
>> +             con2 = readl(ADC_V2_CON2(info->regs));
>> +             con2 &= ~ADC_V2_CON2_ACH_MASK;
>> +             con2 |= ADC_V2_CON2_ACH_SEL(chan->address);
>> +             writel(con2, ADC_V2_CON2(info->regs));
>> +
>> +             con1 = readl(ADC_V2_CON1(info->regs));
>> +             writel(con1 | ADC_CON_EN_START,
>> +                             ADC_V2_CON1(info->regs));
>> +     } else {
>> +             writel(chan->address, ADC_V1_MUX(info->regs));
>> +
>> +             con1 = readl(ADC_V1_CON(info->regs));
>> +             writel(con1 | ADC_CON_EN_START,
>> +                             ADC_V1_CON(info->regs));
>> +     }
>> +
>> +     timeout = wait_for_completion_interruptible_timeout
>> +                     (&info->completion, EXYNOS_ADC_TIMEOUT);
>> +     *val = info->value;
>> +
>> +     mutex_unlock(&indio_dev->mlock);
>> +
>> +     if (timeout == 0)
>> +             return -ETIMEDOUT;
>> +
>> +     return IIO_VAL_INT;
>> +}
>> +
>> +static irqreturn_t exynos_adc_isr(int irq, void *dev_id)
>> +{
>> +     struct exynos_adc *info = (struct exynos_adc *)dev_id;
>> +
>> +     /* Read value */
>> +     info->value = readl(ADC_V1_DATX(info->regs)) &
>> +                                             ADC_DATX_MASK;
>> +     /* clear irq */
>> +     if (info->version == ADC_V2)
>> +             writel(1, ADC_V2_INT_ST(info->regs));
>> +     else
>> +             writel(1, ADC_V1_INTCLR(info->regs));
>> +
>> +     complete(&info->completion);
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +static int exynos_adc_reg_access(struct iio_dev *indio_dev,
>> +                           unsigned reg, unsigned writeval,
>> +                           unsigned *readval)
>> +{
>> +     struct exynos_adc *info = iio_priv(indio_dev);
>> +
>> +     if (readval == NULL)
>> +             return -EINVAL;
>> +
>> +     *readval = readl(info->regs + reg);
>> +
>> +     return 0;
>> +}
>> +
>> +static const struct iio_info exynos_adc_iio_info = {
>> +     .read_raw = &exynos_read_raw,
>> +     .debugfs_reg_access = &exynos_adc_reg_access,
>> +     .driver_module = THIS_MODULE,
>> +};
>> +
>> +#define ADC_CHANNEL(_index, _id) {                   \
>> +     .type = IIO_VOLTAGE,                            \
>> +     .indexed = 1,                                   \
>> +     .channel = _index,                              \
>> +     .address = _index,                              \
>> +     .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT,    \
>> +     .datasheet_name = _id,                          \
>> +}
>> +
>> +static const struct iio_chan_spec exynos_adc_iio_channels[] = {
>> +     ADC_CHANNEL(0, "adc0"),
>> +     ADC_CHANNEL(1, "adc1"),
>> +     ADC_CHANNEL(2, "adc2"),
>> +     ADC_CHANNEL(3, "adc3"),
>> +     ADC_CHANNEL(4, "adc4"),
>> +     ADC_CHANNEL(5, "adc5"),
>> +     ADC_CHANNEL(6, "adc6"),
>> +     ADC_CHANNEL(7, "adc7"),
>> +     ADC_CHANNEL(8, "adc8"),
>> +     ADC_CHANNEL(9, "adc9"),
>> +};
>> +
>> +static int exynos_adc_remove_devices(struct device *dev, void *c)
>> +{
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +
>> +     platform_device_unregister(pdev);
>> +
>> +     return 0;
>> +}
>> +
>> +static void exynos_adc_hw_init(struct exynos_adc *info)
>> +{
>> +     u32 con1, con2;
>> +
>> +     if (info->version == ADC_V2) {
>> +             con1 = ADC_V2_CON1_SOFT_RESET;
>> +             writel(con1, ADC_V2_CON1(info->regs));
>> +
>> +             con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
>> +                     ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0);
>> +             writel(con2, ADC_V2_CON2(info->regs));
>> +
>> +             /* Enable interrupts */
>> +             writel(1, ADC_V2_INT_EN(info->regs));
>> +     } else {
>> +             /* set default prescaler values and Enable prescaler */
>> +             con1 =  ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN;
>> +
>> +             /* Enable 12-bit ADC resolution */
>> +             con1 |= ADC_V1_CON_RES;
>> +             writel(con1, ADC_V1_CON(info->regs));
>> +     }
>> +}
>> +
>> +static int exynos_adc_probe(struct platform_device *pdev)
>> +{
>> +     struct exynos_adc *info = NULL;
>> +     struct device_node *np = pdev->dev.of_node;
>> +     struct iio_dev *indio_dev = NULL;
>> +     struct resource *mem;
>> +     int ret = -ENODEV;
>> +     int irq;
>> +
>> +     if (!np)
>> +             return ret;
>> +
>> +     indio_dev = iio_device_alloc(sizeof(struct exynos_adc));
>> +     if (!indio_dev) {
>> +             dev_err(&pdev->dev, "failed allocating iio device\n");
>> +             return -ENOMEM;
>> +     }
>> +
>> +     info = iio_priv(indio_dev);
>> +
>> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +
>> +     info->regs = devm_request_and_ioremap(&pdev->dev, mem);
>> +     if (!info->regs) {
>> +             ret = -ENOMEM;
>> +             goto err_iio;
>> +     }
>> +
>> +     irq = platform_get_irq(pdev, 0);
>> +     if (irq < 0) {
>> +             dev_err(&pdev->dev, "no irq resource?\n");
>> +             ret = irq;
>> +             goto err_iio;
>> +     }
>> +
>> +     info->irq = irq;
>> +
>> +     init_completion(&info->completion);
>> +
>> +     ret = request_irq(info->irq, exynos_adc_isr,
>> +                                     0, dev_name(&pdev->dev), info);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "failed requesting irq, irq = %d\n",
>> +                                                     info->irq);
>> +             goto err_iio;
>> +     }
>> +
>> +     info->clk = devm_clk_get(&pdev->dev, "adc");
>> +     if (IS_ERR(info->clk)) {
>> +             dev_err(&pdev->dev, "failed getting clock, err = %ld\n",
>> +                                                     PTR_ERR(info->clk));
>> +             ret = PTR_ERR(info->clk);
>> +             goto err_irq;
>> +     }
>> +
>> +     info->vdd = devm_regulator_get(&pdev->dev, "vdd");
>> +     if (IS_ERR(info->vdd)) {
>> +             dev_err(&pdev->dev, "failed getting regulator, err = %ld\n",
>> +                                                     PTR_ERR(info->vdd));
>> +             ret = PTR_ERR(info->vdd);
>> +             goto err_irq;
>> +     }
>> +
>> +     info->version = exynos_adc_get_version(pdev);
>> +
>> +     platform_set_drvdata(pdev, indio_dev);
>> +
>> +     indio_dev->name = dev_name(&pdev->dev);
>> +     indio_dev->dev.parent = &pdev->dev;
>> +     indio_dev->dev.of_node = pdev->dev.of_node;
>> +     indio_dev->info = &exynos_adc_iio_info;
>> +     indio_dev->modes = INDIO_DIRECT_MODE;
>> +     indio_dev->channels = exynos_adc_iio_channels;
>> +
>> +     if (info->version == ADC_V1)
>> +             indio_dev->num_channels = MAX_ADC_V1_CHANNELS;
>> +     else
>> +             indio_dev->num_channels = MAX_ADC_V2_CHANNELS;
>> +
>> +     ret = iio_device_register(indio_dev);
>> +     if (ret)
>> +             goto err_irq;
>> +
>> +     ret = regulator_enable(info->vdd);
>> +     if (ret)
>> +             goto err_iio_dev;
>> +
>> +     clk_prepare_enable(info->clk);
>> +
>> +     exynos_adc_hw_init(info);
>> +
>> +     ret = of_platform_populate(np, exynos_adc_match, NULL, &pdev->dev);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "failed adding child nodes\n");
>> +             goto err_of_populate;
>> +     }
>> +
>> +     return 0;
>> +
>> +err_of_populate:
>> +     device_for_each_child(&pdev->dev, NULL,
>> +                             exynos_adc_remove_devices);
>> +     regulator_disable(info->vdd);
>> +     clk_disable_unprepare(info->clk);
>> +err_iio_dev:
>> +     iio_device_unregister(indio_dev);
>> +err_irq:
>> +     free_irq(info->irq, info);
>> +err_iio:
>> +     iio_device_free(indio_dev);
>> +     return ret;
>> +}
>> +
>> +static int exynos_adc_remove(struct platform_device *pdev)
>> +{
>> +     struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +     struct exynos_adc *info = iio_priv(indio_dev);
>> +
>> +     device_for_each_child(&pdev->dev, NULL,
>> +                             exynos_adc_remove_devices);
>> +     regulator_disable(info->vdd);
>> +     clk_disable_unprepare(info->clk);
>> +     iio_device_unregister(indio_dev);
>> +     free_irq(info->irq, info);
>> +     iio_device_free(indio_dev);
>> +
>> +     return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int exynos_adc_suspend(struct device *dev)
>> +{
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +     struct exynos_adc *info = platform_get_drvdata(pdev);
>> +     u32 con;
>> +
>> +     if (info->version == ADC_V2) {
>> +             con = readl(ADC_V2_CON1(info->regs));
>> +             con &= ~ADC_CON_EN_START;
>> +             writel(con, ADC_V2_CON1(info->regs));
>> +     } else {
>> +             con = readl(ADC_V1_CON(info->regs));
>> +             con |= ADC_V1_CON_STANDBY;
>> +             writel(con, ADC_V1_CON(info->regs));
>> +     }
>> +
>> +     clk_disable_unprepare(info->clk);
>> +     regulator_disable(info->vdd);
>> +
>> +     return 0;
>> +}
>> +
>> +static int exynos_adc_resume(struct device *dev)
>> +{
>> +     struct platform_device *pdev = to_platform_device(dev);
>> +     struct exynos_adc *info = platform_get_drvdata(pdev);
>> +     int ret;
>> +
>> +     ret = regulator_enable(info->vdd);
>> +     if (ret)
>> +             return ret;
>> +
>> +     clk_prepare_enable(info->clk);
>> +
>> +     exynos_adc_hw_init(info);
>> +
>> +     return 0;
>> +}
>> +#endif
>> +
>> +static SIMPLE_DEV_PM_OPS(exynos_adc_pm_ops,
>> +                     exynos_adc_suspend,
>> +                     exynos_adc_resume);
>> +
>> +static struct platform_driver exynos_adc_driver = {
>> +     .probe          = exynos_adc_probe,
>> +     .remove         = exynos_adc_remove,
>> +     .driver         = {
>> +             .name   = "exynos-adc",
>> +             .owner  = THIS_MODULE,
>> +             .of_match_table = of_match_ptr(exynos_adc_match),
>> +             .pm     = &exynos_adc_pm_ops,
>> +     },
>> +};
>> +
>> +module_platform_driver(exynos_adc_driver);
>> +
>> +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>");
>> +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver");
>> +MODULE_LICENSE("GPL v2");
>



--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v7] iio: adc: add exynos adc driver under iio framwork
  2013-02-15 13:17       ` Naveen Krishna Ch
@ 2013-02-15 13:26         ` Lars-Peter Clausen
  2013-02-15 13:35           ` Naveen Krishna Ch
  0 siblings, 1 reply; 35+ messages in thread
From: Lars-Peter Clausen @ 2013-02-15 13:26 UTC (permalink / raw)
  To: Naveen Krishna Ch
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh

On 02/15/2013 02:17 PM, Naveen Krishna Ch wrote:
> On 15 February 2013 18:43, Lars-Peter Clausen <lars@metafoo.de> wrote:
>> On 02/15/2013 07:56 AM, Naveen Krishna Chatradhi wrote:
>>> This patch adds New driver to support:
>>> 1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
>>>    and future SoCs from Samsung
>>> 2. Add ADC driver under iio/adc framework
>>> 3. Also adds the Documentation for device tree bindings
>>>
>>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>
>> Looks good.
>>
>> Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>
>>
>> One minor thing though, there are a couple of places where you break a line
>> into multiple lines, even though the line fits easily inside the 80 chars
>> per line limit. In my opinion this doesn't help the legibility of the code.
>> E.g.:
>>
>> +       info->value = readl(ADC_V1_DATX(info->regs)) &
>> +                                               ADC_DATX_MASK;
>>
>> There is no need to respin the patch just for this, but if you happen to
>> make another version of the patch, that's something to consider.
>>
>>> ---
>>> Changes since v1:
>>>
>>> 1. Fixed comments from Lars
>>> 2. Added support for ADC on EXYNOS5410
>>>
>>> Changes since v2:
>>>
>>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>>> 2. Changed devm_request_irq to request_irq
>>>
>>> Few doubts regarding the mappings and child device handling.
>>> Kindly, suggest me better methods.
>>>
>>> Changes since v3:
>>>
>>> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
>>> 2. Moved init_completion before irq_request
>>> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
>>> 4. Use number of channels as per the ADC version
>>> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
>>> 6. Update the Documentation to include EXYNOS5410 compatible
>>>
>>> Changes since v4:
>>>
>>> 1. if devm_request_and_ioremap() failes, free iio_device before returning
>>>
>>> Changes since v5:
>>>
>>> 1. Fixed comments from Olof (ADC hardware version handling)
>>> 2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".
>>>
>>> Changes since v6:
>>>
>>> 1. Addressed comments from Lars-Peter Clausen
>>
>>
>> btw. these kind of change logs are not really helpful, it would be better to
>> list the actual changes made.
> Hello Lars,
> 
> No other changes from my side. But, I can send another version.
> Do you want me to list the latest change alone instead of the whole
> change list ?

Hi,

No need to resend the patch, this is just something to consider for the
future. A changelog entry which reads like "Addressed Jon Does comments" is
not really useful since most people will probably not know or not longer
remember all the details of those comments, instead a nice list of all the
changes which have been made is much better. E.g.:

Changes since v6:
	* Fixed debugfs_read_reg
	* Introduced timeout when waiting for the data ready IRQ
	* Slightly reformatted exynos_read_raw for better legibility

- Lars



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v7] iio: adc: add exynos adc driver under iio framwork
  2013-02-15 13:26         ` Lars-Peter Clausen
@ 2013-02-15 13:35           ` Naveen Krishna Ch
  2013-03-03 12:16             ` Jonathan Cameron
  0 siblings, 1 reply; 35+ messages in thread
From: Naveen Krishna Ch @ 2013-02-15 13:35 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Naveen Krishna Chatradhi, linux-iio, linux-kernel,
	linux-samsung-soc, dianders, gregkh

On 15 February 2013 18:56, Lars-Peter Clausen <lars@metafoo.de> wrote:
> On 02/15/2013 02:17 PM, Naveen Krishna Ch wrote:
>> On 15 February 2013 18:43, Lars-Peter Clausen <lars@metafoo.de> wrote:
>>> On 02/15/2013 07:56 AM, Naveen Krishna Chatradhi wrote:
>>>> This patch adds New driver to support:
>>>> 1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
>>>>    and future SoCs from Samsung
>>>> 2. Add ADC driver under iio/adc framework
>>>> 3. Also adds the Documentation for device tree bindings
>>>>
>>>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>
>>> Looks good.
>>>
>>> Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>
>>>
>>> One minor thing though, there are a couple of places where you break a line
>>> into multiple lines, even though the line fits easily inside the 80 chars
>>> per line limit. In my opinion this doesn't help the legibility of the code.
>>> E.g.:
>>>
>>> +       info->value = readl(ADC_V1_DATX(info->regs)) &
>>> +                                               ADC_DATX_MASK;
>>>
>>> There is no need to respin the patch just for this, but if you happen to
>>> make another version of the patch, that's something to consider.
>>>
>>>> ---
>>>> Changes since v1:
>>>>
>>>> 1. Fixed comments from Lars
>>>> 2. Added support for ADC on EXYNOS5410
>>>>
>>>> Changes since v2:
>>>>
>>>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>>>> 2. Changed devm_request_irq to request_irq
>>>>
>>>> Few doubts regarding the mappings and child device handling.
>>>> Kindly, suggest me better methods.
>>>>
>>>> Changes since v3:
>>>>
>>>> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
>>>> 2. Moved init_completion before irq_request
>>>> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
>>>> 4. Use number of channels as per the ADC version
>>>> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
>>>> 6. Update the Documentation to include EXYNOS5410 compatible
>>>>
>>>> Changes since v4:
>>>>
>>>> 1. if devm_request_and_ioremap() failes, free iio_device before returning
>>>>
>>>> Changes since v5:
>>>>
>>>> 1. Fixed comments from Olof (ADC hardware version handling)
>>>> 2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".
>>>>
>>>> Changes since v6:
>>>>
>>>> 1. Addressed comments from Lars-Peter Clausen
>>>
>>>
>>> btw. these kind of change logs are not really helpful, it would be better to
>>> list the actual changes made.
>> Hello Lars,
>>
>> No other changes from my side. But, I can send another version.
>> Do you want me to list the latest change alone instead of the whole
>> change list ?
>
> Hi,
>
> No need to resend the patch, this is just something to consider for the
> future. A changelog entry which reads like "Addressed Jon Does comments" is
> not really useful since most people will probably not know or not longer
> remember all the details of those comments, instead a nice list of all the
> changes which have been made is much better. E.g.:
>
> Changes since v6:
>         * Fixed debugfs_read_reg
>         * Introduced timeout when waiting for the data ready IRQ
>         * Slightly reformatted exynos_read_raw for better legibility
>
> - Lars

Thanks for your comments and valuable time.
Sure Lars, Will do it.
>
>



--
Shine bright,
(: Nav :)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v7] iio: adc: add exynos adc driver under iio framwork
  2013-02-15 13:35           ` Naveen Krishna Ch
@ 2013-03-03 12:16             ` Jonathan Cameron
  0 siblings, 0 replies; 35+ messages in thread
From: Jonathan Cameron @ 2013-03-03 12:16 UTC (permalink / raw)
  To: Naveen Krishna Ch
  Cc: Lars-Peter Clausen, Naveen Krishna Chatradhi, linux-iio,
	linux-kernel, linux-samsung-soc, dianders, gregkh

On 02/15/2013 01:35 PM, Naveen Krishna Ch wrote:
> On 15 February 2013 18:56, Lars-Peter Clausen <lars@metafoo.de> wrote:
>> On 02/15/2013 02:17 PM, Naveen Krishna Ch wrote:
>>> On 15 February 2013 18:43, Lars-Peter Clausen <lars@metafoo.de> wrote:
>>>> On 02/15/2013 07:56 AM, Naveen Krishna Chatradhi wrote:
>>>>> This patch adds New driver to support:
>>>>> 1. Supports ADC IF found on EXYNOS4412/EXYNOS5250
>>>>>    and future SoCs from Samsung
>>>>> 2. Add ADC driver under iio/adc framework
>>>>> 3. Also adds the Documentation for device tree bindings
>>>>>
>>>>> Signed-off-by: Naveen Krishna Chatradhi <ch.naveen@samsung.com>
>>>>
>>>> Looks good.
>>>>
>>>> Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>
Applied to togreg branch of iio.git

Thanks,

>>>>
>>>> One minor thing though, there are a couple of places where you break a line
>>>> into multiple lines, even though the line fits easily inside the 80 chars
>>>> per line limit. In my opinion this doesn't help the legibility of the code.
>>>> E.g.:
>>>>
>>>> +       info->value = readl(ADC_V1_DATX(info->regs)) &
>>>> +                                               ADC_DATX_MASK;
>>>>
>>>> There is no need to respin the patch just for this, but if you happen to
>>>> make another version of the patch, that's something to consider.
>>>>
>>>>> ---
>>>>> Changes since v1:
>>>>>
>>>>> 1. Fixed comments from Lars
>>>>> 2. Added support for ADC on EXYNOS5410
>>>>>
>>>>> Changes since v2:
>>>>>
>>>>> 1. Changed the instance name for (struct iio_dev *) to indio_dev
>>>>> 2. Changed devm_request_irq to request_irq
>>>>>
>>>>> Few doubts regarding the mappings and child device handling.
>>>>> Kindly, suggest me better methods.
>>>>>
>>>>> Changes since v3:
>>>>>
>>>>> 1. Added clk_prepare_disable and regulator_disable calls in _remove()
>>>>> 2. Moved init_completion before irq_request
>>>>> 3. Added NULL pointer check for devm_request_and_ioremap() return value.
>>>>> 4. Use number of channels as per the ADC version
>>>>> 5. Change the define ADC_V1_CHANNEL to ADC_CHANNEL
>>>>> 6. Update the Documentation to include EXYNOS5410 compatible
>>>>>
>>>>> Changes since v4:
>>>>>
>>>>> 1. if devm_request_and_ioremap() failes, free iio_device before returning
>>>>>
>>>>> Changes since v5:
>>>>>
>>>>> 1. Fixed comments from Olof (ADC hardware version handling)
>>>>> 2. Rebased on top of comming OF framework for IIO by "Guenter Roeck".
>>>>>
>>>>> Changes since v6:
>>>>>
>>>>> 1. Addressed comments from Lars-Peter Clausen
>>>>
>>>>
>>>> btw. these kind of change logs are not really helpful, it would be better to
>>>> list the actual changes made.
>>> Hello Lars,
>>>
>>> No other changes from my side. But, I can send another version.
>>> Do you want me to list the latest change alone instead of the whole
>>> change list ?
>>
>> Hi,
>>
>> No need to resend the patch, this is just something to consider for the
>> future. A changelog entry which reads like "Addressed Jon Does comments" is
>> not really useful since most people will probably not know or not longer
>> remember all the details of those comments, instead a nice list of all the
>> changes which have been made is much better. E.g.:
>>
>> Changes since v6:
>>         * Fixed debugfs_read_reg
>>         * Introduced timeout when waiting for the data ready IRQ
>>         * Slightly reformatted exynos_read_raw for better legibility
>>
>> - Lars
> 
> Thanks for your comments and valuable time.
> Sure Lars, Will do it.
>>
>>
> 
> 
> 
> --
> Shine bright,
> (: Nav :)
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2013-03-03 12:16 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-01-21 13:37 [PATCH] iio: adc: add exynos5 adc driver under iio framwork Naveen Krishna Chatradhi
2013-01-22  9:44 ` Lars-Peter Clausen
2013-01-22 14:03   ` Naveen Krishna Ch
2013-01-22 14:27 ` Naveen Krishna Chatradhi
2013-01-23  4:58 ` Naveen Krishna Chatradhi
2013-01-23 12:52   ` Lars-Peter Clausen
2013-01-24  0:42     ` Doug Anderson
2013-01-24  9:54       ` Lars-Peter Clausen
2013-01-24 14:20         ` Naveen Krishna Ch
2013-01-24 18:11           ` Lars-Peter Clausen
2013-01-24 16:12         ` Doug Anderson
2013-01-24 18:19           ` Lars-Peter Clausen
2013-01-24 19:15             ` Tomasz Figa
2013-01-24 19:30               ` Lars-Peter Clausen
2013-02-12 21:07   ` Guenter Roeck
2013-02-13  2:48     ` Naveen Krishna Ch
2013-02-13 11:05       ` Naveen Krishna Ch
2013-02-13 13:16       ` Naveen Krishna Ch
2013-02-13 13:30         ` Lars-Peter Clausen
2013-02-13 13:53           ` Naveen Krishna Ch
2013-02-13 14:05             ` Lars-Peter Clausen
2013-02-13 15:51         ` Guenter Roeck
2013-01-24  4:58 ` [PATCH] " Naveen Krishna Chatradhi
2013-01-26 10:57   ` Jonathan Cameron
2013-01-30  6:02     ` Naveen Krishna Ch
2013-01-24  5:05 ` Naveen Krishna Chatradhi
2013-02-12  1:22   ` Olof Johansson
2013-02-14 12:11 ` [PATCH v6] iio: adc: add exynos " Naveen Krishna Chatradhi
2013-02-14 20:55   ` Lars-Peter Clausen
2013-02-15  6:56   ` [PATCH v7] " Naveen Krishna Chatradhi
2013-02-15 13:13     ` Lars-Peter Clausen
2013-02-15 13:17       ` Naveen Krishna Ch
2013-02-15 13:26         ` Lars-Peter Clausen
2013-02-15 13:35           ` Naveen Krishna Ch
2013-03-03 12:16             ` Jonathan Cameron

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).