* [PATCHv5] AT91: Add a driver for the ADC @ 2011-11-14 10:06 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni 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 for v5: - Don't loop through the channels in the irq handler anymore but use the last converted data register instead. Improvements for v4: - Various fixes (Remove the clock at drivers's remove, change the registers access functions prototypes,... ) Improvements for v3: - 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 for v2: - 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 <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCHv5] AT91: Add a driver for the ADC @ 2011-11-14 10:06 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel 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 for v5: - Don't loop through the channels in the irq handler anymore but use the last converted data register instead. Improvements for v4: - Various fixes (Remove the clock at drivers's remove, change the registers access functions prototypes,... ) Improvements for v3: - 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 for v2: - 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 <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 1/3] ARM: AT91: Add platform data for the ADCs 2011-11-14 10:06 ` Maxime Ripard @ 2011-11-14 10:06 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> --- arch/arm/mach-at91/include/mach/board.h | 18 ++++++++++++++++++ 1 files changed, 18 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h index ed544a0..4f27797 100644 --- a/arch/arm/mach-at91/include/mach/board.h +++ b/arch/arm/mach-at91/include/mach/board.h @@ -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 -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 1/3] ARM: AT91: Add platform data for the ADCs @ 2011-11-14 10:06 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> --- arch/arm/mach-at91/include/mach/board.h | 18 ++++++++++++++++++ 1 files changed, 18 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h index ed544a0..4f27797 100644 --- a/arch/arm/mach-at91/include/mach/board.h +++ b/arch/arm/mach-at91/include/mach/board.h @@ -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 -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 1/3] ARM: AT91: Add platform data for the ADCs 2011-11-14 10:06 ` Maxime Ripard @ 2011-11-14 11:29 ` Nicolas Ferre -1 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 11:29 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Thomas Petazzoni On 11/14/2011 11:06 AM, Maxime Ripard : > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> > --- > arch/arm/mach-at91/include/mach/board.h | 18 ++++++++++++++++++ > 1 files changed, 18 insertions(+), 0 deletions(-) > > diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h > index ed544a0..4f27797 100644 > --- a/arch/arm/mach-at91/include/mach/board.h > +++ b/arch/arm/mach-at91/include/mach/board.h > @@ -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 -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 1/3] ARM: AT91: Add platform data for the ADCs @ 2011-11-14 11:29 ` Nicolas Ferre 0 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 11:29 UTC (permalink / raw) To: linux-arm-kernel On 11/14/2011 11:06 AM, Maxime Ripard : > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> > --- > arch/arm/mach-at91/include/mach/board.h | 18 ++++++++++++++++++ > 1 files changed, 18 insertions(+), 0 deletions(-) > > diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h > index ed544a0..4f27797 100644 > --- a/arch/arm/mach-at91/include/mach/board.h > +++ b/arch/arm/mach-at91/include/mach/board.h > @@ -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 -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-14 10:06 ` Maxime Ripard @ 2011-11-14 10:06 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91adc.c | 323 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 332 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..eee591d --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -0,0 +1,323 @@ +/* + * 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) +{ + 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; + + if (status & ((2 << idev->num_channels) - 1)) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 10:06 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91adc.c | 323 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 332 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..eee591d --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -0,0 +1,323 @@ +/* + * 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) +{ + 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; + + if (status & ((2 << idev->num_channels) - 1)) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-14 10:06 ` Maxime Ripard @ 2011-11-14 11:30 ` Nicolas Ferre -1 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 11:30 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni On 11/14/2011 11:06 AM, Maxime Ripard : > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91adc.c | 323 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 332 insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..eee591d > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -0,0 +1,323 @@ > +/* > + * 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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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 <maxime.ripard@free-electrons.com>"); -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 11:30 ` Nicolas Ferre 0 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 11:30 UTC (permalink / raw) To: linux-arm-kernel On 11/14/2011 11:06 AM, Maxime Ripard : > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91adc.c | 323 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 332 insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..eee591d > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -0,0 +1,323 @@ > +/* > + * 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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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 <maxime.ripard@free-electrons.com>"); -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-14 10:06 ` Maxime Ripard @ 2011-11-14 11:37 ` Marek Vasut -1 siblings, 0 replies; 99+ messages in thread From: Marek Vasut @ 2011-11-14 11:37 UTC (permalink / raw) To: linux-arm-kernel Cc: Maxime Ripard, linux-iio, Thomas Petazzoni, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Nicolas Ferre Hi, a few nitpicks > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91adc.c | 323 > +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 332 > insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..eee591d > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -0,0 +1,323 @@ > +/* > + * 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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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), Do you need the parenthesis here ? > + 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; idx++ ... why the preincrement? Besides, can't you use "bit" as the index instead? > + } > + > + 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; > + } newline > + 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 <maxime.ripard@free-electrons.com>"); Looks good otherwise, please add Reviewed-by: Marek Vasut <marek.vasut@gmail.com> M ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 11:37 ` Marek Vasut 0 siblings, 0 replies; 99+ messages in thread From: Marek Vasut @ 2011-11-14 11:37 UTC (permalink / raw) To: linux-arm-kernel Hi, a few nitpicks > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91adc.c | 323 > +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 332 > insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..eee591d > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -0,0 +1,323 @@ > +/* > + * 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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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), Do you need the parenthesis here ? > + 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; idx++ ... why the preincrement? Besides, can't you use "bit" as the index instead? > + } > + > + 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; > + } newline > + 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 <maxime.ripard@free-electrons.com>"); Looks good otherwise, please add Reviewed-by: Marek Vasut <marek.vasut@gmail.com> M ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-14 11:37 ` Marek Vasut @ 2011-11-14 14:23 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 14:23 UTC (permalink / raw) To: Marek Vasut Cc: linux-arm-kernel, linux-iio, Thomas Petazzoni, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Nicolas Ferre Hi On 14/11/2011 12:37, Marek Vasut wrote: >> + idev->num_channels = bitmap_weight(&(pdata->channels_used), > > Do you need the parenthesis here ? No. I just find it clearer this way, but it's all about cosmetic. >> + 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; > > idx++ ... why the preincrement? > > Besides, can't you use "bit" as the index instead? Nope. bit is the index in the pdata->channel_used array, while idx is the index in the chan_array array. And since these two don't have the same length, I need these two variables. -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 14:23 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 14:23 UTC (permalink / raw) To: linux-arm-kernel Hi On 14/11/2011 12:37, Marek Vasut wrote: >> + idev->num_channels = bitmap_weight(&(pdata->channels_used), > > Do you need the parenthesis here ? No. I just find it clearer this way, but it's all about cosmetic. >> + 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; > > idx++ ... why the preincrement? > > Besides, can't you use "bit" as the index instead? Nope. bit is the index in the pdata->channel_used array, while idx is the index in the chan_array array. And since these two don't have the same length, I need these two variables. -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board 2011-11-14 10:06 ` Maxime Ripard @ 2011-11-14 10:06 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ 2 files changed, 63 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c index 39f81f4..0859553 100644 --- a/arch/arm/mach-at91/at91sam9260_devices.c +++ b/arch/arm/mach-at91/at91sam9260_devices.c @@ -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 diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c index 817f59d..e34d41a 100644 --- a/arch/arm/mach-at91/board-sam9g20ek.c +++ b/arch/arm/mach-at91/board-sam9g20ek.c @@ -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) */ -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board @ 2011-11-14 10:06 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 10:06 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ 2 files changed, 63 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c index 39f81f4..0859553 100644 --- a/arch/arm/mach-at91/at91sam9260_devices.c +++ b/arch/arm/mach-at91/at91sam9260_devices.c @@ -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 diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c index 817f59d..e34d41a 100644 --- a/arch/arm/mach-at91/board-sam9g20ek.c +++ b/arch/arm/mach-at91/board-sam9g20ek.c @@ -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) */ -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board 2011-11-14 10:06 ` Maxime Ripard @ 2011-11-14 11:29 ` Nicolas Ferre -1 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 11:29 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Thomas Petazzoni On 11/14/2011 11:06 AM, Maxime Ripard : > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> (even if I have a tiny comment: see below) > --- > arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ > arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ > 2 files changed, 63 insertions(+), 0 deletions(-) > > diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c > index 39f81f4..0859553 100644 > --- a/arch/arm/mach-at91/at91sam9260_devices.c > +++ b/arch/arm/mach-at91/at91sam9260_devices.c > @@ -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 > + * -------------------------------------------------------------------- */ What about adding all this only if the IIO_AT91ADC is selected? Otherwise, you configure pins without actually using them... > + > +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 > diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c > index 817f59d..e34d41a 100644 > --- a/arch/arm/mach-at91/board-sam9g20ek.c > +++ b/arch/arm/mach-at91/board-sam9g20ek.c > @@ -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) */ -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board @ 2011-11-14 11:29 ` Nicolas Ferre 0 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 11:29 UTC (permalink / raw) To: linux-arm-kernel On 11/14/2011 11:06 AM, Maxime Ripard : > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> (even if I have a tiny comment: see below) > --- > arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ > arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ > 2 files changed, 63 insertions(+), 0 deletions(-) > > diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c > index 39f81f4..0859553 100644 > --- a/arch/arm/mach-at91/at91sam9260_devices.c > +++ b/arch/arm/mach-at91/at91sam9260_devices.c > @@ -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 > + * -------------------------------------------------------------------- */ What about adding all this only if the IIO_AT91ADC is selected? Otherwise, you configure pins without actually using them... > + > +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 > diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c > index 817f59d..e34d41a 100644 > --- a/arch/arm/mach-at91/board-sam9g20ek.c > +++ b/arch/arm/mach-at91/board-sam9g20ek.c > @@ -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) */ -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board 2011-11-14 11:29 ` Nicolas Ferre @ 2011-11-14 15:17 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 15:17 UTC (permalink / raw) To: Nicolas Ferre Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Thomas Petazzoni Hi Nicolas, On 14/11/2011 12:29, Nicolas Ferre wrote: > On 11/14/2011 11:06 AM, Maxime Ripard : >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > > > Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> > > (even if I have a tiny comment: see below) > >> --- >> arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ >> arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ >> 2 files changed, 63 insertions(+), 0 deletions(-) >> >> diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c >> index 39f81f4..0859553 100644 >> --- a/arch/arm/mach-at91/at91sam9260_devices.c >> +++ b/arch/arm/mach-at91/at91sam9260_devices.c >> @@ -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 >> + * -------------------------------------------------------------------- */ > > What about adding all this only if the IIO_AT91ADC is selected? > Otherwise, you configure pins without actually using them... I'm not sure on what I should do here. Should I only wrap the pin configuration part in the at91_add_device_adc function in an ifdef, or wrap all there is in this patch (that is structure definitions, at91_add_device_adc declaration and call, etc) by the ifdef ? > >> + >> +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 >> diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c >> index 817f59d..e34d41a 100644 >> --- a/arch/arm/mach-at91/board-sam9g20ek.c >> +++ b/arch/arm/mach-at91/board-sam9g20ek.c >> @@ -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) */ > > -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board @ 2011-11-14 15:17 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 15:17 UTC (permalink / raw) To: linux-arm-kernel Hi Nicolas, On 14/11/2011 12:29, Nicolas Ferre wrote: > On 11/14/2011 11:06 AM, Maxime Ripard : >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > > > Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> > > (even if I have a tiny comment: see below) > >> --- >> arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ >> arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ >> 2 files changed, 63 insertions(+), 0 deletions(-) >> >> diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c >> index 39f81f4..0859553 100644 >> --- a/arch/arm/mach-at91/at91sam9260_devices.c >> +++ b/arch/arm/mach-at91/at91sam9260_devices.c >> @@ -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 >> + * -------------------------------------------------------------------- */ > > What about adding all this only if the IIO_AT91ADC is selected? > Otherwise, you configure pins without actually using them... I'm not sure on what I should do here. Should I only wrap the pin configuration part in the at91_add_device_adc function in an ifdef, or wrap all there is in this patch (that is structure definitions, at91_add_device_adc declaration and call, etc) by the ifdef ? > >> + >> +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 >> diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c >> index 817f59d..e34d41a 100644 >> --- a/arch/arm/mach-at91/board-sam9g20ek.c >> +++ b/arch/arm/mach-at91/board-sam9g20ek.c >> @@ -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) */ > > -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board 2011-11-14 15:17 ` Maxime Ripard @ 2011-11-14 15:23 ` Nicolas Ferre -1 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 15:23 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Thomas Petazzoni On 11/14/2011 04:17 PM, Maxime Ripard : > Hi Nicolas, > > On 14/11/2011 12:29, Nicolas Ferre wrote: >> On 11/14/2011 11:06 AM, Maxime Ripard : >>> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >>> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >>> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >>> >>> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> >> >> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> >> >> (even if I have a tiny comment: see below) >> >>> --- >>> arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ >>> arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ >>> 2 files changed, 63 insertions(+), 0 deletions(-) >>> >>> diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c >>> index 39f81f4..0859553 100644 >>> --- a/arch/arm/mach-at91/at91sam9260_devices.c >>> +++ b/arch/arm/mach-at91/at91sam9260_devices.c >>> @@ -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 >>> + * -------------------------------------------------------------------- */ >> >> What about adding all this only if the IIO_AT91ADC is selected? >> Otherwise, you configure pins without actually using them... > > I'm not sure on what I should do here. Should I only wrap the pin > configuration part in the at91_add_device_adc function in an ifdef, or > wrap all there is in this patch (that is structure definitions, > at91_add_device_adc declaration and call, etc) by the ifdef ? I mean all adc material surrounded by ifdefs with empty function call if not selected. I was only talking about the pin configuration because the additional structure size is not so much a big deal. >>> + >>> +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 >>> diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c >>> index 817f59d..e34d41a 100644 >>> --- a/arch/arm/mach-at91/board-sam9g20ek.c >>> +++ b/arch/arm/mach-at91/board-sam9g20ek.c >>> @@ -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) */ Bye, -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board @ 2011-11-14 15:23 ` Nicolas Ferre 0 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2011-11-14 15:23 UTC (permalink / raw) To: linux-arm-kernel On 11/14/2011 04:17 PM, Maxime Ripard : > Hi Nicolas, > > On 14/11/2011 12:29, Nicolas Ferre wrote: >> On 11/14/2011 11:06 AM, Maxime Ripard : >>> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >>> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >>> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >>> >>> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> >> >> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> >> >> (even if I have a tiny comment: see below) >> >>> --- >>> arch/arm/mach-at91/at91sam9260_devices.c | 52 ++++++++++++++++++++++++++++++ >>> arch/arm/mach-at91/board-sam9g20ek.c | 11 ++++++ >>> 2 files changed, 63 insertions(+), 0 deletions(-) >>> >>> diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c >>> index 39f81f4..0859553 100644 >>> --- a/arch/arm/mach-at91/at91sam9260_devices.c >>> +++ b/arch/arm/mach-at91/at91sam9260_devices.c >>> @@ -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 >>> + * -------------------------------------------------------------------- */ >> >> What about adding all this only if the IIO_AT91ADC is selected? >> Otherwise, you configure pins without actually using them... > > I'm not sure on what I should do here. Should I only wrap the pin > configuration part in the at91_add_device_adc function in an ifdef, or > wrap all there is in this patch (that is structure definitions, > at91_add_device_adc declaration and call, etc) by the ifdef ? I mean all adc material surrounded by ifdefs with empty function call if not selected. I was only talking about the pin configuration because the additional structure size is not so much a big deal. >>> + >>> +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 >>> diff --git a/arch/arm/mach-at91/board-sam9g20ek.c b/arch/arm/mach-at91/board-sam9g20ek.c >>> index 817f59d..e34d41a 100644 >>> --- a/arch/arm/mach-at91/board-sam9g20ek.c >>> +++ b/arch/arm/mach-at91/board-sam9g20ek.c >>> @@ -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) */ Bye, -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH RESEND v13] AT91: Add a driver for the ADC @ 2012-01-16 21:36 Maxime Ripard 2012-01-16 21:36 ` Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2012-01-16 21:36 UTC (permalink / raw) To: linux-iio, linux-arm-kernel Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni Hi, Working on supporting the hardware triggers for these drivers, I made some patches that Jonathan suggested I merge with this patchset. So, basically, what changed from v12: * Indentation fixes, added some comments * Rework of the platform data. Now the SoC-specific data are directly in the driver, as it was kind of odd to declare all the triggers for the driver in the SoC files. So the driver handles all of this now. It has nice side effects, as it will also ease the transition to DT. As it introduce heavy changes, I dropped the Acked-by and Signed-off-by from the two last patches. Maxime Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2012-01-16 21:36 [PATCH RESEND v13] AT91: Add a driver for the ADC Maxime Ripard @ 2012-01-16 21:36 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2012-01-16 21:36 UTC (permalink / raw) To: linux-iio, linux-arm-kernel Cc: Jean-Christophe PLAGNIOL-VILLARD, Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni, Jonathan Cameron, Marek Vasut, Arnd Bergmann Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Cc: Jonathan Cameron <jic23@cam.ac.uk> Cc: Marek Vasut <marek.vasut@gmail.com> Cc: Arnd Bergmann <arnd@arndb.de> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91_adc.c | 377 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 386 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..02ed274 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -14,6 +14,12 @@ config IIO_AD799X i2c analog to digital convertors (ADC). Provides direct access via sysfs. +config IIO_AT91_ADC + 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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..50ceaab7 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 0000000..34d2b29 --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,377 @@ +/* + * 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/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/iio/iio.h> +#include <linux/platform_data/at91_adc.h> + +#include <mach/at91_adc.h> +#include <mach/cpu.h> + +/** + * struct at91_adc_desc - description of the ADC on the board + * @clock: ADC clock as specified by the datasheet, in Hz. + * @num_channels: global number of channels available on the board (to + specify which channels are indeed in use on the + board, see the channels_used bitmask in the platform + data) + * @startup_time: startup time of the ADC in microseconds + */ +struct at91_adc_desc { + u32 clock; + u8 num_channels; + u8 startup_time; +}; + +struct at91_adc_state { + unsigned long channels_mask; + struct clk *clk; + bool done; + struct at91_adc_desc *desc; + int irq; + u16 last_value; + struct mutex lock; + void __iomem *reg_base; + u32 vref_mv; + wait_queue_head_t wq_data_avail; +}; + +static struct at91_adc_desc at91_adc_desc_sam9g20 = { + .clock = 5000000, + .num_channels = 4, + .startup_time = 10, +}; + +static int at91_adc_select_soc(struct at91_adc_state *st) +{ + if (cpu_is_at91sam9g20()) { + st->desc = &at91_adc_desc_sam9g20; + return 0; + } + + return -ENODEV; +} + +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, + u8 reg) +{ + return readl_relaxed(st->reg_base + reg); +} + +static inline void at91_adc_reg_write(struct at91_adc_state *st, + u8 reg, + u32 val) +{ + writel_relaxed(val, st->reg_base + reg); +} + +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); + + if (!(status & AT91_ADC_DRDY)) + return IRQ_HANDLED; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); + } + + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +} + +static int at91_adc_channel_init(struct iio_dev *idev, + struct at91_adc_data *pdata) +{ + struct at91_adc_state *st = iio_priv(idev); + struct iio_chan_spec *chan_array; + int bit, idx = 0; + + idev->num_channels = bitmap_weight(&pdata->channels_used, + st->desc->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, st->desc->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 at91_adc_channel_remove(struct iio_dev *idev) +{ + kfree(idev->channels); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + + at91_adc_reg_write(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_reg_write(st, AT91_ADC_IER, + AT91_ADC_EOC(chan->channel)); + at91_adc_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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *val = st->last_value; + + at91_adc_reg_write(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int __devinit at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks; + int ret; + struct iio_dev *idev; + struct at91_adc_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 at91_adc_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 = &at91_adc_info; + + st = iio_priv(idev); + ret = at91_adc_select_soc(st); + if (ret) { + dev_err(&pdev->dev, "SoC unknown\n"); + goto error_free_device; + } + + 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 + */ + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); + ret = request_irq(st->irq, + at91_adc_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 (!st->desc->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 * st->desc->clock)) - 1; + + if (!st->desc->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((st->desc->startup_time * st->desc->clock / + 1000000) - 1, 8) / 8; + at91_adc_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 = at91_adc_channel_init(idev, pdata); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't initialize the channels.\n"); + goto error_free_clk; + } + + init_waitqueue_head(&st->wq_data_avail); + mutex_init(&st->lock); + + st->vref_mv = pdata->vref; + st->channels_mask = pdata->channels_used; + + ret = iio_device_register(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_free_channels; + } + + return 0; + +error_free_channels: + at91_adc_channel_remove(idev); +error_free_clk: + clk_disable(st->clk); + clk_put(st->clk); +error_free_irq: + free_irq(st->irq, idev); +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 at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + at91_adc_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 at91_adc_driver = { + .probe = at91_adc_probe, + .remove = __devexit_p(at91_adc_remove), + .driver = { + .name = "at91_adc", + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2012-01-16 21:36 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2012-01-16 21:36 UTC (permalink / raw) To: linux-arm-kernel Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Cc: Jonathan Cameron <jic23@cam.ac.uk> Cc: Marek Vasut <marek.vasut@gmail.com> Cc: Arnd Bergmann <arnd@arndb.de> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91_adc.c | 377 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 386 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..02ed274 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -14,6 +14,12 @@ config IIO_AD799X i2c analog to digital convertors (ADC). Provides direct access via sysfs. +config IIO_AT91_ADC + 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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..50ceaab7 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 0000000..34d2b29 --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,377 @@ +/* + * 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/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/iio/iio.h> +#include <linux/platform_data/at91_adc.h> + +#include <mach/at91_adc.h> +#include <mach/cpu.h> + +/** + * struct at91_adc_desc - description of the ADC on the board + * @clock: ADC clock as specified by the datasheet, in Hz. + * @num_channels: global number of channels available on the board (to + specify which channels are indeed in use on the + board, see the channels_used bitmask in the platform + data) + * @startup_time: startup time of the ADC in microseconds + */ +struct at91_adc_desc { + u32 clock; + u8 num_channels; + u8 startup_time; +}; + +struct at91_adc_state { + unsigned long channels_mask; + struct clk *clk; + bool done; + struct at91_adc_desc *desc; + int irq; + u16 last_value; + struct mutex lock; + void __iomem *reg_base; + u32 vref_mv; + wait_queue_head_t wq_data_avail; +}; + +static struct at91_adc_desc at91_adc_desc_sam9g20 = { + .clock = 5000000, + .num_channels = 4, + .startup_time = 10, +}; + +static int at91_adc_select_soc(struct at91_adc_state *st) +{ + if (cpu_is_at91sam9g20()) { + st->desc = &at91_adc_desc_sam9g20; + return 0; + } + + return -ENODEV; +} + +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, + u8 reg) +{ + return readl_relaxed(st->reg_base + reg); +} + +static inline void at91_adc_reg_write(struct at91_adc_state *st, + u8 reg, + u32 val) +{ + writel_relaxed(val, st->reg_base + reg); +} + +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); + + if (!(status & AT91_ADC_DRDY)) + return IRQ_HANDLED; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); + } + + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +} + +static int at91_adc_channel_init(struct iio_dev *idev, + struct at91_adc_data *pdata) +{ + struct at91_adc_state *st = iio_priv(idev); + struct iio_chan_spec *chan_array; + int bit, idx = 0; + + idev->num_channels = bitmap_weight(&pdata->channels_used, + st->desc->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, st->desc->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 at91_adc_channel_remove(struct iio_dev *idev) +{ + kfree(idev->channels); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + + at91_adc_reg_write(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_reg_write(st, AT91_ADC_IER, + AT91_ADC_EOC(chan->channel)); + at91_adc_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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *val = st->last_value; + + at91_adc_reg_write(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int __devinit at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks; + int ret; + struct iio_dev *idev; + struct at91_adc_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 at91_adc_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 = &at91_adc_info; + + st = iio_priv(idev); + ret = at91_adc_select_soc(st); + if (ret) { + dev_err(&pdev->dev, "SoC unknown\n"); + goto error_free_device; + } + + 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 + */ + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); + ret = request_irq(st->irq, + at91_adc_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 (!st->desc->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 * st->desc->clock)) - 1; + + if (!st->desc->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((st->desc->startup_time * st->desc->clock / + 1000000) - 1, 8) / 8; + at91_adc_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 = at91_adc_channel_init(idev, pdata); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't initialize the channels.\n"); + goto error_free_clk; + } + + init_waitqueue_head(&st->wq_data_avail); + mutex_init(&st->lock); + + st->vref_mv = pdata->vref; + st->channels_mask = pdata->channels_used; + + ret = iio_device_register(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_free_channels; + } + + return 0; + +error_free_channels: + at91_adc_channel_remove(idev); +error_free_clk: + clk_disable(st->clk); + clk_put(st->clk); +error_free_irq: + free_irq(st->irq, idev); +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 at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + at91_adc_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 at91_adc_driver = { + .probe = at91_adc_probe, + .remove = __devexit_p(at91_adc_remove), + .driver = { + .name = "at91_adc", + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2012-01-16 21:36 ` Maxime Ripard @ 2012-01-17 17:35 ` Arnd Bergmann -1 siblings, 0 replies; 99+ messages in thread From: Arnd Bergmann @ 2012-01-17 17:35 UTC (permalink / raw) To: Maxime Ripard Cc: linux-iio, linux-arm-kernel, Jean-Christophe PLAGNIOL-VILLARD, Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni, Jonathan Cameron, Marek Vasut On Monday 16 January 2012, Maxime Ripard wrote: > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > > Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > Cc: Jonathan Cameron <jic23@cam.ac.uk> > Cc: Marek Vasut <marek.vasut@gmail.com> > Cc: Arnd Bergmann <arnd@arndb.de> I think I reviewed this driver before, but I don't remember any of the details I may have complained about. I assume everything was fixed, since the driver looks good in this version ;-) Acked-by: Arnd Bergmann <arnd@arndb.de> Two general comments: * Please add a changeset description (i.e. thetext above your Signed-off-by: line) for every patch you do. Just read a few dozen patches that get posted to lkml to get a feeling for what would be useful to have in there. For a new driver, it would be helpful to know what hardware uses this driver and what the device does there. * Over time, we will get stricter in requiring device tree bindings for new platform devices, to replace platform_data where appropriate. It's not required yet, but it would be nice to describe the long-term plan in submissions that add new platform_data but no device tree bindings. Arnd ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2012-01-17 17:35 ` Arnd Bergmann 0 siblings, 0 replies; 99+ messages in thread From: Arnd Bergmann @ 2012-01-17 17:35 UTC (permalink / raw) To: linux-arm-kernel On Monday 16 January 2012, Maxime Ripard wrote: > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > > Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > Cc: Jonathan Cameron <jic23@cam.ac.uk> > Cc: Marek Vasut <marek.vasut@gmail.com> > Cc: Arnd Bergmann <arnd@arndb.de> I think I reviewed this driver before, but I don't remember any of the details I may have complained about. I assume everything was fixed, since the driver looks good in this version ;-) Acked-by: Arnd Bergmann <arnd@arndb.de> Two general comments: * Please add a changeset description (i.e. thetext above your Signed-off-by: line) for every patch you do. Just read a few dozen patches that get posted to lkml to get a feeling for what would be useful to have in there. For a new driver, it would be helpful to know what hardware uses this driver and what the device does there. * Over time, we will get stricter in requiring device tree bindings for new platform devices, to replace platform_data where appropriate. It's not required yet, but it would be nice to describe the long-term plan in submissions that add new platform_data but no device tree bindings. Arnd ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2012-01-17 17:35 ` Arnd Bergmann @ 2012-01-17 19:08 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2012-01-17 19:08 UTC (permalink / raw) To: Arnd Bergmann Cc: linux-iio, linux-arm-kernel, Jean-Christophe PLAGNIOL-VILLARD, Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni, Jonathan Cameron, Marek Vasut Hi Arnd, On 17/01/2012 18:35, Arnd Bergmann wrote: > On Monday 16 January 2012, Maxime Ripard wrote: >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> >> Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> Cc: Jonathan Cameron <jic23@cam.ac.uk> >> Cc: Marek Vasut <marek.vasut@gmail.com> >> Cc: Arnd Bergmann <arnd@arndb.de> > > I think I reviewed this driver before, but I don't remember any of the > details I may have complained about. I assume everything was fixed, > since the driver looks good in this version ;-) You did, and as far as I remember, you did not complained much. but actually, I've rewritten a large enough part of this driver to make the Acked-by you gave at the time at best outdated. That's why I dropped all the SoB and friends to a Cc. I mentionned it in the cover letter, but I forgot to Cc you one that one too, my bad... > Acked-by: Arnd Bergmann <arnd@arndb.de> > > Two general comments: > > * Please add a changeset description (i.e. thetext above your > Signed-off-by: line) for every patch you do. Just read a few > dozen patches that get posted to lkml to get a feeling for > what would be useful to have in there. For a new driver, it > would be helpful to know what hardware uses this driver and > what the device does there. Ok, will do. > * Over time, we will get stricter in requiring device tree > bindings for new platform devices, to replace platform_data > where appropriate. It's not required yet, but it would be nice > to describe the long-term plan in submissions that add new > platform_data but no device tree bindings. The main point of rewriting a part of it was exactly to make the transition to device-tree and single kernel image easier when the time comes. Maxime -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2012-01-17 19:08 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2012-01-17 19:08 UTC (permalink / raw) To: linux-arm-kernel Hi Arnd, On 17/01/2012 18:35, Arnd Bergmann wrote: > On Monday 16 January 2012, Maxime Ripard wrote: >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> >> Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> Cc: Jonathan Cameron <jic23@cam.ac.uk> >> Cc: Marek Vasut <marek.vasut@gmail.com> >> Cc: Arnd Bergmann <arnd@arndb.de> > > I think I reviewed this driver before, but I don't remember any of the > details I may have complained about. I assume everything was fixed, > since the driver looks good in this version ;-) You did, and as far as I remember, you did not complained much. but actually, I've rewritten a large enough part of this driver to make the Acked-by you gave at the time at best outdated. That's why I dropped all the SoB and friends to a Cc. I mentionned it in the cover letter, but I forgot to Cc you one that one too, my bad... > Acked-by: Arnd Bergmann <arnd@arndb.de> > > Two general comments: > > * Please add a changeset description (i.e. thetext above your > Signed-off-by: line) for every patch you do. Just read a few > dozen patches that get posted to lkml to get a feeling for > what would be useful to have in there. For a new driver, it > would be helpful to know what hardware uses this driver and > what the device does there. Ok, will do. > * Over time, we will get stricter in requiring device tree > bindings for new platform devices, to replace platform_data > where appropriate. It's not required yet, but it would be nice > to describe the long-term plan in submissions that add new > platform_data but no device tree bindings. The main point of rewriting a part of it was exactly to make the transition to device-tree and single kernel image easier when the time comes. Maxime -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2012-01-16 21:36 ` Maxime Ripard @ 2012-01-18 10:27 ` Nicolas Ferre -1 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2012-01-18 10:27 UTC (permalink / raw) To: Maxime Ripard Cc: linux-iio, linux-arm-kernel, Jean-Christophe PLAGNIOL-VILLARD, Patrice Vilchez, Thomas Petazzoni, Jonathan Cameron, Marek Vasut, Arnd Bergmann On 01/16/2012 10:36 PM, Maxime Ripard : > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > > Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> You can add my: Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > Cc: Jonathan Cameron <jic23@cam.ac.uk> > Cc: Marek Vasut <marek.vasut@gmail.com> > Cc: Arnd Bergmann <arnd@arndb.de> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91_adc.c | 377 ++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 386 insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91_adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..02ed274 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -14,6 +14,12 @@ config IIO_AD799X > i2c analog to digital convertors (ADC). Provides direct access > via sysfs. > > +config IIO_AT91_ADC > + 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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..50ceaab7 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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_AT91_ADC) += at91_adc.o > diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c > new file mode 100644 > index 0000000..34d2b29 > --- /dev/null > +++ b/drivers/iio/adc/at91_adc.c > @@ -0,0 +1,377 @@ > +/* > + * 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/sched.h> > +#include <linux/slab.h> > +#include <linux/wait.h> > + > +#include <linux/iio/iio.h> > +#include <linux/platform_data/at91_adc.h> > + > +#include <mach/at91_adc.h> > +#include <mach/cpu.h> > + > +/** > + * struct at91_adc_desc - description of the ADC on the board > + * @clock: ADC clock as specified by the datasheet, in Hz. > + * @num_channels: global number of channels available on the board (to > + specify which channels are indeed in use on the > + board, see the channels_used bitmask in the platform > + data) > + * @startup_time: startup time of the ADC in microseconds > + */ > +struct at91_adc_desc { > + u32 clock; > + u8 num_channels; > + u8 startup_time; > +}; > + > +struct at91_adc_state { > + unsigned long channels_mask; > + struct clk *clk; > + bool done; > + struct at91_adc_desc *desc; > + int irq; > + u16 last_value; > + struct mutex lock; > + void __iomem *reg_base; > + u32 vref_mv; > + wait_queue_head_t wq_data_avail; > +}; > + > +static struct at91_adc_desc at91_adc_desc_sam9g20 = { > + .clock = 5000000, > + .num_channels = 4, > + .startup_time = 10, > +}; > + > +static int at91_adc_select_soc(struct at91_adc_state *st) > +{ > + if (cpu_is_at91sam9g20()) { > + st->desc = &at91_adc_desc_sam9g20; > + return 0; > + } > + > + return -ENODEV; > +} > + > +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, > + u8 reg) > +{ > + return readl_relaxed(st->reg_base + reg); > +} > + > +static inline void at91_adc_reg_write(struct at91_adc_state *st, > + u8 reg, > + u32 val) > +{ > + writel_relaxed(val, st->reg_base + reg); > +} > + > +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) > +{ > + struct iio_dev *idev = private; > + struct at91_adc_state *st = iio_priv(idev); > + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); > + > + if (!(status & AT91_ADC_DRDY)) > + return IRQ_HANDLED; > + > + if (status & st->channels_mask) { > + st->done = true; > + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + wake_up_interruptible(&st->wq_data_avail); > + > + return IRQ_HANDLED; > +} > + > +static int at91_adc_channel_init(struct iio_dev *idev, > + struct at91_adc_data *pdata) > +{ > + struct at91_adc_state *st = iio_priv(idev); > + struct iio_chan_spec *chan_array; > + int bit, idx = 0; > + > + idev->num_channels = bitmap_weight(&pdata->channels_used, > + st->desc->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, st->desc->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 at91_adc_channel_remove(struct iio_dev *idev) > +{ > + kfree(idev->channels); > +} > + > +static int at91_adc_read_raw(struct iio_dev *idev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct at91_adc_state *st = iio_priv(idev); > + int ret; > + > + switch (mask) { > + case 0: > + mutex_lock(&st->lock); > + > + at91_adc_reg_write(st, AT91_ADC_CHER, > + AT91_ADC_CH(chan->channel)); > + at91_adc_reg_write(st, AT91_ADC_IER, > + AT91_ADC_EOC(chan->channel)); > + at91_adc_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 == 0) > + return -ETIMEDOUT; > + else if (ret < 0) > + return ret; > + > + *val = st->last_value; > + > + at91_adc_reg_write(st, AT91_ADC_CHDR, > + AT91_ADC_CH(chan->channel)); > + at91_adc_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: > + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; > + *val2 = 0; > + return IIO_VAL_INT_PLUS_MICRO; > + default: > + break; > + } > + return -EINVAL; > +} > + > +static const struct iio_info at91_adc_info = { > + .driver_module = THIS_MODULE, > + .read_raw = &at91_adc_read_raw, > +}; > + > +static int __devinit at91_adc_probe(struct platform_device *pdev) > +{ > + unsigned int prsc, mstrclk, ticks; > + int ret; > + struct iio_dev *idev; > + struct at91_adc_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 at91_adc_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 = &at91_adc_info; > + > + st = iio_priv(idev); > + ret = at91_adc_select_soc(st); > + if (ret) { > + dev_err(&pdev->dev, "SoC unknown\n"); > + goto error_free_device; > + } > + > + 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 > + */ > + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); > + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); > + ret = request_irq(st->irq, > + at91_adc_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 (!st->desc->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 * st->desc->clock)) - 1; > + > + if (!st->desc->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((st->desc->startup_time * st->desc->clock / > + 1000000) - 1, 8) / 8; > + at91_adc_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 = at91_adc_channel_init(idev, pdata); > + if (ret < 0) { > + dev_err(&pdev->dev, "Couldn't initialize the channels.\n"); > + goto error_free_clk; > + } > + > + init_waitqueue_head(&st->wq_data_avail); > + mutex_init(&st->lock); > + > + st->vref_mv = pdata->vref; > + st->channels_mask = pdata->channels_used; > + > + ret = iio_device_register(idev); > + if (ret < 0) { > + dev_err(&pdev->dev, "Couldn't register the device.\n"); > + goto error_free_channels; > + } > + > + return 0; > + > +error_free_channels: > + at91_adc_channel_remove(idev); > +error_free_clk: > + clk_disable(st->clk); > + clk_put(st->clk); > +error_free_irq: > + free_irq(st->irq, idev); > +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 at91_adc_remove(struct platform_device *pdev) > +{ > + struct iio_dev *idev = platform_get_drvdata(pdev); > + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + struct at91_adc_state *st = iio_priv(idev); > + > + iio_device_unregister(idev); > + at91_adc_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 at91_adc_driver = { > + .probe = at91_adc_probe, > + .remove = __devexit_p(at91_adc_remove), > + .driver = { > + .name = "at91_adc", > + }, > +}; > + > +module_platform_driver(at91_adc_driver); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); > +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2012-01-18 10:27 ` Nicolas Ferre 0 siblings, 0 replies; 99+ messages in thread From: Nicolas Ferre @ 2012-01-18 10:27 UTC (permalink / raw) To: linux-arm-kernel On 01/16/2012 10:36 PM, Maxime Ripard : > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > > Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> You can add my: Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > Cc: Jonathan Cameron <jic23@cam.ac.uk> > Cc: Marek Vasut <marek.vasut@gmail.com> > Cc: Arnd Bergmann <arnd@arndb.de> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91_adc.c | 377 ++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 386 insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91_adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..02ed274 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -14,6 +14,12 @@ config IIO_AD799X > i2c analog to digital convertors (ADC). Provides direct access > via sysfs. > > +config IIO_AT91_ADC > + 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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..50ceaab7 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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_AT91_ADC) += at91_adc.o > diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c > new file mode 100644 > index 0000000..34d2b29 > --- /dev/null > +++ b/drivers/iio/adc/at91_adc.c > @@ -0,0 +1,377 @@ > +/* > + * 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/sched.h> > +#include <linux/slab.h> > +#include <linux/wait.h> > + > +#include <linux/iio/iio.h> > +#include <linux/platform_data/at91_adc.h> > + > +#include <mach/at91_adc.h> > +#include <mach/cpu.h> > + > +/** > + * struct at91_adc_desc - description of the ADC on the board > + * @clock: ADC clock as specified by the datasheet, in Hz. > + * @num_channels: global number of channels available on the board (to > + specify which channels are indeed in use on the > + board, see the channels_used bitmask in the platform > + data) > + * @startup_time: startup time of the ADC in microseconds > + */ > +struct at91_adc_desc { > + u32 clock; > + u8 num_channels; > + u8 startup_time; > +}; > + > +struct at91_adc_state { > + unsigned long channels_mask; > + struct clk *clk; > + bool done; > + struct at91_adc_desc *desc; > + int irq; > + u16 last_value; > + struct mutex lock; > + void __iomem *reg_base; > + u32 vref_mv; > + wait_queue_head_t wq_data_avail; > +}; > + > +static struct at91_adc_desc at91_adc_desc_sam9g20 = { > + .clock = 5000000, > + .num_channels = 4, > + .startup_time = 10, > +}; > + > +static int at91_adc_select_soc(struct at91_adc_state *st) > +{ > + if (cpu_is_at91sam9g20()) { > + st->desc = &at91_adc_desc_sam9g20; > + return 0; > + } > + > + return -ENODEV; > +} > + > +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, > + u8 reg) > +{ > + return readl_relaxed(st->reg_base + reg); > +} > + > +static inline void at91_adc_reg_write(struct at91_adc_state *st, > + u8 reg, > + u32 val) > +{ > + writel_relaxed(val, st->reg_base + reg); > +} > + > +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) > +{ > + struct iio_dev *idev = private; > + struct at91_adc_state *st = iio_priv(idev); > + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); > + > + if (!(status & AT91_ADC_DRDY)) > + return IRQ_HANDLED; > + > + if (status & st->channels_mask) { > + st->done = true; > + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + wake_up_interruptible(&st->wq_data_avail); > + > + return IRQ_HANDLED; > +} > + > +static int at91_adc_channel_init(struct iio_dev *idev, > + struct at91_adc_data *pdata) > +{ > + struct at91_adc_state *st = iio_priv(idev); > + struct iio_chan_spec *chan_array; > + int bit, idx = 0; > + > + idev->num_channels = bitmap_weight(&pdata->channels_used, > + st->desc->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, st->desc->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 at91_adc_channel_remove(struct iio_dev *idev) > +{ > + kfree(idev->channels); > +} > + > +static int at91_adc_read_raw(struct iio_dev *idev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct at91_adc_state *st = iio_priv(idev); > + int ret; > + > + switch (mask) { > + case 0: > + mutex_lock(&st->lock); > + > + at91_adc_reg_write(st, AT91_ADC_CHER, > + AT91_ADC_CH(chan->channel)); > + at91_adc_reg_write(st, AT91_ADC_IER, > + AT91_ADC_EOC(chan->channel)); > + at91_adc_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 == 0) > + return -ETIMEDOUT; > + else if (ret < 0) > + return ret; > + > + *val = st->last_value; > + > + at91_adc_reg_write(st, AT91_ADC_CHDR, > + AT91_ADC_CH(chan->channel)); > + at91_adc_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: > + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; > + *val2 = 0; > + return IIO_VAL_INT_PLUS_MICRO; > + default: > + break; > + } > + return -EINVAL; > +} > + > +static const struct iio_info at91_adc_info = { > + .driver_module = THIS_MODULE, > + .read_raw = &at91_adc_read_raw, > +}; > + > +static int __devinit at91_adc_probe(struct platform_device *pdev) > +{ > + unsigned int prsc, mstrclk, ticks; > + int ret; > + struct iio_dev *idev; > + struct at91_adc_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 at91_adc_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 = &at91_adc_info; > + > + st = iio_priv(idev); > + ret = at91_adc_select_soc(st); > + if (ret) { > + dev_err(&pdev->dev, "SoC unknown\n"); > + goto error_free_device; > + } > + > + 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 > + */ > + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); > + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); > + ret = request_irq(st->irq, > + at91_adc_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 (!st->desc->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 * st->desc->clock)) - 1; > + > + if (!st->desc->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((st->desc->startup_time * st->desc->clock / > + 1000000) - 1, 8) / 8; > + at91_adc_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 = at91_adc_channel_init(idev, pdata); > + if (ret < 0) { > + dev_err(&pdev->dev, "Couldn't initialize the channels.\n"); > + goto error_free_clk; > + } > + > + init_waitqueue_head(&st->wq_data_avail); > + mutex_init(&st->lock); > + > + st->vref_mv = pdata->vref; > + st->channels_mask = pdata->channels_used; > + > + ret = iio_device_register(idev); > + if (ret < 0) { > + dev_err(&pdev->dev, "Couldn't register the device.\n"); > + goto error_free_channels; > + } > + > + return 0; > + > +error_free_channels: > + at91_adc_channel_remove(idev); > +error_free_clk: > + clk_disable(st->clk); > + clk_put(st->clk); > +error_free_irq: > + free_irq(st->irq, idev); > +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 at91_adc_remove(struct platform_device *pdev) > +{ > + struct iio_dev *idev = platform_get_drvdata(pdev); > + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + struct at91_adc_state *st = iio_priv(idev); > + > + iio_device_unregister(idev); > + at91_adc_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 at91_adc_driver = { > + .probe = at91_adc_probe, > + .remove = __devexit_p(at91_adc_remove), > + .driver = { > + .name = "at91_adc", > + }, > +}; > + > +module_platform_driver(at91_adc_driver); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); > +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- Nicolas Ferre ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH v13] AT91: Add a driver for the ADC @ 2011-12-14 10:01 Maxime Ripard 2011-12-14 10:01 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-12-14 10:01 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni Hi, Working on supporting the hardware triggers for these drivers, I made some patches that Jonathan suggested I merge with this patchset. So, basically, what changed from v12: * Indentation fixes, added some comments * Rework of the platform data. Now the SoC-specific data are directly in the driver, as it was kind of odd to declare all the triggers for the driver in the SoC files. So the driver handles all of this now. It has nice side effects, as it will also ease the transition to DT. As it introduce heavy changes, I dropped the Acked-by and Signed-off-by from the two last patches. Maxime Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-12-14 10:01 [PATCH v13] AT91: Add a driver for the ADC Maxime Ripard @ 2011-12-14 10:01 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-12-14 10:01 UTC (permalink / raw) To: linux-arm-kernel Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91_adc.c | 377 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 386 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..02ed274 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -14,6 +14,12 @@ config IIO_AD799X i2c analog to digital convertors (ADC). Provides direct access via sysfs. +config IIO_AT91_ADC + 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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..50ceaab7 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 0000000..34d2b29 --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,377 @@ +/* + * 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/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/iio/iio.h> +#include <linux/platform_data/at91_adc.h> + +#include <mach/at91_adc.h> +#include <mach/cpu.h> + +/** + * struct at91_adc_desc - description of the ADC on the board + * @clock: ADC clock as specified by the datasheet, in Hz. + * @num_channels: global number of channels available on the board (to + specify which channels are indeed in use on the + board, see the channels_used bitmask in the platform + data) + * @startup_time: startup time of the ADC in microseconds + */ +struct at91_adc_desc { + u32 clock; + u8 num_channels; + u8 startup_time; +}; + +struct at91_adc_state { + unsigned long channels_mask; + struct clk *clk; + bool done; + struct at91_adc_desc *desc; + int irq; + u16 last_value; + struct mutex lock; + void __iomem *reg_base; + u32 vref_mv; + wait_queue_head_t wq_data_avail; +}; + +static struct at91_adc_desc at91_adc_desc_sam9g20 = { + .clock = 5000000, + .num_channels = 4, + .startup_time = 10, +}; + +static int at91_adc_select_soc(struct at91_adc_state *st) +{ + if (cpu_is_at91sam9g20()) { + st->desc = &at91_adc_desc_sam9g20; + return 0; + } + + return -ENODEV; +} + +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, + u8 reg) +{ + return readl_relaxed(st->reg_base + reg); +} + +static inline void at91_adc_reg_write(struct at91_adc_state *st, + u8 reg, + u32 val) +{ + writel_relaxed(val, st->reg_base + reg); +} + +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); + + if (!(status & AT91_ADC_DRDY)) + return IRQ_HANDLED; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); + } + + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +} + +static int at91_adc_channel_init(struct iio_dev *idev, + struct at91_adc_data *pdata) +{ + struct at91_adc_state *st = iio_priv(idev); + struct iio_chan_spec *chan_array; + int bit, idx = 0; + + idev->num_channels = bitmap_weight(&pdata->channels_used, + st->desc->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, st->desc->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 at91_adc_channel_remove(struct iio_dev *idev) +{ + kfree(idev->channels); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + + at91_adc_reg_write(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_reg_write(st, AT91_ADC_IER, + AT91_ADC_EOC(chan->channel)); + at91_adc_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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *val = st->last_value; + + at91_adc_reg_write(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int __devinit at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks; + int ret; + struct iio_dev *idev; + struct at91_adc_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 at91_adc_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 = &at91_adc_info; + + st = iio_priv(idev); + ret = at91_adc_select_soc(st); + if (ret) { + dev_err(&pdev->dev, "SoC unknown\n"); + goto error_free_device; + } + + 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 + */ + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); + ret = request_irq(st->irq, + at91_adc_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 (!st->desc->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 * st->desc->clock)) - 1; + + if (!st->desc->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((st->desc->startup_time * st->desc->clock / + 1000000) - 1, 8) / 8; + at91_adc_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 = at91_adc_channel_init(idev, pdata); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't initialize the channels.\n"); + goto error_free_clk; + } + + init_waitqueue_head(&st->wq_data_avail); + mutex_init(&st->lock); + + st->vref_mv = pdata->vref; + st->channels_mask = pdata->channels_used; + + ret = iio_device_register(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_free_channels; + } + + return 0; + +error_free_channels: + at91_adc_channel_remove(idev); +error_free_clk: + clk_disable(st->clk); + clk_put(st->clk); +error_free_irq: + free_irq(st->irq, idev); +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 at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + at91_adc_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 at91_adc_driver = { + .probe = at91_adc_probe, + .remove = __devexit_p(at91_adc_remove), + .driver = { + .name = "at91_adc", + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH v12] AT91: Add a driver for the ADC @ 2011-12-02 13:17 Maxime Ripard 2011-12-02 13:17 ` Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-12-02 13:17 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Hi all, Since everyone seemed to be ok with the v11 of this patchset, it seems that it is now near inclusion. Maxime ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-12-02 13:17 [PATCH v12] AT91: Add a driver for the ADC Maxime Ripard @ 2011-12-02 13:17 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-12-02 13:17 UTC (permalink / raw) To: linux-arm-kernel, linux-iio; +Cc: Patrice Vilchez, Thomas Petazzoni Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> Acked-by: Arnd Bergmann <arnd@arndb.de> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91_adc.c | 333 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..02ed274 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -14,6 +14,12 @@ config IIO_AD799X i2c analog to digital convertors (ADC). Provides direct access via sysfs. +config IIO_AT91_ADC + 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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..50ceaab7 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 0000000..69227cd --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,333 @@ +/* + * 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/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/iio/iio.h> +#include <linux/platform_data/at91_adc.h> + +#include <mach/at91_adc.h> + +struct at91_adc_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; + unsigned long channels_mask; +}; + +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, + u8 reg) +{ + return readl_relaxed(st->reg_base + reg); +} + +static inline void at91_adc_reg_write(struct at91_adc_state *st, + u8 reg, + u32 val) +{ + writel_relaxed(val, st->reg_base + reg); +} + +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); + + if (!(status & AT91_ADC_DRDY)) + return IRQ_HANDLED; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); + } + + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +} + +static int at91_adc_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 at91_adc_channel_remove(struct iio_dev *idev) +{ + kfree(idev->channels); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + + at91_adc_reg_write(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_reg_write(st, AT91_ADC_IER, + AT91_ADC_EOC(chan->channel)); + at91_adc_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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *val = st->last_value; + + at91_adc_reg_write(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int __devinit at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks; + int ret; + struct iio_dev *idev; + struct at91_adc_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 at91_adc_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 = &at91_adc_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 + */ + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); + ret = request_irq(st->irq, + at91_adc_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; + at91_adc_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 = at91_adc_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; + st->channels_mask = pdata->channels_used; + + ret = iio_device_register(idev); + if (ret < 0) + goto error_free_channels; + + return 0; + +error_free_channels: + at91_adc_channel_remove(idev); +error_free_clk: + clk_disable(st->clk); + clk_put(st->clk); +error_free_irq: + free_irq(st->irq, idev); +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 at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + at91_adc_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 at91_adc_driver = { + .probe = at91_adc_probe, + .remove = __devexit_p(at91_adc_remove), + .driver = { + .name = "at91_adc", + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-12-02 13:17 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-12-02 13:17 UTC (permalink / raw) To: linux-arm-kernel Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> Acked-by: Arnd Bergmann <arnd@arndb.de> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91_adc.c | 333 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..02ed274 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -14,6 +14,12 @@ config IIO_AD799X i2c analog to digital convertors (ADC). Provides direct access via sysfs. +config IIO_AT91_ADC + 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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..50ceaab7 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 0000000..69227cd --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,333 @@ +/* + * 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/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/iio/iio.h> +#include <linux/platform_data/at91_adc.h> + +#include <mach/at91_adc.h> + +struct at91_adc_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; + unsigned long channels_mask; +}; + +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, + u8 reg) +{ + return readl_relaxed(st->reg_base + reg); +} + +static inline void at91_adc_reg_write(struct at91_adc_state *st, + u8 reg, + u32 val) +{ + writel_relaxed(val, st->reg_base + reg); +} + +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); + + if (!(status & AT91_ADC_DRDY)) + return IRQ_HANDLED; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); + } + + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +} + +static int at91_adc_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 at91_adc_channel_remove(struct iio_dev *idev) +{ + kfree(idev->channels); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + + at91_adc_reg_write(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_reg_write(st, AT91_ADC_IER, + AT91_ADC_EOC(chan->channel)); + at91_adc_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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *val = st->last_value; + + at91_adc_reg_write(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int __devinit at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks; + int ret; + struct iio_dev *idev; + struct at91_adc_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 at91_adc_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 = &at91_adc_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 + */ + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); + ret = request_irq(st->irq, + at91_adc_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; + at91_adc_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 = at91_adc_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; + st->channels_mask = pdata->channels_used; + + ret = iio_device_register(idev); + if (ret < 0) + goto error_free_channels; + + return 0; + +error_free_channels: + at91_adc_channel_remove(idev); +error_free_clk: + clk_disable(st->clk); + clk_put(st->clk); +error_free_irq: + free_irq(st->irq, idev); +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 at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + at91_adc_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 at91_adc_driver = { + .probe = at91_adc_probe, + .remove = __devexit_p(at91_adc_remove), + .driver = { + .name = "at91_adc", + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH v11] AT91: Add a driver for the ADC @ 2011-11-30 9:14 Maxime Ripard 2011-11-30 9:15 ` Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-30 9:14 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni Hi all, As suggested by Arnd, I moved the platform data from the board.h additions in the first patch to a new include/linux/platform_data/at91_adc.h file for this version. Maxime Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-30 9:14 [PATCH v11] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-30 9:15 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-30 9:15 UTC (permalink / raw) To: linux-arm-kernel, linux-iio; +Cc: Patrice Vilchez, Thomas Petazzoni Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91_adc.c | 333 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..02ed274 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -14,6 +14,12 @@ config IIO_AD799X i2c analog to digital convertors (ADC). Provides direct access via sysfs. +config IIO_AT91_ADC + 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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..50ceaab7 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 0000000..d4cbc06 --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,333 @@ +/* + * 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 <linux/platform_data/at91_adc.h> + +#include <mach/board.h> +#include <mach/at91_adc.h> + +struct at91_adc_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; + unsigned long channels_mask; +}; + +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, + u8 reg) +{ + return readl_relaxed(st->reg_base + reg); +} + +static inline void at91_adc_reg_write(struct at91_adc_state *st, + u8 reg, + u32 val) +{ + writel_relaxed(val, st->reg_base + reg); +} + +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); + + if (!(status & AT91_ADC_DRDY)) + return IRQ_HANDLED; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); + } + + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +} + +static int at91_adc_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 at91_adc_channel_remove(struct iio_dev *idev) +{ + kfree(idev->channels); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + + at91_adc_reg_write(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_reg_write(st, AT91_ADC_IER, + AT91_ADC_EOC(chan->channel)); + at91_adc_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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *val = st->last_value; + + at91_adc_reg_write(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int __devinit at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks; + int ret; + struct iio_dev *idev; + struct at91_adc_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 at91_adc_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 = &at91_adc_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 + */ + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); + ret = request_irq(st->irq, + at91_adc_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; + at91_adc_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 = at91_adc_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; + st->channels_mask = pdata->channels_used; + + ret = iio_device_register(idev); + if (ret < 0) + goto error_free_channels; + + return 0; + +error_free_channels: + at91_adc_channel_remove(idev); +error_free_clk: + clk_disable(st->clk); + clk_put(st->clk); +error_free_irq: + free_irq(st->irq, idev); +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 at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + at91_adc_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 at91_adc_driver = { + .probe = at91_adc_probe, + .remove = __devexit_p(at91_adc_remove), + .driver = { + .name = "at91_adc", + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-30 9:15 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-30 9:15 UTC (permalink / raw) To: linux-arm-kernel Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91_adc.c | 333 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..02ed274 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -14,6 +14,12 @@ config IIO_AD799X i2c analog to digital convertors (ADC). Provides direct access via sysfs. +config IIO_AT91_ADC + 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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..50ceaab7 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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_AT91_ADC) += at91_adc.o diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 0000000..d4cbc06 --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,333 @@ +/* + * 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 <linux/platform_data/at91_adc.h> + +#include <mach/board.h> +#include <mach/at91_adc.h> + +struct at91_adc_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; + unsigned long channels_mask; +}; + +static inline u32 at91_adc_reg_read(struct at91_adc_state *st, + u8 reg) +{ + return readl_relaxed(st->reg_base + reg); +} + +static inline void at91_adc_reg_write(struct at91_adc_state *st, + u8 reg, + u32 val) +{ + writel_relaxed(val, st->reg_base + reg); +} + +static irqreturn_t at91_adc_eoc_trigger(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + unsigned int status = at91_adc_reg_read(st, AT91_ADC_SR); + + if (!(status & AT91_ADC_DRDY)) + return IRQ_HANDLED; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91_adc_reg_read(st, AT91_ADC_LCDR); + } + + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +} + +static int at91_adc_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 at91_adc_channel_remove(struct iio_dev *idev) +{ + kfree(idev->channels); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + + at91_adc_reg_write(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_reg_write(st, AT91_ADC_IER, + AT91_ADC_EOC(chan->channel)); + at91_adc_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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *val = st->last_value; + + at91_adc_reg_write(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int __devinit at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks; + int ret; + struct iio_dev *idev; + struct at91_adc_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 at91_adc_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 = &at91_adc_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 + */ + at91_adc_reg_write(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_reg_write(st, AT91_ADC_IDR, 0xFFFFFFFF); + ret = request_irq(st->irq, + at91_adc_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; + at91_adc_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 = at91_adc_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; + st->channels_mask = pdata->channels_used; + + ret = iio_device_register(idev); + if (ret < 0) + goto error_free_channels; + + return 0; + +error_free_channels: + at91_adc_channel_remove(idev); +error_free_clk: + clk_disable(st->clk); + clk_put(st->clk); +error_free_irq: + free_irq(st->irq, idev); +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 at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + at91_adc_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 at91_adc_driver = { + .probe = at91_adc_probe, + .remove = __devexit_p(at91_adc_remove), + .driver = { + .name = "at91_adc", + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-30 9:15 ` Maxime Ripard @ 2011-11-30 17:40 ` Arnd Bergmann -1 siblings, 0 replies; 99+ messages in thread From: Arnd Bergmann @ 2011-11-30 17:40 UTC (permalink / raw) To: linux-arm-kernel Cc: Maxime Ripard, linux-iio, Thomas Petazzoni, Patrice Vilchez On Wednesday 30 November 2011, Maxime Ripard wrote: > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> Acked-by: Arnd Bergmann <arnd@arndb.de> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-30 17:40 ` Arnd Bergmann 0 siblings, 0 replies; 99+ messages in thread From: Arnd Bergmann @ 2011-11-30 17:40 UTC (permalink / raw) To: linux-arm-kernel On Wednesday 30 November 2011, Maxime Ripard wrote: > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> Acked-by: Arnd Bergmann <arnd@arndb.de> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH v9] AT91: Add a driver for the ADC @ 2011-11-24 11:27 Maxime Ripard 2011-11-24 11:27 ` Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-24 11:27 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni Hi all, The last version had a bug in the error handling of the probe function of the adc driver, the free_irq call having the wrong dev_id. This version corrects it. Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-24 11:27 [PATCH v9] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-24 11:27 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-24 11:27 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..a35cc9a --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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; + unsigned long channels_mask; +}; + +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) +{ + 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; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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); + int 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + 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; + st->channels_mask = pdata->channels_used; + + 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, idev); +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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-24 11:27 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-24 11:27 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..a35cc9a --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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; + unsigned long channels_mask; +}; + +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) +{ + 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; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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); + int 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + 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; + st->channels_mask = pdata->channels_used; + + 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, idev); +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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-24 11:27 ` Maxime Ripard @ 2011-11-24 14:28 ` Jean-Christophe PLAGNIOL-VILLARD -1 siblings, 0 replies; 99+ messages in thread From: Jean-Christophe PLAGNIOL-VILLARD @ 2011-11-24 14:28 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni On 12:27 Thu 24 Nov , Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> please remove Cc Best Regards, J. ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-24 14:28 ` Jean-Christophe PLAGNIOL-VILLARD 0 siblings, 0 replies; 99+ messages in thread From: Jean-Christophe PLAGNIOL-VILLARD @ 2011-11-24 14:28 UTC (permalink / raw) To: linux-arm-kernel On 12:27 Thu 24 Nov , Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Acked-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> please remove Cc Best Regards, J. ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH v8] AT91: Add a driver for the ADC @ 2011-11-18 10:12 Maxime Ripard 2011-11-18 10:12 ` Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-18 10:12 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni 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 from v7: - Fix the channels mask bug Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-18 10:12 [PATCH v8] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-18 10:12 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-18 10:12 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..9da6f22 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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; + unsigned long channels_mask; +}; + +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) +{ + 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; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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); + int 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + 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; + st->channels_mask = pdata->channels_used; + + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-18 10:12 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-18 10:12 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..9da6f22 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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; + unsigned long channels_mask; +}; + +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) +{ + 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; + + if (status & st->channels_mask) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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); + int 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + 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; + st->channels_mask = pdata->channels_used; + + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH v7] AT91: Add a driver for the ADC @ 2011-11-15 10:54 Maxime Ripard 2011-11-15 10:54 ` Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-15 10:54 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni 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 from v6: - Fix the unit in the scale read - Minor type fix Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-15 10:54 [PATCH v7] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-15 10:54 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-15 10:54 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91adc.c | 325 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 334 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..8a40db9 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -0,0 +1,325 @@ +/* + * 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) +{ + 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; + + if (status & ((2 << idev->num_channels) - 1)) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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; + int 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-15 10:54 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-15 10:54 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- drivers/iio/adc/Kconfig | 6 + drivers/iio/adc/Makefile | 4 +- drivers/iio/adc/at91adc.c | 325 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 334 insertions(+), 1 deletions(-) create mode 100644 drivers/iio/adc/at91adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..8a40db9 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -0,0 +1,325 @@ +/* + * 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) +{ + 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; + + if (status & ((2 << idev->num_channels) - 1)) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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; + int 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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: + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val2 = 0; + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-15 10:54 ` Maxime Ripard @ 2011-11-16 15:39 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-16 15:39 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Hi, Don't apply this patch. See below. On 15/11/2011 11:54, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91adc.c | 325 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 334 insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..8a40db9 > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -0,0 +1,325 @@ > +/* > + * 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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { This computation is wrong. It doesn't give the correct mask at all when there is for example channel 2 and 4 activated. I guess copying the channel mask from the platform data to the internal state structure could do fine. What do you think of it ? > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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; > + int 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 == 0) > + return -ETIMEDOUT; > + else if (ret < 0) > + return ret; > + > + *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: > + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; > + *val2 = 0; > + 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 <maxime.ripard@free-electrons.com>"); -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-16 15:39 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-16 15:39 UTC (permalink / raw) To: linux-arm-kernel Hi, Don't apply this patch. See below. On 15/11/2011 11:54, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> > --- > drivers/iio/adc/Kconfig | 6 + > drivers/iio/adc/Makefile | 4 +- > drivers/iio/adc/at91adc.c | 325 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 334 insertions(+), 1 deletions(-) > create mode 100644 drivers/iio/adc/at91adc.c > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..8a40db9 > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -0,0 +1,325 @@ > +/* > + * 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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { This computation is wrong. It doesn't give the correct mask at all when there is for example channel 2 and 4 activated. I guess copying the channel mask from the platform data to the internal state structure could do fine. What do you think of it ? > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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; > + int 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 == 0) > + return -ETIMEDOUT; > + else if (ret < 0) > + return ret; > + > + *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: > + *val = (st->vref_mv * 1000) >> chan->scan_type.realbits; > + *val2 = 0; > + 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 <maxime.ripard@free-electrons.com>"); -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH v6] AT91: Add a driver for the ADC @ 2011-11-14 17:30 Maxime Ripard 2011-11-14 17:30 ` Maxime Ripard 0 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 17:30 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni 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 from v5: - Don't run the SoC specific code if the driver is not selected in the configuration - Fix the waitqueue timeout error handling to have a more sensible behaviour - Minor cosmetic improvements Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-14 17:30 [PATCH v6] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-14 17:30 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 17:30 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..4652d73 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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) +{ + 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; + + if (status & ((2 << idev->num_channels) - 1)) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 17:30 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 17:30 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> Reviewed-by: Marek Vasut <marek.vasut@gmail.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..4652d73 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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) +{ + 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; + + if (status & ((2 << idev->num_channels) - 1)) { + st->done = true; + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); + } + + 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 == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + *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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-14 17:30 ` Maxime Ripard @ 2011-11-14 21:32 ` Jonathan Cameron -1 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-14 21:32 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni On 11/14/2011 05:30 PM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Just one more query on the IIO_CHAN_INFO_SCALE read. Sorry, missed this last time. Its late so I may be going mad but the units seem out by 1000. Also mysterious short as return value type in read_raw. If I take this through my tree, I'll switch the ack below for a sign off. Jonathan > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> > --- > 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 > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..4652d73 > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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; Why not int? > + > + 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 == 0) > + return -ETIMEDOUT; > + else if (ret < 0) > + return ret; > + > + *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: There is something a little missleading in the scaling here. If scale_uv is in microvolts as the name would suggest, then *val = scale_uv/1000000 and *val2 = (scale % 1000000); To end up with the right units (I think). Please just check this over. > + 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 <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 21:32 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-14 21:32 UTC (permalink / raw) To: linux-arm-kernel On 11/14/2011 05:30 PM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Just one more query on the IIO_CHAN_INFO_SCALE read. Sorry, missed this last time. Its late so I may be going mad but the units seem out by 1000. Also mysterious short as return value type in read_raw. If I take this through my tree, I'll switch the ack below for a sign off. Jonathan > > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com> > Reviewed-by: Marek Vasut <marek.vasut@gmail.com> > --- > 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 > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..4652d73 > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -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) > +{ > + 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; > + > + if (status & ((2 << idev->num_channels) - 1)) { > + st->done = true; > + st->last_value = at91adc_reg_read(st, AT91_ADC_LCDR); > + } > + > + 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; Why not int? > + > + 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 == 0) > + return -ETIMEDOUT; > + else if (ret < 0) > + return ret; > + > + *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: There is something a little missleading in the scaling here. If scale_uv is in microvolts as the name would suggest, then *val = scale_uv/1000000 and *val2 = (scale % 1000000); To end up with the right units (I think). Please just check this over. > + 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 <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-14 21:32 ` Jonathan Cameron @ 2011-11-15 10:23 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-15 10:23 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni On 14/11/2011 22:32, Jonathan Cameron wrote: > Just one more query on the IIO_CHAN_INFO_SCALE read. > Sorry, missed this last time. Its late so I may be going > mad but the units seem out by 1000. Wow, you're right. Indeed, if you multiply the value returned by read_raw and scale, you get a value in millivolts, not micro... > Also mysterious short as return value type in read_raw. I will resend a new patchset with these two fixes. Maxime -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-15 10:23 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-15 10:23 UTC (permalink / raw) To: linux-arm-kernel On 14/11/2011 22:32, Jonathan Cameron wrote: > Just one more query on the IIO_CHAN_INFO_SCALE read. > Sorry, missed this last time. Its late so I may be going > mad but the units seem out by 1000. Wow, you're right. Indeed, if you multiply the value returned by read_raw and scale, you get a value in millivolts, not micro... > Also mysterious short as return value type in read_raw. I will resend a new patchset with these two fixes. Maxime -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH] AT91: Add a driver for the ADC
@ 2011-10-19 16:18 Maxime Ripard
2011-10-19 16:18 ` Maxime Ripard
` (3 more replies)
0 siblings, 4 replies; 99+ messages in thread
From: Maxime Ripard @ 2011-10-19 16:18 UTC (permalink / raw)
To: linux-arm-kernel, linux-iio; +Cc: Nicolas Ferre, Patrice Vilchez
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 <nicolas.ferre@atmel.com>
Cc: Patrice Vilchez <patrice.vilchez@atmel.com>
^ permalink raw reply [flat|nested] 99+ messages in thread* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-19 16:18 [PATCH] AT91: Add a driver for the ADC Maxime Ripard @ 2011-10-19 16:18 ` Maxime Ripard 2011-11-03 10:11 ` [PATCHv2] AT91: Add a driver for the ADC Maxime Ripard ` (2 subsequent siblings) 3 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-19 16:18 UTC (permalink / raw) To: linux-arm-kernel, linux-iio; +Cc: Nicolas Ferre, Patrice Vilchez Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig index 070efd3..98e8005 100644 --- a/drivers/staging/iio/adc/Kconfig +++ b/drivers/staging/iio/adc/Kconfig @@ -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 diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile index 8adf096..03e517a 100644 --- a/drivers/staging/iio/adc/Makefile +++ b/drivers/staging/iio/adc/Makefile @@ -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 diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c new file mode 100644 index 0000000..2737261 --- /dev/null +++ b/drivers/staging/iio/adc/at91adc.c @@ -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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-19 16:18 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-19 16:18 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig index 070efd3..98e8005 100644 --- a/drivers/staging/iio/adc/Kconfig +++ b/drivers/staging/iio/adc/Kconfig @@ -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 diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile index 8adf096..03e517a 100644 --- a/drivers/staging/iio/adc/Makefile +++ b/drivers/staging/iio/adc/Makefile @@ -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 diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c new file mode 100644 index 0000000..2737261 --- /dev/null +++ b/drivers/staging/iio/adc/at91adc.c @@ -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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-19 16:18 ` Maxime Ripard @ 2011-10-19 16:42 ` Jonathan Cameron -1 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-10-19 16:42 UTC (permalink / raw) To: Maxime Ripard; +Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez 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!) > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > --- > 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 > > diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig > index 070efd3..98e8005 100644 > --- a/drivers/staging/iio/adc/Kconfig > +++ b/drivers/staging/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile > index 8adf096..03e517a 100644 > --- a/drivers/staging/iio/adc/Makefile > +++ b/drivers/staging/iio/adc/Makefile > @@ -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 > diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c > new file mode 100644 > index 0000000..2737261 > --- /dev/null > +++ b/drivers/staging/iio/adc/at91adc.c > @@ -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 <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-19 16:42 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-10-19 16:42 UTC (permalink / raw) To: linux-arm-kernel 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!) > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > --- > 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 > > diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig > index 070efd3..98e8005 100644 > --- a/drivers/staging/iio/adc/Kconfig > +++ b/drivers/staging/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile > index 8adf096..03e517a 100644 > --- a/drivers/staging/iio/adc/Makefile > +++ b/drivers/staging/iio/adc/Makefile > @@ -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 > diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c > new file mode 100644 > index 0000000..2737261 > --- /dev/null > +++ b/drivers/staging/iio/adc/at91adc.c > @@ -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 <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-19 16:42 ` Jonathan Cameron @ 2011-10-19 18:23 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-19 18:23 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez Hi Jonathan, On 19/10/2011 18:42, Jonathan Cameron wrote: > 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 >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> --- >> 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 >> >> diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig >> index 070efd3..98e8005 100644 >> --- a/drivers/staging/iio/adc/Kconfig >> +++ b/drivers/staging/iio/adc/Kconfig >> @@ -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 >> diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile >> index 8adf096..03e517a 100644 >> --- a/drivers/staging/iio/adc/Makefile >> +++ b/drivers/staging/iio/adc/Makefile >> @@ -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 >> diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c >> new file mode 100644 >> index 0000000..2737261 >> --- /dev/null >> +++ b/drivers/staging/iio/adc/at91adc.c >> @@ -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 <maxime.ripard@free-electrons.com>"); > > -- > To unsubscribe from this list: send the line "unsubscribe linux-iio" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-19 18:23 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-19 18:23 UTC (permalink / raw) To: linux-arm-kernel Hi Jonathan, On 19/10/2011 18:42, Jonathan Cameron wrote: > 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 >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> --- >> 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 >> >> diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig >> index 070efd3..98e8005 100644 >> --- a/drivers/staging/iio/adc/Kconfig >> +++ b/drivers/staging/iio/adc/Kconfig >> @@ -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 >> diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile >> index 8adf096..03e517a 100644 >> --- a/drivers/staging/iio/adc/Makefile >> +++ b/drivers/staging/iio/adc/Makefile >> @@ -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 >> diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c >> new file mode 100644 >> index 0000000..2737261 >> --- /dev/null >> +++ b/drivers/staging/iio/adc/at91adc.c >> @@ -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 <maxime.ripard@free-electrons.com>"); > > -- > 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 -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-19 18:23 ` Maxime Ripard @ 2011-10-20 7:05 ` Thomas Petazzoni -1 siblings, 0 replies; 99+ messages in thread From: Thomas Petazzoni @ 2011-10-20 7:05 UTC (permalink / raw) To: Maxime Ripard, Jonathan Cameron Cc: linux-iio, Patrice Vilchez, Nicolas Ferre, linux-arm-kernel Hello, Le Wed, 19 Oct 2011 20:23:50 +0200, Maxime Ripard <maxime.ripard@free-electrons.com> a =C3=A9crit : > On 19/10/2011 18:42, Jonathan Cameron wrote: > > Afraid I'm out of time for today, but just thought I'd ask one quick qu= estion... > >=20 > > Why isn't this touchscreen ADC in input? >=20 > Because what I say in the Kconfig is mostly wrong... Sorry about that. >=20 > 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. >=20 > 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 --=20 Thomas Petazzoni, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-20 7:05 ` Thomas Petazzoni 0 siblings, 0 replies; 99+ messages in thread From: Thomas Petazzoni @ 2011-10-20 7:05 UTC (permalink / raw) To: linux-arm-kernel Hello, Le Wed, 19 Oct 2011 20:23:50 +0200, Maxime Ripard <maxime.ripard@free-electrons.com> a ?crit : > On 19/10/2011 18:42, Jonathan Cameron wrote: > > 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. 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 -- Thomas Petazzoni, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-20 7:05 ` Thomas Petazzoni @ 2011-10-20 8:33 ` Jonathan Cameron -1 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-10-20 8:33 UTC (permalink / raw) To: Thomas Petazzoni Cc: Maxime Ripard, linux-iio, Patrice Vilchez, Nicolas Ferre, linux-arm-kernel, Mark Brown, Linus Walleij Bringing in Mark and Linus as others who have been active in talking ab= out similar issues. > Hello, >=20 > Le Wed, 19 Oct 2011 20:23:50 +0200, > Maxime Ripard <maxime.ripard@free-electrons.com> a =C3=A9crit : >=20 >> On 19/10/2011 18:42, Jonathan Cameron wrote: >>> Afraid I'm out of time for today, but just thought I'd ask one quic= k question... >>> >>> Why isn't this touchscreen ADC in input? >> >> Because what I say in the Kconfig is mostly wrong... Sorry about tha= t. >> >> Actually, like I was saying, this ADC is present in a lot of AT91 So= C, >> and while it is always an ADC, sometimes (like on the G45), it can b= e >> 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 A= DC. >> >> For now, the plan is only to support the ADC mode, so I put it in ad= c. >=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 > 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 > AT91 ADC driver and the AT91 touchscreen driver work simultaneously > (with of course non-conflicting ADC channels usage). However, as show= n > by recent discussions on this list, it is not yet entirely clearly ho= w > this should be done: >=20 > * Should we have some low-level ADC driver in arch/arm/mach-at91/ th= at > allows to request/release/access the ADC channels, this driver > providing an internal kernel API used by the IIO ADC driver and th= e > 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. >=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 > +-------------------+ > | AT91 touchscreen | -> /dev/input/... access to touchsc= reen > +-------------------+ > || > \/ > +-------------------+ > | 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 ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-20 8:33 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-10-20 8:33 UTC (permalink / raw) To: linux-arm-kernel Bringing in Mark and Linus as others who have been active in talking about similar issues. > Hello, > > Le Wed, 19 Oct 2011 20:23:50 +0200, > Maxime Ripard <maxime.ripard@free-electrons.com> a ?crit : > >> On 19/10/2011 18:42, Jonathan Cameron wrote: >>> 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. > > 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 ? 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. > > 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 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. 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 example are the switches on the G45's adc (from datasheet) something that 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 ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-20 8:33 ` Jonathan Cameron @ 2011-10-20 8:49 ` Thomas Petazzoni -1 siblings, 0 replies; 99+ messages in thread From: Thomas Petazzoni @ 2011-10-20 8:49 UTC (permalink / raw) To: Jonathan Cameron Cc: Maxime Ripard, linux-iio, Patrice Vilchez, Nicolas Ferre, linux-arm-kernel, Mark Brown, Linus Walleij Le Thu, 20 Oct 2011 09:33:29 +0100, Jonathan Cameron <jic23@cam.ac.uk> a =C3=A9crit : > > * 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 everyth= ing > that looks like a driver out of the arch directories. An equivalent somew= here > 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). > 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. > 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 gene= ric > 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. > 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 --=20 Thomas Petazzoni, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-20 8:49 ` Thomas Petazzoni 0 siblings, 0 replies; 99+ messages in thread From: Thomas Petazzoni @ 2011-10-20 8:49 UTC (permalink / raw) To: linux-arm-kernel Le Thu, 20 Oct 2011 09:33:29 +0100, Jonathan Cameron <jic23@cam.ac.uk> a ?crit : > > * 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). > 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. > 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. > 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 -- Thomas Petazzoni, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-20 8:49 ` Thomas Petazzoni @ 2011-10-20 9:19 ` Jonathan Cameron -1 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-10-20 9:19 UTC (permalink / raw) To: Thomas Petazzoni Cc: Maxime Ripard, linux-iio, Patrice Vilchez, Nicolas Ferre, linux-arm-kernel, Mark Brown, Linus Walleij On 10/20/11 09:49, Thomas Petazzoni wrote: > Le Thu, 20 Oct 2011 09:33:29 +0100, > Jonathan Cameron <jic23@cam.ac.uk> a =C3=A9crit : >=20 >>> * 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 ev= erything >> that looks like a driver out of the arch directories. An equivalent = somewhere >> 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 >> 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 fo= r IIO >> we want to control the triggering of the push at a far finer scale t= han >> makes sense for input. I'm not yet clear how these two will play tog= ether. >=20 > My IIO knowledge is still too limited to understand what's the proble= m > 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). >=20 >> 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. >=20 > I am not sure it's possible to make the touchscreen driver generic. O= n > 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 > 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 >> For example are the switches on the G45's adc (from datasheet) somet= hing that >> 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 > used for keypad handling. 4 ADCs channels, each connected to 4 keys, > each having a different resistor. Depending on the voltage measured a= t > 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 > 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 ;) >=20 > Regards, >=20 > Thomas ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-20 9:19 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-10-20 9:19 UTC (permalink / raw) To: linux-arm-kernel On 10/20/11 09:49, Thomas Petazzoni wrote: > Le Thu, 20 Oct 2011 09:33:29 +0100, > Jonathan Cameron <jic23@cam.ac.uk> a ?crit : > >>> * 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). > >> 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. I'll give a quick summary: Push in IIO is done from a trigger (imagine an oscilloscope). Those triggers may be supplied by a dataready signal or from somewhere entirely different. Also multiple devices can be triggered by the same signal. Thus the dataready 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 then registered to receive the data the trigger cannot be changed anyway so that will work 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. Note 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 drivers 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 real 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). > >> 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. > >> 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. 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 ;) > > Regards, > > Thomas ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-20 8:33 ` Jonathan Cameron @ 2011-10-20 9:52 ` Mark Brown -1 siblings, 0 replies; 99+ messages in thread From: Mark Brown @ 2011-10-20 9:52 UTC (permalink / raw) To: Jonathan Cameron Cc: Thomas Petazzoni, linux-iio, Patrice Vilchez, Linus Walleij, Nicolas Ferre, Maxime Ripard, linux-arm-kernel On Thu, Oct 20, 2011 at 09:33:29AM +0100, Jonathan Cameron wrote: > 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 > 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. ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-20 9:52 ` Mark Brown 0 siblings, 0 replies; 99+ messages in thread From: Mark Brown @ 2011-10-20 9:52 UTC (permalink / raw) To: linux-arm-kernel On Thu, Oct 20, 2011 at 09:33:29AM +0100, Jonathan Cameron wrote: > 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 > 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. ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-19 16:18 ` Maxime Ripard @ 2011-10-20 7:09 ` Lars-Peter Clausen -1 siblings, 0 replies; 99+ messages in thread From: Lars-Peter Clausen @ 2011-10-20 7:09 UTC (permalink / raw) To: Maxime Ripard; +Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez On 10/19/2011 06:18 PM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > --- > [...] > + > +#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? > + > +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); kcalloc > + 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. > + ++ret; > + } > + > + return ret; Why do you need ret? Shouldn't this be just 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); > + > + 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); Its probably a good idea to add a timeout in case there is a problem with the hardware. > + *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; You don't use device ids. This should cause a NULL pointer deref? Maybe use dev_name(&pdev->dev) instead. > + 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); > + 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. > + return 0; > +} > + > [...] ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-20 7:09 ` Lars-Peter Clausen 0 siblings, 0 replies; 99+ messages in thread From: Lars-Peter Clausen @ 2011-10-20 7:09 UTC (permalink / raw) To: linux-arm-kernel On 10/19/2011 06:18 PM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > --- > [...] > + > +#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? > + > +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); kcalloc > + 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. > + ++ret; > + } > + > + return ret; Why do you need ret? Shouldn't this be just 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); > + > + 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); Its probably a good idea to add a timeout in case there is a problem with the hardware. > + *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; You don't use device ids. This should cause a NULL pointer deref? Maybe use dev_name(&pdev->dev) instead. > + 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); > + 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. > + return 0; > +} > + > [...] ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-20 7:09 ` Lars-Peter Clausen @ 2011-10-21 17:54 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-21 17:54 UTC (permalink / raw) To: Lars-Peter Clausen Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez Hi, On 20/10/2011 09:09, Lars-Peter Clausen wrote: > On 10/19/2011 06:18 PM, Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> --- >> [...] >> + >> +#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. >> + >> +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); > > kcalloc Corrected >> + 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. >> + ++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. >> +} >> + >> +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); > > Its probably a good idea to add a timeout in case there is a problem with > the hardware. Indeed. >> + *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; > > You don't use device ids. This should cause a NULL pointer deref? Maybe use > dev_name(&pdev->dev) instead. Ok. >> + 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); >> + 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, -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-21 17:54 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-21 17:54 UTC (permalink / raw) To: linux-arm-kernel Hi, On 20/10/2011 09:09, Lars-Peter Clausen wrote: > On 10/19/2011 06:18 PM, Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> --- >> [...] >> + >> +#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. >> + >> +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); > > kcalloc Corrected >> + 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. >> + ++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. >> +} >> + >> +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); > > Its probably a good idea to add a timeout in case there is a problem with > the hardware. Indeed. >> + *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; > > You don't use device ids. This should cause a NULL pointer deref? Maybe use > dev_name(&pdev->dev) instead. Ok. >> + 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); >> + 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, -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-21 17:54 ` Maxime Ripard @ 2011-10-21 17:55 ` Lars-Peter Clausen -1 siblings, 0 replies; 99+ messages in thread From: Lars-Peter Clausen @ 2011-10-21 17:55 UTC (permalink / raw) To: Maxime Ripard; +Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez On 10/21/2011 07:54 PM, Maxime Ripard wrote: > [...] >>> +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); >>> + >> >> 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, > Yes, iio_free_device, sorry. - Lars ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-21 17:55 ` Lars-Peter Clausen 0 siblings, 0 replies; 99+ messages in thread From: Lars-Peter Clausen @ 2011-10-21 17:55 UTC (permalink / raw) To: linux-arm-kernel On 10/21/2011 07:54 PM, Maxime Ripard wrote: > [...] >>> +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); >>> + >> >> 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, > Yes, iio_free_device, sorry. - Lars ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-19 16:18 ` Maxime Ripard @ 2011-10-23 9:08 ` Jean-Christophe PLAGNIOL-VILLARD -1 siblings, 0 replies; 99+ messages in thread From: Jean-Christophe PLAGNIOL-VILLARD @ 2011-10-23 9:08 UTC (permalink / raw) To: Maxime Ripard; +Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Nicolas Ferre On 18:18 Wed 19 Oct , Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> please keep me in CC > + > +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; do not refence it copy need for the DT > + > + 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; > + } where is the platform data struct? Best Regards, J. ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-23 9:08 ` Jean-Christophe PLAGNIOL-VILLARD 0 siblings, 0 replies; 99+ messages in thread From: Jean-Christophe PLAGNIOL-VILLARD @ 2011-10-23 9:08 UTC (permalink / raw) To: linux-arm-kernel On 18:18 Wed 19 Oct , Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> please keep me in CC > + > +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; do not refence it copy need for the DT > + > + 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; > + } where is the platform data struct? Best Regards, J. ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-10-23 9:08 ` Jean-Christophe PLAGNIOL-VILLARD @ 2011-10-24 8:21 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-24 8:21 UTC (permalink / raw) To: Jean-Christophe PLAGNIOL-VILLARD Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Nicolas Ferre Hi, On 23/10/2011 11:08, Jean-Christophe PLAGNIOL-VILLARD wrote: > On 18:18 Wed 19 Oct , Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > please keep me in CC ACK. >> + >> +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; > do not refence it copy need for the DT Ok >> + >> + 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; >> + } > where is the platform data struct? In the first patch. Regards, -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-10-24 8:21 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-10-24 8:21 UTC (permalink / raw) To: linux-arm-kernel Hi, On 23/10/2011 11:08, Jean-Christophe PLAGNIOL-VILLARD wrote: > On 18:18 Wed 19 Oct , Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > please keep me in CC ACK. >> + >> +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; > do not refence it copy need for the DT Ok >> + >> + 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; >> + } > where is the platform data struct? In the first patch. Regards, -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCHv2] AT91: Add a driver for the ADC 2011-10-19 16:18 [PATCH] AT91: Add a driver for the ADC Maxime Ripard 2011-10-19 16:18 ` Maxime Ripard @ 2011-11-03 10:11 ` Maxime Ripard 2011-11-03 10:11 ` Maxime Ripard 2011-11-07 16:08 ` [PATCHv3] AT91: Add a driver for the ADC Maxime Ripard 2011-11-09 10:19 ` [PATCHv4] AT91: Add a driver for the ADC Maxime Ripard 3 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-03 10:11 UTC (permalink / raw) To: linux-arm-kernel, linux-iio; +Cc: Nicolas Ferre, Patrice Vilchez 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 <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-03 10:11 ` [PATCHv2] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-03 10:11 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-03 10:11 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig index d9decea..c384894 100644 --- a/drivers/staging/iio/adc/Kconfig +++ b/drivers/staging/iio/adc/Kconfig @@ -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 diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile index ceee7f3..d250d06 100644 --- a/drivers/staging/iio/adc/Makefile +++ b/drivers/staging/iio/adc/Makefile @@ -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 diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c new file mode 100644 index 0000000..acf656d --- /dev/null +++ b/drivers/staging/iio/adc/at91adc.c @@ -0,0 +1,310 @@ +/* + * 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) 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; + struct iio_chan_spec *channels; + int nb_chan; + int irq; + wait_queue_head_t wq_data_avail; + u16 lcdr; + void __iomem *reg_base; + unsigned int vref_mv; +}; + +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; + + 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, + 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; + + for (i = 0; i < st->nb_chan; i++) { + 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; + chan->scan_type.shift = 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); + if (!res) { + dev_err(&pdev->dev, "No resource defined\n"); + ret = -ENXIO; + goto error_ret; + } + + idev = iio_allocate_device(sizeof(struct at91adc_state)); + 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 = 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; + } + + 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) { + goto error_free_clk; + } + + 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); + 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); + + iio_device_unregister(idev); + 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", + }, +}; + +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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-03 10:11 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-03 10:11 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig index d9decea..c384894 100644 --- a/drivers/staging/iio/adc/Kconfig +++ b/drivers/staging/iio/adc/Kconfig @@ -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 diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile index ceee7f3..d250d06 100644 --- a/drivers/staging/iio/adc/Makefile +++ b/drivers/staging/iio/adc/Makefile @@ -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 diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c new file mode 100644 index 0000000..acf656d --- /dev/null +++ b/drivers/staging/iio/adc/at91adc.c @@ -0,0 +1,310 @@ +/* + * 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) 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; + struct iio_chan_spec *channels; + int nb_chan; + int irq; + wait_queue_head_t wq_data_avail; + u16 lcdr; + void __iomem *reg_base; + unsigned int vref_mv; +}; + +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; + + 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, + 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; + + for (i = 0; i < st->nb_chan; i++) { + 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; + chan->scan_type.shift = 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); + if (!res) { + dev_err(&pdev->dev, "No resource defined\n"); + ret = -ENXIO; + goto error_ret; + } + + idev = iio_allocate_device(sizeof(struct at91adc_state)); + 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 = 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; + } + + 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) { + goto error_free_clk; + } + + 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); + 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); + + iio_device_unregister(idev); + 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", + }, +}; + +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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-03 10:11 ` Maxime Ripard @ 2011-11-04 10:27 ` Jonathan Cameron -1 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-04 10:27 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard On 11/03/2011 10:11 AM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > --- > 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 > > diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig > index d9decea..c384894 100644 > --- a/drivers/staging/iio/adc/Kconfig > +++ b/drivers/staging/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile > index ceee7f3..d250d06 100644 > --- a/drivers/staging/iio/adc/Makefile > +++ b/drivers/staging/iio/adc/Makefile > @@ -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 > diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c > new file mode 100644 > index 0000000..acf656d > --- /dev/null > +++ b/drivers/staging/iio/adc/at91adc.c > @@ -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. > + * > + * 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> > + I'd marginally prefer to see these as inlines rather than macros. Far from critical though! > +#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. > + struct iio_chan_spec *channels; likewise, we already have iio_dev->num_channels. > + 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. > + u16 lcdr; > + void __iomem *reg_base; > + unsigned int vref_mv; > +}; > + > +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. > + 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, > + 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. > + 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 ;) > + 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. > + chan->scan_type.shift = 0; don't bother initialising this value as it is zero (and that's an obvious default). > + 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); > + 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? > + if (idev == NULL) { I'm not sure this message adds anything or is even necessarily correct as that function doesn't just allocate memory! > + 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; > + } > + > + 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. > + goto error_free_clk; > + } > + This is what I meant when I asked why you have two copies of the pointer and count? > + 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. > + if (ret < 0) { unwind the channel init? > + 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); > + > + iio_device_unregister(idev); Free channels array? > + 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", > + }, > +}; > + > +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. > +module_init(at91adc_init); > +module_exit(at91adc_exit); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); > +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-04 10:27 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-04 10:27 UTC (permalink / raw) To: linux-arm-kernel On 11/03/2011 10:11 AM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > --- > 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 > > diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig > index d9decea..c384894 100644 > --- a/drivers/staging/iio/adc/Kconfig > +++ b/drivers/staging/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile > index ceee7f3..d250d06 100644 > --- a/drivers/staging/iio/adc/Makefile > +++ b/drivers/staging/iio/adc/Makefile > @@ -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 > diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c > new file mode 100644 > index 0000000..acf656d > --- /dev/null > +++ b/drivers/staging/iio/adc/at91adc.c > @@ -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. > + * > + * 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> > + I'd marginally prefer to see these as inlines rather than macros. Far from critical though! > +#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. > + struct iio_chan_spec *channels; likewise, we already have iio_dev->num_channels. > + 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. > + u16 lcdr; > + void __iomem *reg_base; > + unsigned int vref_mv; > +}; > + > +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. > + 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, > + 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. > + 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 ;) > + 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. > + chan->scan_type.shift = 0; don't bother initialising this value as it is zero (and that's an obvious default). > + 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); > + 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? > + if (idev == NULL) { I'm not sure this message adds anything or is even necessarily correct as that function doesn't just allocate memory! > + 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; > + } > + > + 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. > + goto error_free_clk; > + } > + This is what I meant when I asked why you have two copies of the pointer and count? > + 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. > + if (ret < 0) { unwind the channel init? > + 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); > + > + iio_device_unregister(idev); Free channels array? > + 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", > + }, > +}; > + > +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. > +module_init(at91adc_init); > +module_exit(at91adc_exit); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); > +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-04 10:27 ` Jonathan Cameron (?) @ 2011-11-04 16:29 ` Maxime Ripard 2011-11-04 16:40 ` Jonathan Cameron -1 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-04 16:29 UTC (permalink / raw) To: linux-arm-kernel On 04/11/2011 11:27, Jonathan Cameron wrote: > On 11/03/2011 10:11 AM, Maxime Ripard wrote: >> diff --git a/drivers/staging/iio/adc/at91adc.c b/drivers/staging/iio/adc/at91adc.c >> new file mode 100644 >> index 0000000..acf656d >> --- /dev/null >> +++ b/drivers/staging/iio/adc/at91adc.c >> @@ -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. >> + * >> + * 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> >> + > I'd marginally prefer to see these as inlines rather than macros. Far > from critical though! Ok. >> +#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. >> + struct iio_chan_spec *channels; > likewise, we already have iio_dev->num_channels. >> + int nb_chan; Indeed >> + 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. >> + u16 lcdr; >> + void __iomem *reg_base; >> + unsigned int vref_mv; >> +}; >> + >> +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. >> + 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, >> + 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. >> + 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 ;) >> + 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. > >> + chan->scan_type.shift = 0; > don't bother initialising this value as it is zero (and that's an > obvious default). >> + 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); >> + 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. >> + 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 >> + 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; >> + } >> + >> + 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. >> + goto error_free_clk; >> + } >> + > This is what I meant when I asked why you have two copies of the > pointer and count? >> + 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. >> + if (ret < 0) { > unwind the channel init? Oooh, right... >> + 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); >> + >> + iio_device_unregister(idev); > Free channels array? >> + 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", >> + }, >> +}; >> + >> +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. Aah, nice :) Thanks, -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-04 16:29 ` Maxime Ripard @ 2011-11-04 16:40 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-04 16:40 UTC (permalink / raw) To: linux-arm-kernel ... >>> + 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. > 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. >>> + 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! ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCHv3] AT91: Add a driver for the ADC 2011-10-19 16:18 [PATCH] AT91: Add a driver for the ADC Maxime Ripard 2011-10-19 16:18 ` Maxime Ripard 2011-11-03 10:11 ` [PATCHv2] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-07 16:08 ` Maxime Ripard 2011-11-07 16:08 ` Maxime Ripard 2011-11-09 10:19 ` [PATCHv4] AT91: Add a driver for the ADC Maxime Ripard 3 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-07 16:08 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni 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 <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-07 16:08 ` [PATCHv3] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-07 16:08 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-07 16:08 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..3ca2a64 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -0,0 +1,311 @@ +/* + * 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(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); +} + +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); + + 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)); + } + + 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 = 's'; + 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) +{ + if (idev->channels == NULL) + return; + + 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; + + 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->last_value; + + 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->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->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; + } + + 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 */ + 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); + + 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); + + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-07 16:08 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-07 16:08 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..3ca2a64 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -0,0 +1,311 @@ +/* + * 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(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); +} + +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); + + 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)); + } + + 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 = 's'; + 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) +{ + if (idev->channels == NULL) + return; + + 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; + + 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->last_value; + + 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->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->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; + } + + 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 */ + 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); + + 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); + + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-07 16:08 ` Maxime Ripard @ 2011-11-08 13:30 ` Thomas Petazzoni -1 siblings, 0 replies; 99+ messages in thread From: Thomas Petazzoni @ 2011-11-08 13:30 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Nicolas Ferre Le Mon, 7 Nov 2011 17:08:32 +0100, Maxime Ripard <maxime.ripard@free-electrons.com> a =C3=A9crit : > +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); } > +static irqreturn_t at91adc_eoc_trigger(int irq, void *private) > +{ > + int chan; > + struct iio_dev *idev =3D private; > + struct at91adc_state *st =3D iio_priv(idev); > + unsigned int status =3D at91adc_reg_read(st->reg_base, AT91_ADC_SR)= ; would become + unsigned int status =3D at91adc_reg_read(st, AT91_ADC_SR); > + if (!(status & AT91_ADC_DRDY)) > + return IRQ_HANDLED; > + > + for (chan =3D 0; chan < idev->num_channels; chan++) > + if (status & AT91_ADC_EOC(chan)) { > + st->done =3D true; > + st->last_value =3D at91adc_reg_read(st->reg_base, > + AT91_ADC_CHR(chan)); would become + st->last_value =3D at91adc_reg_read(st, AT91_ADC_CHR(chan)); > +static int at91adc_channel_init(struct iio_dev *idev, > + struct at91_adc_data *pdata) > +{ > + struct iio_chan_spec *chan_array; > + int bit, idx =3D 0; > + > + idev->num_channels =3D bitmap_weight(&(pdata->channels_used), > + pdata->num_channels); > + chan_array =3D kcalloc(idev->num_channels, sizeof(struct iio_chan_s= pec), > + GFP_KERNEL); > + > + if (chan_array =3D=3D NULL) > + return -ENOMEM; > + > + for_each_set_bit(bit, &(pdata->channels_used), pdata->num_channels)= { > + struct iio_chan_spec *chan =3D chan_array + idx; > + chan->type =3D IIO_VOLTAGE; > + chan->indexed =3D 1; > + chan->channel =3D bit; > + chan->scan_type.sign =3D 's'; It's an unsigned value that you're reading from the ADC, so maybe this should be =3D 'u'. > +static void at91adc_channel_remove(struct iio_dev *idev) > +{ > + if (idev->channels =3D=3D 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. > +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 =3D 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)); 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. > + /* > + * 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 =3D 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 =3D clk_get(&pdev->dev, "adc_clk"); > + if (IS_ERR(st->clk)) { > + dev_err(&pdev->dev, "Failed to get the clock.\n"); > + ret =3D PTR_ERR(st->clk); > + goto error_free_irq; > + } > + > + clk_enable(st->clk); > + mstrclk =3D 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(). > + prsc =3D (mstrclk / (2 * pdata->adc_clock)) - 1; [...] > + ticks =3D 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. > +static int __devexit at91adc_remove(struct platform_device *pdev) > +{ > + struct iio_dev *idev =3D platform_get_drvdata(pdev); > + struct resource *res =3D platform_get_resource(pdev, IORESOURCE_MEM= , 0); > + struct at91adc_state *st =3D 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. > +static struct platform_driver at91adc_driver =3D { > + .probe =3D at91adc_probe, > + .remove =3D __devexit_p(at91adc_remove), > + .driver =3D { > + .name =3D "at91adc", > + }, Nitpick: wrongly placed parenthesis. Regards, Thomas --=20 Thomas Petazzoni, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-08 13:30 ` Thomas Petazzoni 0 siblings, 0 replies; 99+ messages in thread From: Thomas Petazzoni @ 2011-11-08 13:30 UTC (permalink / raw) To: linux-arm-kernel Le Mon, 7 Nov 2011 17:08:32 +0100, Maxime Ripard <maxime.ripard@free-electrons.com> a ?crit : > +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); } > +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); > + 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)); > +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 = 's'; It's an unsigned value that you're reading from the ADC, so maybe this should be = 'u'. > +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. > +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)); 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. > + /* > + * 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; > + } > + > + 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(). > + prsc = (mstrclk / (2 * pdata->adc_clock)) - 1; [...] > + 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. > +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. > +static struct platform_driver at91adc_driver = { > + .probe = at91adc_probe, > + .remove = __devexit_p(at91adc_remove), > + .driver = { > + .name = "at91adc", > + }, Nitpick: wrongly placed parenthesis. Regards, Thomas -- Thomas Petazzoni, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCHv4] AT91: Add a driver for the ADC 2011-10-19 16:18 [PATCH] AT91: Add a driver for the ADC Maxime Ripard ` (2 preceding siblings ...) 2011-11-07 16:08 ` [PATCHv3] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-09 10:19 ` Maxime Ripard 2011-11-09 10:19 ` Maxime Ripard 3 siblings, 1 reply; 99+ messages in thread From: Maxime Ripard @ 2011-11-09 10:19 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Thomas Petazzoni 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 <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-09 10:19 ` [PATCHv4] AT91: Add a driver for the ADC Maxime Ripard @ 2011-11-09 10:19 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-09 10:19 UTC (permalink / raw) To: linux-arm-kernel, linux-iio Cc: Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..6aef5d6 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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) +{ + 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++) + if (status & AT91_ADC_EOC(chan)) { + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-09 10:19 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-09 10:19 UTC (permalink / raw) To: linux-arm-kernel Cc: Nicolas Ferre <nicolas.ferre@atmel.com> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> --- 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 diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3d97b21..74f4d9f 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -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 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c197334..776b56f 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -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 diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c new file mode 100644 index 0000000..6aef5d6 --- /dev/null +++ b/drivers/iio/adc/at91adc.c @@ -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) +{ + 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++) + if (status & AT91_ADC_EOC(chan)) { + 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 <maxime.ripard@free-electrons.com>"); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-09 10:19 ` Maxime Ripard @ 2011-11-10 17:35 ` Jonathan Cameron -1 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-10 17:35 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni On 11/09/2011 10:19 AM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > 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! > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > --- > 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 > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..6aef5d6 > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -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) > +{ > + 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. > + 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. > + 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 <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-10 17:35 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-10 17:35 UTC (permalink / raw) To: linux-arm-kernel On 11/09/2011 10:19 AM, Maxime Ripard wrote: > Cc: Nicolas Ferre <nicolas.ferre@atmel.com> > Cc: Patrice Vilchez <patrice.vilchez@atmel.com> > Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> > Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> > 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! > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > --- > 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 > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig > index 3d97b21..74f4d9f 100644 > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -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 > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile > index c197334..776b56f 100644 > --- a/drivers/iio/adc/Makefile > +++ b/drivers/iio/adc/Makefile > @@ -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 > diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c > new file mode 100644 > index 0000000..6aef5d6 > --- /dev/null > +++ b/drivers/iio/adc/at91adc.c > @@ -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) > +{ > + 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. > + 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. > + 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 <maxime.ripard@free-electrons.com>"); ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-10 17:35 ` Jonathan Cameron @ 2011-11-11 12:34 ` Jonathan Cameron -1 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-11 12:34 UTC (permalink / raw) To: Maxime Ripard Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni On 11/10/2011 05:35 PM, Jonathan Cameron wrote: > On 11/09/2011 10:19 AM, Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> > 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! > >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > >> --- >> 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 >> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >> index 3d97b21..74f4d9f 100644 >> --- a/drivers/iio/adc/Kconfig >> +++ b/drivers/iio/adc/Kconfig >> @@ -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 >> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile >> index c197334..776b56f 100644 >> --- a/drivers/iio/adc/Makefile >> +++ b/drivers/iio/adc/Makefile >> @@ -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 >> diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c >> new file mode 100644 >> index 0000000..6aef5d6 >> --- /dev/null >> +++ b/drivers/iio/adc/at91adc.c >> @@ -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) >> +{ >> + 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. >> + 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? >> + 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 <maxime.ripard@free-electrons.com>"); > > -- > To unsubscribe from this list: send the line "unsubscribe linux-iio" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-11 12:34 ` Jonathan Cameron 0 siblings, 0 replies; 99+ messages in thread From: Jonathan Cameron @ 2011-11-11 12:34 UTC (permalink / raw) To: linux-arm-kernel On 11/10/2011 05:35 PM, Jonathan Cameron wrote: > On 11/09/2011 10:19 AM, Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> > 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! > >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > >> --- >> 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 >> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >> index 3d97b21..74f4d9f 100644 >> --- a/drivers/iio/adc/Kconfig >> +++ b/drivers/iio/adc/Kconfig >> @@ -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 >> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile >> index c197334..776b56f 100644 >> --- a/drivers/iio/adc/Makefile >> +++ b/drivers/iio/adc/Makefile >> @@ -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 >> diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c >> new file mode 100644 >> index 0000000..6aef5d6 >> --- /dev/null >> +++ b/drivers/iio/adc/at91adc.c >> @@ -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) >> +{ >> + 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. >> + 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? >> + 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 <maxime.ripard@free-electrons.com>"); > > -- > 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 ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-11 12:34 ` Jonathan Cameron @ 2011-11-14 9:59 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 9:59 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni On 11/11/2011 13:34, Jonathan Cameron wrote: > On 11/10/2011 05:35 PM, Jonathan Cameron wrote: >> On 11/09/2011 10:19 AM, Maxime Ripard wrote: >>> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >>> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >>> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> >>> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >>> >> 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! >> >>> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> >> >>> --- >>> 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 >>> >>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >>> index 3d97b21..74f4d9f 100644 >>> --- a/drivers/iio/adc/Kconfig >>> +++ b/drivers/iio/adc/Kconfig >>> @@ -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 >>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile >>> index c197334..776b56f 100644 >>> --- a/drivers/iio/adc/Makefile >>> +++ b/drivers/iio/adc/Makefile >>> @@ -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 >>> diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c >>> new file mode 100644 >>> index 0000000..6aef5d6 >>> --- /dev/null >>> +++ b/drivers/iio/adc/at91adc.c >>> @@ -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) >>> +{ >>> + 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. >>> + 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? You're right, It will be more consistent and avoid to loop over each channels. I send a new version of the patchset. >>> + 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 <maxime.ripard@free-electrons.com>"); >> >> -- >> To unsubscribe from this list: send the line "unsubscribe linux-iio" in >> the body of a message to majordomo@vger.kernel.org >> More majordomo info at http://vger.kernel.org/majordomo-info.html > > -- > To unsubscribe from this list: send the line "unsubscribe linux-iio" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 9:59 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 9:59 UTC (permalink / raw) To: linux-arm-kernel On 11/11/2011 13:34, Jonathan Cameron wrote: > On 11/10/2011 05:35 PM, Jonathan Cameron wrote: >> On 11/09/2011 10:19 AM, Maxime Ripard wrote: >>> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >>> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >>> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> >>> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >>> >> 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! >> >>> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> >> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> >> >>> --- >>> 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 >>> >>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >>> index 3d97b21..74f4d9f 100644 >>> --- a/drivers/iio/adc/Kconfig >>> +++ b/drivers/iio/adc/Kconfig >>> @@ -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 >>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile >>> index c197334..776b56f 100644 >>> --- a/drivers/iio/adc/Makefile >>> +++ b/drivers/iio/adc/Makefile >>> @@ -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 >>> diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c >>> new file mode 100644 >>> index 0000000..6aef5d6 >>> --- /dev/null >>> +++ b/drivers/iio/adc/at91adc.c >>> @@ -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) >>> +{ >>> + 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. >>> + 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? You're right, It will be more consistent and avoid to loop over each channels. I send a new version of the patchset. >>> + 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 <maxime.ripard@free-electrons.com>"); >> >> -- >> 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 -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. 2011-11-10 17:35 ` Jonathan Cameron @ 2011-11-14 9:06 ` Maxime Ripard -1 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 9:06 UTC (permalink / raw) To: Jonathan Cameron Cc: linux-arm-kernel, linux-iio, Nicolas Ferre, Patrice Vilchez, Jean-Christophe Plagniol-Villard, Thomas Petazzoni On 10/11/2011 18:35, Jonathan Cameron wrote: > On 11/09/2011 10:19 AM, Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> > 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. >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > >> --- >> 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 >> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >> index 3d97b21..74f4d9f 100644 >> --- a/drivers/iio/adc/Kconfig >> +++ b/drivers/iio/adc/Kconfig >> @@ -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 >> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile >> index c197334..776b56f 100644 >> --- a/drivers/iio/adc/Makefile >> +++ b/drivers/iio/adc/Makefile >> @@ -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 >> diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c >> new file mode 100644 >> index 0000000..6aef5d6 >> --- /dev/null >> +++ b/drivers/iio/adc/at91adc.c >> @@ -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) >> +{ >> + 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. >> + 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. > >> + 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 <maxime.ripard@free-electrons.com>"); > > -- > To unsubscribe from this list: send the line "unsubscribe linux-iio" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
* [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver. @ 2011-11-14 9:06 ` Maxime Ripard 0 siblings, 0 replies; 99+ messages in thread From: Maxime Ripard @ 2011-11-14 9:06 UTC (permalink / raw) To: linux-arm-kernel On 10/11/2011 18:35, Jonathan Cameron wrote: > On 11/09/2011 10:19 AM, Maxime Ripard wrote: >> Cc: Nicolas Ferre <nicolas.ferre@atmel.com> >> Cc: Patrice Vilchez <patrice.vilchez@atmel.com> >> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com> >> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> >> > 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. >> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk> > >> --- >> 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 >> >> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig >> index 3d97b21..74f4d9f 100644 >> --- a/drivers/iio/adc/Kconfig >> +++ b/drivers/iio/adc/Kconfig >> @@ -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 >> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile >> index c197334..776b56f 100644 >> --- a/drivers/iio/adc/Makefile >> +++ b/drivers/iio/adc/Makefile >> @@ -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 >> diff --git a/drivers/iio/adc/at91adc.c b/drivers/iio/adc/at91adc.c >> new file mode 100644 >> index 0000000..6aef5d6 >> --- /dev/null >> +++ b/drivers/iio/adc/at91adc.c >> @@ -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) >> +{ >> + 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. >> + 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. > >> + 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 <maxime.ripard@free-electrons.com>"); > > -- > 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 -- Maxime Ripard, Free Electrons Kernel, drivers, real-time and embedded Linux development, consulting, training and support. http://free-electrons.com ^ permalink raw reply [flat|nested] 99+ messages in thread
end of thread, other threads:[~2012-01-18 10:27 UTC | newest] Thread overview: 99+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-11-14 10:06 [PATCHv5] AT91: Add a driver for the ADC Maxime Ripard 2011-11-14 10:06 ` Maxime Ripard 2011-11-14 10:06 ` [PATCH 1/3] ARM: AT91: Add platform data for the ADCs Maxime Ripard 2011-11-14 10:06 ` Maxime Ripard 2011-11-14 11:29 ` Nicolas Ferre 2011-11-14 11:29 ` Nicolas Ferre 2011-11-14 10:06 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-14 10:06 ` Maxime Ripard 2011-11-14 11:30 ` Nicolas Ferre 2011-11-14 11:30 ` Nicolas Ferre 2011-11-14 11:37 ` Marek Vasut 2011-11-14 11:37 ` Marek Vasut 2011-11-14 14:23 ` Maxime Ripard 2011-11-14 14:23 ` Maxime Ripard 2011-11-14 10:06 ` [PATCH 3/3] ARM: AT91: Add the ADC to the sam9g20ek board Maxime Ripard 2011-11-14 10:06 ` Maxime Ripard 2011-11-14 11:29 ` Nicolas Ferre 2011-11-14 11:29 ` Nicolas Ferre 2011-11-14 15:17 ` Maxime Ripard 2011-11-14 15:17 ` Maxime Ripard 2011-11-14 15:23 ` Nicolas Ferre 2011-11-14 15:23 ` Nicolas Ferre -- strict thread matches above, loose matches on Subject: below -- 2012-01-16 21:36 [PATCH RESEND v13] AT91: Add a driver for the ADC Maxime Ripard 2012-01-16 21:36 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2012-01-16 21:36 ` Maxime Ripard 2012-01-17 17:35 ` Arnd Bergmann 2012-01-17 17:35 ` Arnd Bergmann 2012-01-17 19:08 ` Maxime Ripard 2012-01-17 19:08 ` Maxime Ripard 2012-01-18 10:27 ` Nicolas Ferre 2012-01-18 10:27 ` Nicolas Ferre 2011-12-14 10:01 [PATCH v13] AT91: Add a driver for the ADC Maxime Ripard 2011-12-14 10:01 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-12-02 13:17 [PATCH v12] AT91: Add a driver for the ADC Maxime Ripard 2011-12-02 13:17 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-12-02 13:17 ` Maxime Ripard 2011-11-30 9:14 [PATCH v11] AT91: Add a driver for the ADC Maxime Ripard 2011-11-30 9:15 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-30 9:15 ` Maxime Ripard 2011-11-30 17:40 ` Arnd Bergmann 2011-11-30 17:40 ` Arnd Bergmann 2011-11-24 11:27 [PATCH v9] AT91: Add a driver for the ADC Maxime Ripard 2011-11-24 11:27 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-24 11:27 ` Maxime Ripard 2011-11-24 14:28 ` Jean-Christophe PLAGNIOL-VILLARD 2011-11-24 14:28 ` Jean-Christophe PLAGNIOL-VILLARD 2011-11-18 10:12 [PATCH v8] AT91: Add a driver for the ADC Maxime Ripard 2011-11-18 10:12 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-18 10:12 ` Maxime Ripard 2011-11-15 10:54 [PATCH v7] AT91: Add a driver for the ADC Maxime Ripard 2011-11-15 10:54 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-15 10:54 ` Maxime Ripard 2011-11-16 15:39 ` Maxime Ripard 2011-11-16 15:39 ` Maxime Ripard 2011-11-14 17:30 [PATCH v6] AT91: Add a driver for the ADC Maxime Ripard 2011-11-14 17:30 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-14 17:30 ` Maxime Ripard 2011-11-14 21:32 ` Jonathan Cameron 2011-11-14 21:32 ` Jonathan Cameron 2011-11-15 10:23 ` Maxime Ripard 2011-11-15 10:23 ` Maxime Ripard 2011-10-19 16:18 [PATCH] AT91: Add a driver for the ADC Maxime Ripard 2011-10-19 16:18 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-10-19 16:18 ` Maxime Ripard 2011-10-19 16:42 ` Jonathan Cameron 2011-10-19 16:42 ` Jonathan Cameron 2011-10-19 18:23 ` Maxime Ripard 2011-10-19 18:23 ` Maxime Ripard 2011-10-20 7:05 ` Thomas Petazzoni 2011-10-20 7:05 ` Thomas Petazzoni 2011-10-20 8:33 ` Jonathan Cameron 2011-10-20 8:33 ` Jonathan Cameron 2011-10-20 8:49 ` Thomas Petazzoni 2011-10-20 8:49 ` Thomas Petazzoni 2011-10-20 9:19 ` Jonathan Cameron 2011-10-20 9:19 ` Jonathan Cameron 2011-10-20 9:52 ` Mark Brown 2011-10-20 9:52 ` Mark Brown 2011-10-20 7:09 ` Lars-Peter Clausen 2011-10-20 7:09 ` Lars-Peter Clausen 2011-10-21 17:54 ` Maxime Ripard 2011-10-21 17:54 ` Maxime Ripard 2011-10-21 17:55 ` Lars-Peter Clausen 2011-10-21 17:55 ` Lars-Peter Clausen 2011-10-23 9:08 ` Jean-Christophe PLAGNIOL-VILLARD 2011-10-23 9:08 ` Jean-Christophe PLAGNIOL-VILLARD 2011-10-24 8:21 ` Maxime Ripard 2011-10-24 8:21 ` Maxime Ripard 2011-11-03 10:11 ` [PATCHv2] AT91: Add a driver for the ADC Maxime Ripard 2011-11-03 10:11 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-03 10:11 ` Maxime Ripard 2011-11-04 10:27 ` Jonathan Cameron 2011-11-04 10:27 ` Jonathan Cameron 2011-11-04 16:29 ` Maxime Ripard 2011-11-04 16:40 ` Jonathan Cameron 2011-11-07 16:08 ` [PATCHv3] AT91: Add a driver for the ADC Maxime Ripard 2011-11-07 16:08 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-07 16:08 ` Maxime Ripard 2011-11-08 13:30 ` Thomas Petazzoni 2011-11-08 13:30 ` Thomas Petazzoni 2011-11-09 10:19 ` [PATCHv4] AT91: Add a driver for the ADC Maxime Ripard 2011-11-09 10:19 ` [PATCH 2/3] ARM: AT91: IIO: Add AT91 ADC driver Maxime Ripard 2011-11-09 10:19 ` Maxime Ripard 2011-11-10 17:35 ` Jonathan Cameron 2011-11-10 17:35 ` Jonathan Cameron 2011-11-11 12:34 ` Jonathan Cameron 2011-11-11 12:34 ` Jonathan Cameron 2011-11-14 9:59 ` Maxime Ripard 2011-11-14 9:59 ` Maxime Ripard 2011-11-14 9:06 ` Maxime Ripard 2011-11-14 9:06 ` Maxime Ripard
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.