summaryrefslogtreecommitdiff
path: root/drivers/iio/dac/ad5360.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-07-26 18:14:49 (GMT)
committerLinus Torvalds <torvalds@linux-foundation.org>2012-07-26 18:14:49 (GMT)
commitb13bc8dda81c54a66a1c84e66f60b8feba659f28 (patch)
tree100a26eada424fa5d9b0e5eaaf4e23b8fa036fc8 /drivers/iio/dac/ad5360.c
parent9fc377799bc9bfd8d5cb35d0d1ea2e2458cbdbb3 (diff)
parent419e9266884fa853179ab726c27a63a9d3ae46e3 (diff)
downloadlinux-fsl-qoriq-b13bc8dda81c54a66a1c84e66f60b8feba659f28.tar.xz
Merge tag 'staging-3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging tree patches from Greg Kroah-Hartman: "Here's the big staging tree merge for the 3.6-rc1 merge window. There are some patches in here outside of drivers/staging/, notibly the iio code (which is still stradeling the staging / not staging boundry), the pstore code, and the tracing code. All of these have gotten acks from the various subsystem maintainers to be included in this tree. The pstore and tracing patches are related, and are coming here as they replace one of the android staging drivers. Otherwise, the normal staging mess. Lots of cleanups and a few new drivers (some iio drivers, and the large csr wireless driver abomination.) Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" Fixed up trivial conflicts in drivers/staging/comedi/drivers/s626.h and drivers/staging/gdm72xx/netlink_k.c * tag 'staging-3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (1108 commits) staging: csr: delete a bunch of unused library functions staging: csr: remove csr_utf16.c staging: csr: remove csr_pmem.h staging: csr: remove CsrPmemAlloc staging: csr: remove CsrPmemFree() staging: csr: remove CsrMemAllocDma() staging: csr: remove CsrMemCalloc() staging: csr: remove CsrMemAlloc() staging: csr: remove CsrMemFree() and CsrMemFreeDma() staging: csr: remove csr_util.h staging: csr: remove CsrOffSetOf() stating: csr: remove unneeded #includes in csr_util.c staging: csr: make CsrUInt16ToHex static staging: csr: remove CsrMemCpy() staging: csr: remove CsrStrLen() staging: csr: remove CsrVsnprintf() staging: csr: remove CsrStrDup staging: csr: remove CsrStrChr() staging: csr: remove CsrStrNCmp staging: csr: remove CsrStrCmp ...
Diffstat (limited to 'drivers/iio/dac/ad5360.c')
-rw-r--r--drivers/iio/dac/ad5360.c570
1 files changed, 570 insertions, 0 deletions
diff --git a/drivers/iio/dac/ad5360.c b/drivers/iio/dac/ad5360.c
new file mode 100644
index 0000000..8fce84f
--- /dev/null
+++ b/drivers/iio/dac/ad5360.c
@@ -0,0 +1,570 @@
+/*
+ * Analog devices AD5360, AD5361, AD5362, AD5363, AD5370, AD5371, AD5373
+ * multi-channel Digital to Analog Converters driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define AD5360_CMD(x) ((x) << 22)
+#define AD5360_ADDR(x) ((x) << 16)
+
+#define AD5360_READBACK_TYPE(x) ((x) << 13)
+#define AD5360_READBACK_ADDR(x) ((x) << 7)
+
+#define AD5360_CHAN_ADDR(chan) ((chan) + 0x8)
+
+#define AD5360_CMD_WRITE_DATA 0x3
+#define AD5360_CMD_WRITE_OFFSET 0x2
+#define AD5360_CMD_WRITE_GAIN 0x1
+#define AD5360_CMD_SPECIAL_FUNCTION 0x0
+
+/* Special function register addresses */
+#define AD5360_REG_SF_NOP 0x0
+#define AD5360_REG_SF_CTRL 0x1
+#define AD5360_REG_SF_OFS(x) (0x2 + (x))
+#define AD5360_REG_SF_READBACK 0x5
+
+#define AD5360_SF_CTRL_PWR_DOWN BIT(0)
+
+#define AD5360_READBACK_X1A 0x0
+#define AD5360_READBACK_X1B 0x1
+#define AD5360_READBACK_OFFSET 0x2
+#define AD5360_READBACK_GAIN 0x3
+#define AD5360_READBACK_SF 0x4
+
+
+/**
+ * struct ad5360_chip_info - chip specific information
+ * @channel_template: channel specification template
+ * @num_channels: number of channels
+ * @channels_per_group: number of channels per group
+ * @num_vrefs: number of vref supplies for the chip
+*/
+
+struct ad5360_chip_info {
+ struct iio_chan_spec channel_template;
+ unsigned int num_channels;
+ unsigned int channels_per_group;
+ unsigned int num_vrefs;
+};
+
+/**
+ * struct ad5360_state - driver instance specific data
+ * @spi: spi_device
+ * @chip_info: chip model specific constants, available modes etc
+ * @vref_reg: vref supply regulators
+ * @ctrl: control register cache
+ * @data: spi transfer buffers
+ */
+
+struct ad5360_state {
+ struct spi_device *spi;
+ const struct ad5360_chip_info *chip_info;
+ struct regulator_bulk_data vref_reg[3];
+ unsigned int ctrl;
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ union {
+ __be32 d32;
+ u8 d8[4];
+ } data[2] ____cacheline_aligned;
+};
+
+enum ad5360_type {
+ ID_AD5360,
+ ID_AD5361,
+ ID_AD5362,
+ ID_AD5363,
+ ID_AD5370,
+ ID_AD5371,
+ ID_AD5372,
+ ID_AD5373,
+};
+
+#define AD5360_CHANNEL(bits) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .output = 1, \
+ .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT | \
+ IIO_CHAN_INFO_SCALE_SEPARATE_BIT | \
+ IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | \
+ IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | \
+ IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, \
+ .scan_type = IIO_ST('u', (bits), 16, 16 - (bits)) \
+}
+
+static const struct ad5360_chip_info ad5360_chip_info_tbl[] = {
+ [ID_AD5360] = {
+ .channel_template = AD5360_CHANNEL(16),
+ .num_channels = 16,
+ .channels_per_group = 8,
+ .num_vrefs = 2,
+ },
+ [ID_AD5361] = {
+ .channel_template = AD5360_CHANNEL(14),
+ .num_channels = 16,
+ .channels_per_group = 8,
+ .num_vrefs = 2,
+ },
+ [ID_AD5362] = {
+ .channel_template = AD5360_CHANNEL(16),
+ .num_channels = 8,
+ .channels_per_group = 4,
+ .num_vrefs = 2,
+ },
+ [ID_AD5363] = {
+ .channel_template = AD5360_CHANNEL(14),
+ .num_channels = 8,
+ .channels_per_group = 4,
+ .num_vrefs = 2,
+ },
+ [ID_AD5370] = {
+ .channel_template = AD5360_CHANNEL(16),
+ .num_channels = 40,
+ .channels_per_group = 8,
+ .num_vrefs = 2,
+ },
+ [ID_AD5371] = {
+ .channel_template = AD5360_CHANNEL(14),
+ .num_channels = 40,
+ .channels_per_group = 8,
+ .num_vrefs = 3,
+ },
+ [ID_AD5372] = {
+ .channel_template = AD5360_CHANNEL(16),
+ .num_channels = 32,
+ .channels_per_group = 8,
+ .num_vrefs = 2,
+ },
+ [ID_AD5373] = {
+ .channel_template = AD5360_CHANNEL(14),
+ .num_channels = 32,
+ .channels_per_group = 8,
+ .num_vrefs = 2,
+ },
+};
+
+static unsigned int ad5360_get_channel_vref_index(struct ad5360_state *st,
+ unsigned int channel)
+{
+ unsigned int i;
+
+ /* The first groups have their own vref, while the remaining groups
+ * share the last vref */
+ i = channel / st->chip_info->channels_per_group;
+ if (i >= st->chip_info->num_vrefs)
+ i = st->chip_info->num_vrefs - 1;
+
+ return i;
+}
+
+static int ad5360_get_channel_vref(struct ad5360_state *st,
+ unsigned int channel)
+{
+ unsigned int i = ad5360_get_channel_vref_index(st, channel);
+
+ return regulator_get_voltage(st->vref_reg[i].consumer);
+}
+
+
+static int ad5360_write_unlocked(struct iio_dev *indio_dev,
+ unsigned int cmd, unsigned int addr, unsigned int val,
+ unsigned int shift)
+{
+ struct ad5360_state *st = iio_priv(indio_dev);
+
+ val <<= shift;
+ val |= AD5360_CMD(cmd) | AD5360_ADDR(addr);
+ st->data[0].d32 = cpu_to_be32(val);
+
+ return spi_write(st->spi, &st->data[0].d8[1], 3);
+}
+
+static int ad5360_write(struct iio_dev *indio_dev, unsigned int cmd,
+ unsigned int addr, unsigned int val, unsigned int shift)
+{
+ int ret;
+
+ mutex_lock(&indio_dev->mlock);
+ ret = ad5360_write_unlocked(indio_dev, cmd, addr, val, shift);
+ mutex_unlock(&indio_dev->mlock);
+
+ return ret;
+}
+
+static int ad5360_read(struct iio_dev *indio_dev, unsigned int type,
+ unsigned int addr)
+{
+ struct ad5360_state *st = iio_priv(indio_dev);
+ struct spi_message m;
+ int ret;
+ struct spi_transfer t[] = {
+ {
+ .tx_buf = &st->data[0].d8[1],
+ .len = 3,
+ .cs_change = 1,
+ }, {
+ .rx_buf = &st->data[1].d8[1],
+ .len = 3,
+ },
+ };
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t[0], &m);
+ spi_message_add_tail(&t[1], &m);
+
+ mutex_lock(&indio_dev->mlock);
+
+ st->data[0].d32 = cpu_to_be32(AD5360_CMD(AD5360_CMD_SPECIAL_FUNCTION) |
+ AD5360_ADDR(AD5360_REG_SF_READBACK) |
+ AD5360_READBACK_TYPE(type) |
+ AD5360_READBACK_ADDR(addr));
+
+ ret = spi_sync(st->spi, &m);
+ if (ret >= 0)
+ ret = be32_to_cpu(st->data[1].d32) & 0xffff;
+
+ mutex_unlock(&indio_dev->mlock);
+
+ return ret;
+}
+
+static ssize_t ad5360_read_dac_powerdown(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ad5360_state *st = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", (bool)(st->ctrl & AD5360_SF_CTRL_PWR_DOWN));
+}
+
+static int ad5360_update_ctrl(struct iio_dev *indio_dev, unsigned int set,
+ unsigned int clr)
+{
+ struct ad5360_state *st = iio_priv(indio_dev);
+ unsigned int ret;
+
+ mutex_lock(&indio_dev->mlock);
+
+ st->ctrl |= set;
+ st->ctrl &= ~clr;
+
+ ret = ad5360_write_unlocked(indio_dev, AD5360_CMD_SPECIAL_FUNCTION,
+ AD5360_REG_SF_CTRL, st->ctrl, 0);
+
+ mutex_unlock(&indio_dev->mlock);
+
+ return ret;
+}
+
+static ssize_t ad5360_write_dac_powerdown(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ bool pwr_down;
+ int ret;
+
+ ret = strtobool(buf, &pwr_down);
+ if (ret)
+ return ret;
+
+ if (pwr_down)
+ ret = ad5360_update_ctrl(indio_dev, AD5360_SF_CTRL_PWR_DOWN, 0);
+ else
+ ret = ad5360_update_ctrl(indio_dev, 0, AD5360_SF_CTRL_PWR_DOWN);
+
+ return ret ? ret : len;
+}
+
+static IIO_DEVICE_ATTR(out_voltage_powerdown,
+ S_IRUGO | S_IWUSR,
+ ad5360_read_dac_powerdown,
+ ad5360_write_dac_powerdown, 0);
+
+static struct attribute *ad5360_attributes[] = {
+ &iio_dev_attr_out_voltage_powerdown.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ad5360_attribute_group = {
+ .attrs = ad5360_attributes,
+};
+
+static int ad5360_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct ad5360_state *st = iio_priv(indio_dev);
+ int max_val = (1 << chan->scan_type.realbits);
+ unsigned int ofs_index;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (val >= max_val || val < 0)
+ return -EINVAL;
+
+ return ad5360_write(indio_dev, AD5360_CMD_WRITE_DATA,
+ chan->address, val, chan->scan_type.shift);
+
+ case IIO_CHAN_INFO_CALIBBIAS:
+ if (val >= max_val || val < 0)
+ return -EINVAL;
+
+ return ad5360_write(indio_dev, AD5360_CMD_WRITE_OFFSET,
+ chan->address, val, chan->scan_type.shift);
+
+ case IIO_CHAN_INFO_CALIBSCALE:
+ if (val >= max_val || val < 0)
+ return -EINVAL;
+
+ return ad5360_write(indio_dev, AD5360_CMD_WRITE_GAIN,
+ chan->address, val, chan->scan_type.shift);
+
+ case IIO_CHAN_INFO_OFFSET:
+ if (val <= -max_val || val > 0)
+ return -EINVAL;
+
+ val = -val;
+
+ /* offset is supposed to have the same scale as raw, but it
+ * is always 14bits wide, so on a chip where the raw value has
+ * more bits, we need to shift offset. */
+ val >>= (chan->scan_type.realbits - 14);
+
+ /* There is one DAC offset register per vref. Changing one
+ * channels offset will also change the offset for all other
+ * channels which share the same vref supply. */
+ ofs_index = ad5360_get_channel_vref_index(st, chan->channel);
+ return ad5360_write(indio_dev, AD5360_CMD_SPECIAL_FUNCTION,
+ AD5360_REG_SF_OFS(ofs_index), val, 0);
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int ad5360_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long m)
+{
+ struct ad5360_state *st = iio_priv(indio_dev);
+ unsigned int ofs_index;
+ int scale_uv;
+ int ret;
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ad5360_read(indio_dev, AD5360_READBACK_X1A,
+ chan->address);
+ if (ret < 0)
+ return ret;
+ *val = ret >> chan->scan_type.shift;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /* vout = 4 * vref * dac_code */
+ scale_uv = ad5360_get_channel_vref(st, chan->channel) * 4 * 100;
+ if (scale_uv < 0)
+ return scale_uv;
+
+ scale_uv >>= (chan->scan_type.realbits);
+ *val = scale_uv / 100000;
+ *val2 = (scale_uv % 100000) * 10;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ ret = ad5360_read(indio_dev, AD5360_READBACK_OFFSET,
+ chan->address);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ ret = ad5360_read(indio_dev, AD5360_READBACK_GAIN,
+ chan->address);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_OFFSET:
+ ofs_index = ad5360_get_channel_vref_index(st, chan->channel);
+ ret = ad5360_read(indio_dev, AD5360_READBACK_SF,
+ AD5360_REG_SF_OFS(ofs_index));
+ if (ret < 0)
+ return ret;
+
+ ret <<= (chan->scan_type.realbits - 14);
+ *val = -ret;
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info ad5360_info = {
+ .read_raw = ad5360_read_raw,
+ .write_raw = ad5360_write_raw,
+ .attrs = &ad5360_attribute_group,
+ .driver_module = THIS_MODULE,
+};
+
+static const char * const ad5360_vref_name[] = {
+ "vref0", "vref1", "vref2"
+};
+
+static int __devinit ad5360_alloc_channels(struct iio_dev *indio_dev)
+{
+ struct ad5360_state *st = iio_priv(indio_dev);
+ struct iio_chan_spec *channels;
+ unsigned int i;
+
+ channels = kcalloc(st->chip_info->num_channels,
+ sizeof(struct iio_chan_spec), GFP_KERNEL);
+
+ if (!channels)
+ return -ENOMEM;
+
+ for (i = 0; i < st->chip_info->num_channels; ++i) {
+ channels[i] = st->chip_info->channel_template;
+ channels[i].channel = i;
+ channels[i].address = AD5360_CHAN_ADDR(i);
+ }
+
+ indio_dev->channels = channels;
+
+ return 0;
+}
+
+static int __devinit ad5360_probe(struct spi_device *spi)
+{
+ enum ad5360_type type = spi_get_device_id(spi)->driver_data;
+ struct iio_dev *indio_dev;
+ struct ad5360_state *st;
+ unsigned int i;
+ int ret;
+
+ indio_dev = iio_device_alloc(sizeof(*st));
+ if (indio_dev == NULL) {
+ dev_err(&spi->dev, "Failed to allocate iio device\n");
+ return -ENOMEM;
+ }
+
+ st = iio_priv(indio_dev);
+ spi_set_drvdata(spi, indio_dev);
+
+ st->chip_info = &ad5360_chip_info_tbl[type];
+ st->spi = spi;
+
+ indio_dev->dev.parent = &spi->dev;
+ indio_dev->name = spi_get_device_id(spi)->name;
+ indio_dev->info = &ad5360_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->num_channels = st->chip_info->num_channels;
+
+ ret = ad5360_alloc_channels(indio_dev);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to allocate channel spec: %d\n", ret);
+ goto error_free;
+ }
+
+ for (i = 0; i < st->chip_info->num_vrefs; ++i)
+ st->vref_reg[i].supply = ad5360_vref_name[i];
+
+ ret = regulator_bulk_get(&st->spi->dev, st->chip_info->num_vrefs,
+ st->vref_reg);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to request vref regulators: %d\n", ret);
+ goto error_free_channels;
+ }
+
+ ret = regulator_bulk_enable(st->chip_info->num_vrefs, st->vref_reg);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to enable vref regulators: %d\n", ret);
+ goto error_free_reg;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to register iio device: %d\n", ret);
+ goto error_disable_reg;
+ }
+
+ return 0;
+
+error_disable_reg:
+ regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg);
+error_free_reg:
+ regulator_bulk_free(st->chip_info->num_vrefs, st->vref_reg);
+error_free_channels:
+ kfree(indio_dev->channels);
+error_free:
+ iio_device_free(indio_dev);
+
+ return ret;
+}
+
+static int __devexit ad5360_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct ad5360_state *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ kfree(indio_dev->channels);
+
+ regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg);
+ regulator_bulk_free(st->chip_info->num_vrefs, st->vref_reg);
+
+ iio_device_free(indio_dev);
+
+ return 0;
+}
+
+static const struct spi_device_id ad5360_ids[] = {
+ { "ad5360", ID_AD5360 },
+ { "ad5361", ID_AD5361 },
+ { "ad5362", ID_AD5362 },
+ { "ad5363", ID_AD5363 },
+ { "ad5370", ID_AD5370 },
+ { "ad5371", ID_AD5371 },
+ { "ad5372", ID_AD5372 },
+ { "ad5373", ID_AD5373 },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad5360_ids);
+
+static struct spi_driver ad5360_driver = {
+ .driver = {
+ .name = "ad5360",
+ .owner = THIS_MODULE,
+ },
+ .probe = ad5360_probe,
+ .remove = __devexit_p(ad5360_remove),
+ .id_table = ad5360_ids,
+};
+module_spi_driver(ad5360_driver);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Analog Devices AD5360/61/62/63/70/71/72/73 DAC");
+MODULE_LICENSE("GPL v2");