[PATCH 1/1] v3, staging/comedi/drivers: add driver for ad7739 analog to digital converter chip on an spi bus
Alexander Pazdnikov
pazdnikov at gmail.com
Tue Mar 20 09:14:41 UTC 2012
Thank you Greg for comments.
Thank you Dan for comments.
I've done my best to apply yours comments.
Signed-off-by: Alexander Pazdnikov <pazdnikov at gmail.com>
---
drivers/staging/comedi/Kconfig | 22 ++
drivers/staging/comedi/drivers/Makefile | 3 +
drivers/staging/comedi/drivers/ad7739.c | 402 +++++++++++++++++++++++++++++++
include/linux/platform_data/ad7739.h | 9 +
4 files changed, 436 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/comedi/drivers/ad7739.c
create mode 100644 include/linux/platform_data/ad7739.h
diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig
index 4c77e50..d713031 100644
--- a/drivers/staging/comedi/Kconfig
+++ b/drivers/staging/comedi/Kconfig
@@ -1381,3 +1381,25 @@ 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.
+
+config COMEDI_AD7739
+ tristate "AD7739 driver"
+ depends on COMEDI_SPI_DRIVERS
+ 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.
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..5c7655c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ad7739.c
@@ -0,0 +1,402 @@
+/*
+ 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 at schleef.org>
+
+ This software is licensed under the terms of the GNU General Public
+ License version 2, as published by the Free Software Foundation, and
+ may be copied, distributed, and modified under those terms.
+*/
+/*
+Driver: ad7739
+Description: Analog Devices AD7739
+Author: Alexander Pazdnikov <pazdnikov at prosoftsystems.ru> <pazdnikov at gmail.com>
+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,
+ },
+};
+
+*/
+
+#include "../comedidev.h"
+#include <linux/gpio.h>
+#include <linux/completion.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/platform_data/ad7739.h>
+
+#define MAX_DATA 0xFFFFFF /* 24-bit mode only */
+#define CONVERT_TIMEOUT_MSECS 100 /* spec max conv time = 2689 usecs */
+#define DRIVER_NAME KBUILD_MODNAME
+
+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 */
+};
+
+static 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)
+{
+ 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);
+
+ return spi_write(spi, out, sizeof(out));
+}
+
+/* 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;
+ u8 cmd = reg | COMM_REG_READ;
+
+ int 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;
+ int ret;
+ struct ad7739_platform_data *pdata;
+ const unsigned chan = CR_CHAN(insn->chanspec);
+ const unsigned range = CR_RANGE(insn->chanspec);
+ const unsigned aref = CR_AREF(insn->chanspec);
+
+ 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;
+}
+
+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];
+ int ret;
+
+ ret = alloc_private(dev, sizeof(struct ad7739_private));
+ if (ret < 0)
+ return ret;
+
+ ret = alloc_subdevices(dev, 1);
+ if (ret < 0)
+ return ret;
+
+ 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];
+
+ 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);
+
+ ret = request_irq(priv->spi->irq, ad7739_irq, 0, dev->board_name, priv);
+ if (ret) {
+ dev_err(dev->class_dev, "IRQ %u request failed\n",
+ priv->spi->irq);
+ return ret;
+ }
+
+ 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 at gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AD7739 SPI based A/D converter chip");
+MODULE_ALIAS("spi:" DRIVER_NAME);
diff --git a/include/linux/platform_data/ad7739.h b/include/linux/platform_data/ad7739.h
new file mode 100644
index 0000000..2aa0a0d
--- /dev/null
+++ b/include/linux/platform_data/ad7739.h
@@ -0,0 +1,9 @@
+#ifndef AD7739_H
+#define AD7739_H
+
+struct ad7739_platform_data {
+ /* if p0,p1 output chans are used as multiplexer for ai chans */
+ u8 chanselect_p0p1;
+};
+
+#endif /* AD7739_H */
--
1.7.4.1
More information about the devel
mailing list