Re: [PATCH 3/3] iio:dac:ad5686: Add AD5671R/75R/94/94R/95R/96/96R support

From: Jonathan Cameron
Date: Fri Apr 06 2018 - 11:33:26 EST


On Fri, 30 Mar 2018 16:56:49 +0300
Stefan Popa <stefan.popa@xxxxxxxxxx> wrote:

> The AD5694/AD5694R/AD5695R/AD5696/AD5696R are a family of 4 channel DAC
> s with 12-bit, 14-bit and 16-bit precision respectively. The devices have
> either no built-in reference, or built-in 2.5V reference.
>
> The AD5671R/AD5675R are similar, except that they have 8 instead of 4
> channels.
>
> These devices are similar to AD5672R/AD5676/AD5676R and
> AD5684/AD5684R/AD5684/AD5685R/AD5686/AD5686R, except that they use i2c
> instead of spi. In order to add support in the previously existing driver,
> three modules were created. One baseline module that has all the
> chip-logic and then one module for SPI and one for I2C.
>
> Datasheets:
> http://www.analog.com/media/en/technical-documentation/data-sheets/AD5671R_5675R.pdf
> http://www.analog.com/media/en/technical-documentation/data-sheets/AD5696R_5695R_5694R.pdf
>
> Signed-off-by: Stefan Popa <stefan.popa@xxxxxxxxxx>
Should be multiple patches. First refactor then follow up with the new stuff.

Comments inline.

