[PATCH 1/1] staging/comedi/drivers: add driver for ad7739 analog to digital converter chip on an spi bus

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



   Add support for an ad7739 chip.

Signed-off-by: Alexander Pazdnikov <pazdnikov@xxxxxxx>
---
 drivers/staging/comedi/Kconfig          |   25 ++
 drivers/staging/comedi/drivers/Makefile |    3 +
 drivers/staging/comedi/drivers/ad7739.c |  443 +++++++++++++++++++++++++++++++
 drivers/staging/comedi/drivers/ad7739.h |    9 +
 4 files changed, 480 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/comedi/drivers/ad7739.c
 create mode 100644 drivers/staging/comedi/drivers/ad7739.h

diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig
index 4c77e50..c4bcab6 100644
--- a/drivers/staging/comedi/Kconfig
+++ b/drivers/staging/comedi/Kconfig
@@ -1381,3 +1381,28 @@ config COMEDI_FC
 
 	  To compile this driver as a module, choose M here: the module will be
 	  called comedi_fc.
+
+menuconfig COMEDI_SPI_DRIVERS
+	tristate "Comedi SPI drivers"
+	depends on COMEDI && SPI
+	default N
+	---help---
+	  Enable comedi SPI drivers to be built
+
+	  Note that the answer to this question won't directly affect the
+	  kernel: saying N will just cause the configurator to skip all
+	  the questions about SPI comedi drivers.
+
+if COMEDI_SPI_DRIVERS && SPI
+
+config COMEDI_AD7739
+	tristate "AD7739 driver"
+	select SPI_SPIDEV
+	default N
+	---help---
+	  Enable support for AD7739 A/D converter.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called ad7739.
+
+endif # COMEDI_SPI_DRIVERS
diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile
index 170da60..844d51c 100644
--- a/drivers/staging/comedi/drivers/Makefile
+++ b/drivers/staging/comedi/drivers/Makefile
@@ -138,3 +138,6 @@ obj-$(CONFIG_COMEDI_NI_LABPC)		+= ni_labpc.o
 obj-$(CONFIG_COMEDI_8255)		+= 8255.o
 obj-$(CONFIG_COMEDI_DAS08)		+= das08.o
 obj-$(CONFIG_COMEDI_FC)			+= comedi_fc.o
