diff options
Diffstat (limited to 'drivers/staging/iio/adc/ad7476_core.c')
-rw-r--r-- | drivers/staging/iio/adc/ad7476_core.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/drivers/staging/iio/adc/ad7476_core.c b/drivers/staging/iio/adc/ad7476_core.c new file mode 100644 index 0000000..deb68c8 --- /dev/null +++ b/drivers/staging/iio/adc/ad7476_core.c @@ -0,0 +1,293 @@ +/* + * AD7466/7/8 AD7476/5/7/8 (A) SPI ADC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> + +#include "../iio.h" +#include "../sysfs.h" +#include "../ring_generic.h" +#include "adc.h" + +#include "ad7476.h" + +static int ad7476_scan_direct(struct ad7476_state *st) +{ + int ret; + + ret = spi_sync(st->spi, &st->msg); + if (ret) + return ret; + + return (st->data[0] << 8) | st->data[1]; +} + +static ssize_t ad7476_scan(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct ad7476_state *st = dev_info->dev_data; + int ret; + + mutex_lock(&dev_info->mlock); + if (iio_ring_enabled(dev_info)) + ret = ad7476_scan_from_ring(st); + else + ret = ad7476_scan_direct(st); + mutex_unlock(&dev_info->mlock); + + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", (ret >> st->chip_info->res_shift) & + RES_MASK(st->chip_info->bits)); +} +static IIO_DEV_ATTR_IN_RAW(0, ad7476_scan, 0); + +static ssize_t ad7476_show_scale(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + /* Driver currently only support internal vref */ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct ad7476_state *st = iio_dev_get_devdata(dev_info); + /* Corresponds to Vref / 2^(bits) */ + unsigned int scale_uv = (st->int_vref_mv * 1000) >> st->chip_info->bits; + + return sprintf(buf, "%d.%d\n", scale_uv / 1000, scale_uv % 1000); +} +static IIO_DEVICE_ATTR(in_scale, S_IRUGO, ad7476_show_scale, NULL, 0); + +static ssize_t ad7476_show_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct ad7476_state *st = iio_dev_get_devdata(dev_info); + + return sprintf(buf, "%s\n", spi_get_device_id(st->spi)->name); +} +static IIO_DEVICE_ATTR(name, S_IRUGO, ad7476_show_name, NULL, 0); + +static struct attribute *ad7476_attributes[] = { + &iio_dev_attr_in0_raw.dev_attr.attr, + &iio_dev_attr_in_scale.dev_attr.attr, + &iio_dev_attr_name.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7476_attribute_group = { + .attrs = ad7476_attributes, +}; + +static const struct ad7476_chip_info ad7476_chip_info_tbl[] = { + [ID_AD7466] = { + .bits = 12, + .storagebits = 16, + .res_shift = 0, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, + [ID_AD7467] = { + .bits = 10, + .storagebits = 16, + .res_shift = 2, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, + [ID_AD7468] = { + .bits = 8, + .storagebits = 16, + .res_shift = 4, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, + [ID_AD7475] = { + .bits = 12, + .storagebits = 16, + .res_shift = 0, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, + [ID_AD7476] = { + .bits = 12, + .storagebits = 16, + .res_shift = 0, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, + [ID_AD7477] = { + .bits = 10, + .storagebits = 16, + .res_shift = 2, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, + [ID_AD7478] = { + .bits = 8, + .storagebits = 16, + .res_shift = 4, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, + [ID_AD7495] = { + .bits = 12, + .storagebits = 16, + .res_shift = 0, + .int_vref_mv = 2500, + .sign = IIO_SCAN_EL_TYPE_UNSIGNED, + }, +}; + +static int __devinit ad7476_probe(struct spi_device *spi) +{ + struct ad7476_platform_data *pdata = spi->dev.platform_data; + struct ad7476_state *st; + int ret, voltage_uv = 0; + + st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + 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 = + &ad7476_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (st->chip_info->int_vref_mv) + st->int_vref_mv = st->chip_info->int_vref_mv; + else 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, st); + + atomic_set(&st->protect_ring, 0); + st->spi = spi; + + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_reg; + } + + /* Estabilish that the iio_dev is a child of the i2c device */ + st->indio_dev->dev.parent = &spi->dev; + st->indio_dev->attrs = &ad7476_attribute_group; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default message */ + + st->xfer.rx_buf = &st->data; + st->xfer.len = st->chip_info->storagebits / 8; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + ret = ad7476_register_ring_funcs_and_init(st->indio_dev); + if (ret) + goto error_free_device; + + ret = iio_device_register(st->indio_dev); + if (ret) + goto error_free_device; + + ret = iio_ring_buffer_register(st->indio_dev->ring, 0); + if (ret) + goto error_cleanup_ring; + return 0; + +error_cleanup_ring: + ad7476_ring_cleanup(st->indio_dev); + iio_device_unregister(st->indio_dev); +error_free_device: + iio_free_device(st->indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + kfree(st); +error_ret: + return ret; +} + +static int ad7476_remove(struct spi_device *spi) +{ + struct ad7476_state *st = spi_get_drvdata(spi); + struct iio_dev *indio_dev = st->indio_dev; + iio_ring_buffer_unregister(indio_dev->ring); + ad7476_ring_cleanup(indio_dev); + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + kfree(st); + return 0; +} + +static const struct spi_device_id ad7476_id[] = { + {"ad7466", ID_AD7466}, + {"ad7467", ID_AD7467}, + {"ad7468", ID_AD7468}, + {"ad7475", ID_AD7475}, + {"ad7476", ID_AD7476}, + {"ad7476a", ID_AD7476}, + {"ad7477", ID_AD7477}, + {"ad7477a", ID_AD7477}, + {"ad7478", ID_AD7478}, + {"ad7478a", ID_AD7478}, + {"ad7495", ID_AD7495}, + {} +}; + +static struct spi_driver ad7476_driver = { + .driver = { + .name = "ad7476", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ad7476_probe, + .remove = __devexit_p(ad7476_remove), + .id_table = ad7476_id, +}; + +static int __init ad7476_init(void) +{ + return spi_register_driver(&ad7476_driver); +} +module_init(ad7476_init); + +static void __exit ad7476_exit(void) +{ + spi_unregister_driver(&ad7476_driver); +} +module_exit(ad7476_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD7475/6/7/8(A) AD7466/7/8 ADC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad7476"); |