RE: [PATCH v2] staging:iio:dac: Add AD5360 driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxx>

Looks good!
Acked-by: Michael Hennerich <michael.hennerich@xxxxxxxxxx>

>
> ---
> 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@xxxxxxxxxx>"); +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, Margaret Seif


--
To unsubscribe from this list: send the line "unsubscribe linux-iio" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux