diff options
Diffstat (limited to 'drivers/staging/iio/adc/ad7780.c')
-rw-r--r-- | drivers/staging/iio/adc/ad7780.c | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/drivers/staging/iio/adc/ad7780.c b/drivers/staging/iio/adc/ad7780.c new file mode 100644 index 000000000000..e0c7b6cc05c7 --- /dev/null +++ b/drivers/staging/iio/adc/ad7780.c @@ -0,0 +1,301 @@ +/* + * AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/gpio.h> + +#include "../iio.h" +#include "../sysfs.h" +#include "../ring_generic.h" +#include "adc.h" + +#include "ad7780.h" + +#define AD7780_RDY (1 << 7) +#define AD7780_FILTER (1 << 6) +#define AD7780_ERR (1 << 5) +#define AD7780_ID1 (1 << 4) +#define AD7780_ID0 (1 << 3) +#define AD7780_GAIN (1 << 2) +#define AD7780_PAT1 (1 << 1) +#define AD7780_PAT0 (1 << 0) + +struct ad7780_chip_info { + struct iio_chan_spec channel; +}; + +struct ad7780_state { + struct spi_device *spi; + const struct ad7780_chip_info *chip_info; + struct regulator *reg; + struct ad7780_platform_data *pdata; + wait_queue_head_t wq_data_avail; + bool done; + u16 int_vref_mv; + struct spi_transfer xfer; + struct spi_message msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned int data ____cacheline_aligned; +}; + +enum ad7780_supported_device_ids { + ID_AD7780, + ID_AD7781, +}; + +static int ad7780_read(struct ad7780_state *st, int *val) +{ + int ret; + + spi_bus_lock(st->spi->master); + + enable_irq(st->spi->irq); + st->done = false; + gpio_set_value(st->pdata->gpio_pdrst, 1); + + ret = wait_event_interruptible(st->wq_data_avail, st->done); + disable_irq_nosync(st->spi->irq); + if (ret) + goto out; + + ret = spi_sync_locked(st->spi, &st->msg); + *val = be32_to_cpu(st->data); +out: + gpio_set_value(st->pdata->gpio_pdrst, 0); + spi_bus_unlock(st->spi->master); + + return ret; +} + +static int ad7780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7780_state *st = iio_priv(indio_dev); + struct iio_chan_spec channel = st->chip_info->channel; + int ret, smpl = 0; + unsigned long scale_uv; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + ret = ad7780_read(st, &smpl); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + if ((smpl & AD7780_ERR) || + !((smpl & AD7780_PAT0) && !(smpl & AD7780_PAT1))) + return -EIO; + + *val = (smpl >> channel.scan_type.shift) & + ((1 << (channel.scan_type.realbits)) - 1); + *val -= (1 << (channel.scan_type.realbits - 1)); + + if (!(smpl & AD7780_GAIN)) + *val *= 128; + + return IIO_VAL_INT; + case (1 << IIO_CHAN_INFO_SCALE_SHARED): + scale_uv = (st->int_vref_mv * 100000) + >> (channel.scan_type.realbits - 1); + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct ad7780_chip_info ad7780_chip_info_tbl[] = { + [ID_AD7780] = { + .channel = IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 0, 0, + (1 << IIO_CHAN_INFO_SCALE_SHARED), + 0, 0, IIO_ST('s', 24, 32, 8), 0), + }, + [ID_AD7781] = { + .channel = IIO_CHAN(IIO_IN, 0, 1, 0, NULL, 0, 0, + (1 << IIO_CHAN_INFO_SCALE_SHARED), + 0, 0, IIO_ST('s', 20, 32, 12), 0), + }, +}; + +/** + * Interrupt handler + */ +static irqreturn_t ad7780_interrupt(int irq, void *dev_id) +{ + struct ad7780_state *st = dev_id; + + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +}; + +static const struct iio_info ad7780_info = { + .read_raw = &ad7780_read_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad7780_probe(struct spi_device *spi) +{ + struct ad7780_platform_data *pdata = spi->dev.platform_data; + struct ad7780_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->chip_info = + &ad7780_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + st->pdata = pdata; + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + indio_dev->info = &ad7780_info; + + init_waitqueue_head(&st->wq_data_avail); + + /* Setup default message */ + + st->xfer.rx_buf = &st->data; + st->xfer.len = st->chip_info->channel.scan_type.storagebits / 8; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + ret = gpio_request_one(st->pdata->gpio_pdrst, GPIOF_OUT_INIT_LOW, + "AD7780 /PDRST"); + if (ret) { + dev_err(&spi->dev, "failed to request GPIO PDRST\n"); + goto error_disable_reg; + } + + ret = request_irq(spi->irq, ad7780_interrupt, + IRQF_TRIGGER_FALLING, spi_get_device_id(spi)->name, st); + if (ret) + goto error_free_gpio; + + disable_irq(spi->irq); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(spi->irq, st); +error_free_gpio: + gpio_free(st->pdata->gpio_pdrst); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_free_device(indio_dev); + + return ret; +} + +static int ad7780_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7780_state *st = iio_priv(indio_dev); + + free_irq(spi->irq, st); + gpio_free(st->pdata->gpio_pdrst); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7780_id[] = { + {"ad7780", ID_AD7780}, + {"ad7781", ID_AD7781}, + {} +}; + +static struct spi_driver ad7780_driver = { + .driver = { + .name = "ad7780", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ad7780_probe, + .remove = __devexit_p(ad7780_remove), + .id_table = ad7780_id, +}; + +static int __init ad7780_init(void) +{ + return spi_register_driver(&ad7780_driver); +} +module_init(ad7780_init); + +static void __exit ad7780_exit(void) +{ + spi_unregister_driver(&ad7780_driver); +} +module_exit(ad7780_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD7780/1 ADC"); +MODULE_LICENSE("GPL v2"); |