* [PATCH v2] staging:iio:dac: Add AD5360 driver
@ 2011-10-17 11:19 Lars-Peter Clausen
2011-10-17 14:12 ` Hennerich, Michael
2011-10-17 14:15 ` Jonathan Cameron
0 siblings, 2 replies; 3+ messages in thread
From: Lars-Peter Clausen @ 2011-10-17 11:19 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Michael Hennerich, linux-iio, drivers, device-drivers-devel,
Lars-Peter Clausen
This patch adds support for the Analog Devices AD5360, AD5361, AD5362, AD5363,
AD5370, AD5371, AD5372, AD5373 multi-channel digital-to-analog converters.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
Changes since v1:
* Minor code cleanups
* List full part names in Kconfig
---
drivers/staging/iio/dac/Kconfig | 11 +
drivers/staging/iio/dac/Makefile | 1 +
drivers/staging/iio/dac/ad5360.c | 580 ++++++++++++++++++++++++++++++++++++++
3 files changed, 592 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/iio/dac/ad5360.c
diff --git a/drivers/staging/iio/dac/Kconfig b/drivers/staging/iio/dac/Kconfig
index 3000156..9211a39 100644
--- a/drivers/staging/iio/dac/Kconfig
+++ b/drivers/staging/iio/dac/Kconfig
@@ -3,6 +3,17 @@
#
menu "Digital to analog convertors"
+config AD5360
+ tristate "Analog Devices Analog Devices AD5360/61/62/63/70/71/73 DAC driver"
+ depends on SPI
+ help
+ Say yes here to build support for Analog Devices AD5360, AD5361,
+ AD5362, AD5363, AD5370, AD5371, AD5373 multi-channel
+ Digital to Analog Converters (DAC).
+
+ To compile this driver as module choose M here: the module will be called
+ ad5360.
+
config AD5624R_SPI
tristate "Analog Devices AD5624/44/64R DAC spi driver"
depends on SPI
diff --git a/drivers/staging/iio/dac/Makefile b/drivers/staging/iio/dac/Makefile
index 7f4f2ed..e0a8c97 100644
--- a/drivers/staging/iio/dac/Makefile
+++ b/drivers/staging/iio/dac/Makefile
@@ -2,6 +2,7 @@
# Makefile for industrial I/O DAC drivers
#
+obj-$(CONFIG_AD5360) += ad5360.o
obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o
obj-$(CONFIG_AD5504) += ad5504.o
obj-$(CONFIG_AD5446) += ad5446.o
diff --git a/drivers/staging/iio/dac/ad5360.c b/drivers/staging/iio/dac/ad5360.c
new file mode 100644
index 0000000..fdbfb48
--- /dev/null
+++ b/drivers/staging/iio/dac/ad5360.c
@@ -0,0 +1,580 @@
+/*
+ * 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 "../iio.h"
+#include "../sysfs.h"
+#include "dac.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 = (1 << IIO_CHAN_INFO_SCALE_SEPARATE) | \
+ (1 << IIO_CHAN_INFO_OFFSET_SEPARATE) | \
+ (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE) | \
+ (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE), \
+ .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_get_drvdata(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_get_drvdata(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 0:
+ 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 (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE):
+ 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 (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE):
+ 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 (1 << IIO_CHAN_INFO_OFFSET_SEPARATE):
+ 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 long scale_uv;
+ unsigned int ofs_index;
+ int ret;
+
+ switch (m) {
+ case 0:
+ 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 (1 << IIO_CHAN_INFO_SCALE_SEPARATE):
+ /* 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 (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE):
+ ret = ad5360_read(indio_dev, AD5360_READBACK_OFFSET,
+ chan->address);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE):
+ ret = ad5360_read(indio_dev, AD5360_READBACK_GAIN,
+ chan->address);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case (1 << IIO_CHAN_INFO_OFFSET_SEPARATE):
+ 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(sizeof(struct iio_chan_spec),
+ st->chip_info->num_channels, 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_allocate_device(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_free_device(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_free_device(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 },
+ {}
+};
+
+static struct spi_driver ad5360_driver = {
+ .driver = {
+ .name = "ad5360",
+ .owner = THIS_MODULE,
+ },
+ .probe = ad5360_probe,
+ .remove = __devexit_p(ad5360_remove),
+ .id_table = ad5360_ids,
+};
+
+static __init int ad5360_spi_init(void)
+{
+ return spi_register_driver(&ad5360_driver);
+}
+module_init(ad5360_spi_init);
+
+static __exit void ad5360_spi_exit(void)
+{
+ spi_unregister_driver(&ad5360_driver);
+}
+module_exit(ad5360_spi_exit);
+
+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");
--
1.7.6.3
^ permalink raw reply related [flat|nested] 3+ messages in thread
* RE: [PATCH v2] staging:iio:dac: Add AD5360 driver
2011-10-17 11:19 [PATCH v2] staging:iio:dac: Add AD5360 driver Lars-Peter Clausen
@ 2011-10-17 14:12 ` Hennerich, Michael
2011-10-17 14:15 ` Jonathan Cameron
1 sibling, 0 replies; 3+ messages in thread
From: Hennerich, Michael @ 2011-10-17 14:12 UTC (permalink / raw)
To: Lars-Peter Clausen, Jonathan Cameron
Cc: linux-iio@vger.kernel.org, Drivers,
device-drivers-devel@blackfin.uclinux.org
Lars-Peter Clausen wrote on 2011-10-17:
> This patch adds support for the Analog Devices AD5360, AD5361, AD5362,
> AD5363, AD5370, AD5371, AD5372, AD5373 multi-channel digital-to-analog
> converters.
>
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Looks good!
Acked-by: Michael Hennerich <michael.hennerich@analog.com>
>
> ---
> Changes since v1:
> * Minor code cleanups
> * List full part names in Kconfig
> ---
> drivers/staging/iio/dac/Kconfig | 11 +
> drivers/staging/iio/dac/Makefile | 1 +
> drivers/staging/iio/dac/ad5360.c | 580
> ++++++++++++++++++++++++++++++++++++++ 3 files changed, 592
> insertions(+), 0 deletions(-) create mode 100644
> drivers/staging/iio/dac/ad5360.c
> diff --git a/drivers/staging/iio/dac/Kconfig
> b/drivers/staging/iio/dac/Kconfig index 3000156..9211a39 100644 ---
> a/drivers/staging/iio/dac/Kconfig +++ b/drivers/staging/iio/dac/Kconfig
> @@ -3,6 +3,17 @@
> #
> menu "Digital to analog convertors"
> +config AD5360 + tristate "Analog Devices Analog Devices
> AD5360/61/62/63/70/71/73 DAC driver" + depends on SPI + hel=
p + Say yes
> here to build support for Analog Devices AD5360, AD5361, + AD5362,
> AD5363, AD5370, AD5371, AD5373 multi-channel + Digital to Analog
> Converters (DAC). + + To compile this driver as module choose M here:
> the module will be called + ad5360. +
> config AD5624R_SPI
> tristate "Analog Devices AD5624/44/64R DAC spi driver"
> depends on SPI
> diff --git a/drivers/staging/iio/dac/Makefile
> b/drivers/staging/iio/dac/Makefile index 7f4f2ed..e0a8c97 100644 ---
> a/drivers/staging/iio/dac/Makefile +++
> b/drivers/staging/iio/dac/Makefile @@ -2,6 +2,7 @@
> # Makefile for industrial I/O DAC drivers
> #
> +obj-$(CONFIG_AD5360) +=3D ad5360.o
> obj-$(CONFIG_AD5624R_SPI) +=3D ad5624r_spi.o
> obj-$(CONFIG_AD5504) +=3D ad5504.o
> obj-$(CONFIG_AD5446) +=3D ad5446.o
> diff --git a/drivers/staging/iio/dac/ad5360.c
> b/drivers/staging/iio/dac/ad5360.c new file mode 100644 index
> 0000000..fdbfb48 --- /dev/null +++ b/drivers/staging/iio/dac/ad5360.c @@
> -0,0 +1,580 @@ +/* + * 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 "../iio.h" +#include
> "../sysfs.h" +#include "dac.h" + +#define AD5360_CMD(x) =
((x) << 22)
> +#define AD5360_ADDR(x) ((x) << 16) + +#def=
ine
> 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_OFFSE=
T 0x2
> +#define AD5360_CMD_WRITE_GAIN 0x1 +#define
> AD5360_CMD_SPECIAL_FUNCTION 0x0 + +/* Special function register
> addresses */ +#define AD5360_REG_SF_NOP 0x0 +#defin=
e
> 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_RE=
ADBACK_X1A 0x0
> +#define AD5360_READBACK_X1B 0x1 +#define AD5360_READBAC=
K_OFFSET 0x2
> +#define AD5360_READBACK_GAIN 0x3 +#define AD5360_READBAC=
K_SF 0x4 +
> + +/** + * struct ad5360_chip_info - chip specific information + *
> @channel_template: channel specification template + *
> @num_channels: number of channels + * @channels_per_group: num=
ber 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_channel=
s; + 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; + con=
st struct
> ad5360_chip_info *chip_info; + struct regulator_bulk_data vre=
f_reg[3];
> + unsigned int ctrl; + + /* + * DMA (thu=
s 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 =3D IIO_VOLTAGE, \ + .=
indexed =3D 1, \ + .output =
=3D
> 1, \ + .info_mask =3D (1 <=
< IIO_CHAN_INFO_SCALE_SEPARATE) | \ + (1
> << IIO_CHAN_INFO_OFFSET_SEPARATE) | \ + (1 <<
> IIO_CHAN_INFO_CALIBSCALE_SEPARATE) | \ + (1 <<
> IIO_CHAN_INFO_CALIBBIAS_SEPARATE), \ + .scan_type =3D IIO_ST('u', =
(bits),
> 16, 16 - (bits)) \ +} + +static const struct ad5360_chip_info
> ad5360_chip_info_tbl[] =3D { + [ID_AD5360] =3D { + .channe=
l_template =3D
> AD5360_CHANNEL(16), + .num_channels =3D 16, + .channels=
_per_group =3D 8,
> + .num_vrefs =3D 2, + }, + [ID_AD5361] =3D { + =
.channel_template =3D
> AD5360_CHANNEL(14), + .num_channels =3D 16, + .channels=
_per_group =3D 8,
> + .num_vrefs =3D 2, + }, + [ID_AD5362] =3D { + =
.channel_template =3D
> AD5360_CHANNEL(16), + .num_channels =3D 8, + .channels=
_per_group =3D 4,
> + .num_vrefs =3D 2, + }, + [ID_AD5363] =3D { + =
.channel_template =3D
> AD5360_CHANNEL(14), + .num_channels =3D 8, + .channels=
_per_group =3D 4,
> + .num_vrefs =3D 2, + }, + [ID_AD5370] =3D { + =
.channel_template =3D
> AD5360_CHANNEL(16), + .num_channels =3D 40, + .channels=
_per_group =3D 8,
> + .num_vrefs =3D 2, + }, + [ID_AD5371] =3D { + =
.channel_template =3D
> AD5360_CHANNEL(14), + .num_channels =3D 40, + .channels=
_per_group =3D 8,
> + .num_vrefs =3D 3, + }, + [ID_AD5372] =3D { + =
.channel_template =3D
> AD5360_CHANNEL(16), + .num_channels =3D 32, + .channels=
_per_group =3D 8,
> + .num_vrefs =3D 2, + }, + [ID_AD5373] =3D { + =
.channel_template =3D
> AD5360_CHANNEL(14), + .num_channels =3D 32, + .channels=
_per_group =3D 8,
> + .num_vrefs =3D 2, + }, +}; + +static unsigned int
> ad5360_get_channel_vref_index(struct ad5360_state *st, + unsigned in=
t
> channel) +{ + unsigned int i; + + /* The first groups have their own
> vref, while the remaining groups + * share the last vref */ + i =
=3D
> channel / st->chip_info->channels_per_group; + if (i >=3D
> st->chip_info->num_vrefs) + i =3D st->chip_info->num_vrefs - 1;=
+
> + return i; +} + +static int ad5360_get_channel_vref(struct ad5360_st=
ate
> *st, + unsigned int channel) +{ + unsigned int i =3D
> 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 =3D iio_priv(indio_dev); + + val <<=3D shift; + =
val |=3D
> AD5360_CMD(cmd) | AD5360_ADDR(addr); + st->data[0].d32 =3D
> 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 =3D
> ad5360_write_unlocked(indio_dev, cmd, addr, val, shift);
> + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static in=
t
> ad5360_read(struct iio_dev *indio_dev, unsigned int type, + unsigned in=
t
> addr) +{ + struct ad5360_state *st =3D iio_priv(indio_dev); + s=
truct
> spi_message m; + int ret; + struct spi_transfer t[] =3D { + =
{ + .tx_buf
> =3D &st->data[0].d8[1], + .len =3D 3, + =
.cs_change =3D 1, + }, {
> + .rx_buf =3D &st->data[1].d8[1], + .=
len =3D 3, + }, + }; +
> + spi_message_init(&m); + spi_message_add_tail(&t[0], &m);
> + spi_message_add_tail(&t[1], &m); + + mutex_lock(&indio_dev->mloc=
k); +
> + st->data[0].d32 =3D cpu_to_be32(AD5360_CMD(AD5360_CMD_SPECIAL_FUNCT=
ION)
> | + AD5360_ADDR(AD5360_REG_SF_READBACK) | + AD5360_READ=
BACK_TYPE(type)
> | + AD5360_READBACK_ADDR(addr)); + + ret =3D spi_sync(st=
->spi, &m); + if
> (ret >=3D 0) + ret =3D be32_to_cpu(st->data[1].d32) & 0xffff; +
> + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static ss=
ize_t
> ad5360_read_dac_powerdown(struct device *dev, + =
struct
> device_attribute *attr, + char *bu=
f) +{ + struct iio_dev
> *indio_dev =3D dev_get_drvdata(dev); + struct ad5360_state *st =3D
> 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) +{ + str=
uct
> ad5360_state *st =3D iio_priv(indio_dev); + unsigned int ret; +
> + mutex_lock(&indio_dev->mlock); + + st->ctrl |=3D set; + s=
t->ctrl &=3D
> ~clr; + + ret =3D ad5360_write_unlocked(indio_dev,
> AD5360_CMD_SPECIAL_FUNCTION, + AD5360_REG_SF_CTRL,=
st->ctrl, 0); +
> + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static ss=
ize_t
> ad5360_write_dac_powerdown(struct device *dev, + struct device_attri=
bute
> *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev =
=3D
> dev_get_drvdata(dev); + bool pwr_down; + int ret; + + ret=
=3D
> strtobool(buf, &pwr_down); + if (ret) + return ret; + + if =
(pwr_down)
> + ret =3D ad5360_update_ctrl(indio_dev, AD5360_SF_CTRL_PWR_DO=
WN, 0);
> + else + ret =3D 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, + ad5=
360_write_dac_powerdown, 0); +
> +static struct attribute *ad5360_attributes[] =3D {
> + &iio_dev_attr_out_voltage_powerdown.dev_attr.attr, + NULL, +}; +
> +static const struct attribute_group ad5360_attribute_group =3D { + .=
attrs
> =3D 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 =3D iio_priv(indio_dev); + int max_val =3D (1 <<
> chan->scan_type.realbits); + unsigned int ofs_index; + + switch (mas=
k) {
> + case 0: + if (val >=3D max_val || val < 0) + =
return -EINVAL; +
> + return ad5360_write(indio_dev, AD5360_CMD_WRITE_DATA, +
> chan->address, val, + chan->scan_type.shift); + =
case (1 <<
> IIO_CHAN_INFO_CALIBBIAS_SEPARATE): + if (val >=3D max_val || val=
< 0)
> + return -EINVAL; + + return ad5360_write=
(indio_dev,
> AD5360_CMD_WRITE_OFFSET, + chan->address, val=
, +
> chan->scan_type.shift); + case (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARAT=
E):
> + if (val >=3D max_val || val < 0) + r=
eturn -EINVAL; + + return
> ad5360_write(indio_dev, AD5360_CMD_WRITE_GAIN, + =
chan->address, val,
> + chan->scan_type.shift); + case (1 <<
> IIO_CHAN_INFO_OFFSET_SEPARATE): + if (val <=3D -max_val || va=
l > 0)
> + return -EINVAL; + + val =3D -val; + + =
/* offset is supposed to have
> the same scale as raw, but it + * is always 14bits wide, s=
o on a chip
> where the raw value has + * more bits, we need to shift offs=
et. */
> + val >>=3D (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 =3D ad5360_get_channel_vref_index(st, cha=
n-
>> channel);
> + return ad5360_write(indio_dev, AD5360_CMD_SPECIAL_FUNCTION,=
+
> AD5360_REG_SF_OFS(ofs_index), val, 0); + default: + bre=
ak; + } +
> + return -EINVAL; +} + +static int ad5360_read_raw(struct iio_dev
> *indio_dev, + struct iio_chan_spec const *chan, + =
int *val,
> + int *val2, + long m) +{ + str=
uct ad5360_state *st =3D
> iio_priv(indio_dev); + unsigned long scale_uv; + unsigned in=
t ofs_index;
> + int ret; + + switch (m) { + case 0: + ret =3D ad5=
360_read(indio_dev,
> AD5360_READBACK_X1A, + chan->address); + =
if (ret < 0) + return ret;
> + *val =3D ret >> chan->scan_type.shift; + return II=
O_VAL_INT; + case (1
> << IIO_CHAN_INFO_SCALE_SEPARATE): + /* vout =3D 4 * vref * dac_=
code */
> + scale_uv =3D ad5360_get_channel_vref(st, chan->channel) * 4=
* 100;
> + if (scale_uv < 0) + return scale_uv; + =
+ scale_uv >>=3D
> (chan->scan_type.realbits); + *val =3D scale_uv / 100000; + =
*val2 =3D
> (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + =
case (1 <<
> IIO_CHAN_INFO_CALIBBIAS_SEPARATE): + ret =3D ad5360_read(indio_d=
ev,
> AD5360_READBACK_OFFSET, + chan->address); + =
if (ret < 0) + return
> ret; + *val =3D ret; + return IIO_VAL_INT; + c=
ase (1 <<
> IIO_CHAN_INFO_CALIBSCALE_SEPARATE): + ret =3D ad5360_read(indio_d=
ev,
> AD5360_READBACK_GAIN, + chan->address); + =
if (ret < 0) + return
> ret; + *val =3D ret; + return IIO_VAL_INT; + c=
ase (1 <<
> IIO_CHAN_INFO_OFFSET_SEPARATE): + ofs_index =3D
> ad5360_get_channel_vref_index(st, chan-
>> channel);
> + ret =3D ad5360_read(indio_dev, AD5360_READBACK_SF,
> + AD5360_REG_SF_OFS(ofs_index)); + if =
(ret < 0) + return ret; +
> + ret <<=3D (chan->scan_type.realbits - 14); + *=
val =3D -ret; + return
> IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct i=
io_info
> ad5360_info =3D { + .read_raw =3D ad5360_read_raw, + .write_raw =3D
> ad5360_write_raw, + .attrs =3D &ad5360_attribute_group, + .driver_m=
odule =3D
> THIS_MODULE, +}; + +static const char * const ad5360_vref_name[] =3D { +
> "vref0", "vref1", "vref2" +}; + +static int __devinit
> ad5360_alloc_channels(struct iio_dev *indio_dev) +{ + struct
> ad5360_state *st =3D iio_priv(indio_dev); + struct iio_chan_spec
> *channels; + unsigned int i; + + channels =3D kcalloc(sizeof(struct
> iio_chan_spec), + st->chip_info->num_channels, GFP_KE=
RNEL); + + if
> (!channels) + return -ENOMEM; + + for (i =3D 0; i <
> st->chip_info->num_channels; ++i) { + channels[i] =3D
> st->chip_info->channel_template; + channels[i].channel =3D i;
> + channels[i].address =3D AD5360_CHAN_ADDR(i); + } +
> + indio_dev->channels =3D channels; + + return 0; +} + +static in=
t
> __devinit ad5360_probe(struct spi_device *spi) +{ + enum ad5360_type
> type =3D spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev=
;
> + struct ad5360_state *st; + unsigned int i; + int ret; + =
+ indio_dev =3D
> iio_allocate_device(sizeof(*st)); + if (indio_dev =3D=3D NULL) {
> + dev_err(&spi->dev, "Failed to allocate iio device\n"); + =
return
> -ENOMEM; + } + + st =3D iio_priv(indio_dev); + spi_set_drvdata(s=
pi,
> indio_dev); + + st->chip_info =3D &ad5360_chip_info_tbl[type]; + s=
t->spi =3D
> spi; + + indio_dev->dev.parent =3D &spi->dev; + indio_dev->name =
=3D
> spi_get_device_id(spi)->name; + indio_dev->info =3D &ad5360_info;
> + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->num_channels =
=3D
> st->chip_info->num_channels; + + ret =3D ad5360_alloc_channels(indio=
_dev);
> + if (ret) { + dev_err(&spi->dev, "Failed to allocate chan=
nel spec:
> %d\n", ret); + goto error_free; + } + + for (i =3D =
0; i <
> st->chip_info->num_vrefs; ++i) + st->vref_reg[i].supply =3D
> ad5360_vref_name[i]; + + ret =3D 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 =3D
> regulator_bulk_enable(st->chip_info->num_vrefs, st-
>> vref_reg);
+ if (ret) { + dev_err(&spi->dev, "Failed to enable vref r=
egulators:
%d\n", ret); + goto error_free_reg; + } + + ret =3D
iio_device_register(indio_dev); + if (ret) { + dev_err(&sp=
i->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_free_device(indio_dev); + + return ret; +} + +static
int __devexit ad5360_remove(struct spi_device *spi) +{ + struct iio_=
dev
*indio_dev =3D spi_get_drvdata(spi); + struct ad5360_state *st =3D
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_free_device(indio_dev); + + return 0; +} + +static const struct
spi_device_id ad5360_ids[] =3D { + { "ad5360", ID_AD5360 }, + {=
"ad5361",
ID_AD5361 }, + { "ad5362", ID_AD5362 }, + { "ad5363", ID_AD5363 }, + =
{
"ad5370", ID_AD5370 }, + { "ad5371", ID_AD5371 }, + { "ad5372",=
ID_AD5372
}, + { "ad5373", ID_AD5373 }, + {} +}; + +static struct spi_driver
ad5360_driver =3D { + .driver =3D { + .name =3D "ad5360", =
+ .owner =3D
THIS_MODULE, + }, + .probe =3D ad5360_probe, + .remove =3D
__devexit_p(ad5360_remove), + .id_table =3D ad5360_ids, +}; + +static
__init int ad5360_spi_init(void) +{ + return
spi_register_driver(&ad5360_driver); +} +module_init(ad5360_spi_init); +
+static __exit void ad5360_spi_exit(void) +{
+ spi_unregister_driver(&ad5360_driver); +}
+module_exit(ad5360_spi_exit); + +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");
Greetings,
Michael
--
Analog Devices GmbH Wilhelm-Wagenfeld-Str. 6 80807 Muenchen
Sitz der Gesellschaft: Muenchen; Registergericht: Muenchen HRB 40368;
Geschaeftsfuehrer:Dr.Carsten Suckrow, Thomas Wessel, William A. Martin, Mar=
garet Seif
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] staging:iio:dac: Add AD5360 driver
2011-10-17 11:19 [PATCH v2] staging:iio:dac: Add AD5360 driver Lars-Peter Clausen
2011-10-17 14:12 ` Hennerich, Michael
@ 2011-10-17 14:15 ` Jonathan Cameron
1 sibling, 0 replies; 3+ messages in thread
From: Jonathan Cameron @ 2011-10-17 14:15 UTC (permalink / raw)
To: Lars-Peter Clausen
Cc: Michael Hennerich, linux-iio, drivers, device-drivers-devel
On 10/17/11 12:19, Lars-Peter Clausen wrote:
> This patch adds support for the Analog Devices AD5360, AD5361, AD5362, AD5363,
> AD5370, AD5371, AD5372, AD5373 multi-channel digital-to-analog converters.
>
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Acked-by: Jonathan Cameron <jic23@cam.ac.uk>
Thanks,
>
> ---
> Changes since v1:
> * Minor code cleanups
> * List full part names in Kconfig
> ---
> drivers/staging/iio/dac/Kconfig | 11 +
> drivers/staging/iio/dac/Makefile | 1 +
> drivers/staging/iio/dac/ad5360.c | 580 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 592 insertions(+), 0 deletions(-)
> create mode 100644 drivers/staging/iio/dac/ad5360.c
>
> diff --git a/drivers/staging/iio/dac/Kconfig b/drivers/staging/iio/dac/Kconfig
> index 3000156..9211a39 100644
> --- a/drivers/staging/iio/dac/Kconfig
> +++ b/drivers/staging/iio/dac/Kconfig
> @@ -3,6 +3,17 @@
> #
> menu "Digital to analog convertors"
>
> +config AD5360
> + tristate "Analog Devices Analog Devices AD5360/61/62/63/70/71/73 DAC driver"
> + depends on SPI
> + help
> + Say yes here to build support for Analog Devices AD5360, AD5361,
> + AD5362, AD5363, AD5370, AD5371, AD5373 multi-channel
> + Digital to Analog Converters (DAC).
> +
> + To compile this driver as module choose M here: the module will be called
> + ad5360.
> +
> config AD5624R_SPI
> tristate "Analog Devices AD5624/44/64R DAC spi driver"
> depends on SPI
> diff --git a/drivers/staging/iio/dac/Makefile b/drivers/staging/iio/dac/Makefile
> index 7f4f2ed..e0a8c97 100644
> --- a/drivers/staging/iio/dac/Makefile
> +++ b/drivers/staging/iio/dac/Makefile
> @@ -2,6 +2,7 @@
> # Makefile for industrial I/O DAC drivers
> #
>
> +obj-$(CONFIG_AD5360) += ad5360.o
> obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o
> obj-$(CONFIG_AD5504) += ad5504.o
> obj-$(CONFIG_AD5446) += ad5446.o
> diff --git a/drivers/staging/iio/dac/ad5360.c b/drivers/staging/iio/dac/ad5360.c
> new file mode 100644
> index 0000000..fdbfb48
> --- /dev/null
> +++ b/drivers/staging/iio/dac/ad5360.c
> @@ -0,0 +1,580 @@
> +/*
> + * 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 "../iio.h"
> +#include "../sysfs.h"
> +#include "dac.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 = (1 << IIO_CHAN_INFO_SCALE_SEPARATE) | \
> + (1 << IIO_CHAN_INFO_OFFSET_SEPARATE) | \
> + (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE) | \
> + (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE), \
> + .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_get_drvdata(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_get_drvdata(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 0:
> + 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 (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE):
> + 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 (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE):
> + 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 (1 << IIO_CHAN_INFO_OFFSET_SEPARATE):
> + 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 long scale_uv;
> + unsigned int ofs_index;
> + int ret;
> +
> + switch (m) {
> + case 0:
> + 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 (1 << IIO_CHAN_INFO_SCALE_SEPARATE):
> + /* 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 (1 << IIO_CHAN_INFO_CALIBBIAS_SEPARATE):
> + ret = ad5360_read(indio_dev, AD5360_READBACK_OFFSET,
> + chan->address);
> + if (ret < 0)
> + return ret;
> + *val = ret;
> + return IIO_VAL_INT;
> + case (1 << IIO_CHAN_INFO_CALIBSCALE_SEPARATE):
> + ret = ad5360_read(indio_dev, AD5360_READBACK_GAIN,
> + chan->address);
> + if (ret < 0)
> + return ret;
> + *val = ret;
> + return IIO_VAL_INT;
> + case (1 << IIO_CHAN_INFO_OFFSET_SEPARATE):
> + 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(sizeof(struct iio_chan_spec),
> + st->chip_info->num_channels, 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_allocate_device(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_free_device(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_free_device(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 },
> + {}
> +};
> +
> +static struct spi_driver ad5360_driver = {
> + .driver = {
> + .name = "ad5360",
> + .owner = THIS_MODULE,
> + },
> + .probe = ad5360_probe,
> + .remove = __devexit_p(ad5360_remove),
> + .id_table = ad5360_ids,
> +};
> +
> +static __init int ad5360_spi_init(void)
> +{
> + return spi_register_driver(&ad5360_driver);
> +}
> +module_init(ad5360_spi_init);
> +
> +static __exit void ad5360_spi_exit(void)
> +{
> + spi_unregister_driver(&ad5360_driver);
> +}
> +module_exit(ad5360_spi_exit);
> +
> +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");
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2011-10-17 14:15 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-10-17 11:19 [PATCH v2] staging:iio:dac: Add AD5360 driver Lars-Peter Clausen
2011-10-17 14:12 ` Hennerich, Michael
2011-10-17 14:15 ` Jonathan Cameron
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).