> ---
> MAINTAINERS | 8 ++
> drivers/iio/dac/Kconfig | 27 ++++--
> drivers/iio/dac/ad5686-spi.c | 93 ++++++++++++++++++
> drivers/iio/dac/ad5686.c | 222 +++++++++++++------------------------------
> drivers/iio/dac/ad5686.h | 114 ++++++++++++++++++++++
> drivers/iio/dac/ad5696-i2c.c | 98 +++++++++++++++++++
> 6 files changed, 400 insertions(+), 162 deletions(-)
> create mode 100644 drivers/iio/dac/ad5686-spi.c
> create mode 100644 drivers/iio/dac/ad5686.h
> create mode 100644 drivers/iio/dac/ad5696-i2c.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 473ac00..002cb01 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -791,6 +791,14 @@ M: Michael Hanselmann <linux-kernel@xxxxxxxxx>
> S: Supported
> F: drivers/macintosh/ams/
>
> +ANALOG DEVICES INC AD5686 DRIVER
> +M: Stefan Popa <stefan.popa@xxxxxxxxxx>
> +L: linux-pm@xxxxxxxxxxxxxxx
> +W: http://ez.analog.com/community/linux-device-drivers
> +S: Supported
> +F: drivers/iio/dac/ad5686*
> +F: drivers/iio/dac/ad5696*
> +
> ANALOG DEVICES INC AD9389B DRIVER
> M: Hans Verkuil <hans.verkuil@xxxxxxxxx>
> L: linux-media@xxxxxxxxxxxxxxx
> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
> index 965d5c0..dab0b8a 100644
> --- a/drivers/iio/dac/Kconfig
> +++ b/drivers/iio/dac/Kconfig
> @@ -131,15 +131,30 @@ config LTC2632
> module will be called ltc2632.
>
> config AD5686
> - tristate "Analog Devices AD5686R/AD5685R/AD5684R DAC SPI driver"
> + tristate
> +
> +config AD5686_SPI
> + tristate "Analog Devices AD5686 and similar multi-channel DACs (SPI)"
> depends on SPI
> + select AD5686
> help
> - Say yes here to build support for Analog Devices AD5686R, AD5685R,
> - AD5684R, AD5791 Voltage Output Digital to
> - Analog Converter.
> + Say yes here to build support for Analog Devices AD5672R, AD5676,
> + AD5676R, AD5684, AD5684R, AD5684R, AD5685R, AD5686, AD5686R.
> + Voltage Output Digital to Analog Converter.
>
> - To compile this driver as a module, choose M here: the
> - module will be called ad5686.
> + To compile this driver as a module, choose M here: the
> + module will be called ad5686.
> +
> +config AD5696_I2C
> + tristate "Analog Devices AD5696 and similar multi-channel DACs (I2C)"
> + depends on I2C
> + select AD5686
> + help
> + Say yes here to build support for Analog Devices AD5671R, AD5675R,
> + AD5694, AD5694R, AD5695R, AD5696, AD5696R Voltage Output Digital to
> + Analog Converter.
> + To compile this driver as a module, choose M here: the module will be
> + called ad5696.
>
> config AD5755
> tristate "Analog Devices AD5755/AD5755-1/AD5757/AD5735/AD5737 DAC driver"
> diff --git a/drivers/iio/dac/ad5686-spi.c b/drivers/iio/dac/ad5686-spi.c
> new file mode 100644
> index 0000000..d68579b
> --- /dev/null
> +++ b/drivers/iio/dac/ad5686-spi.c
> @@ -0,0 +1,93 @@
> +/*
> + * AD5672R, AD5676, AD5676R, AD5684, AD5684R, AD5684R, AD5685R, AD5686, AD5686R
> + * Digital to analog converters driver
> + *
> + * Copyright 2018 Analog Devices Inc.
> + *
> + * Licensed under the GPL-2.
> + */
> +
> +#include "ad5686.h"
> +
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +
> +static int ad5686_spi_write(struct ad5686_state *st,
> + u8 cmd, u8 addr, u16 val)
> +{
> + struct spi_device *spi = to_spi_device(st->dev);
> +
> + st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
> + AD5686_ADDR(addr) |
> + val);
> +
> + return spi_write(spi, &st->data[0].d8[1], 3);
> +}
> +
> +static int ad5686_spi_read(struct ad5686_state *st, u8 addr)
> +{
> + struct spi_transfer t[] = {
> + {
> + .tx_buf = &st->data[0].d8[1],
> + .len = 3,
> + .cs_change = 1,
> + }, {
> + .tx_buf = &st->data[1].d8[1],
> + .rx_buf = &st->data[2].d8[1],
> + .len = 3,
> + },
> + };
> + struct spi_device *spi = to_spi_device(st->dev);
> + int ret;
> +
> + st->data[0].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_READBACK_ENABLE) |
> + AD5686_ADDR(addr));
> + st->data[1].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_NOOP));
> +
> + ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t));
> + if (ret < 0)
> + return ret;
> +
> + return be32_to_cpu(st->data[2].d32);
> +}
> +
> +static int ad5686_spi_probe(struct spi_device *spi)
> +{
> + const struct spi_device_id *id = spi_get_device_id(spi);
> +
> + return ad5686_probe(&spi->dev, id->driver_data, id->name,
> + ad5686_spi_write, ad5686_spi_read);
> +}
> +
> +static int ad5686_spi_remove(struct spi_device *spi)
> +{
> + return ad5686_remove(&spi->dev);
> +}
> +
> +static const struct spi_device_id ad5686_spi_id[] = {
> + {"ad5672r", ID_AD5672R},
> + {"ad5676", ID_AD5676},
> + {"ad5676r", ID_AD5676R},
> + {"ad5684", ID_AD5684},
> + {"ad5684r", ID_AD5684R},
> + {"ad5685r", ID_AD5685R},
> + {"ad5686", ID_AD5686},
> + {"ad5686r", ID_AD5686R},
> + {}
> +};
> +MODULE_DEVICE_TABLE(spi, ad5686_spi_id);
> +
> +static struct spi_driver ad5686_spi_driver = {
> + .driver = {
> + .name = "ad5686",
> + },
> + .probe = ad5686_spi_probe,
> + .remove = ad5686_spi_remove,
> + .id_table = ad5686_spi_id,
> +};
> +
> +module_spi_driver(ad5686_spi_driver);
> +
> +MODULE_AUTHOR("Stefan Popa <stefan.popa@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("Analog Devices AD5686 and similar multi-channel DACs");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
> index 5fb0179..e4ea0dc 100644
> --- a/drivers/iio/dac/ad5686.c
> +++ b/drivers/iio/dac/ad5686.c
> @@ -11,7 +11,6 @@
> #include <linux/device.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>
> @@ -19,128 +18,28 @@
> #include <linux/iio/iio.h>
> #include <linux/iio/sysfs.h>
>
> -#define AD5686_ADDR(x) ((x) << 16)
> -#define AD5686_CMD(x) ((x) << 20)
> -
> -#define AD5686_ADDR_DAC(chan) (0x1 << (chan))
> -#define AD5686_ADDR_ALL_DAC 0xF
> -
> -#define AD5686_CMD_NOOP 0x0
> -#define AD5686_CMD_WRITE_INPUT_N 0x1
> -#define AD5686_CMD_UPDATE_DAC_N 0x2
> -#define AD5686_CMD_WRITE_INPUT_N_UPDATE_N 0x3
> -#define AD5686_CMD_POWERDOWN_DAC 0x4
> -#define AD5686_CMD_LDAC_MASK 0x5
> -#define AD5686_CMD_RESET 0x6
> -#define AD5686_CMD_INTERNAL_REFER_SETUP 0x7
> -#define AD5686_CMD_DAISY_CHAIN_ENABLE 0x8
> -#define AD5686_CMD_READBACK_ENABLE 0x9
> -
> -#define AD5686_LDAC_PWRDN_NONE 0x0
> -#define AD5686_LDAC_PWRDN_1K 0x1
> -#define AD5686_LDAC_PWRDN_100K 0x2
> -#define AD5686_LDAC_PWRDN_3STATE 0x3
> -
> -/**
> - * struct ad5686_chip_info - chip specific information
> - * @int_vref_mv: AD5620/40/60: the internal reference voltage
> - * @num_channels: number of channels
> - * @channel: channel specification
> -*/
> -
> -struct ad5686_chip_info {
> - u16 int_vref_mv;
> - unsigned int num_channels;
> - struct iio_chan_spec *channels;
> -};
> -
> -/**
> - * struct ad5446_state - driver instance specific data
> - * @spi: spi_device
> - * @chip_info: chip model specific constants, available modes etc
> - * @reg: supply regulator
> - * @vref_mv: actual reference voltage used
> - * @pwr_down_mask: power down mask
> - * @pwr_down_mode: current power down mode
> - * @data: spi transfer buffers
> - */
> +#include "ad5686.h"
>
> -struct ad5686_state {
> - struct spi_device *spi;
> - const struct ad5686_chip_info *chip_info;
> - struct regulator *reg;
> - unsigned short vref_mv;
> - unsigned pwr_down_mask;
> - unsigned pwr_down_mode;
> - /*
> - * DMA (thus cache coherency maintenance) requires the
> - * transfer buffers to live in their own cache lines.
> - */
> -
> - union {
> - __be32 d32;
> - u8 d8[4];
> - } data[3] ____cacheline_aligned;
> +static const char * const ad5686_powerdown_modes[] = {
> + "1kohm_to_gnd",
> + "100kohm_to_gnd",
> + "three_state"
> };
>
> -/**
> - * ad5686_supported_device_ids:
> - */
> -
> -enum ad5686_supported_device_ids {
> - ID_AD5672R,
> - ID_AD5676,
> - ID_AD5676R,
> - ID_AD5684,
> - ID_AD5684R,
> - ID_AD5685R,
> - ID_AD5686,
> - ID_AD5686R
> -};
> -static int ad5686_spi_write(struct ad5686_state *st,
> - u8 cmd, u8 addr, u16 val, u8 shift)
> +static int ad5686_write(struct ad5686_state *st,
> + u8 cmd, u8 addr, u16 val, u8 shift)
> {
> val <<= shift;

Given the shift is only applied in one location in the code (and that
one is pretty obvious), I would push the shift inline then
drop the wrapper around st->write in favour of calling
st->write directly.

>
> - st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
> - AD5686_ADDR(addr) |
> - val);
> -
> - return spi_write(st->spi, &st->data[0].d8[1], 3);
> + return st->write(st, cmd, addr, val);
> }
>
> -static int ad5686_spi_read(struct ad5686_state *st, u8 addr)
> +static int ad5686_read(struct ad5686_state *st, u8 addr)
> {
> - struct spi_transfer t[] = {
> - {
> - .tx_buf = &st->data[0].d8[1],
> - .len = 3,
> - .cs_change = 1,
> - }, {
> - .tx_buf = &st->data[1].d8[1],
> - .rx_buf = &st->data[2].d8[1],
> - .len = 3,
> - },
> - };
> - int ret;
>
> - st->data[0].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_READBACK_ENABLE) |
> - AD5686_ADDR(addr));
> - st->data[1].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_NOOP));
> -
> - ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t));
> - if (ret < 0)
> - return ret;
> -
> - return be32_to_cpu(st->data[2].d32);
This wrapper adds very little, just call st->read directly.

> + return st->read(st, addr);
> }
>
> -static const char * const ad5686_powerdown_modes[] = {
> - "1kohm_to_gnd",
> - "100kohm_to_gnd",
> - "three_state"
> -};
> -
> static int ad5686_get_powerdown_mode(struct iio_dev *indio_dev,
> const struct iio_chan_spec *chan)
> {
> @@ -196,8 +95,8 @@ static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev,
> else
> st->pwr_down_mask &= ~(0x3 << (chan->channel * 2));
>
> - ret = ad5686_spi_write(st, AD5686_CMD_POWERDOWN_DAC, 0,
> - st->pwr_down_mask & st->pwr_down_mode, 0);
> + ret = ad5686_write(st, AD5686_CMD_POWERDOWN_DAC, 0,
> + st->pwr_down_mask & st->pwr_down_mode, 0);
>
> return ret ? ret : len;
> }
> @@ -214,7 +113,7 @@ static int ad5686_read_raw(struct iio_dev *indio_dev,
> switch (m) {
> case IIO_CHAN_INFO_RAW:
> mutex_lock(&indio_dev->mlock);
> - ret = ad5686_spi_read(st, chan->address);
> + ret = ad5686_read(st, chan->address);
> mutex_unlock(&indio_dev->mlock);
> if (ret < 0)
> return ret;
> @@ -243,11 +142,11 @@ static int ad5686_write_raw(struct iio_dev *indio_dev,
> return -EINVAL;
>
> mutex_lock(&indio_dev->mlock);
> - ret = ad5686_spi_write(st,
> - AD5686_CMD_WRITE_INPUT_N_UPDATE_N,
> - chan->address,
> - val,
> - chan->scan_type.shift);
> + ret = ad5686_write(st,
> + AD5686_CMD_WRITE_INPUT_N_UPDATE_N,
> + chan->address,
> + val,
> + chan->scan_type.shift);
> mutex_unlock(&indio_dev->mlock);
> break;
> default:
> @@ -318,11 +217,21 @@ DECLARE_AD5686_CHANNELS(ad5685r_channels, 14, 2);
> DECLARE_AD5686_CHANNELS(ad5686_channels, 16, 0);
>
> static const struct ad5686_chip_info ad5686_chip_info_tbl[] = {
> + [ID_AD5671R] = {
> + .channels = ad5672_channels,
> + .int_vref_mv = 2500,
> + .num_channels = 8,
> + },
> [ID_AD5672R] = {
> .channels = ad5672_channels,
> .int_vref_mv = 2500,
> .num_channels = 8,
> },
> + [ID_AD5675R] = {
> + .channels = ad5676_channels,
> + .int_vref_mv = 2500,
> + .num_channels = 8,
> + },
> [ID_AD5676] = {
> .channels = ad5676_channels,
> .num_channels = 8,
> @@ -355,22 +264,47 @@ static const struct ad5686_chip_info ad5686_chip_info_tbl[] = {
> .int_vref_mv = 2500,
> .num_channels = 4,
> },
> + [ID_AD5694] = {
> + .channels = ad5684_channels,
> + .num_channels = 4,
> + },
> + [ID_AD5694R] = {
> + .channels = ad5684_channels,
> + .int_vref_mv = 2500,
> + .num_channels = 4,
> + },
> + [ID_AD5696] = {
> + .channels = ad5686_channels,
> + .num_channels = 4,
> + },
> + [ID_AD5696R] = {
> + .channels = ad5686_channels,
> + .int_vref_mv = 2500,
> + .num_channels = 4,
> + },
> };
>
> -static int ad5686_probe(struct spi_device *spi)
> +int ad5686_probe(struct device *dev,
> + enum ad5686_supported_device_ids chip_type,
> + const char *name, ad5686_write_func write,
> + ad5686_read_func read)
> {
> struct ad5686_state *st;
> struct iio_dev *indio_dev;
> int ret, voltage_uv = 0;
>
> - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> if (indio_dev == NULL)
> return -ENOMEM;
>
> st = iio_priv(indio_dev);
> - spi_set_drvdata(spi, indio_dev);
> + dev_set_drvdata(dev, indio_dev);
> +
> + st->dev = dev;
> + st->write = write;
> + st->read = read;
>
> - st->reg = devm_regulator_get_optional(&spi->dev, "vcc");
> + st->reg = devm_regulator_get_optional(dev, "vcc");
> if (!IS_ERR(st->reg)) {
> ret = regulator_enable(st->reg);
> if (ret)
> @@ -383,28 +317,25 @@ static int ad5686_probe(struct spi_device *spi)
> voltage_uv = ret;
> }
>
> - st->chip_info =
> - &ad5686_chip_info_tbl[spi_get_device_id(spi)->driver_data];
> + st->chip_info = &ad5686_chip_info_tbl[chip_type];
>
> if (voltage_uv)
> st->vref_mv = voltage_uv / 1000;
> else
> st->vref_mv = st->chip_info->int_vref_mv;
>
> - st->spi = spi;
> -
> /* Set all the power down mode for all channels to 1K pulldown */
> st->pwr_down_mode = 0x55;
>
> - indio_dev->dev.parent = &spi->dev;
> - indio_dev->name = spi_get_device_id(spi)->name;
> + indio_dev->dev.parent = dev;
> + indio_dev->name = name;
> indio_dev->info = &ad5686_info;
> indio_dev->modes = INDIO_DIRECT_MODE;
> indio_dev->channels = st->chip_info->channels;
> indio_dev->num_channels = st->chip_info->num_channels;
>
> - ret = ad5686_spi_write(st, AD5686_CMD_INTERNAL_REFER_SETUP, 0,
> - !!voltage_uv, 0);
> + ret = ad5686_write(st, AD5686_CMD_INTERNAL_REFER_SETUP,
> + 0, !!voltage_uv, 0);
> if (ret)
> goto error_disable_reg;
>
> @@ -419,10 +350,11 @@ static int ad5686_probe(struct spi_device *spi)
> regulator_disable(st->reg);
> return ret;
> }
> +EXPORT_SYMBOL_GPL(ad5686_probe);
>
> -static int ad5686_remove(struct spi_device *spi)
> +int ad5686_remove(struct device *dev)
> {
> - struct iio_dev *indio_dev = spi_get_drvdata(spi);
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> struct ad5686_state *st = iio_priv(indio_dev);
>
> iio_device_unregister(indio_dev);
> @@ -431,29 +363,7 @@ static int ad5686_remove(struct spi_device *spi)
>
> return 0;
> }
> -
> -static const struct spi_device_id ad5686_id[] = {
> - {"ad5672r", ID_AD5672R},
> - {"ad5676", ID_AD5676},
> - {"ad5676r", ID_AD5676R},
> - {"ad5684", ID_AD5684},
> - {"ad5684r", ID_AD5684R},
> - {"ad5685r", ID_AD5685R},
> - {"ad5686", ID_AD5686},
> - {"ad5686r", ID_AD5686R},
> - {}
> -};
> -MODULE_DEVICE_TABLE(spi, ad5686_id);
> -
> -static struct spi_driver ad5686_driver = {
> - .driver = {
> - .name = "ad5686",
> - },
> - .probe = ad5686_probe,
> - .remove = ad5686_remove,
> - .id_table = ad5686_id,
> -};
> -module_spi_driver(ad5686_driver);
> +EXPORT_SYMBOL_GPL(ad5686_remove);
>
> MODULE_AUTHOR("Michael Hennerich <hennerich@xxxxxxxxxxxxxxxxxxxx>");
> MODULE_DESCRIPTION("Analog Devices AD5686/85/84 DAC");
> diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h
> new file mode 100644
> index 0000000..b2b0110
> --- /dev/null
> +++ b/drivers/iio/dac/ad5686.h
> @@ -0,0 +1,114 @@
> +#ifndef __DRIVERS_IIO_DAC_AD5686_H__
> +#define __DRIVERS_IIO_DAC_AD5686_H__
> +
> +#include <linux/types.h>
> +#include <linux/cache.h>
> +#include <linux/mutex.h>
> +#include <linux/kernel.h>
> +
> +#define AD5686_ADDR(x) ((x) << 16)
> +#define AD5686_CMD(x) ((x) << 20)
> +
> +#define AD5686_ADDR_DAC(chan) (0x1 << (chan))
> +#define AD5686_ADDR_ALL_DAC 0xF
> +
> +#define AD5686_CMD_NOOP 0x0
> +#define AD5686_CMD_WRITE_INPUT_N 0x1
> +#define AD5686_CMD_UPDATE_DAC_N 0x2
> +#define AD5686_CMD_WRITE_INPUT_N_UPDATE_N 0x3
> +#define AD5686_CMD_POWERDOWN_DAC 0x4
> +#define AD5686_CMD_LDAC_MASK 0x5
> +#define AD5686_CMD_RESET 0x6
> +#define AD5686_CMD_INTERNAL_REFER_SETUP 0x7
> +#define AD5686_CMD_DAISY_CHAIN_ENABLE 0x8
> +#define AD5686_CMD_READBACK_ENABLE 0x9
> +
> +#define AD5686_LDAC_PWRDN_NONE 0x0
> +#define AD5686_LDAC_PWRDN_1K 0x1
> +#define AD5686_LDAC_PWRDN_100K 0x2
> +#define AD5686_LDAC_PWRDN_3STATE 0x3
> +
> +/**
> + * ad5686_supported_device_ids:
> + */
> +enum ad5686_supported_device_ids {
> + ID_AD5671R,
> + ID_AD5672R,
> + ID_AD5675R,
> + ID_AD5676,
> + ID_AD5676R,
> + ID_AD5684,
> + ID_AD5684R,
> + ID_AD5685R,
> + ID_AD5686,
> + ID_AD5686R,
> + ID_AD5694,
> + ID_AD5694R,
> + ID_AD5695R,
> + ID_AD5696,
> + ID_AD5696R,
> +};
> +
> +struct ad5686_state;
> +
> +typedef int (*ad5686_write_func)(struct ad5686_state *st,
> + u8 cmd, u8 addr, u16 val);
> +
> +typedef int (*ad5686_read_func)(struct ad5686_state *st, u8 addr);
> +
> +/**
> + * struct ad5686_chip_info - chip specific information
> + * @int_vref_mv: AD5620/40/60: the internal reference voltage
> + * @num_channels: number of channels
> + * @channel: channel specification
> + */
> +
> +struct ad5686_chip_info {
> + u16 int_vref_mv;
> + unsigned int num_channels;
> + struct iio_chan_spec *channels;
> +};
> +
> +/**
> + * struct ad5446_state - driver instance specific data
> + * @spi: spi_device
> + * @chip_info: chip model specific constants, available modes etc
> + * @reg: supply regulator
> + * @vref_mv: actual reference voltage used
> + * @pwr_down_mask: power down mask
> + * @pwr_down_mode: current power down mode
> + * @data: spi transfer buffers
> + */
> +
> +struct ad5686_state {
> + struct device *dev;
> + const struct ad5686_chip_info *chip_info;
> + struct regulator *reg;
> + unsigned short vref_mv;
> + unsigned int pwr_down_mask;
> + unsigned int pwr_down_mode;
> + ad5686_write_func write;
> + ad5686_read_func read;
> +
> + /*
> + * DMA (thus cache coherency maintenance) requires the
> + * transfer buffers to live in their own cache lines.
> + */
> +
> + union {
> + __be32 d32;
> + __be16 d16;
> + u8 d8[4];
> + } data[3] ____cacheline_aligned;
> +};
> +
> +
> +int ad5686_probe(struct device *dev,
> + enum ad5686_supported_device_ids chip_type,
> + const char *name, ad5686_write_func write,
> + ad5686_read_func read);
> +
> +int ad5686_remove(struct device *dev);
> +
> +
> +#endif /* __DRIVERS_IIO_DAC_AD5686_H__ */
> diff --git a/drivers/iio/dac/ad5696-i2c.c b/drivers/iio/dac/ad5696-i2c.c
> new file mode 100644
> index 0000000..c9187de
> --- /dev/null
> +++ b/drivers/iio/dac/ad5696-i2c.c
> @@ -0,0 +1,98 @@
> +/*
> + * AD5671R, AD5675R, AD5694, AD5694R, AD5695R, AD5696, AD5696R
> + * Digital to analog converters driver
> + *
> + * Copyright 2018 Analog Devices Inc.
> + *
> + * Licensed under the GPL-2.
> + */
> +
> +#include "ad5686.h"
> +
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +
> +static int ad5686_i2c_read(struct ad5686_state *st, u8 addr)
> +{
> + struct i2c_client *i2c = to_i2c_client(st->dev);
> + struct i2c_msg msg[2] = {
> + {
> + .addr = i2c->addr,
> + .flags = i2c->flags,
> + .len = 3,
> + .buf = &st->data[0].d8[1],
> + },
> + {
> + .addr = i2c->addr,
> + .flags = i2c->flags | I2C_M_RD,
> + .len = 2,
> + .buf = (char *)&st->data[0].d16,
> + },
> + };
> + int ret;
> +
> + st->data[0].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_NOOP) |
> + AD5686_ADDR(addr) |
> + 0x00);
> +
> + ret = i2c_transfer(i2c->adapter, msg, 2);
> + if (ret < 0)
> + return ret;
> +
> + return be16_to_cpu(st->data[0].d16);
> +}
> +
> +static int ad5686_i2c_write(struct ad5686_state *st,
> + u8 cmd, u8 addr, u16 val)
> +{
> + struct i2c_client *i2c = to_i2c_client(st->dev);
> + int ret;
> +
> + st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) | AD5686_ADDR(addr)
> + | val);
> +
> + ret = i2c_master_send(i2c, &st->data[0].d8[1], 3);
> + if (ret < 0)
> + return ret;
> +
> + return (ret != 3) ? -EIO : 0;
> +}
> +
> +static int ad5686_i2c_probe(struct i2c_client *i2c,
> + const struct i2c_device_id *id)
> +{
> + return ad5686_probe(&i2c->dev, id->driver_data, id->name,
> + ad5686_i2c_write, ad5686_i2c_read);
> +}
> +
> +static int ad5686_i2c_remove(struct i2c_client *i2c)
> +{
> + return ad5686_remove(&i2c->dev);
> +}
> +
> +static const struct i2c_device_id ad5686_i2c_id[] = {
> + {"ad5671r", ID_AD5671R},
> + {"ad5675r", ID_AD5675R},
> + {"ad5694", ID_AD5694},
> + {"ad5694r", ID_AD5694R},
> + {"ad5695r", ID_AD5695R},
> + {"ad5696", ID_AD5696},
> + {"ad5696r", ID_AD5696R},
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, i2c_device_id);
> +
> +static struct i2c_driver ad5686_i2c_driver = {
> + .driver = {
> + .name = "ad5696",
> + },
> + .probe = ad5686_i2c_probe,
> + .remove = ad5686_i2c_remove,
> + .id_table = ad5686_i2c_id,
> +};
> +
> +module_i2c_driver(ad5686_i2c_driver);
> +
> +MODULE_AUTHOR("Stefan Popa <stefan.popa@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("Analog Devices AD5686 and similar multi-channel DACs");
> +MODULE_LICENSE("GPL v2");