+
+# Comedi SPI drivers
+obj-$(CONFIG_COMEDI_AD7739)		+= ad7739.o
diff --git a/drivers/staging/comedi/drivers/ad7739.c b/drivers/staging/comedi/drivers/ad7739.c
new file mode 100644
index 0000000..a9fe4dd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ad7739.c
@@ -0,0 +1,443 @@
+/*
+    comedi/drivers/ad7739.c
+    Driver for AD7739 A/D converter chip on an SPI bus
+
+    Copyright (C) 2011 Prosoft Systems Ltd. <http://www.prosoftsystems.ru/>
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1998,2000 David A. Schleef <ds@xxxxxxxxxxx>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+/*
+Driver: ad7739
+Description: Analog Devices AD7739
+Author: Alexander Pazdnikov <pazdnikov@xxxxxxxxxxxxxxxxx> <pazdnikov@xxxxxxx>
+Devices: AD7739
+Updated: Thu, 15 Mar 2012 16:20:29 +0600
+Status: experimental
+
+Supports:
+
+  - ai_insn read
+       24 bit mode support only
+       using max measurement time only
+
+Configuration Options:
+  [0] - Bits: 0-7 - ChipSelect, 8-16 - SPI Bus Number
+
+*/
+/*
+    Usage example through board-setup.
+
+static const struct ad7739_platform_data dd11_adc =.
+{
+    .chanselect_p0p1 = 1,
+};
+
+static struct spi_board_info spi_devices[] = {
+    {
+        .modalias = "spidev",
+        .irq = AT91_PIN_PC7,
+        .chip_select = 5,
+        .max_speed_hz = 0,
+        .bus_num = 1,
+        .platform_data = &dd11_adc,
+    },
+};
+
+*/
+
+//#define DEBUG 1
+
+#include "../comedidev.h"
+#include "ad7739.h"
+
+#include <asm/gpio.h>
+
+#include <linux/completion.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+
+#define MAX_DATA 0xFFFFFF // 24-bit mode only
+#define CONVERT_TIMEOUT_MSECS 100 // spec max conv time = 2689 usecs
+
+#define DRIVER_NAME "ad7739"
+static const char *board_name = DRIVER_NAME;
+
+static int ad7739_attach(struct comedi_device * dev, struct comedi_devconfig * it);
+static int ad7739_detach(struct comedi_device * dev);
+
+static struct comedi_driver driver_ad7739 = {
+        .driver_name = DRIVER_NAME,
+        .module = THIS_MODULE,
+        .attach = ad7739_attach,
+        .detach = ad7739_detach,
+};
+
+static int __init ad7739_init_module(void)
+{
+    return comedi_driver_register(&driver_ad7739);
+}
+
+static void __exit ad7739_cleanup_module(void)
+{
+    comedi_driver_unregister(&driver_ad7739);
+}
+
+module_init(ad7739_init_module);
+module_exit(ad7739_cleanup_module);
+
+/* analog input ranges */
+static const struct comedi_lrange range_ad7739 = {
+        6,
+        {
+                RANGE(-1.25, 1.25),
+                RANGE(0, 1.25),
+                RANGE(-0.625, 0.625),
+                RANGE(0, 0.625),
+                RANGE(-2.5, 2.5),
+                RANGE(0, 2.5),
+        }
+};
+
+
+struct ad7739_private {
+        struct completion ready; // channel data ready
+        struct spi_device *spi; // appropriate spi device
+};
+
+
+struct ad7739_private * devpriv(struct comedi_device * dev) {
+        return dev->private;
+}
+
+/* write buffer */
+static 
+int ad7739_write_msg(struct comedi_device * dev, const u8 *buf, size_t len)
+{
+        return spi_write(devpriv(dev)->spi, buf, len);
+}
+
+#define COMM_REG_READ 0x40
+
+/* write 8-bit register */
+static int ad7739_write(struct comedi_device * dev, u8 reg, u8 val)
+{
+        int ret = 0 ;
+        struct spi_device *spi = devpriv(dev)->spi;
+
+        u8 out[2];
+
+        out[0] = reg & ~COMM_REG_READ;
+        out[1] = val;
+
+        dev_dbg(&spi->dev, "write reg %#x, val %#x\n", reg, val);
+
+        ret = spi_write(spi, out, sizeof(out));
+
+        return ret;
+}
+
+/* read register of desired size */
+static 
+int ad7739_read(struct comedi_device * dev, u8 reg, u8 *in, size_t size)
+{
+        struct spi_device *spi = devpriv(dev)->spi;
+        int ret;
+
+        u8 cmd = reg | COMM_REG_READ;
+
+        ret = spi_write_then_read(spi, &cmd, 1, in, size);
+
+        switch(size) {
+        case 1:
+                dev_dbg(&spi->dev, "read reg %#x, ret %#x, in %#x",
+                        reg, ret, in[0]);
+                break;
+        case 2:
+                dev_dbg(&spi->dev, "read reg %#x, ret %#x, in %#x, %#x",
+                        reg, ret, in[0], in[1]);
+                break;
+        case 3:
+                dev_dbg(&spi->dev, "read reg %#x, ret %#x, in %#x, %#x, %#x",
+                        reg, ret, in[0], in[1], in[2]);
+                break;
+        default:
+                dev_dbg(&spi->dev, "read reg %#x, ret %#x, in %#x, %#x, %#x, %#x",
+                        reg, ret, in[0], in[1], in[2], in[3]);
+        }
+
+        if (ret != 0) {
+                dev_info(&spi->dev, "read error %i\n", ret);
+        }
+
+        return ret;
+}
+
+/* software reset */
+static void ad7739_reset(struct comedi_device * dev)
+{
+        static const char buf[] = { 0, 0xFF, 0xFF, 0xFF, 0xFF };
+
+        ad7739_write_msg(dev, buf, sizeof(buf));
+}
+
+#define CHAN_STATUS 0x20
+#define     STATUS_CHAN_NUM(x) (((x) & 0xE0) >> 5)
+#define     STATUS_NOREF 0x04
+#define     STATUS_SIGN 0x02
+#define     STATUS_OVERFLOW 0x01
+
+
+#define CHAN_DATA 0x08
+
+#define CHAN_SETUP 0x28
+#define     SETUP_COM0 0x40
+#define     SETUP_COM1 0x20
+#define     SETUP_DIFFER (SETUP_COM0 | SETUP_COM1)
+#define     SETUP_ENABLE 0x08
+
+#define TIME_CONVERSION 0x30
+#define     CHOP 0x80
+#define     FILTER_MAX  0x7F
+
+#define CHAN_MODE 0x38
+#define     MODE_SINGLE 0x40
+#define     MODE_DUMP  0x08
+#define     MODE_24BIT 0x02
+#define     MODE_CLAMP 0x01
+
+#define IOPORT 0x01
+#define     P0_STATE 0x80
+#define     P0_HIGH  0x80
+#define     P1_STATE 0x40
+#define     P1_HIGH 0x40
+#define     P0_INPUT 0x20
+#define     P1_INPUT 0x10
+#define     READY_ON_ALL_CHANS 0x80
+
+static int ai_insn_read(struct comedi_device * dev, struct comedi_subdevice * s,
+                        struct comedi_insn * insn, unsigned int * data)
+{
+        u8 buf[4];
+        u8 status = 0;
+
+        const unsigned chan = CR_CHAN(insn->chanspec);
+        const unsigned range = CR_RANGE(insn->chanspec);
+        const unsigned aref = CR_AREF(insn->chanspec);
+        struct ad7739_platform_data *pdata;
+
+
+        int ret = 0;
+
+        u8 setup = range + ((aref == AREF_DIFF) ? SETUP_DIFFER : 0) + SETUP_ENABLE;
+
+        /* select chan in demux */
+        pdata = devpriv(dev)->spi->dev.platform_data;
+        if (pdata->chanselect_p0p1) {
+                ad7739_write(dev, IOPORT, (chan & 0x03) << 6);
+        }
+
+        ret = ad7739_write(dev, TIME_CONVERSION + chan, CHOP + FILTER_MAX);
+        if (ret != 0) {
+                return ret;
+        }
+
+        ret = ad7739_write(dev, CHAN_SETUP + chan, setup);
+        if (ret != 0) {
+                return ret;
+        }
+
+        INIT_COMPLETION(devpriv(dev)->ready);
+
+        /* start conversion */
+        ret = ad7739_write(dev, CHAN_MODE + chan,
+                           MODE_SINGLE | MODE_DUMP | MODE_24BIT | MODE_CLAMP);
+
+        if (ret != 0) {
+                return ret;
+        }
+
+        ret = wait_for_completion_interruptible_timeout(
+                      &devpriv(dev)->ready, 
+                      msecs_to_jiffies(CONVERT_TIMEOUT_MSECS));
+
+        if (0 == ret)
+                return -ETIME;
+        if (ret < 0)
+                return ret;
+
+        ret = ad7739_read(dev, CHAN_DATA + chan, buf, sizeof(buf));
+        if (ret != 0)
+                return ret;
+
+        *data = buf[1] + (buf[2] << 8) + (buf[3] << 16);
+        status = buf[0];
+
+        dev_dbg(dev->class_dev, "data %#x, status %#x", *data, status);
+
+        if (chan != STATUS_CHAN_NUM(status)) {
+                /* invalid chan, spi sync error */
+                dev_dbg(dev->class_dev, "chan %#x, reply chan %#x",
+                        chan, (status & 0xE0) >> 5);
+
+                return -EBADE;
+        }
+
+        if (status & STATUS_NOREF) {
+
+                dev_dbg(dev->class_dev, "NOREF");
+
+                return -ERANGE;
+        }
+
+        if (status & STATUS_OVERFLOW) {
+
+                *data = (status & STATUS_SIGN) ? 0 : MAX_DATA;
+
+                dev_dbg(dev->class_dev, "OVERFLOW");
+
+                return -EOVERFLOW;
+        }
+
+        if (status & STATUS_SIGN) {
+                /* polarity mismatch */
+                *data = ~*data + 1;
+        }
+
+        /* good conversion */
+
+        return 0;
+}
+
+static irqreturn_t ad7739_irq(int irq, void *data)
+{
+        struct ad7739_private *priv = data;
+
+        complete(&priv->ready);
+
+//    dev_dbg(&priv->spi->dev, "IRQ handled %u", gpio_get_value(irq));
+
+        return IRQ_HANDLED;
+}
+
+#ifdef DEBUG
+static int ad7739_print(struct device *dev, void *data)
+{
+        dev_info(dev, "dev_name = %s\n", dev_name(dev));
+
+        return 0;
+}
+#endif
+
+static
+int ad7739_attach(struct comedi_device * dev, struct comedi_devconfig * it)
+{
+        struct comedi_subdevice *s = NULL;
+        struct ad7739_private *priv = NULL;
+        struct device *d = NULL;
+
+        char devname[64];
+
+        if (alloc_private(dev, sizeof(struct ad7739_private)) < 0)
+                return -ENOMEM;
+
+        if (alloc_subdevices(dev, 1) < 0)
+                return -ENOMEM;
+
+        priv = dev->private;
+
+        dev->board_name = board_name;
+
+        s = dev->subdevices;
+
+        // ai
+        s->type = COMEDI_SUBD_AI;
+        s->subdev_flags = SDF_READABLE;
+        s->maxdata = MAX_DATA;
+        s->n_chan = 8;
+        s->insn_read = ai_insn_read;
+        s->range_table = &range_ad7739;
+
+        /* Bits: 0-7 - ChipSelect, 8-16 - SPI Bus Number*/
+        dev->iobase = it->options[0];
+
+#ifdef DEBUG
+        bus_for_each_dev(&spi_bus_type, NULL, NULL, ad7739_print);
+#endif
+
+        snprintf(devname, sizeof(devname), "%s%u.%u", spi_bus_type.name,
+                 (unsigned)((dev->iobase >> 8) & 0xFF),
+                 (unsigned)(dev->iobase & 0xFF));
+
+        d = bus_find_device_by_name(&spi_bus_type, NULL, devname);
+        if (!d) {
+                dev_err(dev->class_dev, "devices %s not found\n", devname);
+                return -EINVAL;
+        }
+
+        priv->spi = to_spi_device(d);
+        
+        /* hold device */
+        get_device(&priv->spi->dev);
+
+        /* Reset the chip */
+        ad7739_reset(dev);
+        
+        init_completion(&priv->ready);
+
+        if (request_irq(priv->spi->irq, ad7739_irq, 0, dev->board_name, priv)) {
+
+                dev_err(dev->class_dev, "IRQ %u request failed\n", priv->spi->irq);
+                return -EBUSY;
+        }
+
+        dev->irq = priv->spi->irq;
+
+        dev_info(dev->class_dev, "connected to device %s, irq %u\n",
+                 devname, priv->spi->irq);
+
+        return 0;
+}
+
+static int ad7739_detach(struct comedi_device * dev)
+{
+        struct ad7739_private *priv = devpriv(dev);
+
+        if (!priv)
+                return 0;
+
+        /* Free the interrupt */
+        if (dev->irq)
+                free_irq(dev->irq, priv);
+
+        if (priv->spi) {
+
+                dev_info(dev->class_dev, "removed %s\n", dev_name(&priv->spi->dev));
+
+                /* release device */
+                put_device(&priv->spi->dev);
+        }
+
+        return 0;
+}
+
+
+MODULE_AUTHOR("Alexander Pazdnikov <pazdnikov@xxxxxxx>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AD7739 SPI based A/D converter chip");
+MODULE_ALIAS("spi:" DRIVER_NAME);
+
diff --git a/drivers/staging/comedi/drivers/ad7739.h b/drivers/staging/comedi/drivers/ad7739.h
new file mode 100644
index 0000000..aa9b8d6
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ad7739.h
@@ -0,0 +1,9 @@
+#ifndef AD7739_H
+#define AD7739_H
+
+struct ad7739_platform_data 
+{
+        u8 chanselect_p0p1; // multiplex switch chans
+};
+
+#endif // AD7739_H
-- 
1.7.4.1

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/devel


[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux