Skip to contentSkip to conversation

AT91: Add a driver for the ADC

PATCH Superseded Maxime Ripard 11 participants 55 messages Oct 19, 2011 – Nov 14, 2011

Subsystems (6)

v1 of 13 Superseded Latest is v13 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 AT91: Add a driver for the ADC

Hi all,

This patchset adds the driver for the ADC in the AT91 SoC. It has been tested on the at91sam9g20ek and should work on sam9g45 as well.

For now, it only reads values when asked for by sysfs, but eventually will support hardware triggers and more boards.

This patchset is based on the branch master of Jonathan Cameron present at :
https://github.com/jic23/linux-iio/

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
#1 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |    8 ++++++++
 1 files changed, 8 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +8 -0
index ed544a0..adbc431 100644
@@ -207,4 +207,12 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 
+/* ADC */
+struct at91_adc_data {
+	unsigned int adc_clock;
+	u8 channels;
+	u8 startup_time;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif
Signature · 1 line
1.7.4.1
#2 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 drivers/staging/iio/adc/Kconfig   |    6 +
 drivers/staging/iio/adc/Makefile  |    1 +
 drivers/staging/iio/adc/at91adc.c |  295 +++++++++++++++++++++++++++++++++++++
 3 files changed, 302 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/adc/at91adc.c
drivers/staging/iio/adc/Kconfig +6 -0
index 070efd3..98e8005 100644
@@ -206,6 +206,12 @@ config AD7280
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad7280a
 
+config AT91ADC
+	tristate "Atmel Touchscreen ADC Controller"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel TSADCC.
+
 config MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/staging/iio/adc/Makefile +1 -0
index 8adf096..03e517a 100644
@@ -42,3 +42,4 @@ obj-$(CONFIG_ADT75) += adt75.o
 obj-$(CONFIG_ADT7310) += adt7310.o
 obj-$(CONFIG_ADT7410) += adt7410.o
 obj-$(CONFIG_AD7280) += ad7280a.o
+obj-$(CONFIG_AT91ADC) += at91adc.o
drivers/staging/iio/adc/at91adc.c new file +295 -0

295 lines not on this page · raw message

Signature · 1 line
1.7.4.1
#3 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Hi Maxime,

Afraid I'm out of time for today, but just thought I'd ask one quick question...

Why isn't this touchscreen ADC in input?

(needs to be firmly stated as early as possible!)

Quoted 337 lines
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 drivers/staging/iio/adc/Kconfig   |    6 +
 drivers/staging/iio/adc/Makefile  |    1 +
 drivers/staging/iio/adc/at91adc.c |  295 +++++++++++++++++++++++++++++++++++++
 3 files changed, 302 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/adc/at91adc.c
drivers/staging/iio/adc/Kconfig +6 -0
index 070efd3..98e8005 100644
@@ -206,6 +206,12 @@ config AD7280
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad7280a
 
+config AT91ADC
+	tristate "Atmel Touchscreen ADC Controller"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel TSADCC.
+
 config MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/staging/iio/adc/Makefile +1 -0
index 8adf096..03e517a 100644
@@ -42,3 +42,4 @@ obj-$(CONFIG_ADT75) += adt75.o
 obj-$(CONFIG_ADT7310) += adt7310.o
 obj-$(CONFIG_ADT7410) += adt7410.o
 obj-$(CONFIG_AD7280) += ad7280a.o
+obj-$(CONFIG_AT91ADC) += at91adc.o
drivers/staging/iio/adc/at91adc.c new file +295 -0
new file mode 100644
index 0000000..2737261
@@ -0,0 +1,295 @@
+/*
+ * Driver for the TouchScreen ADC Controller present in the Atmel AT91
+ * evaluation boards.
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "../iio.h"
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+
+#define at91adc_reg_read(base, reg)        __raw_readl((base) + (reg))
+#define at91adc_reg_write(base, reg, val)  __raw_writel((val), (base) + (reg))
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	struct iio_chan_spec *channels;
+	int nb_chan;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 lcdr;
+	void __iomem *reg_base;
+
+};
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)
+{
+	int chan;
+	struct at91adc_state *st = private;
+	struct iio_dev *idev = iio_priv_to_dev(st);
+	unsigned int status = at91adc_reg_read(st->reg_base, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < st->nb_chan; chan++) {
+		if (status & AT91_ADC_EOC(chan)) {
+			st->done = true;
+			st->lcdr = at91adc_reg_read(st->reg_base,
+						    AT91_ADC_CHR(chan));
+		}
+	}
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
+
+static int at91adc_channel_init(struct at91adc_state *st)
+{
+	int ret = 0, i;
+	st->channels = kzalloc(sizeof(struct iio_chan_spec) * st->nb_chan,
+			       GFP_KERNEL);
+	if (st->channels == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < st->nb_chan; i++) {
+		struct iio_chan_spec *chan = st->channels + i;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = i;
+		++ret;
+	}
+
+	return ret;
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible(st->wq_data_avail, st->done);
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	dev_dbg(&pdev->dev, "AT91ADC probed\n");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(*st));
+	if (idev == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = platform_get_device_id(pdev)->name;
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st->reg_base, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, st);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st->reg_base, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	st->nb_chan = pdata->channels;
+	ret = at91adc_channel_init(st);
+	if (ret < 0) {
+		goto error_free_clk;
+	}
+
+	idev->channels = st->channels;
+	idev->num_channels = st->nb_chan;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	ret = iio_device_register(idev);
+	if (ret < 0) {
+		goto error_free_clk;
+	}
+
+	return 0;
+
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_free_device(idev);
+error_ret:
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	free_irq(st->irq, st);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_unregister(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),
+	.driver = {
+		   .name = "at91adc",
+		   },
+};
+
+static int __init at91adc_init(void)
+{
+	return platform_driver_register(&at91adc_driver);
+}
+
+static void __exit at91adc_exit(void)
+{
+	platform_driver_unregister(&at91adc_driver);
+}
+
+module_init(at91adc_init);
+module_exit(at91adc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atmel AT91 ADC Driver");
+MODULE_AUTHOR("Maxime Ripard <[email protected]>");
#4 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Hi Jonathan,

Quoted 3 lines · Jonathan Cameron

Afraid I'm out of time for today, but just thought I'd ask one quick question...

Why isn't this touchscreen ADC in input?

Because what I say in the Kconfig is mostly wrong... Sorry about that.

Actually, like I was saying, this ADC is present in a lot of AT91 SoC, and while it is always an ADC, sometimes (like on the G45), it can be used as a touchscreen controller, or even as both, with channels for the touchscreen and channels as ADC. On the G20 however, it is just an ADC.

For now, the plan is only to support the ADC mode, so I put it in adc.

Maxime

Quoted 342 lines
Quoted 327 lines
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 drivers/staging/iio/adc/Kconfig   |    6 +
 drivers/staging/iio/adc/Makefile  |    1 +
 drivers/staging/iio/adc/at91adc.c |  295 +++++++++++++++++++++++++++++++++++++
 3 files changed, 302 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/adc/at91adc.c
drivers/staging/iio/adc/Kconfig +6 -0
index 070efd3..98e8005 100644
@@ -206,6 +206,12 @@ config AD7280
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad7280a
 
+config AT91ADC
+	tristate "Atmel Touchscreen ADC Controller"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel TSADCC.
+
 config MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/staging/iio/adc/Makefile +1 -0
index 8adf096..03e517a 100644
@@ -42,3 +42,4 @@ obj-$(CONFIG_ADT75) += adt75.o
 obj-$(CONFIG_ADT7310) += adt7310.o
 obj-$(CONFIG_ADT7410) += adt7410.o
 obj-$(CONFIG_AD7280) += ad7280a.o
+obj-$(CONFIG_AT91ADC) += at91adc.o
drivers/staging/iio/adc/at91adc.c new file +285 -0
new file mode 100644
index 0000000..2737261
@@ -0,0 +1,295 @@
+/*
+ * Driver for the TouchScreen ADC Controller present in the Atmel AT91
+ * evaluation boards.
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "../iio.h"
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+
+#define at91adc_reg_read(base, reg)        __raw_readl((base) + (reg))
+#define at91adc_reg_write(base, reg, val)  __raw_writel((val), (base) + (reg))
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	struct iio_chan_spec *channels;
+	int nb_chan;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 lcdr;
+	void __iomem *reg_base;
+
+};
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)
+{
+	int chan;
+	struct at91adc_state *st = private;
+	struct iio_dev *idev = iio_priv_to_dev(st);
+	unsigned int status = at91adc_reg_read(st->reg_base, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < st->nb_chan; chan++) {
+		if (status & AT91_ADC_EOC(chan)) {
+			st->done = true;
+			st->lcdr = at91adc_reg_read(st->reg_base,
+						    AT91_ADC_CHR(chan));
+		}
+	}
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
+
+static int at91adc_channel_init(struct at91adc_state *st)
+{
+	int ret = 0, i;
+	st->channels = kzalloc(sizeof(struct iio_chan_spec) * st->nb_chan,
+			       GFP_KERNEL);
+	if (st->channels == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < st->nb_chan; i++) {
+		struct iio_chan_spec *chan = st->channels + i;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = i;
+		++ret;
+	}
+
+	return ret;
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible(st->wq_data_avail, st->done);
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	dev_dbg(&pdev->dev, "AT91ADC probed\n");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(*st));
+	if (idev == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = platform_get_device_id(pdev)->name;
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st->reg_base, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, st);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st->reg_base, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	st->nb_chan = pdata->channels;
+	ret = at91adc_channel_init(st);
+	if (ret < 0) {
+		goto error_free_clk;
+	}
+
+	idev->channels = st->channels;
+	idev->num_channels = st->nb_chan;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	ret = iio_device_register(idev);
+	if (ret < 0) {
+		goto error_free_clk;
+	}
+
+	return 0;
+
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_free_device(idev);
+error_ret:
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	free_irq(st->irq, st);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_unregister(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),
+	.driver = {
+		   .name = "at91adc",
+		   },
+};
+
+static int __init at91adc_init(void)
+{
+	return platform_driver_register(&at91adc_driver);
+}
+
+static void __exit at91adc_exit(void)

drivers/staging/iio/adc/at91adc.c line 295

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+{
+	platform_driver_unregister(&at91adc_driver);
+}
+
+module_init(at91adc_init);
+module_exit(at91adc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atmel AT91 ADC Driver");
+MODULE_AUTHOR("Maxime Ripard <[email protected]>");

--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#5 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Hello,

Le Wed, 19 Oct 2011 20:23:50 +0200,
Maxime Ripard <[email protected]> a ?crit :

Quoted 5 lines · Jonathan Cameron
Quoted 3 lines

Afraid I'm out of time for today, but just thought I'd ask one quick question...

Why isn't this touchscreen ADC in input?

Quoted 8 lines

Because what I say in the Kconfig is mostly wrong... Sorry about that.

Actually, like I was saying, this ADC is present in a lot of AT91 SoC, and while it is always an ADC, sometimes (like on the G45), it can be used as a touchscreen controller, or even as both, with channels for the touchscreen and channels as ADC. On the G20 however, it is just an ADC.

For now, the plan is only to support the ADC mode, so I put it in adc.

Just to expand a bit on this: there is already in the kernel an input driver for the AT91 touchscreen which uses some ADC channels (that have additional touchscreen-related features: on the G45, you have 8 ADCs channels, 4 can optionally be used for touchscreen, the 4 other are just raw ADCs).

The ultimate goal is of course to make it possible to use both the IIO AT91 ADC driver and the AT91 touchscreen driver work simultaneously (with of course non-conflicting ADC channels usage). However, as shown by recent discussions on this list, it is not yet entirely clearly how this should be done:

* Should we have some low-level ADC driver in arch/arm/mach-at91/ that allows to request/release/access the ADC channels, this driver providing an internal kernel API used by the IIO ADC driver and the touchscreen driver ?

sysfs access /dev/input/...
to ADC channels access to touchscreen
|| ||
\/ \/
+------------------+ +------------------+
| AT91 IIO | | AT91 touchscreen |
+------------------+ +------------------+
| |
\_______________________________/
|
+---------------------+
| Some low-level ADC |
| code to request, |
| release and access |
| ADC channels |
+---------------------+

* Should IIO provide some internal kernel API (as you suggested some time ago) so that kernel drivers can use ADC channels ?

            +-------------------+
            | AT91 touchscreen  | -> /dev/input/... access to touchscreen
            +-------------------+
                     ||
                     \/
            +-------------------+
            |   AT91 IIO        | -> sysfs access to ADC channels
            +-------------------+

Regards,

Thomas

Signature · 4 lines
Thomas Petazzoni, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#6 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Bringing in Mark and Linus as others who have been active in talking ab= out
similar issues.

Quoted 7 lines

Hello,
=20
Le Wed, 19 Oct 2011 20:23:50 +0200,
Maxime Ripard <[email protected]> a =C3=A9crit :
=20

Quoted 2 lines · Jonathan Cameron
Quoted 1 line

Afraid I'm out of time for today, but just thought I'd ask one quic=

k question...

Quoted 4 lines
Quoted 4 lines
Quoted 2 lines

Why isn't this touchscreen ADC in input?

Because what I say in the Kconfig is mostly wrong... Sorry about tha=

t.

Quoted 2 lines
Quoted 2 lines

Actually, like I was saying, this ADC is present in a lot of AT91 So=

C,

Quoted 1 line
Quoted 1 line

and while it is always an ADC, sometimes (like on the G45), it can b=

e

Quoted 1 line
Quoted 1 line

used as a touchscreen controller, or even as both, with channels for=

the

Quoted 1 line
Quoted 1 line

touchscreen and channels as ADC. On the G20 however, it is just an A=

DC.

Quoted 2 lines
Quoted 2 lines

For now, the plan is only to support the ADC mode, so I put it in ad=

c.

Quoted 3 lines

=20
Just to expand a bit on this: there is already in the kernel an input driver for the AT91 touchscreen which uses some ADC channels (that ha=

ve

Quoted 5 lines

additional touchscreen-related features: on the G45, you have 8 ADCs channels, 4 can optionally be used for touchscreen, the 4 other are just raw ADCs).
=20
The ultimate goal is of course to make it possible to use both the II=

O

Quoted 2 lines

AT91 ADC driver and the AT91 touchscreen driver work simultaneously (with of course non-conflicting ADC channels usage). However, as show=

n

Quoted 1 line

by recent discussions on this list, it is not yet entirely clearly ho=

w

Quoted 3 lines

this should be done:
=20
* Should we have some low-level ADC driver in arch/arm/mach-at91/ th=

at

Quoted 2 lines

allows to request/release/access the ADC channels, this driver providing an internal kernel API used by the IIO ADC driver and th=

e

Quoted 1 line

touchscreen driver ?

That's definitely not going to go down well against moves to move every= thing
that looks like a driver out of the arch directories. An equivalent som= ewhere
else might work though.

Quoted 22 lines

=20
sysfs access /dev/input/...
to ADC channels access to touchscreen
|| ||
\/ \/
+------------------+ +------------------+
| AT91 IIO | | AT91 touchscreen |
+------------------+ +------------------+
| |
\_______________________________/
|
+---------------------+
| Some low-level ADC |
| code to request, |
| release and access |
| ADC channels |
+---------------------+
=20
* Should IIO provide some internal kernel API (as you suggested some time ago) so that kernel drivers can use ADC channels ?
=20
=20

Quoted 2 lines
            +-------------------+
            | AT91 touchscreen  | -> /dev/input/... access to touchsc=

reen

Quoted 10 lines
            +-------------------+
                     ||
                     \/
            +-------------------+
            |   AT91 IIO        | -> sysfs access to ADC channels
            +-------------------+

=20
Regards,
=20
Thomas

Agreed. This is currently up in the air. The current state of those II= O
hooks is that they do pull only. Push based capture is tricky as for I= IO
we want to control the triggering of the push at a far finer scale than makes sense for input. I'm not yet clear how these two will play togeth= er.

If we sit something underneath the IIO and input drivers (or use the lo= wer
portions of IIO to do this) then the intent would be to have a fully ge= neric
input touchscreen driver on top. I'm not sure that's possible. For example are the switches on the G45's adc (from datasheet) something th= at
all/many similar touch screen controllers have?
I suppose we could support these in the chan_spec but only if it helps us to write a generic touch screen driver on top. I have no real feeling for whether this would be possible...
(all my boards are headless ;)

Jonathan

#7 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Le Thu, 20 Oct 2011 09:33:29 +0100,
Jonathan Cameron <[email protected]> a ?crit :

Quoted 7 lines
Quoted 4 lines

* Should we have some low-level ADC driver in arch/arm/mach-at91/ that allows to request/release/access the ADC channels, this driver providing an internal kernel API used by the IIO ADC driver and the touchscreen driver ?

That's definitely not going to go down well against moves to move everything that looks like a driver out of the arch directories. An equivalent somewhere else might work though.

Yes, of course if this needs to be implemented, it should be in some other location than arch/arm, but not sure where (which is basically the discussion that was started some time ago).

Quoted 4 lines

Agreed. This is currently up in the air. The current state of those IIO hooks is that they do pull only. Push based capture is tricky as for IIO we want to control the triggering of the push at a far finer scale than makes sense for input. I'm not yet clear how these two will play together.

My IIO knowledge is still too limited to understand what's the problem with exposing an API for push-based capture.

Quoted 3 lines

If we sit something underneath the IIO and input drivers (or use the lower portions of IIO to do this) then the intent would be to have a fully generic input touchscreen driver on top. I'm not sure that's possible.

I am not sure it's possible to make the touchscreen driver generic. On the G45, the ADC IP has some generic ADC registers, but also some touchscreen-specific registers. So the touchscreen driver probably cannot be completely generic and some cooperation between the AT91 ADC driver and the AT91 touchscreen driver might be needed. I'd have to look into more details on how the AT91 touchscreen thing works to provide some more details here.

Quoted 2 lines

For example are the switches on the G45's adc (from datasheet) something that all/many similar touch screen controllers have?

I have no idea.

However, I have also seen a platform (SuperH 2A) where ADC channels are used for keypad handling. 4 ADCs channels, each connected to 4 keys, each having a different resistor. Depending on the voltage measured at the ADC, you're capable of knowing which key of the 4x4 keypad is pressed. This is a different situation where the ADC values need to be used by another in-kernel driver.

Regards,

Thomas

Signature · 4 lines
Thomas Petazzoni, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#8 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Quoted 4 lines · Thomas Petazzoni

Le Thu, 20 Oct 2011 09:33:29 +0100,
Jonathan Cameron <[email protected]> a =C3=A9crit :
=20

Quoted 1 line
Quoted 1 line

* Should we have some low-level ADC driver in arch/arm/mach-at91/ =

that

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

allows to request/release/access the ADC channels, this driver providing an internal kernel API used by the IIO ADC driver and =

the

Quoted 2 lines
Quoted 2 lines
Quoted 1 line

touchscreen driver ?

That's definitely not going to go down well against moves to move ev=

erything

Quoted 1 line
Quoted 1 line

that looks like a driver out of the arch directories. An equivalent =

somewhere

Quoted 7 lines
Quoted 1 line

else might work though.

=20
Yes, of course if this needs to be implemented, it should be in some other location than arch/arm, but not sure where (which is basically the discussion that was started some time ago).
=20

Quoted 1 line

Agreed. This is currently up in the air. The current state of those=

IIO

Quoted 1 line
Quoted 1 line

hooks is that they do pull only. Push based capture is tricky as fo=

r IIO

Quoted 1 line
Quoted 1 line

we want to control the triggering of the push at a far finer scale t=

han

Quoted 1 line
Quoted 1 line

makes sense for input. I'm not yet clear how these two will play tog=

ether.

Quoted 2 lines

=20
My IIO knowledge is still too limited to understand what's the proble=

m

Quoted 1 line

with exposing an API for push-based capture.

I'll give a quick summary:

Push in IIO is done from a trigger (imagine an oscilloscope). Those tr= iggers
may be supplied by a dataready signal or from somewhere entirely differ= ent.
Also multiple devices can be triggered by the same signal. Thus the da= taready
from one chip can trigger a whole load of others.

Right now filtering functions prevent some devices from using anything = other than
their own trigger. Setting defaults is also possible. If anything is t= hen registered
to receive the data the trigger cannot be changed anyway so that will w= ork here.

The other bit that makes it complex is the data flow.
This is done in the form of scans of a set of channels. The description=
of these
are more or less available to the core. Note the scan you get may well=
have
more channels in it than you explicitly requested.

These scans are then pushed into one of a set of different buffers. No= te
All of the scan is currently pushed. That makes sense if you only have=
one
user of the data. Right now the pushing is done by the individual driv= ers
but this could pass through the core.

So things that stand in the way of more general use:

1) Userspace controlled triggers (apply to whole ADC) - can lock these = down if
say a touchscreen driver is using the device.
2) Single output stream of data.
3) Note there is deliberately no metadata in the stream. It is all out = of band.

Push to one driver is easy. Anything else needs a demux that is aware = of the
stream contents. This bit doesn't exist and looks non trivial. The re= al
trick is going to be keeping it light and fast (or entirely out of the way when not needed.)

At some point I'll have a play with rerouting the data so such a demux can sit in the current flow (can use it to kill off unwanted channels).

Quoted 2 lines

=20

Quoted 1 line

If we sit something underneath the IIO and input drivers (or use the=

lower

Quoted 1 line
Quoted 1 line

portions of IIO to do this) then the intent would be to have a fully=

generic

Quoted 3 lines
Quoted 1 line

input touchscreen driver on top. I'm not sure that's possible.

=20
I am not sure it's possible to make the touchscreen driver generic. O=

n

Quoted 3 lines

the G45, the ADC IP has some generic ADC registers, but also some touchscreen-specific registers. So the touchscreen driver probably cannot be completely generic and some cooperation between the AT91 AD=

C

Quoted 5 lines

driver and the AT91 touchscreen driver might be needed. I'd have to look into more details on how the AT91 touchscreen thing works to provide some more details here.
=20

Quoted 1 line

For example are the switches on the G45's adc (from datasheet) somet=

hing that

Quoted 5 lines
Quoted 1 line

all/many similar touch screen controllers have?

=20
I have no idea.
=20
However, I have also seen a platform (SuperH 2A) where ADC channels a=

re

Quoted 2 lines

used for keypad handling. 4 ADCs channels, each connected to 4 keys, each having a different resistor. Depending on the voltage measured a=

t

Quoted 2 lines

the ADC, you're capable of knowing which key of the 4x4 keypad is pressed. This is a different situation where the ADC values need to b=

e

Quoted 1 line

used by another in-kernel driver.

That one is much easier to handle and indeed not that uncommon a hack. It really is just using the ADCs to get a value. Hence with suitable descriptive map it's easy to write a generic driver for it. Even easier if it's simply polled ;)

Quoted 4 lines

=20
Regards,
=20
Thomas

#9 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Jonathan Cameron
Quoted 4 lines · Jonathan Cameron

Agreed. This is currently up in the air. The current state of those IIO hooks is that they do pull only. Push based capture is tricky as for IIO we want to control the triggering of the push at a far finer scale than makes sense for input. I'm not yet clear how these two will play together.

What's the issue here? The touchscreen input is just a trigger

Quoted 3 lines

If we sit something underneath the IIO and input drivers (or use the lower portions of IIO to do this) then the intent would be to have a fully generic input touchscreen driver on top. I'm not sure that's possible. For

I'm not sure that's going to be tractable, while the ADC usage may be the same I'd expect to see a bunch of other logic around them for pen down detection. I'd guess it is going to be more tractable to have a library that most ADC touchscreens can share which handles the actual data transfer bit of things but has extra glue logic around it to make the actual device.

#10 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Maxime Ripard

drivers/staging/iio/adc/at91adc.c line 27

Quoted 8 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>

---
[...]

drivers/staging/iio/adc/at91adc.c new file +3 -0
+
+#define at91adc_reg_read(base, reg)        __raw_readl((base) + (reg))
+#define at91adc_reg_write(base, reg, val)  __raw_writel((val), (base) + (reg))

Shouldn't this rather be {readl,writel}_relaxed?

Quoted 31 lines
drivers/staging/iio/adc/at91adc.c new file +31 -0
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	struct iio_chan_spec *channels;
+	int nb_chan;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 lcdr;
+	void __iomem *reg_base;
+
+};
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)
+{
+	int chan;
+	struct at91adc_state *st = private;
+	struct iio_dev *idev = iio_priv_to_dev(st);
+	unsigned int status = at91adc_reg_read(st->reg_base, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < st->nb_chan; chan++) {
+		if (status & AT91_ADC_EOC(chan)) {
+			st->done = true;
+			st->lcdr = at91adc_reg_read(st->reg_base,
+						    AT91_ADC_CHR(chan));
+		}
+	}

drivers/staging/iio/adc/at91adc.c line 68

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
+
+static int at91adc_channel_init(struct at91adc_state *st)
+{
+	int ret = 0, i;
+	st->channels = kzalloc(sizeof(struct iio_chan_spec) * st->nb_chan,
+			       GFP_KERNEL);

kcalloc

drivers/staging/iio/adc/at91adc.c line 76

Quoted 8 lines
drivers/staging/iio/adc/at91adc.c new file +8 -0
+	if (st->channels == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < st->nb_chan; i++) {
+		struct iio_chan_spec *chan = st->channels + i;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = i;

It probably makes sense to initialize scan_type as well.

drivers/staging/iio/adc/at91adc.c line 80

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +4 -0
+		++ret;
+	}
+
+	return ret;

Why do you need ret? Shouldn't this be just st->nb_chan?

Quoted 9 lines
drivers/staging/iio/adc/at91adc.c new file +9 -0
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+
+	switch (mask) {

drivers/staging/iio/adc/at91adc.c line 99

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible(st->wq_data_avail, st->done);

Its probably a good idea to add a timeout in case there is a problem with the hardware.

Quoted 40 lines
drivers/staging/iio/adc/at91adc.c new file +40 -0
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	dev_dbg(&pdev->dev, "AT91ADC probed\n");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+

drivers/staging/iio/adc/at91adc.c line 149

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	idev = iio_allocate_device(sizeof(*st));
+	if (idev == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = platform_get_device_id(pdev)->name;

You don't use device ids. This should cause a NULL pointer deref? Maybe use dev_name(&pdev->dev) instead.

Quoted 7 lines
drivers/staging/iio/adc/at91adc.c new file +7 -0
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+[...]
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)

drivers/staging/iio/adc/at91adc.c line 268

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	free_irq(st->irq, st);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_unregister(idev);
+

The iio API changed a bit recently and uses a two stage unregistration now. You should call iio_device_unregister before freeing resources and iio_device_free afterwards.

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +3 -0
+	return 0;
+}
+
[...]
#11 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Hi,

Quoted 11 lines · Lars-Peter Clausen

drivers/staging/iio/adc/at91adc.c line 27

Quoted 8 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>

---
[...]

drivers/staging/iio/adc/at91adc.c new file +3 -0
+
+#define at91adc_reg_read(base, reg)        __raw_readl((base) + (reg))
+#define at91adc_reg_write(base, reg, val)  __raw_writel((val), (base) + (reg))

Shouldn't this rather be {readl,writel}_relaxed?

Yep, corrected.

Quoted 35 lines
Quoted 35 lines
drivers/staging/iio/adc/at91adc.c new file +35 -0
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	struct iio_chan_spec *channels;
+	int nb_chan;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 lcdr;
+	void __iomem *reg_base;
+
+};
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)
+{
+	int chan;
+	struct at91adc_state *st = private;
+	struct iio_dev *idev = iio_priv_to_dev(st);
+	unsigned int status = at91adc_reg_read(st->reg_base, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < st->nb_chan; chan++) {
+		if (status & AT91_ADC_EOC(chan)) {
+			st->done = true;
+			st->lcdr = at91adc_reg_read(st->reg_base,
+						    AT91_ADC_CHR(chan));
+		}
+	}
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 68

Quoted 6 lines
drivers/staging/iio/adc/at91adc.c new file +6 -0
+
+static int at91adc_channel_init(struct at91adc_state *st)
+{
+	int ret = 0, i;
+	st->channels = kzalloc(sizeof(struct iio_chan_spec) * st->nb_chan,
+			       GFP_KERNEL);

kcalloc

Corrected

Quoted 11 lines

drivers/staging/iio/adc/at91adc.c line 76

Quoted 8 lines
drivers/staging/iio/adc/at91adc.c new file +8 -0
+	if (st->channels == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < st->nb_chan; i++) {
+		struct iio_chan_spec *chan = st->channels + i;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = i;

It probably makes sense to initialize scan_type as well.

Ah, didn't see that one, but yeah, you're right.

Quoted 6 lines

drivers/staging/iio/adc/at91adc.c line 80

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +4 -0
+		++ret;
+	}
+
+	return ret;

Why do you need ret? Shouldn't this be just st->nb_chan?

The initial plan was to make sure all channels were initialised, but obviously, my code isn't making such use of ret. You're right.

Quoted 14 lines
Quoted 14 lines
drivers/staging/iio/adc/at91adc.c new file +14 -0
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 99

Quoted 5 lines
drivers/staging/iio/adc/at91adc.c new file +5 -0
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible(st->wq_data_avail, st->done);

Its probably a good idea to add a timeout in case there is a problem with the hardware.

Indeed.

Quoted 45 lines
Quoted 45 lines
drivers/staging/iio/adc/at91adc.c new file +45 -0
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	dev_dbg(&pdev->dev, "AT91ADC probed\n");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(*st));
+	if (idev == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 149

Quoted 5 lines
drivers/staging/iio/adc/at91adc.c new file +5 -0
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = platform_get_device_id(pdev)->name;

You don't use device ids. This should cause a NULL pointer deref? Maybe use dev_name(&pdev->dev) instead.

Ok.

Quoted 13 lines
Quoted 13 lines
drivers/staging/iio/adc/at91adc.c new file +13 -0
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+[...]
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	free_irq(st->irq, st);
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 268

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +4 -0
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_unregister(idev);
+

The iio API changed a bit recently and uses a two stage unregistration now. You should call iio_device_unregister before freeing resources and iio_device_free afterwards.

Hmmm, I don't see the iio_device_free in Jonathan's git. Is it iio_free_device, or should I use another git repo/branch as base ?

Thanks,

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#12 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Quoted 17 lines · Maxime Ripard

[...]

Quoted 7 lines
Quoted 7 lines
drivers/staging/iio/adc/at91adc.c new file +7 -0
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	free_irq(st->irq, st);
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 268

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +4 -0
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_unregister(idev);
+

The iio API changed a bit recently and uses a two stage unregistration now. You should call iio_device_unregister before freeing resources and iio_device_free afterwards.

Quoted 5 lines

Hmmm, I don't see the iio_device_free in Jonathan's git. Is it iio_free_device, or should I use another git repo/branch as base ?

Thanks,

Yes, iio_free_device, sorry.

- Lars

#13 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Maxime Ripard
Quoted 3 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>

please keep me in CC

Quoted 57 lines
drivers/staging/iio/adc/at91adc.c new file +57 -0
+
+static int at91adc_channel_init(struct at91adc_state *st)
+{
+	int ret = 0, i;
+	st->channels = kzalloc(sizeof(struct iio_chan_spec) * st->nb_chan,
+			       GFP_KERNEL);
+	if (st->channels == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < st->nb_chan; i++) {
+		struct iio_chan_spec *chan = st->channels + i;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = i;
+		++ret;
+	}
+
+	return ret;
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible(st->wq_data_avail, st->done);
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,

drivers/staging/iio/adc/at91adc.c line 129

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;

do not refence it copy need for the DT

Quoted 68 lines
drivers/staging/iio/adc/at91adc.c new file +68 -0
+
+	dev_dbg(&pdev->dev, "AT91ADC probed\n");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(*st));
+	if (idev == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = platform_get_device_id(pdev)->name;
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st->reg_base, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, st);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {

drivers/staging/iio/adc/at91adc.c line 207

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}

where is the platform data struct?

Best Regards,
J.

#14 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Hi,

Quoted 5 lines · Jean-Christophe PLAGNIOL-VILLARD
Quoted 3 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>

please keep me in CC

ACK.

Quoted 60 lines
Quoted 60 lines
drivers/staging/iio/adc/at91adc.c new file +60 -0
+
+static int at91adc_channel_init(struct at91adc_state *st)
+{
+	int ret = 0, i;
+	st->channels = kzalloc(sizeof(struct iio_chan_spec) * st->nb_chan,
+			       GFP_KERNEL);
+	if (st->channels == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < st->nb_chan; i++) {
+		struct iio_chan_spec *chan = st->channels + i;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = i;
+		++ret;
+	}
+
+	return ret;
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible(st->wq_data_avail, st->done);
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 129

Quoted 7 lines
drivers/staging/iio/adc/at91adc.c new file +7 -0
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;

do not refence it copy need for the DT

Ok

Quoted 71 lines
Quoted 71 lines
drivers/staging/iio/adc/at91adc.c new file +71 -0
+
+	dev_dbg(&pdev->dev, "AT91ADC probed\n");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(*st));
+	if (idev == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = platform_get_device_id(pdev)->name;
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st->reg_base, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, st);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 207

Quoted 7 lines
drivers/staging/iio/adc/at91adc.c new file +7 -0
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}

where is the platform data struct?

In the first patch.

Regards,

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#15 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/at91sam9260_devices.c |   44 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   12 ++++++++
 2 files changed, 56 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +44 -0
index 39f81f4..4f5e0f9 100644
@@ -1312,6 +1312,50 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+
+	at91_set_A_periph(AT91_PIN_PC0, 0);
+	at91_set_A_periph(AT91_PIN_PC1, 0);
+	at91_set_A_periph(AT91_PIN_PC2, 0);
+	at91_set_A_periph(AT91_PIN_PC3, 0);
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +12 -0
index 817f59d..fd373c3 100644
@@ -314,6 +314,16 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels = 4,
+	.adc_clock = 5000000,
+	.startup_time = 10,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),
@@ -389,6 +399,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */
Signature · 1 line
1.7.4.1
#16 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

Quoted 20 lines · On Wednesday 19 October 2011 18:18:54 Maxime Ripard wrote:
(quoted diff) +0 -0

b/arch/arm/mach-at91/at91sam9260_devices.c index 39f81f4..4f5e0f9 100644

arch/arm/mach-at91/at91sam9260_devices.c +15 -0
[...]
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+

arch/arm/mach-at91/at91sam9260_devices.c void __init at91_add_device_cf(struct at91_cf_data *data) · line 1358

Quoted 10 lines
arch/arm/mach-at91/at91sam9260_devices.c +10 -0
+	at91_set_A_periph(AT91_PIN_PC0, 0);
+	at91_set_A_periph(AT91_PIN_PC1, 0);
+	at91_set_A_periph(AT91_PIN_PC2, 0);
+	at91_set_A_periph(AT91_PIN_PC3, 0);
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+

This assumes that all 4 channels are used every time. How about only using an AD channel on PC2 or all but the one on PC1?

Regards,
Alexander

#17 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

Hi,

Quoted 26 lines · Alexander Stein
Quoted 25 lines · On Wednesday 19 October 2011 18:18:54 Maxime Ripard wrote:
(quoted diff) +0 -0

b/arch/arm/mach-at91/at91sam9260_devices.c index 39f81f4..4f5e0f9 100644

arch/arm/mach-at91/at91sam9260_devices.c +20 -0
[...]
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+
+	at91_set_A_periph(AT91_PIN_PC0, 0);
+	at91_set_A_periph(AT91_PIN_PC1, 0);
+	at91_set_A_periph(AT91_PIN_PC2, 0);
+	at91_set_A_periph(AT91_PIN_PC3, 0);
+
Quoted 8 lines

arch/arm/mach-at91/at91sam9260_devices.c void __init at91_add_device_cf(struct at91_cf_data *data) · line 1358

Quoted 5 lines
arch/arm/mach-at91/at91sam9260_devices.c +5 -0
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+

This assumes that all 4 channels are used every time. How about only using an AD channel on PC2 or all but the one on PC1?

You're right. I've implemented Thomas suggestion to define the channels in use in a board specific manner. I need to test my changes and will submit a new version by monday.

Thanks,

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#18 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

In reply to Maxime Ripard

Le Wed, 19 Oct 2011 18:18:54 +0200,
Maxime Ripard <[email protected]> a ?crit :

arch/arm/mach-at91/at91sam9260_devices.c void __init at91_add_device_cf(struct at91_cf_data *data) · line 1352

Quoted 9 lines
arch/arm/mach-at91/at91sam9260_devices.c +9 -0
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+
+	at91_set_A_periph(AT91_PIN_PC0, 0);
+	at91_set_A_periph(AT91_PIN_PC1, 0);
+	at91_set_A_periph(AT91_PIN_PC2, 0);
+	at91_set_A_periph(AT91_PIN_PC3, 0);

As suggested, the decision of which ADC channels are used and therefore which pins should be muxed to function A is a board-specific decision and should not be enforced by SoC code.

arch/arm/mach-at91/board-sam9g20ek.c static void __init ek_add_device_buttons(void) · line 325

Quoted 5 lines
arch/arm/mach-at91/board-sam9g20ek.c +5 -0
+static struct at91_adc_data ek_adc_data = {
+	.channels = 4,
+	.adc_clock = 5000000,
+	.startup_time = 10,
+};

And on the opposite, those informations are specific to a SoC, not to a board. So to avoid duplication in all board files, they should be moved to the corresponding SoC file.

Maybe something like:

#define AT91_ADC_MAX_CHANNELS 8

struct at91_adc_data {
    unsigned int adc_clock;
    u8 channels_used[AT91_ADC_MAX_CHANNELS];
    u8 startup_time;
    u8 channels;
};
at91_add_device_adc() {
	if (data->channels_used[0])
		at91_set_A_periph(AT91_PIN_PC0, 0);
	if (data->channels_used[1])
		at91_set_A_periph(AT91_PIN_PC1, 0);
	if (data->channels_used[2])
		at91_set_A_periph(AT91_PIN_PC2, 0);
	if (data->channels_used[3])
		at91_set_A_periph(AT91_PIN_PC3, 0);
	data->startup_time = ...
	data->channels = ...
	data->adc_clock = ...
}

and the board file would do:

struct at91_adc_data ek_adc_data = {
	.channels_used = { 1, 0, 1, 1 },;
};

Or maybe others have different suggestions ?

Regards,

Thomas

Signature · 4 lines
Thomas Petazzoni, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#19 lore raw

PATCH v2 1/1 AT91: Add a driver for the ADC

In reply to Maxime Ripard

Hi all,

This patchset adds the driver for the ADC in the AT91 SoC. It has been tested on the at91sam9g20ek and should work on sam9g45 as well.

For now, it only reads values when asked for by sysfs, but eventually will support hardware triggers and more boards.

This patchset is based on the branch master of Jonathan Cameron present at :
https://github.com/jic23/linux-iio/, commit 85d8ff8

Improvements since v1:
- Rebase on top of commit 85d8ff8
- Initialise scan_types
- Added scale informations in the driver
- Allow to use only a subset of adc channels available on the SoC
- Various fix according to reviews

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
#20 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |   22 ++++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +22 -0
index ed544a0..b837da8 100644
@@ -207,4 +207,26 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 
+/* ADC */
+#define AT91_ADC_MAX_CHANNELS   16
+
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */
+	u8 channels_used[AT91_ADC_MAX_CHANNELS];
+	/* Number of channels in use */
+	u8 num_channels_used;
+	/* Startup time of the ADC, in microseconds. */
+	u8 startup_time;
+	/* Reference voltage for the ADC in millivolts */
+	unsigned short vref;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif
Signature · 1 line
1.7.4.1
#21 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

2011/11/3 Maxime Ripard <[email protected]>:

Quoted 6 lines
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>

---
?arch/arm/mach-at91/include/mach/board.h | ? 22 ++++++++++++++++++++++ ?1 files changed, 22 insertions(+), 0 deletions(-)

We're not supposed to have platform data dependent to stuff in staging under arch/arm or anyplace else in the main kernel tree.

Please move this to
drivers/staging/iio/adc/at91adc-board.h
or so.

As for calling the at91_add_device_adc() function (which I guess you want to do at some point) the pattern I followed for other drivers is to declare a dummy function in arch/arm/mach-* with __weak and let the staging driver override that. This way the staging driver can go away without any compilation trouble happening.

Yours,
Linus Walleij

#22 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Hi Linus,

Quoted 9 lines · Linus Walleij

2011/11/3 Maxime Ripard <[email protected]>:

Quoted 6 lines
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |   22 ++++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
Quoted 6 lines

We're not supposed to have platform data dependent to stuff in staging under arch/arm or anyplace else in the main kernel tree.

Please move this to
drivers/staging/iio/adc/at91adc-board.h
or so.

Won't moving this part to staging prevent from using this structure in board files ? If so, how will I be able to declare a new board that is using this ADC (or add the support for the ADC to a new one) ?

Quoted 6 lines

As for calling the at91_add_device_adc() function (which I guess you want to do at some point) the pattern I followed for other drivers is to declare a dummy function in arch/arm/mach-* with __weak and let the staging driver override that. This way the staging driver can go away without any compilation trouble happening.

I don't really see why my changes will break the compilation if the driver is no longer present in staging. At worst, the structure will be filled but used by no one, right ?

Regards,

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#23 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Quoted 11 lines · Maxime Ripard

[Me]

Quoted 6 lines

We're not supposed to have platform data dependent to stuff in staging under arch/arm or anyplace else in the main kernel tree.

Please move this to
drivers/staging/iio/adc/at91adc-board.h
or so.

Won't moving this part to staging prevent from using this structure in board files ? If so, how will I be able to declare a new board that is using this ADC (or add the support for the ADC to a new one) ?

Put this into a separate board file living under staging/iio/adc

Compare:
drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c

Quoted 10 lines
Quoted 6 lines

As for calling the at91_add_device_adc() function (which I guess you want to do at some point) the pattern I followed for other drivers is to declare a dummy function in arch/arm/mach-* with __weak and let the staging driver override that. This way the staging driver can go away without any compilation trouble happening.

I don't really see why my changes will break the compilation if the driver is no longer present in staging. At worst, the structure will be filled but used by no one, right ?

You're right.

But still, we cannot add that header file for a driver that is in the staging tree. Header files go into the staging dir too.

Yours,
Linus Walleij

#24 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Quoted 26 lines · Linus Walleij
Quoted 11 lines · Maxime Ripard

[Me]

Quoted 6 lines

We're not supposed to have platform data dependent to stuff in staging under arch/arm or anyplace else in the main kernel tree.

Please move this to
drivers/staging/iio/adc/at91adc-board.h
or so.

Won't moving this part to staging prevent from using this structure in board files ? If so, how will I be able to declare a new board that is using this ADC (or add the support for the ADC to a new one) ?

Put this into a separate board file living under staging/iio/adc

Compare:
drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c

Quoted 7 lines
Quoted 6 lines

As for calling the at91_add_device_adc() function (which I guess you want to do at some point) the pattern I followed for other drivers is to declare a dummy function in arch/arm/mach-* with __weak and let the staging driver override that. This way the staging driver can go away without any compilation trouble happening.

Quoted 8 lines
Quoted 3 lines

I don't really see why my changes will break the compilation if the driver is no longer present in staging. At worst, the structure will be filled but used by no one, right ?

You're right.

But still, we cannot add that header file for a driver that is in the staging tree. Header files go into the staging dir too.

agreed

Best Regards,
J.

#25 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

In reply to Maxime Ripard
Quoted 38 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |   22 ++++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +22 -0
index ed544a0..b837da8 100644
@@ -207,4 +207,26 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 
+/* ADC */
+#define AT91_ADC_MAX_CHANNELS   16
+
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */
+	u8 channels_used[AT91_ADC_MAX_CHANNELS];
+	/* Number of channels in use */
+	u8 num_channels_used;
+	/* Startup time of the ADC, in microseconds. */
+	u8 startup_time;
+	/* Reference voltage for the ADC in millivolts */
+	unsigned short vref;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif
#26 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Quoted 31 lines · Jonathan Cameron
Quoted 30 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |   22 ++++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +15 -0
index ed544a0..b837da8 100644
@@ -207,4 +207,26 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 
+/* ADC */
+#define AT91_ADC_MAX_CHANNELS   16
+
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */
+	u8 channels_used[AT91_ADC_MAX_CHANNELS];
+	/* Number of channels in use */
+	u8 num_channels_used;
Quoted 8 lines

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 232

Quoted 8 lines
arch/arm/mach-at91/include/mach/board.h +7 -0
+	/* Startup time of the ADC, in microseconds. */
+	u8 startup_time;
+	/* Reference voltage for the ADC in millivolts */
+	unsigned short vref;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif

oops. version with actual comments should also have gone out.

#27 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

In reply to Maxime Ripard
Quoted 17 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |   22 ++++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +2 -0
index ed544a0..b837da8 100644
@@ -207,4 +207,26 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 
+/* ADC */
+#define AT91_ADC_MAX_CHANNELS   16

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 221

Quoted 10 lines
arch/arm/mach-at91/include/mach/board.h +10 -0
+
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */

put it in a unsigned long and use the bitmap functions.

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 223

Quoted 2 lines
arch/arm/mach-at91/include/mach/board.h +2 -0
+	u8 channels_used[AT91_ADC_MAX_CHANNELS];
+	/* Number of channels in use */

just use bitmap_weight with the long above and
length set to num_channels.

Quoted 9 lines
arch/arm/mach-at91/include/mach/board.h +8 -0
+	u8 num_channels_used;
+	/* Startup time of the ADC, in microseconds. */
+	u8 startup_time;
+	/* Reference voltage for the ADC in millivolts */
+	unsigned short vref;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif
#28 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Hi Jonathan,

Quoted 14 lines · Jonathan Cameron

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 221

Quoted 12 lines · Maxime Ripard
arch/arm/mach-at91/include/mach/board.h +12 -0
+/* ADC */
+#define AT91_ADC_MAX_CHANNELS   16
+
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */

put it in a unsigned long and use the bitmap functions.

Quoted 5 lines

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 223

Quoted 2 lines
arch/arm/mach-at91/include/mach/board.h +2 -0
+	u8 channels_used[AT91_ADC_MAX_CHANNELS];
+	/* Number of channels in use */

just use bitmap_weight with the long above and
length set to num_channels.

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 224

Quoted 1 line
arch/arm/mach-at91/include/mach/board.h +1 -0
+	u8 num_channels_used;

I didn't know bitmap functions before you mentioned it, but if I read it correctly, while I agree with your point, I will lose the ability to define a combination of enabled and disabled channel in a board specific manner.

In the third patch, I defined channels_used as an array of one. But let's say that instead I want all channels except the second one. With what's in the driver for now, I initialise it to {1, 0, 1, 1}. I don't think it's possible to do so with bitmap functions, or am I missing something ?

Or put the calls to bitmap_set in the board_init function ?

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#29 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Quoted 28 lines · Maxime Ripard

Hi Jonathan,

Quoted 14 lines · Jonathan Cameron

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 221

Quoted 12 lines · Maxime Ripard
arch/arm/mach-at91/include/mach/board.h +12 -0
+/* ADC */
+#define AT91_ADC_MAX_CHANNELS   16
+
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */

put it in a unsigned long and use the bitmap functions.

Quoted 5 lines

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 223

Quoted 2 lines
arch/arm/mach-at91/include/mach/board.h +2 -0
+	u8 channels_used[AT91_ADC_MAX_CHANNELS];
+	/* Number of channels in use */

just use bitmap_weight with the long above and
length set to num_channels.

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 224

Quoted 1 line
arch/arm/mach-at91/include/mach/board.h +1 -0
+	u8 num_channels_used;

I didn't know bitmap functions before you mentioned it, but if I read it correctly, while I agree with your point, I will lose the ability to define a combination of enabled and disabled channel in a board specific manner.

Quoted 5 lines

In the third patch, I defined channels_used as an array of one. But let's say that instead I want all channels except the second one. With what's in the driver for now, I initialise it to {1, 0, 1, 1}. I don't think it's possible to do so with bitmap functions, or am I missing something ?

unsigned long bitmask = BIT(0) | BIT(1) | BIT(3);

Bitmaps are just arrays of unsigned longs. If you 'know' you have less than 32 elements, you can just use a single unsigned long and this assignment gets easier.

Most of the stuff you want comes from bitops.h anyway (maybe all come to think of it?)

Quoted 2 lines

Or put the calls to bitmap_set in the board_init function ?

Just edit the underlying unsigned long directly. The fact that the bitops.h stuff is supposed to work on a bitmap as well means that will always work.

Jonathan

#30 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
---
 drivers/staging/iio/adc/Kconfig   |    6 +
 drivers/staging/iio/adc/Makefile  |    1 +
 drivers/staging/iio/adc/at91adc.c |  310 +++++++++++++++++++++++++++++++++++++
 3 files changed, 317 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/adc/at91adc.c
drivers/staging/iio/adc/Kconfig +6 -0
index d9decea..c384894 100644
@@ -169,6 +169,12 @@ config AD7280
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad7280a
 
+config AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/staging/iio/adc/Makefile +1 -0
index ceee7f3..d250d06 100644
@@ -37,3 +37,4 @@ obj-$(CONFIG_AD7192) += ad7192.o
 obj-$(CONFIG_ADT7310) += adt7310.o
 obj-$(CONFIG_ADT7410) += adt7410.o
 obj-$(CONFIG_AD7280) += ad7280a.o
+obj-$(CONFIG_AT91ADC) += at91adc.o
drivers/staging/iio/adc/at91adc.c new file +310 -0

310 lines not on this page · raw message

Signature · 1 line
1.7.4.1
#31 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Quoted 43 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
---
 drivers/staging/iio/adc/Kconfig   |    6 +
 drivers/staging/iio/adc/Makefile  |    1 +
 drivers/staging/iio/adc/at91adc.c |  310 +++++++++++++++++++++++++++++++++++++
 3 files changed, 317 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/adc/at91adc.c
drivers/staging/iio/adc/Kconfig +6 -0
index d9decea..c384894 100644
@@ -169,6 +169,12 @@ config AD7280
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad7280a
 
+config AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/staging/iio/adc/Makefile +1 -0
index ceee7f3..d250d06 100644
@@ -37,3 +37,4 @@ obj-$(CONFIG_AD7192) += ad7192.o
 obj-$(CONFIG_ADT7310) += adt7310.o
 obj-$(CONFIG_ADT7410) += adt7410.o
 obj-$(CONFIG_AD7280) += ad7280a.o
+obj-$(CONFIG_AT91ADC) += at91adc.o
drivers/staging/iio/adc/at91adc.c new file +0 -0
new file mode 100644
index 0000000..acf656d

drivers/staging/iio/adc/at91adc.c line 3

Quoted 3 lines
drivers/staging/iio/adc/at91adc.c new file +3 -0
@@ -0,0 +1,310 @@
+/*
+ * Driver for the TouchScreen ADC Controller present in the Atmel AT91
+ * evaluation boards.

I'd add something here to make it clear that it isn't being used for touchscreen input in this driver.

Quoted 12 lines
drivers/staging/iio/adc/at91adc.c new file +12 -0
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>

drivers/staging/iio/adc/at91adc.c line 25

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "../iio.h"
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+

I'd marginally prefer to see these as inlines rather than macros. Far from critical though!

drivers/staging/iio/adc/at91adc.c line 32

Quoted 7 lines
drivers/staging/iio/adc/at91adc.c new file +7 -0
+#define at91adc_reg_read(base, reg)        readl_relaxed((base) + (reg))
+#define at91adc_reg_write(base, reg, val)  writel_relaxed((val), (base) + (reg))
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;

why hold a local pointer? Just put these
directly into iio_dev->channels.

drivers/staging/iio/adc/at91adc.c line 33

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	struct iio_chan_spec *channels;

likewise, we already have iio_dev->num_channels.

drivers/staging/iio/adc/at91adc.c line 36

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +3 -0
+	int nb_chan;
 
+	int irq;
+	wait_queue_head_t wq_data_avail;

cryptic element. Nice to have everything documented, but this one is most important as it isn't self explanatory.

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +4 -0
+	u16 lcdr;
+	void __iomem *reg_base;
+	unsigned int vref_mv;
+};

drivers/staging/iio/adc/at91adc.c line 50

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)
+{
+	int chan;
+	struct at91adc_state *st = private;
+	unsigned int status = at91adc_reg_read(st->reg_base, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+

extra unneeded brackets for the for loop.

Quoted 11 lines
drivers/staging/iio/adc/at91adc.c new file +11 -0
+	for (chan = 0; chan < st->nb_chan; chan++) {
+		if (status & AT91_ADC_EOC(chan)) {
+			st->done = true;
+			st->lcdr = at91adc_reg_read(st->reg_base,
+						    AT91_ADC_CHR(chan));
+		}
+	}
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}

drivers/staging/iio/adc/at91adc.c line 71

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+
+static int at91adc_channel_init(struct at91adc_state *st,
+				struct at91_adc_data *pdata)
+{
+	int i, idx = 0;
+	st->channels = kcalloc(pdata->num_channels_used,
+			       sizeof(struct iio_chan_spec), GFP_KERNEL);
+	if (st->channels == NULL)
+		return -ENOMEM;
+

Again, extra brackets. Also, some wrong spacing for the if. can you run checkpatch.pl over this again and make sure you have fixed everything.

drivers/staging/iio/adc/at91adc.c line 72

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	for (i = 0; i < st->nb_chan; i++) {

as stated for the previous patch, this should be a bitmap rather
than array of u8's. You can then use for_each_bit_set
to clean this code up nicely ;)

drivers/staging/iio/adc/at91adc.c line 80

Quoted 8 lines
drivers/staging/iio/adc/at91adc.c new file +8 -0
+		if(pdata->channels_used[i]) {
+			struct iio_chan_spec *chan = st->channels + idx;
+			chan->type = IIO_VOLTAGE;
+			chan->indexed = 1;
+			chan->channel = i;
+			chan->scan_type.sign = 's';
+			chan->scan_type.realbits = 10;
+			chan->scan_type.storagebits = 32;

That is pretty inefficient storage! If we do implement buffering on this, given mass reads don't look to be quicker, we'll just munge it down to 16 bits.

drivers/staging/iio/adc/at91adc.c line 81

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+			chan->scan_type.shift = 0;

don't bother initialising this value as it is zero (and that's an obvious default).

Quoted 61 lines
drivers/staging/iio/adc/at91adc.c new file +61 -0
+			chan->info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT;
+			++idx;
+		}
+	}
+
+	return st->nb_chan;
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int scale_uv;
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible_timeout(st->wq_data_avail, st->done,
+						 msecs_to_jiffies(10 * 1000));
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits;
+		*val = scale_uv / 1000;
+		*val2 = (scale_uv % 1000) * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;

drivers/staging/iio/adc/at91adc.c line 152

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(struct at91adc_state));

sizeof(*st) is a little neater?

drivers/staging/iio/adc/at91adc.c line 153

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	if (idev == NULL) {

I'm not sure this message adds anything or is even necessarily correct as that function doesn't just allocate memory!

Quoted 75 lines
drivers/staging/iio/adc/at91adc.c new file +75 -0
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = dev_name(&pdev->dev);
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st->reg_base, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, st);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+

drivers/staging/iio/adc/at91adc.c line 238

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st->reg_base, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	st->nb_chan = pdata->num_channels;
+	ret = at91adc_channel_init(st, pdata);
+	if (ret < 0) {

no brackets please. Again, checkpatch would probably have shouted about this.

drivers/staging/iio/adc/at91adc.c line 214

Quoted 3 lines
drivers/staging/iio/adc/at91adc.c new file +3 -0
+		goto error_free_clk;
+	}
+

This is what I meant when I asked why you have two copies of the pointer and count?

drivers/staging/iio/adc/at91adc.c line 250

Quoted 9 lines
drivers/staging/iio/adc/at91adc.c new file +9 -0
+	idev->channels = st->channels;
+	idev->num_channels = pdata->num_channels_used;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	st->vref_mv = pdata->vref;
+
+	ret = iio_device_register(idev);

again incorrect brackets for the code style.

drivers/staging/iio/adc/at91adc.c

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	if (ret < 0) {

unwind the channel init?

Quoted 17 lines
drivers/staging/iio/adc/at91adc.c new file +17 -0
+		goto error_free_clk;
+	}
+
+	return 0;
+
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_free_device(idev);
+error_ret:

drivers/staging/iio/adc/at91adc.c line 278

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	iio_device_unregister(idev);

Free channels array?

Quoted 16 lines
drivers/staging/iio/adc/at91adc.c new file +16 -0
+	free_irq(st->irq, st);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_free_device(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),
+	.driver = {
+		   .name = "at91adc",
+		   },
+};
+

drivers/staging/iio/adc/at91adc.c line 304

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+static int __init at91adc_init(void)
+{
+	return platform_driver_register(&at91adc_driver);
+}
+
+static void __exit at91adc_exit(void)
+{
+	platform_driver_unregister(&at91adc_driver);
+}
+

Some neat boiler plate removal stuff was just merged for platform drivers. See module_platform_driver in include/linux/platform.h

Definitely want to use that! I'm hoping the spi and i2c versions turn up soon as it will save us a lot of silly cut and paste code.

Quoted 6 lines
drivers/staging/iio/adc/at91adc.c new file +6 -0
+module_init(at91adc_init);
+module_exit(at91adc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atmel AT91 ADC Driver");
+MODULE_AUTHOR("Maxime Ripard <[email protected]>");
#32 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Quoted 12 lines · Jonathan Cameron

drivers/staging/iio/adc/at91adc.c line 3

Quoted 9 lines · Maxime Ripard
drivers/staging/iio/adc/at91adc.c new file +3 -0
new file mode 100644
index 0000000..acf656d
@@ -0,0 +1,310 @@
+/*
+ * Driver for the TouchScreen ADC Controller present in the Atmel AT91
+ * evaluation boards.

I'd add something here to make it clear that it isn't being used for touchscreen input in this driver.

Indeed, I forgot to change it here as well.

Quoted 16 lines
Quoted 16 lines
drivers/staging/iio/adc/at91adc.c new file +16 -0
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 25

Quoted 6 lines
drivers/staging/iio/adc/at91adc.c new file +6 -0
+
+#include "../iio.h"
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+

I'd marginally prefer to see these as inlines rather than macros. Far from critical though!

Ok.

Quoted 9 lines

drivers/staging/iio/adc/at91adc.c line 32

Quoted 7 lines
drivers/staging/iio/adc/at91adc.c new file +7 -0
+#define at91adc_reg_read(base, reg)        readl_relaxed((base) + (reg))
+#define at91adc_reg_write(base, reg, val)  writel_relaxed((val), (base) + (reg))
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;

why hold a local pointer? Just put these
directly into iio_dev->channels.

Quoted 3 lines

drivers/staging/iio/adc/at91adc.c line 33

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	struct iio_chan_spec *channels;

likewise, we already have iio_dev->num_channels.

drivers/staging/iio/adc/at91adc.c line 34

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	int nb_chan;

Indeed

Quoted 4 lines

drivers/staging/iio/adc/at91adc.c line 36

Quoted 2 lines
drivers/staging/iio/adc/at91adc.c new file +2 -0
+	int irq;
+	wait_queue_head_t wq_data_avail;

cryptic element. Nice to have everything documented, but this one is most important as it isn't self explanatory.

Quoted 15 lines
Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +4 -0
+	u16 lcdr;
+	void __iomem *reg_base;
+	unsigned int vref_mv;
+};

drivers/staging/iio/adc/at91adc.c line 50

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)
+{
+	int chan;
+	struct at91adc_state *st = private;
+	unsigned int status = at91adc_reg_read(st->reg_base, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+

extra unneeded brackets for the for loop.

I'm confused here. While it makes sense, I find it more readable around large blocks of code and checkpatch doesn't complain on that one. But coding style is a huge troll, so will change.

Quoted 28 lines
Quoted 11 lines
drivers/staging/iio/adc/at91adc.c new file +11 -0
+	for (chan = 0; chan < st->nb_chan; chan++) {
+		if (status & AT91_ADC_EOC(chan)) {
+			st->done = true;
+			st->lcdr = at91adc_reg_read(st->reg_base,
+						    AT91_ADC_CHR(chan));
+		}
+	}
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}

drivers/staging/iio/adc/at91adc.c line 71

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+
+static int at91adc_channel_init(struct at91adc_state *st,
+				struct at91_adc_data *pdata)
+{
+	int i, idx = 0;
+	st->channels = kcalloc(pdata->num_channels_used,
+			       sizeof(struct iio_chan_spec), GFP_KERNEL);
+	if (st->channels == NULL)
+		return -ENOMEM;
+

Again, extra brackets. Also, some wrong spacing for the if. can you run checkpatch.pl over this again and make sure you have fixed everything.

drivers/staging/iio/adc/at91adc.c line 72

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	for (i = 0; i < st->nb_chan; i++) {

as stated for the previous patch, this should be a bitmap rather
than array of u8's. You can then use for_each_bit_set
to clean this code up nicely ;)

Quoted 11 lines

drivers/staging/iio/adc/at91adc.c line 80

Quoted 8 lines
drivers/staging/iio/adc/at91adc.c new file +8 -0
+		if(pdata->channels_used[i]) {
+			struct iio_chan_spec *chan = st->channels + idx;
+			chan->type = IIO_VOLTAGE;
+			chan->indexed = 1;
+			chan->channel = i;
+			chan->scan_type.sign = 's';
+			chan->scan_type.realbits = 10;
+			chan->scan_type.storagebits = 32;

That is pretty inefficient storage! If we do implement buffering on this, given mass reads don't look to be quicker, we'll just munge it down to 16 bits.

Well, I thought that storage bits was here to represent how the hardware stored the values. Since these values are stored on 10 bits alone in a 32 bits register, I thought that these were the right values, but we can definitely lower the storagebits to 16 here.

Quoted 68 lines

drivers/staging/iio/adc/at91adc.c line 81

Quoted 2 lines
drivers/staging/iio/adc/at91adc.c new file +1 -0
+			chan->scan_type.shift = 0;

don't bother initialising this value as it is zero (and that's an obvious default).

Quoted 64 lines
drivers/staging/iio/adc/at91adc.c new file +64 -0
+			chan->info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT;
+			++idx;
+		}
+	}
+
+	return st->nb_chan;
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int scale_uv;
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible_timeout(st->wq_data_avail, st->done,
+						 msecs_to_jiffies(10 * 1000));
+		*val = st->lcdr;
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->lcdr = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits;
+		*val = scale_uv / 1000;
+		*val2 = (scale_uv % 1000) * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Quoted 8 lines

drivers/staging/iio/adc/at91adc.c line 152

Quoted 7 lines
drivers/staging/iio/adc/at91adc.c new file +7 -0
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(struct at91adc_state));

sizeof(*st) is a little neater?

Well, I find sizeof(struct at91adc_state) clearer, but ok.

Quoted 3 lines

drivers/staging/iio/adc/at91adc.c line 153

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	if (idev == NULL) {

I'm not sure this message adds anything or is even necessarily correct as that function doesn't just allocate memory!

Right

Quoted 102 lines
Quoted 75 lines
drivers/staging/iio/adc/at91adc.c new file +75 -0
+		dev_err(&pdev->dev, "Failed to allocate memory.\n");
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = dev_name(&pdev->dev);
+	idev->modes = INDIO_DIRECT_MODE;
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st->reg_base, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, st);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+

drivers/staging/iio/adc/at91adc.c line 238

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st->reg_base, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	st->nb_chan = pdata->num_channels;
+	ret = at91adc_channel_init(st, pdata);
+	if (ret < 0) {

no brackets please. Again, checkpatch would probably have shouted about this.

drivers/staging/iio/adc/at91adc.c line 214

Quoted 3 lines
drivers/staging/iio/adc/at91adc.c new file +3 -0
+		goto error_free_clk;
+	}
+

This is what I meant when I asked why you have two copies of the pointer and count?

drivers/staging/iio/adc/at91adc.c line 250

Quoted 9 lines
drivers/staging/iio/adc/at91adc.c new file +9 -0
+	idev->channels = st->channels;
+	idev->num_channels = pdata->num_channels_used;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	st->vref_mv = pdata->vref;
+
+	ret = iio_device_register(idev);

again incorrect brackets for the code style.

Quoted 2 lines

drivers/staging/iio/adc/at91adc.c line 251

Quoted 1 line
drivers/staging/iio/adc/at91adc.c new file +1 -0
+	if (ret < 0) {

unwind the channel init?

Oooh, right...

Quoted 58 lines
Quoted 17 lines
drivers/staging/iio/adc/at91adc.c new file +17 -0
+		goto error_free_clk;
+	}
+
+	return 0;
+
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_free_device(idev);
+error_ret:

drivers/staging/iio/adc/at91adc.c line 278

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	iio_device_unregister(idev);

Free channels array?

Quoted 16 lines
drivers/staging/iio/adc/at91adc.c new file +16 -0
+	free_irq(st->irq, st);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_free_device(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),
+	.driver = {
+		   .name = "at91adc",
+		   },
+};
+

drivers/staging/iio/adc/at91adc.c line 304

Quoted 10 lines
drivers/staging/iio/adc/at91adc.c new file +10 -0
+static int __init at91adc_init(void)
+{
+	return platform_driver_register(&at91adc_driver);
+}
+
+static void __exit at91adc_exit(void)
+{
+	platform_driver_unregister(&at91adc_driver);
+}
+

Some neat boiler plate removal stuff was just merged for platform drivers. See module_platform_driver in include/linux/platform.h

Quoted 2 lines

Definitely want to use that! I'm hoping the spi and i2c versions turn up soon as it will save us a lot of silly cut and paste code.

Aah, nice :)

Thanks,

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
maintainer
#33 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

...

Quoted 12 lines
Quoted 11 lines

drivers/staging/iio/adc/at91adc.c line 80

Quoted 8 lines
drivers/staging/iio/adc/at91adc.c new file +8 -0
+		if(pdata->channels_used[i]) {
+			struct iio_chan_spec *chan = st->channels + idx;
+			chan->type = IIO_VOLTAGE;
+			chan->indexed = 1;
+			chan->channel = i;
+			chan->scan_type.sign = 's';
+			chan->scan_type.realbits = 10;
+			chan->scan_type.storagebits = 32;

That is pretty inefficient storage! If we do implement buffering on this, given mass reads don't look to be quicker, we'll just munge it down to 16 bits.

Quoted 5 lines

Well, I thought that storage bits was here to represent how the hardware stored the values. Since these values are stored on 10 bits alone in a 32 bits register, I thought that these were the right values, but we can definitely lower the storagebits to 16 here.

It's actually the storage in a buffer if we use one. scan_type doesn't actually need defining at all if we have no buffered support (it's not used by the core). Clearly if buffering is used, 32 bits 'might' give us slightly fewer copies, but at the cost of double the memory usage. It would take some pretty impressive benchmark figures to convince that it was worth the space.

Quoted 7 lines
Quoted 5 lines

drivers/staging/iio/adc/at91adc.c line 152

Quoted 4 lines
drivers/staging/iio/adc/at91adc.c new file +4 -0
+		goto error_ret;
+	}
+
+	idev = iio_allocate_device(sizeof(struct at91adc_state));

sizeof(*st) is a little neater?

Well, I find sizeof(struct at91adc_state) clearer, but ok.

I don't care so stick with it if you prefer!

#34 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/at91sam9260_devices.c |   52 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   12 +++++++
 2 files changed, 64 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +52 -0
index 39f81f4..bfc50ac 100644
@@ -1312,6 +1312,58 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+
+	if(data->channels_used[0])
+		at91_set_A_periph(AT91_PIN_PC0, 0);
+	if(data->channels_used[1])
+		at91_set_A_periph(AT91_PIN_PC1, 0);
+	if(data->channels_used[2])
+		at91_set_A_periph(AT91_PIN_PC2, 0);
+	if(data->channels_used[3])
+		at91_set_A_periph(AT91_PIN_PC3, 0);
+
+	data->adc_clock = 5000000;
+	data->num_channels = 4;
+	data->startup_time = 10;
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +12 -0
index 817f59d..6f2542fb 100644
@@ -314,6 +314,16 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels_used = { 1, 1, 1, 1} ,
+	.num_channels_used = 4,
+	.vref = 3300,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),
@@ -389,6 +399,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */
Signature · 1 line
1.7.4.1
#35 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

Forgot to put some general comments in the previous patch reviews.

Please run checkpatch.pl over all patches as it will clean up quite a few issues.

Mostly looking good. At the moment it's simple enough that we can rapidly move this one out of staging once the core has gone.

Then we can build up more interesting stuff as support goes into the non staging core.

Thanks,

Jonathan

Quoted 48 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/at91sam9260_devices.c |   52 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   12 +++++++
 2 files changed, 64 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +32 -0
index 39f81f4..bfc50ac 100644
@@ -1312,6 +1312,58 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)

arch/arm/mach-at91/at91sam9260_devices.c void __init at91_add_device_cf(struct at91_cf_data *data) · line 1356

Quoted 10 lines
arch/arm/mach-at91/at91sam9260_devices.c +10 -0
+		return;
+
+	if(data->channels_used[0])
+		at91_set_A_periph(AT91_PIN_PC0, 0);
+	if(data->channels_used[1])
+		at91_set_A_periph(AT91_PIN_PC1, 0);
+	if(data->channels_used[2])
+		at91_set_A_periph(AT91_PIN_PC2, 0);
+	if(data->channels_used[3])
+		at91_set_A_periph(AT91_PIN_PC3, 0);

formatting issues so another one for checkpatch.

Also define AT91_PIN_PC(n) then make this another for_each_bit_set call. That should get you down from 8 lines to about 3.

Quoted 43 lines
arch/arm/mach-at91/at91sam9260_devices.c +10 -0
+
+	data->adc_clock = 5000000;
+	data->num_channels = 4;
+	data->startup_time = 10;
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +12 -0
index 817f59d..6f2542fb 100644
@@ -314,6 +314,16 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels_used = { 1, 1, 1, 1} ,
+	.num_channels_used = 4,
+	.vref = 3300,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),
@@ -389,6 +399,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */
#36 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

