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