]> git.karo-electronics.de Git - linux-beck.git/commitdiff
staging:iio:adc: Add SPEAr ADC driver
authorStefan Roese <sr@denx.de>
Thu, 12 Apr 2012 09:05:35 +0000 (11:05 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 13 Apr 2012 18:18:23 +0000 (11:18 -0700)
This patch implements the basic single data conversion support for
the SPEAr600 SoC ADC. The register layout of SPEAr600 differs a bit
from other SPEAr SoC variants (e.g. SPEAr3xx). These differences are
handled via DT compatible testing. Resulting in a multi-arch binary.

This driver is currently tested only on SPEAr600. Future patches may add
support for other SoC variants (SPEAr3xx) and features like software
buffer or DMA.

Signed-off-by: Stefan Roese <sr@denx.de>
Acked-by: Jonathan Cameron <jic23@kernel.org>
Acked-by: Viresh Kumar <viresh.kumar@st.com>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/devicetree/bindings/staging/iio/adc/spear-adc.txt [new file with mode: 0644]
drivers/staging/iio/adc/Kconfig
drivers/staging/iio/adc/Makefile
drivers/staging/iio/adc/spear_adc.c [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/staging/iio/adc/spear-adc.txt b/Documentation/devicetree/bindings/staging/iio/adc/spear-adc.txt
new file mode 100644 (file)
index 0000000..02ea23a
--- /dev/null
@@ -0,0 +1,26 @@
+* ST SPEAr ADC device driver
+
+Required properties:
+- compatible: Should be "st,spear600-adc"
+- reg: Address and length of the register set for the device
+- interrupt-parent: Should be the phandle for the interrupt controller
+  that services interrupts for this device
+- interrupts: Should contain the ADC interrupt
+- sampling-frequency: Default sampling frequency
+
+Optional properties:
+- vref-external: External voltage reference in milli-volts. If omitted
+  the internal voltage reference will be used.
+- average-samples: Number of samples to generate an average value. If
+  omitted, single data conversion will be used.
+
+Examples:
+
+       adc: adc@d8200000 {
+               compatible = "st,spear600-adc";
+               reg = <0xd8200000 0x1000>;
+               interrupt-parent = <&vic1>;
+               interrupts = <6>;
+               sampling-frequency = <5000000>;
+               vref-external = <2500>; /* 2.5V VRef */
+       };
index 592eabd85f3651c69c93be0fb3acbf0e4fa80f87..ec006e749ffef9eb6a46c3ed18a0fd26943bf0c5 100644 (file)
@@ -202,4 +202,11 @@ config LPC32XX_ADC
          touchscreen driver, so you can only select one of the two drivers
          (lpc32xx_adc or lpc32xx_ts). Provides direct access via sysfs.
 
+config SPEAR_ADC
+       tristate "ST SPEAr ADC"
+       depends on PLAT_SPEAR
+       help
+         Say yes here to build support for the integrated ADC inside the
+         ST SPEAr SoC. Provides direct access via sysfs.
+
 endmenu
index f83ab9551d8ee003c96e9bb707f0ac0cf5f0d424..14e98b62b70a26bc3b2399477d40a9f5e3b2be98 100644 (file)
@@ -38,3 +38,4 @@ obj-$(CONFIG_ADT7310) += adt7310.o
 obj-$(CONFIG_ADT7410) += adt7410.o
 obj-$(CONFIG_AD7280) += ad7280a.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
+obj-$(CONFIG_SPEAR_ADC) += spear_adc.o
diff --git a/drivers/staging/iio/adc/spear_adc.c b/drivers/staging/iio/adc/spear_adc.c
new file mode 100644 (file)
index 0000000..bea5778
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * ST SPEAr ADC driver
+ *
+ * Copyright 2012 Stefan Roese <sr@denx.de>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include "../iio.h"
+#include "../sysfs.h"
+
+/*
+ * SPEAR registers definitions
+ */
+
+#define SCAN_RATE_LO(x)                ((x) & 0xFFFF)
+#define SCAN_RATE_HI(x)                (((x) >> 0x10) & 0xFFFF)
+#define CLK_LOW(x)             (((x) & 0xf) << 0)
+#define CLK_HIGH(x)            (((x) & 0xf) << 4)
+
+/* Bit definitions for SPEAR_ADC_STATUS */
+#define START_CONVERSION       (1 << 0)
+#define CHANNEL_NUM(x)         ((x) << 1)
+#define ADC_ENABLE             (1 << 4)
+#define AVG_SAMPLE(x)          ((x) << 5)
+#define VREF_INTERNAL          (1 << 9)
+
+#define DATA_MASK              0x03ff
+#define DATA_BITS              10
+
+#define MOD_NAME "spear-adc"
+
+#define ADC_CHANNEL_NUM                8
+
+#define CLK_MIN                        2500000
+#define CLK_MAX                        20000000
+
+struct adc_regs_spear3xx {
+       u32 status;
+       u32 average;
+       u32 scan_rate;
+       u32 clk;        /* Not avail for 1340 & 1310 */
+       u32 ch_ctrl[ADC_CHANNEL_NUM];
+       u32 ch_data[ADC_CHANNEL_NUM];
+};
+
+struct chan_data {
+       u32 lsb;
+       u32 msb;
+};
+
+struct adc_regs_spear6xx {
+       u32 status;
+       u32 pad[2];
+       u32 clk;
+       u32 ch_ctrl[ADC_CHANNEL_NUM];
+       struct chan_data ch_data[ADC_CHANNEL_NUM];
+       u32 scan_rate_lo;
+       u32 scan_rate_hi;
+       struct chan_data average;
+};
+
+struct spear_adc_info {
+       struct device_node *np;
+       struct adc_regs_spear3xx __iomem *adc_base_spear3xx;
+       struct adc_regs_spear6xx __iomem *adc_base_spear6xx;
+       struct clk *clk;
+       struct completion completion;
+       u32 current_clk;
+       u32 sampling_freq;
+       u32 avg_samples;
+       u32 vref_external;
+       u32 value;
+};
+
+/*
+ * Functions to access some SPEAr ADC register. Abstracted into
+ * static inline functions, because of different register offsets
+ * on different SoC variants (SPEAr300 vs SPEAr600 etc).
+ */
+static void spear_adc_set_status(struct spear_adc_info *info, u32 val)
+{
+       __raw_writel(val, &info->adc_base_spear6xx->status);
+}
+
+static void spear_adc_set_clk(struct spear_adc_info *info, u32 val)
+{
+       u32 clk_high, clk_low, count;
+       u32 apb_clk = clk_get_rate(info->clk);
+
+       count = (apb_clk + val - 1) / val;
+       clk_low = count / 2;
+       clk_high = count - clk_low;
+       info->current_clk = apb_clk / count;
+
+       __raw_writel(CLK_LOW(clk_low) | CLK_HIGH(clk_high),
+                    &info->adc_base_spear6xx->clk);
+}
+
+static void spear_adc_set_ctrl(struct spear_adc_info *info, int n,
+                              u32 val)
+{
+       __raw_writel(val, &info->adc_base_spear6xx->ch_ctrl[n]);
+}
+
+static u32 spear_adc_get_average(struct spear_adc_info *info)
+{
+       if (of_device_is_compatible(info->np, "st,spear600-adc")) {
+               return __raw_readl(&info->adc_base_spear6xx->average.msb) &
+                       DATA_MASK;
+       } else {
+               return __raw_readl(&info->adc_base_spear3xx->average) &
+                       DATA_MASK;
+       }
+}
+
+static void spear_adc_set_scanrate(struct spear_adc_info *info, u32 rate)
+{
+       if (of_device_is_compatible(info->np, "st,spear600-adc")) {
+               __raw_writel(SCAN_RATE_LO(rate),
+                            &info->adc_base_spear6xx->scan_rate_lo);
+               __raw_writel(SCAN_RATE_HI(rate),
+                            &info->adc_base_spear6xx->scan_rate_hi);
+       } else {
+               __raw_writel(rate, &info->adc_base_spear3xx->scan_rate);
+       }
+}
+
+static int spear_read_raw(struct iio_dev *indio_dev,
+                         struct iio_chan_spec const *chan,
+                         int *val,
+                         int *val2,
+                         long mask)
+{
+       struct spear_adc_info *info = iio_priv(indio_dev);
+       u32 scale_mv;
+       u32 status;
+
+       switch (mask) {
+       case 0:
+               mutex_lock(&indio_dev->mlock);
+
+               status = CHANNEL_NUM(chan->channel) |
+                       AVG_SAMPLE(info->avg_samples) |
+                       START_CONVERSION | ADC_ENABLE;
+               if (info->vref_external == 0)
+                       status |= VREF_INTERNAL;
+
+               spear_adc_set_status(info, status);
+               wait_for_completion(&info->completion); /* set by ISR */
+               *val = info->value;
+
+               mutex_unlock(&indio_dev->mlock);
+
+               return IIO_VAL_INT;
+
+       case IIO_CHAN_INFO_SCALE:
+               scale_mv = (info->vref_external * 1000) >> DATA_BITS;
+               *val =  scale_mv / 1000;
+               *val2 = (scale_mv % 1000) * 1000;
+               return IIO_VAL_INT_PLUS_MICRO;
+       }
+
+       return -EINVAL;
+}
+
+#define SPEAR_ADC_CHAN(idx) {                          \
+       .type = IIO_VOLTAGE,                            \
+       .indexed = 1,                                   \
+       .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT,    \
+       .channel = idx,                                 \
+       .scan_type = {                                  \
+               .sign = 'u',                            \
+               .storagebits = 16,                      \
+       },                                              \
+}
+
+static struct iio_chan_spec spear_adc_iio_channels[] = {
+       SPEAR_ADC_CHAN(0),
+       SPEAR_ADC_CHAN(1),
+       SPEAR_ADC_CHAN(2),
+       SPEAR_ADC_CHAN(3),
+       SPEAR_ADC_CHAN(4),
+       SPEAR_ADC_CHAN(5),
+       SPEAR_ADC_CHAN(6),
+       SPEAR_ADC_CHAN(7),
+};
+
+static irqreturn_t spear_adc_isr(int irq, void *dev_id)
+{
+       struct spear_adc_info *info = (struct spear_adc_info *)dev_id;
+
+       /* Read value to clear IRQ */
+       info->value = spear_adc_get_average(info);
+       complete(&info->completion);
+
+       return IRQ_HANDLED;
+}
+
+static int spear_adc_configure(struct spear_adc_info *info)
+{
+       int i;
+
+       /* Reset ADC core */
+       spear_adc_set_status(info, 0);
+       __raw_writel(0, &info->adc_base_spear6xx->clk);
+       for (i = 0; i < 8; i++)
+               spear_adc_set_ctrl(info, i, 0);
+       spear_adc_set_scanrate(info, 0);
+
+       spear_adc_set_clk(info, info->sampling_freq);
+
+       return 0;
+}
+
+static ssize_t spear_adc_read_frequency(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct spear_adc_info *info = iio_priv(indio_dev);
+
+       return sprintf(buf, "%d\n", info->current_clk);
+}
+
+static ssize_t spear_adc_write_frequency(struct device *dev,
+                                        struct device_attribute *attr,
+                                        const char *buf,
+                                        size_t len)
+{
+       struct iio_dev *indio_dev = dev_get_drvdata(dev);
+       struct spear_adc_info *info = iio_priv(indio_dev);
+       u32 clk_high, clk_low, count;
+       u32 apb_clk = clk_get_rate(info->clk);
+       unsigned long lval;
+       int ret;
+
+       ret = kstrtoul(buf, 10, &lval);
+       if (ret)
+               return ret;
+
+       mutex_lock(&indio_dev->mlock);
+
+       if ((lval < CLK_MIN) || (lval > CLK_MAX)) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       count = (apb_clk + lval - 1) / lval;
+       clk_low = count / 2;
+       clk_high = count - clk_low;
+       info->current_clk = apb_clk / count;
+       spear_adc_set_clk(info, lval);
+
+out:
+       mutex_unlock(&indio_dev->mlock);
+
+       return ret ? ret : len;
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO,
+                             spear_adc_read_frequency,
+                             spear_adc_write_frequency);
+
+static struct attribute *spear_attributes[] = {
+       &iio_dev_attr_sampling_frequency.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group spear_attribute_group = {
+       .attrs = spear_attributes,
+};
+
+static const struct iio_info spear_adc_iio_info = {
+       .read_raw = &spear_read_raw,
+       .attrs = &spear_attribute_group,
+       .driver_module = THIS_MODULE,
+};
+
+static int __devinit spear_adc_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct device *dev = &pdev->dev;
+       struct spear_adc_info *info;
+       struct iio_dev *iodev = NULL;
+       int ret = -ENODEV;
+       int irq;
+
+       iodev = iio_allocate_device(sizeof(struct spear_adc_info));
+       if (!iodev) {
+               dev_err(dev, "failed allocating iio device\n");
+               ret = -ENOMEM;
+               goto errout1;
+       }
+
+       info = iio_priv(iodev);
+       info->np = np;
+
+       /*
+        * SPEAr600 has a different register layout than other SPEAr SoC's
+        * (e.g. SPEAr3xx). Let's provide two register base addresses
+        * to support multi-arch kernels.
+        */
+       info->adc_base_spear6xx = of_iomap(np, 0);
+       if (!info->adc_base_spear6xx) {
+               dev_err(dev, "failed mapping memory\n");
+               ret = -ENOMEM;
+               goto errout2;
+       }
+       info->adc_base_spear3xx =
+               (struct adc_regs_spear3xx *)info->adc_base_spear6xx;
+
+       info->clk = clk_get(dev, NULL);
+       if (IS_ERR(info->clk)) {
+               dev_err(dev, "failed getting clock\n");
+               goto errout3;
+       }
+
+       ret = clk_prepare(info->clk);
+       if (ret) {
+               dev_err(dev, "failed preparing clock\n");
+               goto errout4;
+       }
+
+       ret = clk_enable(info->clk);
+       if (ret) {
+               dev_err(dev, "failed enabling clock\n");
+               goto errout5;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if ((irq < 0) || (irq >= NR_IRQS)) {
+               dev_err(dev, "failed getting interrupt resource\n");
+               ret = -EINVAL;
+               goto errout6;
+       }
+
+       ret = devm_request_irq(dev, irq, spear_adc_isr, 0, MOD_NAME, info);
+       if (ret < 0) {
+               dev_err(dev, "failed requesting interrupt\n");
+               goto errout6;
+       }
+
+       if (of_property_read_u32(np, "sampling-frequency",
+                                &info->sampling_freq)) {
+               dev_err(dev, "sampling-frequency missing in DT\n");
+               ret = -EINVAL;
+               goto errout6;
+       }
+
+       /*
+        * Optional avg_samples defaults to 0, resulting in single data
+        * conversion
+        */
+       of_property_read_u32(np, "average-samples", &info->avg_samples);
+
+       /*
+        * Optional vref_external defaults to 0, resulting in internal vref
+        * selection
+        */
+       of_property_read_u32(np, "vref-external", &info->vref_external);
+
+       spear_adc_configure(info);
+
+       platform_set_drvdata(pdev, iodev);
+
+       init_completion(&info->completion);
+
+       iodev->name = MOD_NAME;
+       iodev->dev.parent = dev;
+       iodev->info = &spear_adc_iio_info;
+       iodev->modes = INDIO_DIRECT_MODE;
+       iodev->channels = spear_adc_iio_channels;
+       iodev->num_channels = ARRAY_SIZE(spear_adc_iio_channels);
+
+       ret = iio_device_register(iodev);
+       if (ret)
+               goto errout6;
+
+       dev_info(dev, "SPEAR ADC driver loaded, IRQ %d\n", irq);
+
+       return 0;
+
+errout6:
+       clk_disable(info->clk);
+errout5:
+       clk_unprepare(info->clk);
+errout4:
+       clk_put(info->clk);
+errout3:
+       iounmap(info->adc_base_spear6xx);
+errout2:
+       iio_free_device(iodev);
+errout1:
+       return ret;
+}
+
+static int __devexit spear_adc_remove(struct platform_device *pdev)
+{
+       struct iio_dev *iodev = platform_get_drvdata(pdev);
+       struct spear_adc_info *info = iio_priv(iodev);
+
+       iio_device_unregister(iodev);
+       platform_set_drvdata(pdev, NULL);
+       clk_disable(info->clk);
+       clk_unprepare(info->clk);
+       clk_put(info->clk);
+       iounmap(info->adc_base_spear6xx);
+       iio_free_device(iodev);
+
+       return 0;
+}
+
+static const struct of_device_id spear_adc_dt_ids[] = {
+       { .compatible = "st,spear600-adc", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, spear_adc_dt_ids);
+
+static struct platform_driver spear_adc_driver = {
+       .probe          = spear_adc_probe,
+       .remove         = __devexit_p(spear_adc_remove),
+       .driver         = {
+               .name   = MOD_NAME,
+               .owner  = THIS_MODULE,
+               .of_match_table = of_match_ptr(spear_adc_dt_ids),
+       },
+};
+
+module_platform_driver(spear_adc_driver);
+
+MODULE_AUTHOR("Stefan Roese <sr@denx.de>");
+MODULE_DESCRIPTION("SPEAr ADC driver");
+MODULE_LICENSE("GPL");