Hi Jonathan,

Thanks for your review, I'm working on it and will submit a new version asap.

Quoted 2 lines · Jonathan Cameron

Please run checkpatch.pl over all patches as it will clean up quite a few issues.

Yep, I forgot to run it, my bad.

Quoted 2 lines

Mostly looking good. At the moment it's simple enough that we can rapidly move this one out of staging once the core has gone.

Ok. Rebasing on your outofstaging branch for the next version would be a good idea ?

Or do you want the driver to still go through the staging step ?

Linus, I guess that if we put the driver directly into the main tree you are ok with the changes made to boards files (code reviews apart) ?

Thanks,
Maxime

Quoted 113 lines · Maxime Ripard
Quoted 48 lines
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/at91sam9260_devices.c |   52 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   12 +++++++
 2 files changed, 64 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +32 -0
index 39f81f4..bfc50ac 100644
@@ -1312,6 +1312,58 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)

arch/arm/mach-at91/at91sam9260_devices.c void __init at91_add_device_cf(struct at91_cf_data *data) · line 1356

Quoted 10 lines
arch/arm/mach-at91/at91sam9260_devices.c +10 -0
+		return;
+
+	if(data->channels_used[0])
+		at91_set_A_periph(AT91_PIN_PC0, 0);
+	if(data->channels_used[1])
+		at91_set_A_periph(AT91_PIN_PC1, 0);
+	if(data->channels_used[2])
+		at91_set_A_periph(AT91_PIN_PC2, 0);
+	if(data->channels_used[3])
+		at91_set_A_periph(AT91_PIN_PC3, 0);

