[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