Thank you Greg, I've corrected driver taking into account your comments. This is my first friver, I've missed to check the first version with checkpatch script. Today it goes through checkpatch script with no errors and warnings. Appreciate any comments. The ad7739.h file is need for inclusion into board specific initialization file and to configure the exact chip. Signed-off-by: Alexander Pazdnikov <pazdnikov@xxxxxxx> --- drivers/staging/comedi/Kconfig | 22 ++ drivers/staging/comedi/drivers/Makefile | 3 + drivers/staging/comedi/drivers/ad7739.c | 422 +++++++++++++++++++++++++++++++ drivers/staging/comedi/drivers/ad7739.h | 9 + 4 files changed, 456 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..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..10053d9 --- /dev/null +++ b/drivers/staging/comedi/drivers/ad7739.c @@ -0,0 +1,422 @@ +/* + 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@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 <linux/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..2aa0a0d --- /dev/null +++ b/drivers/staging/comedi/drivers/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