formatting issues so another one for checkpatch.

Also define AT91_PIN_PC(n) then make this another for_each_bit_set call. That should get you down from 8 lines to about 3.

Quoted 35 lines
arch/arm/mach-at91/at91sam9260_devices.c +10 -0
+
+	data->adc_clock = 5000000;
+	data->num_channels = 4;
+	data->startup_time = 10;
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +10 -0
index 817f59d..6f2542fb 100644
@@ -314,6 +314,16 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels_used = { 1, 1, 1, 1} ,
+	.num_channels_used = 4,
+	.vref = 3300,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),

arch/arm/mach-at91/board-sam9g20ek.c static void __init ek_board_init(void) · line 406

Quoted 8 lines
arch/arm/mach-at91/board-sam9g20ek.c +2 -0
@@ -389,6 +399,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel at lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#37 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

Quoted 7 lines · Maxime Ripard

Ok. Rebasing on your outofstaging branch for the next version would be a good idea ?

Or do you want the driver to still go through the staging step ?

Linus, I guess that if we put the driver directly into the main tree you are ok with the changes made to boards files (code reviews apart) ?

Yes that's fine.

Linus Walleij

maintainer
#38 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

In reply to Maxime Ripard
Quoted 11 lines · Maxime Ripard

Hi Jonathan,

