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@xxxxxxxxx> --- 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@xxxxxxxxxxx> + + 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@xxxxxxxxxxxxxxxxx> <pazdnikov@xxxxxxxxx> +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@xxxxxxxxx>"); +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 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/devel