Thanks for your review, I'm working on it and will submit a new version asap.

Quoted 2 lines · Jonathan Cameron

Please run checkpatch.pl over all patches as it will clean up quite a few issues.

Yep, I forgot to run it, my bad.

Quoted 7 lines
Quoted 2 lines

Mostly looking good. At the moment it's simple enough that we can rapidly move this one out of staging once the core has gone.

Ok. Rebasing on your outofstaging branch for the next version would be a good idea ?

Or do you want the driver to still go through the staging step ?

I don't really mind. I doubt Greg would send it on in this merge window anyway. Going from one to the other is trivial change of about 4 function names anyway for a simple driver like this. It probably mostly depends on whether you want to mess around with the more complex bits that are going to stay in staging for sometime yet (buffering, in kernel push interface, events).

Quoted 122 lines

Linus, I guess that if we put the driver directly into the main tree you are ok with the changes made to boards files (code reviews apart) ?

Thanks,
Maxime

Quoted 113 lines · Maxime Ripard
Quoted 48 lines
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
---
 arch/arm/mach-at91/at91sam9260_devices.c |   52 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   12 +++++++
 2 files changed, 64 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +32 -0
index 39f81f4..bfc50ac 100644
@@ -1312,6 +1312,58 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)

arch/arm/mach-at91/at91sam9260_devices.c void __init at91_add_device_cf(struct at91_cf_data *data) · line 1356

Quoted 10 lines
arch/arm/mach-at91/at91sam9260_devices.c +10 -0
+		return;
+
+	if(data->channels_used[0])
+		at91_set_A_periph(AT91_PIN_PC0, 0);
+	if(data->channels_used[1])
+		at91_set_A_periph(AT91_PIN_PC1, 0);
+	if(data->channels_used[2])
+		at91_set_A_periph(AT91_PIN_PC2, 0);
+	if(data->channels_used[3])
+		at91_set_A_periph(AT91_PIN_PC3, 0);

formatting issues so another one for checkpatch.

Also define AT91_PIN_PC(n) then make this another for_each_bit_set call. That should get you down from 8 lines to about 3.

Quoted 35 lines
arch/arm/mach-at91/at91sam9260_devices.c +10 -0
+
+	data->adc_clock = 5000000;
+	data->num_channels = 4;
+	data->startup_time = 10;
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +10 -0
index 817f59d..6f2542fb 100644
@@ -314,6 +314,16 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels_used = { 1, 1, 1, 1} ,
+	.num_channels_used = 4,
+	.vref = 3300,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),

arch/arm/mach-at91/board-sam9g20ek.c static void __init ek_board_init(void) · line 406

Quoted 8 lines
arch/arm/mach-at91/board-sam9g20ek.c +2 -0
@@ -389,6 +399,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel at lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

#39 lore raw

PATCH v3 1/1 AT91: Add a driver for the ADC

In reply to Maxime Ripard

Hi all,

This patchset adds the driver for the ADC in the AT91 SoC. It has been tested on the at91sam9g20ek and should work on sam9g45 as well.

For now, it only reads values when asked for by sysfs, but eventually will support hardware triggers and more boards.

This patchset is based on the "[PATCH 0/6 V2] IIO: Out of staging step 1: The core" patchset from Jonathan Cameron, applied on top of 3.1

Improvements since v2:
- Move the driver out of staging
- Slightly modify the definition of channels to use bitmaps
- The driver no longer leaks the channels array
- Various minor fixes and codestyle improvements

Improvements since v1:
- Rebase on top of commit 85d8ff8
- Initialise scan_types
- Added scale informations in the driver
- Allow to use only a subset of adc channels available on the SoC
- Various fix according to reviews

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>
#40 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +18 -0
index ed544a0..afdcac0 100644
@@ -207,4 +207,22 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 
+/* ADC */
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */
+	unsigned long channels_used;
+	/* Startup time of the ADC, in microseconds. */
+	u8 startup_time;
+	/* Reference voltage for the ADC in millivolts */
+	unsigned short vref;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif
Signature · 1 line
1.7.4.1
maintainer
#41 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Looks sensible to me though obviously this is bit is really for the at91 lot to comment on.

Quoted 4 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>
Quoted 12 lines
---
 arch/arm/mach-at91/include/mach/board.h |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +0 -0
index ed544a0..afdcac0 100644
@@ -207,4 +207,22 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 219

Quoted 10 lines
arch/arm/mach-at91/include/mach/board.h +10 -0
+/* ADC */
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board */

One nitpick here. You could make it more obvious that this is a mask...

Quoted 9 lines
arch/arm/mach-at91/include/mach/board.h +8 -0
+	unsigned long channels_used;
+	/* Startup time of the ADC, in microseconds. */
+	u8 startup_time;
+	/* Reference voltage for the ADC in millivolts */
+	unsigned short vref;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif
#42 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

In reply to Maxime Ripard

Le Mon, 7 Nov 2011 17:08:31 +0100,
Maxime Ripard <[email protected]> a ?crit :

arch/arm/mach-at91/include/mach/board.h extern void __init at91_pwm_leds(struct gpio_led *leds, int nr); · line 216

Quoted 7 lines
arch/arm/mach-at91/include/mach/board.h +7 -0
+/* ADC */
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used array).

channels_used is no longer an array, but a bitmap.

Regards,

Thomas

Signature · 4 lines
Thomas Petazzoni, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#43 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
Cc Thomas Petazzoni <[email protected]>
---
 drivers/iio/adc/Kconfig   |    6 +
 drivers/iio/adc/Makefile  |    4 +-
 drivers/iio/adc/at91adc.c |  311 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 320 insertions(+), 1 deletions(-)
 create mode 100644 drivers/iio/adc/at91adc.c
drivers/iio/adc/Kconfig +6 -0
index 3d97b21..74f4d9f 100644
@@ -14,6 +14,12 @@ config IIO_AD799X
 	  i2c analog to digital convertors (ADC). Provides direct access
 	  via sysfs.
 
+config IIO_AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config IIO_MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +3 -1
index c197334..776b56f 100644
@@ -6,4 +6,6 @@ iio_ad799x-y := ad799x_core.o
 obj-$(CONFIG_IIO_AD799X) += iio_ad799x.o
 
 iio_max1363-y := max1363_core.o
-obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
\ No newline at end of file
+obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
+
+obj-$(CONFIG_IIO_AT91ADC) += at91adc.o
drivers/iio/adc/at91adc.c new file +311 -0

311 lines not on this page · raw message

Signature · 1 line
1.7.4.1
#44 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Le Mon, 7 Nov 2011 17:08:32 +0100,
Maxime Ripard <[email protected]> a ?crit :

drivers/iio/adc/at91adc.c line 46

Quoted 9 lines
drivers/iio/adc/at91adc.c new file +9 -0
+static inline u32 at91adc_reg_read(void *base, u8 reg)
+{
+	return readl_relaxed(base + reg);
+}
+
+static inline void at91adc_reg_write(void *base, u8 reg, u32 val)
+{
+	writel_relaxed(val, base + reg);
+}

Those could probably be written to take a at91adc_state structure as argument, in order to simplify the call sites. I.e:

static inline u32 at91adc_reg_read(struct at91adc_state *st, u8 reg)

{
	return readl_relaxed(st->reg_base + reg);
}

static inline void at91adc_reg_write(struct at91adc_state *st, u8 reg, u32 val)

{
	writel_relaxed(val, st->reg_base + reg);
}

drivers/iio/adc/at91adc.c line 53

Quoted 6 lines
drivers/iio/adc/at91adc.c new file +6 -0
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)
+{
+	int chan;
+	struct iio_dev *idev = private;
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int status = at91adc_reg_read(st->reg_base, AT91_ADC_SR);

would become

+ unsigned int status = at91adc_reg_read(st, AT91_ADC_SR);

drivers/iio/adc/at91adc.c line 62

Quoted 8 lines
drivers/iio/adc/at91adc.c new file +8 -0
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < idev->num_channels; chan++)
+		if (status & AT91_ADC_EOC(chan)) {
+			st->done = true;
+			st->last_value = at91adc_reg_read(st->reg_base,
+							  AT91_ADC_CHR(chan));

would become

+ st->last_value = at91adc_reg_read(st, AT91_ADC_CHR(chan));

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+static int at91adc_channel_init(struct iio_dev *idev,
+				struct at91_adc_data *pdata)
+{
+	struct iio_chan_spec *chan_array;
+	int bit, idx = 0;
+
+	idev->num_channels = bitmap_weight(&(pdata->channels_used),
+					   pdata->num_channels);
+	chan_array = kcalloc(idev->num_channels, sizeof(struct iio_chan_spec),
+			     GFP_KERNEL);

drivers/iio/adc/at91adc.c line 89

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+
+	if (chan_array == NULL)
+		return -ENOMEM;
+
+	for_each_set_bit(bit, &(pdata->channels_used), pdata->num_channels) {
+		struct iio_chan_spec *chan = chan_array + idx;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = bit;
+		chan->scan_type.sign = 's';

It's an unsigned value that you're reading from the ADC, so maybe this should be = 'u'.

drivers/iio/adc/at91adc.c line 104

Quoted 6 lines
drivers/iio/adc/at91adc.c new file +6 -0
+static void at91adc_channel_remove(struct iio_dev *idev)
+{
+	if (idev->channels == NULL)
+		return;
+
+	kfree(idev->channels);

kfree() is fine with having its argument being NULL, so the if test is useless here. See http://lxr.free-electrons.com/source/mm/slab.c#L3863.

Quoted 9 lines
drivers/iio/adc/at91adc.c new file +9 -0
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int scale_uv;
+
+	switch (mask) {
+	case 0:

drivers/iio/adc/at91adc.c line 125

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st->reg_base, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_START);
+
+		wait_event_interruptible_timeout(st->wq_data_avail, st->done,
+						 msecs_to_jiffies(10 * 1000));

You forgot to handle the cases where wait_event_interruptible_timeout() exits because of a signal or because of the timeout, and make the assumption that the wait_event_interruptible_timeout() is also successful.

Quoted 11 lines
drivers/iio/adc/at91adc.c new file +11 -0
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st->reg_base, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st->reg_base, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}

drivers/iio/adc/at91adc.c line 222

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);

You do a clk_get()/clk_enable() here but nowhere in the ->remove() function you do a clk_disable()/clk_put().

Quoted 1 line
drivers/iio/adc/at91adc.c new file +1 -0
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;

[...]

drivers/iio/adc/at91adc.c line 245

Quoted 2 lines
drivers/iio/adc/at91adc.c new file +2 -0
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;

Both of those computations would be better with a small comment explaining what's happening.

drivers/iio/adc/at91adc.c line 294

Quoted 12 lines
drivers/iio/adc/at91adc.c new file +12 -0
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	at91adc_channel_remove(idev);
+	iio_device_unregister(idev);
+	free_irq(st->irq, idev);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_free(idev);

As said above, clk_disable()/clk_put() is missing.

drivers/iio/adc/at91adc.c line 304

Quoted 6 lines
drivers/iio/adc/at91adc.c new file +6 -0
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),
+	.driver = {
+		   .name = "at91adc",
+		   },

Nitpick: wrongly placed parenthesis.

Regards,

Thomas

Signature · 4 lines
Thomas Petazzoni, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#45 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>
---
 arch/arm/mach-at91/at91sam9260_devices.c |   52 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   11 ++++++
 2 files changed, 63 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +52 -0
index 39f81f4..0859553 100644
@@ -1312,6 +1312,58 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+
+	if (test_bit(0, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC0, 0);
+	if (test_bit(1, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC1, 0);
+	if (test_bit(2, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC2, 0);
+	if (test_bit(3, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC3, 0);
+
+	data->adc_clock = 5000000;
+	data->num_channels = 4;
+	data->startup_time = 10;
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +11 -0
index 817f59d..e34d41a 100644
@@ -314,6 +314,15 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels_used = BIT(0) | BIT(1) | BIT(2) | BIT(3),
+	.vref = 3300,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),
@@ -389,6 +398,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */
Signature · 1 line
1.7.4.1
#46 lore raw

PATCH v4 1/1 AT91: Add a driver for the ADC

In reply to Maxime Ripard

Hi all,

This patchset adds the driver for the ADC in the AT91 SoC. It has been tested on the at91sam9g20ek and should work on sam9g45 as well.

For now, it only reads values when asked for by sysfs, but eventually will support hardware triggers and more boards.

This patchset is based on the "[PATCH 0/6 V2] IIO: Out of staging step 1: The core" patchset from Jonathan Cameron, applied on top of 3.1

Improvements since v3:
- Various fixes (Remove the clock at drivers's remove, change the registers
access functions prototypes,... )

Improvements since v2:
- Move the driver out of staging
- Slightly modify the definition of channels to use bitmaps
- The driver no longer leaks the channels array
- Various minor fixes and codestyle improvements

Improvements since v1:
- Rebase on top of commit 85d8ff8
- Initialise scan_types
- Added scale informations in the driver
- Allow to use only a subset of adc channels available on the SoC
- Various fix according to reviews

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>
#47 lore raw

PATCH 1/3 ARM: AT91: Add platform data for the ADCs

Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>
---
 arch/arm/mach-at91/include/mach/board.h |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)
arch/arm/mach-at91/include/mach/board.h +18 -0
index ed544a0..4f27797 100644
@@ -207,4 +207,22 @@ extern void __init at91_pwm_leds(struct gpio_led *leds, int nr);
 /* FIXME: this needs a better location, but gets stuff building again */
 extern int at91_suspend_entering_slow_clock(void);
 
+/* ADC */
+struct at91_adc_data {
+	/* ADC Clock as specified by the datasheet, in Hz. */
+	unsigned int adc_clock;
+	/*
+	 * Global number of channels available (to specify which channels are
+	 * indeed used on the board, see the channels_used bitmask).
+	 */
+	u8 num_channels;
+	/* Channels in use on the board as a bitmask */
+	unsigned long channels_used;
+	/* Startup time of the ADC, in microseconds. */
+	u8 startup_time;
+	/* Reference voltage for the ADC in millivolts */
+	unsigned short vref;
+};
+extern void __init at91_add_device_adc(struct at91_adc_data *data);
+
 #endif
Signature · 1 line
1.7.4.1
#48 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
Cc Thomas Petazzoni <[email protected]>
---
 drivers/iio/adc/Kconfig   |    6 +
 drivers/iio/adc/Makefile  |    4 +-
 drivers/iio/adc/at91adc.c |  326 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 335 insertions(+), 1 deletions(-)
 create mode 100644 drivers/iio/adc/at91adc.c
drivers/iio/adc/Kconfig +6 -0
index 3d97b21..74f4d9f 100644
@@ -14,6 +14,12 @@ config IIO_AD799X
 	  i2c analog to digital convertors (ADC). Provides direct access
 	  via sysfs.
 
+config IIO_AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config IIO_MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +3 -1
index c197334..776b56f 100644
@@ -6,4 +6,6 @@ iio_ad799x-y := ad799x_core.o
 obj-$(CONFIG_IIO_AD799X) += iio_ad799x.o
 
 iio_max1363-y := max1363_core.o
-obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
\ No newline at end of file
+obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
+
+obj-$(CONFIG_IIO_AT91ADC) += at91adc.o
drivers/iio/adc/at91adc.c new file +326 -0

326 lines not on this page · raw message

Signature · 1 line
1.7.4.1
maintainer
#49 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Quoted 5 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
Cc Thomas Petazzoni <[email protected]>

One little comment / query inline. I'm happy with this either way though. Beware as you are dependent on a series under review though and it's always possible little things in there might change!

Quoted 1 line
Quoted 91 lines
---
 drivers/iio/adc/Kconfig   |    6 +
 drivers/iio/adc/Makefile  |    4 +-
 drivers/iio/adc/at91adc.c |  326 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 335 insertions(+), 1 deletions(-)
 create mode 100644 drivers/iio/adc/at91adc.c
drivers/iio/adc/Kconfig +6 -0
index 3d97b21..74f4d9f 100644
@@ -14,6 +14,12 @@ config IIO_AD799X
 	  i2c analog to digital convertors (ADC). Provides direct access
 	  via sysfs.
 
+config IIO_AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config IIO_MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +3 -1
index c197334..776b56f 100644
@@ -6,4 +6,6 @@ iio_ad799x-y := ad799x_core.o
 obj-$(CONFIG_IIO_AD799X) += iio_ad799x.o
 
 iio_max1363-y := max1363_core.o
-obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
\ No newline at end of file
+obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
+
+obj-$(CONFIG_IIO_AT91ADC) += at91adc.o
drivers/iio/adc/at91adc.c new file +48 -0
new file mode 100644
index 0000000..6aef5d6
@@ -0,0 +1,326 @@
+/*
+ * Driver for the ADC present in the Atmel AT91 evaluation boards.
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include <linux/iio/iio.h>
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 last_value;
+	void __iomem *reg_base;
+	unsigned int vref_mv;
+};
+
+static inline u32 at91adc_reg_read(struct at91adc_state *st, u8 reg)
+{
+	return readl_relaxed(st->reg_base + reg);
+}
+
+static inline void at91adc_reg_write(struct at91adc_state *st, u8 reg, u32 val)
+{
+	writel_relaxed(val, st->reg_base + reg);
+}
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)

drivers/iio/adc/at91adc.c line 58

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+{
+	int chan;
+	struct iio_dev *idev = private;
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int status = at91adc_reg_read(st, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < idev->num_channels; chan++)

Given we know only one bit should be set, it really feels like there should be
a cleaner way of doing this. Could it use find_first_bit(&status, idev->num_channels)?
It kind of bypasses the defines though so it's far from clean in that sense.

drivers/iio/adc/at91adc.c line 59

Quoted 1 line
drivers/iio/adc/at91adc.c new file +1 -0
+		if (status & AT91_ADC_EOC(chan)) {

The logic in here very much assumes that only one channel is being converted and the interrupt indicates that one of the channels has finished. Hence logically this st->done doesn't need to be in the loop.

Quoted 267 lines
drivers/iio/adc/at91adc.c new file +267 -0
+			st->done = true;
+			st->last_value = at91adc_reg_read(st,
+							  AT91_ADC_CHR(chan));
+		}
+
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
+
+static int at91adc_channel_init(struct iio_dev *idev,
+				struct at91_adc_data *pdata)
+{
+	struct iio_chan_spec *chan_array;
+	int bit, idx = 0;
+
+	idev->num_channels = bitmap_weight(&(pdata->channels_used),
+					   pdata->num_channels);
+	chan_array = kcalloc(idev->num_channels, sizeof(struct iio_chan_spec),
+			     GFP_KERNEL);
+
+	if (chan_array == NULL)
+		return -ENOMEM;
+
+	for_each_set_bit(bit, &(pdata->channels_used), pdata->num_channels) {
+		struct iio_chan_spec *chan = chan_array + idx;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = bit;
+		chan->scan_type.sign = 'u';
+		chan->scan_type.realbits = 10;
+		chan->info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT;
+		++idx;
+	}
+
+	idev->channels = chan_array;
+	return idev->num_channels;
+}
+
+static void at91adc_channel_remove(struct iio_dev *idev)
+{
+	kfree(idev->channels);
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int scale_uv;
+	short ret;
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_START);
+
+		ret = wait_event_interruptible_timeout(st->wq_data_avail,
+						       st->done,
+						       msecs_to_jiffies(1000));
+		if (ret == -ERESTARTSYS)
+			break;
+
+		*val = st->last_value;
+
+		at91adc_reg_write(st, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->last_value = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits;
+		*val = scale_uv / 1000;
+		*val2 = (scale_uv % 1000) * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_device_allocate(sizeof(struct at91adc_state));
+	if (idev == NULL) {
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = dev_name(&pdev->dev);
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Prescaler rate computation using the formula from the Atmel's
+	 * datasheet : ADC Clock = MCK / ((Prescaler + 1) * 2), ADC Clock being
+	 * specified by the electrical characteristics of the board.
+	 */
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Number of ticks needed to cover the startup time of the ADC as
+	 * defined in the electrical characteristics of the board, divided by 8.
+	 * The formula thus is : Startup Time = (ticks + 1) * 8 / ADC Clock
+	 */
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	ret = at91adc_channel_init(idev, pdata);
+	if (ret < 0)
+		goto error_free_clk;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	st->vref_mv = pdata->vref;
+
+	ret = iio_device_register(idev);
+	if (ret < 0)
+		goto error_free_channels;
+
+	return 0;
+
+error_free_channels:
+	at91adc_channel_remove(idev);
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_device_free(idev);
+error_ret:
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	iio_device_unregister(idev);
+	at91adc_channel_remove(idev);
+	clk_disable(st->clk);
+	clk_put(st->clk);
+	free_irq(st->irq, idev);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_free(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),
+	.driver = {
+		   .name = "at91adc",
+	},
+};
+
+module_platform_driver(at91adc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atmel AT91 ADC Driver");
+MODULE_AUTHOR("Maxime Ripard <[email protected]>");
maintainer
#50 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Quoted 119 lines · Jonathan Cameron
Quoted 5 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
Cc Thomas Petazzoni <[email protected]>

One little comment / query inline. I'm happy with this either way though. Beware as you are dependent on a series under review though and it's always possible little things in there might change!

Quoted 1 line
Quoted 91 lines
---
 drivers/iio/adc/Kconfig   |    6 +
 drivers/iio/adc/Makefile  |    4 +-
 drivers/iio/adc/at91adc.c |  326 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 335 insertions(+), 1 deletions(-)
 create mode 100644 drivers/iio/adc/at91adc.c
drivers/iio/adc/Kconfig +6 -0
index 3d97b21..74f4d9f 100644
@@ -14,6 +14,12 @@ config IIO_AD799X
 	  i2c analog to digital convertors (ADC). Provides direct access
 	  via sysfs.
 
+config IIO_AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config IIO_MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +3 -1
index c197334..776b56f 100644
@@ -6,4 +6,6 @@ iio_ad799x-y := ad799x_core.o
 obj-$(CONFIG_IIO_AD799X) += iio_ad799x.o
 
 iio_max1363-y := max1363_core.o
-obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
\ No newline at end of file
+obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
+
+obj-$(CONFIG_IIO_AT91ADC) += at91adc.o
drivers/iio/adc/at91adc.c new file +48 -0
new file mode 100644
index 0000000..6aef5d6
@@ -0,0 +1,326 @@
+/*
+ * Driver for the ADC present in the Atmel AT91 evaluation boards.
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include <linux/iio/iio.h>
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 last_value;
+	void __iomem *reg_base;
+	unsigned int vref_mv;
+};
+
+static inline u32 at91adc_reg_read(struct at91adc_state *st, u8 reg)
+{
+	return readl_relaxed(st->reg_base + reg);
+}
+
+static inline void at91adc_reg_write(struct at91adc_state *st, u8 reg, u32 val)
+{
+	writel_relaxed(val, st->reg_base + reg);
+}
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)

drivers/iio/adc/at91adc.c line 58

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+{
+	int chan;
+	struct iio_dev *idev = private;
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int status = at91adc_reg_read(st, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < idev->num_channels; chan++)

Given we know only one bit should be set, it really feels like there should be
a cleaner way of doing this. Could it use find_first_bit(&status, idev->num_channels)?
It kind of bypasses the defines though so it's far from clean in that sense.

Quoted 6 lines

drivers/iio/adc/at91adc.c line 59

Quoted 1 line
drivers/iio/adc/at91adc.c new file +1 -0
+		if (status & AT91_ADC_EOC(chan)) {

The logic in here very much assumes that only one channel is being converted and the interrupt indicates that one of the channels has finished. Hence logically this st->done doesn't need to be in the loop.

Having had a look at the datasheet, could you just check against the whole mask and use the lastdata converted register? Should under current conditions always give you the right thing I think?

Quoted 272 lines
Quoted 257 lines
drivers/iio/adc/at91adc.c new file +257 -0
+			st->done = true;
+			st->last_value = at91adc_reg_read(st,
+							  AT91_ADC_CHR(chan));
+		}
+
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
+
+static int at91adc_channel_init(struct iio_dev *idev,
+				struct at91_adc_data *pdata)
+{
+	struct iio_chan_spec *chan_array;
+	int bit, idx = 0;
+
+	idev->num_channels = bitmap_weight(&(pdata->channels_used),
+					   pdata->num_channels);
+	chan_array = kcalloc(idev->num_channels, sizeof(struct iio_chan_spec),
+			     GFP_KERNEL);
+
+	if (chan_array == NULL)
+		return -ENOMEM;
+
+	for_each_set_bit(bit, &(pdata->channels_used), pdata->num_channels) {
+		struct iio_chan_spec *chan = chan_array + idx;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = bit;
+		chan->scan_type.sign = 'u';
+		chan->scan_type.realbits = 10;
+		chan->info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT;
+		++idx;
+	}
+
+	idev->channels = chan_array;
+	return idev->num_channels;
+}
+
+static void at91adc_channel_remove(struct iio_dev *idev)
+{
+	kfree(idev->channels);
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int scale_uv;
+	short ret;
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_START);
+
+		ret = wait_event_interruptible_timeout(st->wq_data_avail,
+						       st->done,
+						       msecs_to_jiffies(1000));
+		if (ret == -ERESTARTSYS)
+			break;
+
+		*val = st->last_value;
+
+		at91adc_reg_write(st, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->last_value = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits;
+		*val = scale_uv / 1000;
+		*val2 = (scale_uv % 1000) * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_device_allocate(sizeof(struct at91adc_state));
+	if (idev == NULL) {
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = dev_name(&pdev->dev);
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Prescaler rate computation using the formula from the Atmel's
+	 * datasheet : ADC Clock = MCK / ((Prescaler + 1) * 2), ADC Clock being
+	 * specified by the electrical characteristics of the board.
+	 */
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Number of ticks needed to cover the startup time of the ADC as
+	 * defined in the electrical characteristics of the board, divided by 8.
+	 * The formula thus is : Startup Time = (ticks + 1) * 8 / ADC Clock
+	 */
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	ret = at91adc_channel_init(idev, pdata);
+	if (ret < 0)
+		goto error_free_clk;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	st->vref_mv = pdata->vref;
+
+	ret = iio_device_register(idev);
+	if (ret < 0)
+		goto error_free_channels;
+
+	return 0;
+
+error_free_channels:
+	at91adc_channel_remove(idev);
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_device_free(idev);
+error_ret:
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	iio_device_unregister(idev);
+	at91adc_channel_remove(idev);
+	clk_disable(st->clk);
+	clk_put(st->clk);
+	free_irq(st->irq, idev);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_free(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),

drivers/iio/adc/at91adc.c line 326

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+	.driver = {
+		   .name = "at91adc",
+	},
+};
+
+module_platform_driver(at91adc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atmel AT91 ADC Driver");
+MODULE_AUTHOR("Maxime Ripard <[email protected]>");

--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#51 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

Quoted 126 lines · Jonathan Cameron
Quoted 119 lines · Jonathan Cameron
Quoted 5 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
Cc Thomas Petazzoni <[email protected]>

One little comment / query inline. I'm happy with this either way though. Beware as you are dependent on a series under review though and it's always possible little things in there might change!

Quoted 1 line
Quoted 91 lines
---
 drivers/iio/adc/Kconfig   |    6 +
 drivers/iio/adc/Makefile  |    4 +-
 drivers/iio/adc/at91adc.c |  326 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 335 insertions(+), 1 deletions(-)
 create mode 100644 drivers/iio/adc/at91adc.c
drivers/iio/adc/Kconfig +6 -0
index 3d97b21..74f4d9f 100644
@@ -14,6 +14,12 @@ config IIO_AD799X
 	  i2c analog to digital convertors (ADC). Provides direct access
 	  via sysfs.
 
+config IIO_AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config IIO_MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +3 -1
index c197334..776b56f 100644
@@ -6,4 +6,6 @@ iio_ad799x-y := ad799x_core.o
 obj-$(CONFIG_IIO_AD799X) += iio_ad799x.o
 
 iio_max1363-y := max1363_core.o
-obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
\ No newline at end of file
+obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
+
+obj-$(CONFIG_IIO_AT91ADC) += at91adc.o
drivers/iio/adc/at91adc.c new file +48 -0
new file mode 100644
index 0000000..6aef5d6
@@ -0,0 +1,326 @@
+/*
+ * Driver for the ADC present in the Atmel AT91 evaluation boards.
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include <linux/iio/iio.h>
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 last_value;
+	void __iomem *reg_base;
+	unsigned int vref_mv;
+};
+
+static inline u32 at91adc_reg_read(struct at91adc_state *st, u8 reg)
+{
+	return readl_relaxed(st->reg_base + reg);
+}
+
+static inline void at91adc_reg_write(struct at91adc_state *st, u8 reg, u32 val)
+{
+	writel_relaxed(val, st->reg_base + reg);
+}
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)

drivers/iio/adc/at91adc.c line 58

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+{
+	int chan;
+	struct iio_dev *idev = private;
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int status = at91adc_reg_read(st, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < idev->num_channels; chan++)

Given we know only one bit should be set, it really feels like there should be
a cleaner way of doing this. Could it use find_first_bit(&status, idev->num_channels)?
It kind of bypasses the defines though so it's far from clean in that sense.

Quoted 6 lines

drivers/iio/adc/at91adc.c line 59

Quoted 1 line
drivers/iio/adc/at91adc.c new file +1 -0
+		if (status & AT91_ADC_EOC(chan)) {

The logic in here very much assumes that only one channel is being converted and the interrupt indicates that one of the channels has finished. Hence logically this st->done doesn't need to be in the loop.

Quoted 3 lines

Having had a look at the datasheet, could you just check against the whole mask and use the lastdata converted register? Should under current conditions always give you the right thing I think?

You're right, It will be more consistent and avoid to loop over each channels. I send a new version of the patchset.

Quoted 277 lines
Quoted 264 lines
Quoted 264 lines
drivers/iio/adc/at91adc.c new file +264 -0
+			st->done = true;
+			st->last_value = at91adc_reg_read(st,
+							  AT91_ADC_CHR(chan));
+		}
+
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
+
+static int at91adc_channel_init(struct iio_dev *idev,
+				struct at91_adc_data *pdata)
+{
+	struct iio_chan_spec *chan_array;
+	int bit, idx = 0;
+
+	idev->num_channels = bitmap_weight(&(pdata->channels_used),
+					   pdata->num_channels);
+	chan_array = kcalloc(idev->num_channels, sizeof(struct iio_chan_spec),
+			     GFP_KERNEL);
+
+	if (chan_array == NULL)
+		return -ENOMEM;
+
+	for_each_set_bit(bit, &(pdata->channels_used), pdata->num_channels) {
+		struct iio_chan_spec *chan = chan_array + idx;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = bit;
+		chan->scan_type.sign = 'u';
+		chan->scan_type.realbits = 10;
+		chan->info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT;
+		++idx;
+	}
+
+	idev->channels = chan_array;
+	return idev->num_channels;
+}
+
+static void at91adc_channel_remove(struct iio_dev *idev)
+{
+	kfree(idev->channels);
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int scale_uv;
+	short ret;
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_START);
+
+		ret = wait_event_interruptible_timeout(st->wq_data_avail,
+						       st->done,
+						       msecs_to_jiffies(1000));
+		if (ret == -ERESTARTSYS)
+			break;
+
+		*val = st->last_value;
+
+		at91adc_reg_write(st, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->last_value = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits;
+		*val = scale_uv / 1000;
+		*val2 = (scale_uv % 1000) * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_device_allocate(sizeof(struct at91adc_state));
+	if (idev == NULL) {
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = dev_name(&pdev->dev);
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Prescaler rate computation using the formula from the Atmel's
+	 * datasheet : ADC Clock = MCK / ((Prescaler + 1) * 2), ADC Clock being
+	 * specified by the electrical characteristics of the board.
+	 */
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Number of ticks needed to cover the startup time of the ADC as
+	 * defined in the electrical characteristics of the board, divided by 8.
+	 * The formula thus is : Startup Time = (ticks + 1) * 8 / ADC Clock
+	 */
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	ret = at91adc_channel_init(idev, pdata);
+	if (ret < 0)
+		goto error_free_clk;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	st->vref_mv = pdata->vref;
+
+	ret = iio_device_register(idev);
+	if (ret < 0)
+		goto error_free_channels;
+
+	return 0;
+
+error_free_channels:
+	at91adc_channel_remove(idev);
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_device_free(idev);
+error_ret:
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	iio_device_unregister(idev);
+	at91adc_channel_remove(idev);
+	clk_disable(st->clk);
+	clk_put(st->clk);
+	free_irq(st->irq, idev);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_free(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),
+	.driver = {
+		   .name = "at91adc",
+	},
+};
+
+module_platform_driver(at91adc_driver);
+
Quoted 8 lines

drivers/iio/adc/at91adc.c line 326

Quoted 3 lines
drivers/iio/adc/at91adc.c new file +3 -0
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atmel AT91 ADC Driver");
+MODULE_AUTHOR("Maxime Ripard <[email protected]>");

--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#52 lore raw

PATCH 2/3 ARM: AT91: IIO: Add AT91 ADC driver.

In reply to Jonathan Cameron
Quoted 9 lines · Jonathan Cameron
Quoted 5 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Jean-Christophe Plagniol-Villard <[email protected]>
Cc Thomas Petazzoni <[email protected]>

One little comment / query inline. I'm happy with this either way though. Beware as you are dependent on a series under review though and it's always possible little things in there might change!

Yes, I know.
Maybe it would be better if you took this patchset for that reason. I guess you have more control over the move out of staging than AT91 maintaners have.

Quoted 387 lines
Quoted 1 line
Quoted 91 lines
---
 drivers/iio/adc/Kconfig   |    6 +
 drivers/iio/adc/Makefile  |    4 +-
 drivers/iio/adc/at91adc.c |  326 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 335 insertions(+), 1 deletions(-)
 create mode 100644 drivers/iio/adc/at91adc.c
drivers/iio/adc/Kconfig +6 -0
index 3d97b21..74f4d9f 100644
@@ -14,6 +14,12 @@ config IIO_AD799X
 	  i2c analog to digital convertors (ADC). Provides direct access
 	  via sysfs.
 
+config IIO_AT91ADC
+	tristate "Atmel AT91 ADC"
+	depends on SYSFS && ARCH_AT91
+	help
+	  Say yes here to build support for Atmel AT91 ADC.
+
 config IIO_MAX1363
 	tristate "Maxim max1363 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +3 -1
index c197334..776b56f 100644
@@ -6,4 +6,6 @@ iio_ad799x-y := ad799x_core.o
 obj-$(CONFIG_IIO_AD799X) += iio_ad799x.o
 
 iio_max1363-y := max1363_core.o
-obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
\ No newline at end of file
+obj-$(CONFIG_IIO_MAX1363) += iio_max1363.o
+
+obj-$(CONFIG_IIO_AT91ADC) += at91adc.o
drivers/iio/adc/at91adc.c new file +48 -0
new file mode 100644
index 0000000..6aef5d6
@@ -0,0 +1,326 @@
+/*
+ * Driver for the ADC present in the Atmel AT91 evaluation boards.
+ *
+ * Copyright 2011 Free Electrons
+ *
+ * Licensed under the GPLv2 or later.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include <linux/iio/iio.h>
+
+#include <mach/at91_adc.h>
+#include <mach/board.h>
+
+struct at91adc_state {
+	struct clk *clk;
+	bool done;
+	struct mutex lock;
+	int irq;
+	wait_queue_head_t wq_data_avail;
+	u16 last_value;
+	void __iomem *reg_base;
+	unsigned int vref_mv;
+};
+
+static inline u32 at91adc_reg_read(struct at91adc_state *st, u8 reg)
+{
+	return readl_relaxed(st->reg_base + reg);
+}
+
+static inline void at91adc_reg_write(struct at91adc_state *st, u8 reg, u32 val)
+{
+	writel_relaxed(val, st->reg_base + reg);
+}
+
+static irqreturn_t at91adc_eoc_trigger(int irq, void *private)

drivers/iio/adc/at91adc.c line 58

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+{
+	int chan;
+	struct iio_dev *idev = private;
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int status = at91adc_reg_read(st, AT91_ADC_SR);
+
+	if (!(status & AT91_ADC_DRDY))
+		return IRQ_HANDLED;
+
+	for (chan = 0; chan < idev->num_channels; chan++)

Given we know only one bit should be set, it really feels like there should be
a cleaner way of doing this. Could it use find_first_bit(&status, idev->num_channels)?
It kind of bypasses the defines though so it's far from clean in that sense.

drivers/iio/adc/at91adc.c line 59

Quoted 1 line
drivers/iio/adc/at91adc.c new file +1 -0
+		if (status & AT91_ADC_EOC(chan)) {

The logic in here very much assumes that only one channel is being converted and the interrupt indicates that one of the channels has finished. Hence logically this st->done doesn't need to be in the loop.

Quoted 257 lines
drivers/iio/adc/at91adc.c new file +257 -0
+			st->done = true;
+			st->last_value = at91adc_reg_read(st,
+							  AT91_ADC_CHR(chan));
+		}
+
+	wake_up_interruptible(&st->wq_data_avail);
+
+	return IRQ_HANDLED;
+}
+
+static int at91adc_channel_init(struct iio_dev *idev,
+				struct at91_adc_data *pdata)
+{
+	struct iio_chan_spec *chan_array;
+	int bit, idx = 0;
+
+	idev->num_channels = bitmap_weight(&(pdata->channels_used),
+					   pdata->num_channels);
+	chan_array = kcalloc(idev->num_channels, sizeof(struct iio_chan_spec),
+			     GFP_KERNEL);
+
+	if (chan_array == NULL)
+		return -ENOMEM;
+
+	for_each_set_bit(bit, &(pdata->channels_used), pdata->num_channels) {
+		struct iio_chan_spec *chan = chan_array + idx;
+		chan->type = IIO_VOLTAGE;
+		chan->indexed = 1;
+		chan->channel = bit;
+		chan->scan_type.sign = 'u';
+		chan->scan_type.realbits = 10;
+		chan->info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT;
+		++idx;
+	}
+
+	idev->channels = chan_array;
+	return idev->num_channels;
+}
+
+static void at91adc_channel_remove(struct iio_dev *idev)
+{
+	kfree(idev->channels);
+}
+
+static int at91adc_read_raw(struct iio_dev *idev,
+			    struct iio_chan_spec const *chan,
+			    int *val, int *val2, long mask)
+{
+	struct at91adc_state *st = iio_priv(idev);
+	unsigned int scale_uv;
+	short ret;
+
+	switch (mask) {
+	case 0:
+		mutex_lock(&st->lock);
+
+		at91adc_reg_write(st, AT91_ADC_CHER,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IER,
+				  AT91_ADC_EOC(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_START);
+
+		ret = wait_event_interruptible_timeout(st->wq_data_avail,
+						       st->done,
+						       msecs_to_jiffies(1000));
+		if (ret == -ERESTARTSYS)
+			break;
+
+		*val = st->last_value;
+
+		at91adc_reg_write(st, AT91_ADC_CHDR,
+				  AT91_ADC_CH(chan->channel));
+		at91adc_reg_write(st, AT91_ADC_IDR,
+				  AT91_ADC_EOC(chan->channel));
+
+		st->last_value = 0;
+		st->done = false;
+		mutex_unlock(&st->lock);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits;
+		*val = scale_uv / 1000;
+		*val2 = (scale_uv % 1000) * 1000;
+		return IIO_VAL_INT_PLUS_MICRO;
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+static const struct iio_info at91adc_info = {
+	.driver_module = THIS_MODULE,
+	.read_raw = &at91adc_read_raw,
+};
+
+static int __devinit at91adc_probe(struct platform_device *pdev)
+{
+	unsigned int prsc, mstrclk, ticks;
+	int ret;
+	struct iio_dev *idev;
+	struct at91adc_state *st;
+	struct resource *res;
+	struct at91_adc_data *pdata = pdev->dev.platform_data;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "No resource defined\n");
+		ret = -ENXIO;
+		goto error_ret;
+	}
+
+	idev = iio_device_allocate(sizeof(struct at91adc_state));
+	if (idev == NULL) {
+		ret = -ENOMEM;
+		goto error_ret;
+	}
+	platform_set_drvdata(pdev, idev);
+
+	idev->dev.parent = &pdev->dev;
+	idev->name = dev_name(&pdev->dev);
+	idev->info = &at91adc_info;
+
+	st = iio_priv(idev);
+	st->irq = platform_get_irq(pdev, 0);
+	if (st->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ ID is designated\n");
+		ret = -ENODEV;
+		goto error_free_device;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res),
+				"AT91 adc registers")) {
+		dev_err(&pdev->dev, "Resources are unavailable.\n");
+		ret = -EBUSY;
+		goto error_free_device;
+	}
+
+	st->reg_base = ioremap(res->start, resource_size(res));
+	if (!st->reg_base) {
+		dev_err(&pdev->dev, "Failed to map registers.\n");
+		ret = -ENOMEM;
+		goto error_release_mem;
+	}
+
+	/*
+	 * Disable all IRQs before setting up the handler
+	 */
+	at91adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST);
+	at91adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF);
+	ret = request_irq(st->irq,
+			  at91adc_eoc_trigger, 0, pdev->dev.driver->name, idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to allocate IRQ.\n");
+		goto error_unmap_reg;
+	}
+
+	st->clk = clk_get(&pdev->dev, "adc_clk");
+	if (IS_ERR(st->clk)) {
+		dev_err(&pdev->dev, "Failed to get the clock.\n");
+		ret = PTR_ERR(st->clk);
+		goto error_free_irq;
+	}
+
+	clk_enable(st->clk);
+	mstrclk = clk_get_rate(st->clk);
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	if (!pdata->adc_clock) {
+		dev_err(&pdev->dev, "No ADCClock available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Prescaler rate computation using the formula from the Atmel's
+	 * datasheet : ADC Clock = MCK / ((Prescaler + 1) * 2), ADC Clock being
+	 * specified by the electrical characteristics of the board.
+	 */
+	prsc = (mstrclk / (2 * pdata->adc_clock)) - 1;
+
+	if (!pdata->startup_time) {
+		dev_err(&pdev->dev, "No startup time available.\n");
+		ret = -EINVAL;
+		goto error_free_clk;
+	}
+
+	/*
+	 * Number of ticks needed to cover the startup time of the ADC as
+	 * defined in the electrical characteristics of the board, divided by 8.
+	 * The formula thus is : Startup Time = (ticks + 1) * 8 / ADC Clock
+	 */
+	ticks = round_up((pdata->startup_time * pdata->adc_clock /
+			  1000000) - 1, 8) / 8;
+	at91adc_reg_write(st, AT91_ADC_MR,
+			  (AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL) |
+			  (AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP));
+
+	/* Setup the ADC channels available on the board */
+	ret = at91adc_channel_init(idev, pdata);
+	if (ret < 0)
+		goto error_free_clk;
+
+	init_waitqueue_head(&st->wq_data_avail);
+	mutex_init(&st->lock);
+
+	st->vref_mv = pdata->vref;
+
+	ret = iio_device_register(idev);
+	if (ret < 0)
+		goto error_free_channels;
+
+	return 0;
+
+error_free_channels:
+	at91adc_channel_remove(idev);
+error_free_clk:
+	clk_disable(st->clk);
+	clk_put(st->clk);
+error_free_irq:
+	free_irq(st->irq, st);
+error_unmap_reg:
+	iounmap(st->reg_base);
+error_release_mem:
+	release_mem_region(res->start, resource_size(res));
+error_free_device:
+	iio_device_free(idev);
+error_ret:
+	return ret;
+}
+
+static int __devexit at91adc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *idev = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct at91adc_state *st = iio_priv(idev);
+
+	iio_device_unregister(idev);
+	at91adc_channel_remove(idev);
+	clk_disable(st->clk);
+	clk_put(st->clk);
+	free_irq(st->irq, idev);
+	iounmap(st->reg_base);
+	release_mem_region(res->start, resource_size(res));
+	iio_device_free(idev);
+
+	return 0;
+}
+
+static struct platform_driver at91adc_driver = {
+	.probe = at91adc_probe,
+	.remove = __devexit_p(at91adc_remove),

drivers/iio/adc/at91adc.c line 326

Quoted 10 lines
drivers/iio/adc/at91adc.c new file +10 -0
+	.driver = {
+		   .name = "at91adc",
+	},
+};
+
+module_platform_driver(at91adc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Atmel AT91 ADC Driver");
+MODULE_AUTHOR("Maxime Ripard <[email protected]>");

--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

Signature · 4 lines
Maxime Ripard, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
#53 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

In reply to Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>
---
 arch/arm/mach-at91/at91sam9260_devices.c |   52 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   11 ++++++
 2 files changed, 63 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +52 -0
index 39f81f4..0859553 100644
@@ -1312,6 +1312,58 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+
+	if (test_bit(0, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC0, 0);
+	if (test_bit(1, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC1, 0);
+	if (test_bit(2, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC2, 0);
+	if (test_bit(3, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC3, 0);
+
+	data->adc_clock = 5000000;
+	data->num_channels = 4;
+	data->startup_time = 10;
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +11 -0
index 817f59d..e34d41a 100644
@@ -314,6 +314,15 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels_used = BIT(0) | BIT(1) | BIT(2) | BIT(3),
+	.vref = 3300,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),
@@ -389,6 +398,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */
Signature · 1 line
1.7.4.1
maintainer
#54 lore raw

PATCH 3/3 ARM: AT91: Add the ADC to the sam9g20ek board

Quoted 4 lines · Maxime Ripard
Cc Nicolas Ferre <[email protected]>
Cc Patrice Vilchez <[email protected]>
Cc Thomas Petazzoni <[email protected]>

Fine as far as I am concerned but this one is definitely one for acks from others as well.

Quoted 1 line
Quoted 97 lines
---
 arch/arm/mach-at91/at91sam9260_devices.c |   52 ++++++++++++++++++++++++++++++
 arch/arm/mach-at91/board-sam9g20ek.c     |   11 ++++++
 2 files changed, 63 insertions(+), 0 deletions(-)
arch/arm/mach-at91/at91sam9260_devices.c +52 -0
index 39f81f4..0859553 100644
@@ -1312,6 +1312,58 @@ void __init at91_add_device_cf(struct at91_cf_data *data)
 void __init at91_add_device_cf(struct at91_cf_data * data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  ADCs
+ * -------------------------------------------------------------------- */
+
+static struct at91_adc_data adc_data;
+
+static struct resource adc_resources[] = {
+	[0] = {
+		.start	= AT91SAM9260_BASE_ADC,
+		.end	= AT91SAM9260_BASE_ADC + SZ_16K - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= AT91SAM9260_ID_ADC,
+		.end	= AT91SAM9260_ID_ADC,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static struct platform_device at91_adc_device = {
+	.name		= "at91adc",
+	.id		= -1,
+	.dev		= {
+				.platform_data		= &adc_data,
+	},
+	.resource	= adc_resources,
+	.num_resources	= ARRAY_SIZE(adc_resources),
+};
+
+void __init at91_add_device_adc(struct at91_adc_data *data)
+{
+	if (!data)
+		return;
+
+	if (test_bit(0, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC0, 0);
+	if (test_bit(1, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC1, 0);
+	if (test_bit(2, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC2, 0);
+	if (test_bit(3, &(data->channels_used)))
+		at91_set_A_periph(AT91_PIN_PC3, 0);
+
+	data->adc_clock = 5000000;
+	data->num_channels = 4;
+	data->startup_time = 10;
+
+	adc_data = *data;
+	platform_device_register(&at91_adc_device);
+}
+
+
 /* -------------------------------------------------------------------- */
 /*
  * These devices are always present and don't need any board-specific
arch/arm/mach-at91/board-sam9g20ek.c +11 -0
index 817f59d..e34d41a 100644
@@ -314,6 +314,15 @@ static void __init ek_add_device_buttons(void)
 static void __init ek_add_device_buttons(void) {}
 #endif
 
+/*
+ * ADCs
+ */
+
+static struct at91_adc_data ek_adc_data = {
+	.channels_used = BIT(0) | BIT(1) | BIT(2) | BIT(3),
+	.vref = 3300,
+};
+
 #if defined(CONFIG_REGULATOR_FIXED_VOLTAGE) || defined(CONFIG_REGULATOR_FIXED_VOLTAGE_MODULE)
 static struct regulator_consumer_supply ek_audio_consumer_supplies[] = {
 	REGULATOR_SUPPLY("AVDD", "0-001b"),
@@ -389,6 +398,8 @@ static void __init ek_board_init(void)
 	ek_add_device_gpio_leds();
 	/* Push Buttons */
 	ek_add_device_buttons();
+	/* ADCs */
+	at91_add_device_adc(&ek_adc_data);
 	/* PCK0 provides MCLK to the WM8731 */
 	at91_set_B_periph(AT91_PIN_PC1, 0);
 	/* SSC (for WM8731) */