summaryrefslogtreecommitdiff
path: root/drivers/iio/dac
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-05-11 09:50:04 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-05-11 09:50:04 +0200
commit6a8b25abf1b79db6877645335c73ad6a5061d9b0 (patch)
tree5c18fa75ece496f0b4b77483ad2645e1fbdde29d /drivers/iio/dac
parent26a5e9b834a647b9d4dfbeef8b42f201cb3293ae (diff)
parentd58c67d1d851d40b0094b6dd8b1a27ac9076ffa2 (diff)
downloadlinux-6a8b25abf1b79db6877645335c73ad6a5061d9b0.tar.gz
Merge tag 'iio-for-4.18a' of git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio into staging-next
Jonathan writes: 1st round of IIO new device support, features and cleanup for the 4.18 cycle A nice mix this time of excellent cleanups (many to send drivers speeding toward staging graduations) and new drivers / device support. A good part of this is Brian Masney's never ending task on the tsl2x7x driver. The end is in sight so hopefully we'll get that one out of staging very soon! New device support * AD5686 - Support AD5685R (was wrongly present as AD5685) - Support AD5672R, AD5676, AD5676, AD5684R and AD5686R 4 and 8 channel SPI DACs with various precisions. - Support AD5671R, AD5675R, AD5694, AD5694R, AD5695R, AD5696 and AD5696R I2C DACs with various percisions and numbers of channels. * Analog front end rescale driver - New driver. - Support current sensing usings a shunt resistor. - Support simple voltage dividers. - support simple current sense amplifiers. * TI dac5571 - New driver and device bindings supporting: dac5571, dac6571, dac7571, dac5574, dac6574, dac7574, dac5573, dac6573 and dac7573 * Meson-adc - Support for Meson AXG with DT bindings. * mpu6050 - Support the mpu9255 which only requires additional WHOAMI entry and compatible string. * st_lsm6dsx - Support for lsm330dlc combinded accelerometer and gyro sensors with DT bindings. * stm32_adc - Add support for STM32MP1 with bindings. Staging graduations * adis16201 after some excelent cleanup by Himanshu Jha. * adis16029 after some excelent cleanup by Shreeya Patel. New features: * ABI docs - Add core ABI docs for angle channels. * inv_mpu6050 - Provide support for the full range of interrupts the device supports. * st_accel - Add SMO8840 ACPI ID seen in the wild on some Lenovo machines. * stx104 - Provide a multiple gpio get function. Cleanups / Minor fixes * core - Use new nested structure support to improve kernel-doc. * ad2s1200 - Use be16_to_cpup instead of opencoding. * ad5686 - Indentation tidy up. - Switch to SPDX - Refactor to allow various numbers of channels. - Refactor to separate core and SPI specific support, prior to addition of i2c equivalent devices. * ad7606 - Use drvdata directly from device rather than boucing via the platform_device structure. * ad7746 - Replace opencoded byte swapped i2c calls with _swapped variants. - White space and line break readability improvements. - Reorder includes and variable declarations where appropriate. * ad7791 - Changes to the AD ADC library used by this driver took in the sampling frequency. This lead to be the wrong path being the one tied to the resulting attribute, so it didn't work, and a warning to be printed. * ad7780 - Remove apparent support for sampling frequency control on devices that don't support changing the sampling attributes. * ade7854 - Fix a read of the wrong number of bits. - Improve error handling on i2c read/write errors. - Rework i2c and spi code to reduce duplication. * adis16201 (staging) - Improve meaning inherent in some macro names by adding units etc where relevant. - Adjust comments to improve detail and drop the irrelevant. - Rename register address definitions definitions to add a _REG postfix, clearly separating them from field definitions. Reorganize the definitions to group register address and fields. - Use sign_extend32 rather than open coding. - Reverse Xmas tree ordering where appropriate and align function args. - Remove unused headers. - Use GENMASK where appropriate instead of open coding. * adis16209 (staging) - Indent field definitions to visually separate them from register address definitions. - Use reverse xmas tree ordering where appropriate. - Add some whitespace where it will help readability. - Drop some unused headers. - Use GENMASK where appropriate. * ad2s1200 - Drop unnecessary includes and reorder alphabetically. - Reverse xmas tree and blank line cleanups. * atlas-ph-sensor - Use msleep instead of usleep_range where the precise value doesn't matter and the delays are long. * bcm150 - Drop transaction splitting as core now handles it. * cros_ec - Move the shared header to the include/iio/common directory. This brings it inline with the other multiple type devices. - Use drvdata directly from device rather than boucing via the platform_device structure. * hid-sensors - Use drvdata directly from device rather than boucing via the platform_device structure. * inv_mpu6050 - Clear out a second function definition for the same function. - Don't flush fifo when the iio buffer is full but just drop excess data. - Tidy up set_power_itg and ensure it is used in the right places. - Use set_power_itg rather than opencoding it again in the i2c mux control. - Make sure error paths disable the power if undoing power on. - Used managed devm_ functions during probe. Delete remove function. - Refactor to pull raw data read out of read_raw function. - Simplify data reading error paths. - Only enable the i2c mux for chips with the i2c aux bus (not icm20608) - Fix a potential deadlock due to varying lock ordering. - Fix an issue where first sample from gyro after enabling is unstable by dropping the first sample. - Fix an issue where the user_ctrl register is incorrectly overwritten. - Tidy up some grammar and spelling minor issus. * mcp320x - Use vendor compatible strings. * mcp4018 - Switch to using i2c .probe_new. * mcp4351 - switch to using i2c .probe_new. * meson-adc - rework handing on common ADC platform data so it can be shared across multiple families of SoCs. * sca3000 - Fix an error handling path if the ring configure fails. * st_lsm6dsx - Fix a wrong fifo threshold mask (no actual effect) * stm32-dfsdm - Style fixes and cleanups. - Check filter ID is in range and check spi-max-frequency. * tsl2x7x (staging) - Drop some unnecessary function calls, unused variables and unnecessary local variables. - Fix wrong interrupt type. - Avoid unnecessary double clear of interrupt. - Simplify proximity calibration call which did various things unrelated to actually calibrating. - Separate control of the proximity and ALS interrupts. - Improve consistency of logging. - Separate ALS and proximity persistence settings as they have separate hardware controls. - Tidy up variable ordering. - Add Brian to copyright notice given consider work on this driver. - Take advantage of hardware support for I2C address auto increment. - Combine individuaal enable and period attributes for the two directions on the threshold events into a single value as the hardware doesn't separate them. - Move integration_time* attributes from light channel to intensity value as they effect the intensity readings directly and the light reading only indirectly. Hence this better reflects reality. Also move the calibscale_available. - Avoid returning an error in the IRQ handler. - Hard code the reg value in _clear_interrupts as it only takes one value in the code. Result is the function has little purpose so opencode the two remaining i2c_smbus_write_byte calls. - Drop some unnecessary checking of the chip status register. - Tidy up return path in _write_interrupt_config. - Tidy up the ID verification code. - Move the power and diode settings defines into the header as these are needed for platform data configuration. - Various renames and comment cleanups for consistency and clarity. - Use actual device defaults for default startup settings. - SPDX - Add some range sanity checking to sysfs attribute writes. - Don't provide event interfaces if the interrupt line isn't available. - Use IIO_CONST_ATTR macro for calibscale_available as it's a constant string. - Fix the integration time and lux equations. - Make device IDs explicit index values in the device_channel_config array.
Diffstat (limited to 'drivers/iio/dac')
-rw-r--r--drivers/iio/dac/Kconfig33
-rw-r--r--drivers/iio/dac/Makefile3
-rw-r--r--drivers/iio/dac/ad5686-spi.c93
-rw-r--r--drivers/iio/dac/ad5686.c311
-rw-r--r--drivers/iio/dac/ad5686.h121
-rw-r--r--drivers/iio/dac/ad5696-i2c.c97
-rw-r--r--drivers/iio/dac/ti-dac5571.c439
7 files changed, 912 insertions, 185 deletions
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 76db0768e454..06e90debb9f5 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -131,16 +131,31 @@ 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.
+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"
depends on SPI_MASTER
@@ -321,6 +336,16 @@ config TI_DAC082S085
If compiled as a module, it will be called ti-dac082s085.
+config TI_DAC5571
+ tristate "Texas Instruments 8/10/12/16-bit 1/2/4-channel DAC driver"
+ depends on I2C
+ help
+ Driver for the Texas Instruments
+ DAC5571, DAC6571, DAC7571, DAC5574, DAC6574, DAC7574, DAC5573,
+ DAC6573, DAC7573, DAC8571, DAC8574.
+
+ If compiled as a module, it will be called ti-dac5571.
+
config VF610_DAC
tristate "Vybrid vf610 DAC driver"
depends on OF
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 81e710ed7491..57aa230d34ab 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -20,6 +20,8 @@ obj-$(CONFIG_AD5761) += ad5761.o
obj-$(CONFIG_AD5764) += ad5764.o
obj-$(CONFIG_AD5791) += ad5791.o
obj-$(CONFIG_AD5686) += ad5686.o
+obj-$(CONFIG_AD5686_SPI) += ad5686-spi.o
+obj-$(CONFIG_AD5696_I2C) += ad5696-i2c.o
obj-$(CONFIG_AD7303) += ad7303.o
obj-$(CONFIG_AD8801) += ad8801.o
obj-$(CONFIG_CIO_DAC) += cio-dac.o
@@ -35,4 +37,5 @@ obj-$(CONFIG_MCP4922) += mcp4922.o
obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o
obj-$(CONFIG_STM32_DAC) += stm32-dac.o
obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o
+obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o
obj-$(CONFIG_VF610_DAC) += vf610_dac.o
diff --git a/drivers/iio/dac/ad5686-spi.c b/drivers/iio/dac/ad5686-spi.c
new file mode 100644
index 000000000000..6bb09e9259e6
--- /dev/null
+++ b/drivers/iio/dac/ad5686-spi.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * AD5672R, AD5676, AD5676R, AD5684, AD5684R, AD5684R, AD5685R, AD5686, AD5686R
+ * Digital to analog converters driver
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+
+#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},
+ {"ad5685", ID_AD5685R}, /* Does not exist */
+ {"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@analog.com>");
+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 20254df7f9c7..89c5f089ae7f 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -1,9 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0+
/*
* AD5686R, AD5685R, AD5684R Digital to analog converters driver
*
* Copyright 2011 Analog Devices Inc.
- *
- * Licensed under the GPL-2.
*/
#include <linux/interrupt.h>
@@ -11,7 +10,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,116 +17,7 @@
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
-#define AD5686_DAC_CHANNELS 4
-
-#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
- * @channel: channel specification
-*/
-
-struct ad5686_chip_info {
- u16 int_vref_mv;
- struct iio_chan_spec channel[AD5686_DAC_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 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;
-};
-
-/**
- * ad5686_supported_device_ids:
- */
-
-enum ad5686_supported_device_ids {
- ID_AD5684,
- ID_AD5685,
- ID_AD5686,
-};
-static int ad5686_spi_write(struct ad5686_state *st,
- u8 cmd, u8 addr, u16 val, u8 shift)
-{
- val <<= shift;
-
- 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);
-}
-
-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,
- },
- };
- 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);
-}
+#include "ad5686.h"
static const char * const ad5686_powerdown_modes[] = {
"1kohm_to_gnd",
@@ -137,7 +26,7 @@ static const char * const ad5686_powerdown_modes[] = {
};
static int ad5686_get_powerdown_mode(struct iio_dev *indio_dev,
- const struct iio_chan_spec *chan)
+ const struct iio_chan_spec *chan)
{
struct ad5686_state *st = iio_priv(indio_dev);
@@ -145,7 +34,8 @@ static int ad5686_get_powerdown_mode(struct iio_dev *indio_dev,
}
static int ad5686_set_powerdown_mode(struct iio_dev *indio_dev,
- const struct iio_chan_spec *chan, unsigned int mode)
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
{
struct ad5686_state *st = iio_priv(indio_dev);
@@ -163,17 +53,19 @@ static const struct iio_enum ad5686_powerdown_mode_enum = {
};
static ssize_t ad5686_read_dac_powerdown(struct iio_dev *indio_dev,
- uintptr_t private, const struct iio_chan_spec *chan, char *buf)
+ uintptr_t private, const struct iio_chan_spec *chan, char *buf)
{
struct ad5686_state *st = iio_priv(indio_dev);
return sprintf(buf, "%d\n", !!(st->pwr_down_mask &
- (0x3 << (chan->channel * 2))));
+ (0x3 << (chan->channel * 2))));
}
static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev,
- uintptr_t private, const struct iio_chan_spec *chan, const char *buf,
- size_t len)
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf,
+ size_t len)
{
bool readin;
int ret;
@@ -188,8 +80,9 @@ 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 = st->write(st, AD5686_CMD_POWERDOWN_DAC, 0,
+ st->pwr_down_mask & st->pwr_down_mode);
+
return ret ? ret : len;
}
@@ -206,7 +99,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 = st->read(st, chan->address);
mutex_unlock(&indio_dev->mlock);
if (ret < 0)
return ret;
@@ -221,10 +114,10 @@ static int ad5686_read_raw(struct iio_dev *indio_dev,
}
static int ad5686_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
{
struct ad5686_state *st = iio_priv(indio_dev);
int ret;
@@ -235,11 +128,10 @@ 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 = st->write(st,
+ AD5686_CMD_WRITE_INPUT_N_UPDATE_N,
+ chan->address,
+ val << chan->scan_type.shift);
mutex_unlock(&indio_dev->mlock);
break;
default:
@@ -266,14 +158,14 @@ static const struct iio_chan_spec_ext_info ad5686_ext_info[] = {
{ },
};
-#define AD5868_CHANNEL(chan, bits, _shift) { \
+#define AD5868_CHANNEL(chan, addr, bits, _shift) { \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.output = 1, \
.channel = chan, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
- .address = AD5686_ADDR_DAC(chan), \
+ .address = addr, \
.scan_type = { \
.sign = 'u', \
.realbits = (bits), \
@@ -283,45 +175,121 @@ static const struct iio_chan_spec_ext_info ad5686_ext_info[] = {
.ext_info = ad5686_ext_info, \
}
+#define DECLARE_AD5686_CHANNELS(name, bits, _shift) \
+static struct iio_chan_spec name[] = { \
+ AD5868_CHANNEL(0, 1, bits, _shift), \
+ AD5868_CHANNEL(1, 2, bits, _shift), \
+ AD5868_CHANNEL(2, 4, bits, _shift), \
+ AD5868_CHANNEL(3, 8, bits, _shift), \
+}
+
+#define DECLARE_AD5676_CHANNELS(name, bits, _shift) \
+static struct iio_chan_spec name[] = { \
+ AD5868_CHANNEL(0, 0, bits, _shift), \
+ AD5868_CHANNEL(1, 1, bits, _shift), \
+ AD5868_CHANNEL(2, 2, bits, _shift), \
+ AD5868_CHANNEL(3, 3, bits, _shift), \
+ AD5868_CHANNEL(4, 4, bits, _shift), \
+ AD5868_CHANNEL(5, 5, bits, _shift), \
+ AD5868_CHANNEL(6, 6, bits, _shift), \
+ AD5868_CHANNEL(7, 7, bits, _shift), \
+}
+
+DECLARE_AD5676_CHANNELS(ad5672_channels, 12, 4);
+DECLARE_AD5676_CHANNELS(ad5676_channels, 16, 0);
+DECLARE_AD5686_CHANNELS(ad5684_channels, 12, 4);
+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,
+ },
+ [ID_AD5676R] = {
+ .channels = ad5676_channels,
+ .int_vref_mv = 2500,
+ .num_channels = 8,
+ },
[ID_AD5684] = {
- .channel[0] = AD5868_CHANNEL(0, 12, 4),
- .channel[1] = AD5868_CHANNEL(1, 12, 4),
- .channel[2] = AD5868_CHANNEL(2, 12, 4),
- .channel[3] = AD5868_CHANNEL(3, 12, 4),
+ .channels = ad5684_channels,
+ .num_channels = 4,
+ },
+ [ID_AD5684R] = {
+ .channels = ad5684_channels,
.int_vref_mv = 2500,
+ .num_channels = 4,
},
- [ID_AD5685] = {
- .channel[0] = AD5868_CHANNEL(0, 14, 2),
- .channel[1] = AD5868_CHANNEL(1, 14, 2),
- .channel[2] = AD5868_CHANNEL(2, 14, 2),
- .channel[3] = AD5868_CHANNEL(3, 14, 2),
+ [ID_AD5685R] = {
+ .channels = ad5685r_channels,
.int_vref_mv = 2500,
+ .num_channels = 4,
},
[ID_AD5686] = {
- .channel[0] = AD5868_CHANNEL(0, 16, 0),
- .channel[1] = AD5868_CHANNEL(1, 16, 0),
- .channel[2] = AD5868_CHANNEL(2, 16, 0),
- .channel[3] = AD5868_CHANNEL(3, 16, 0),
+ .channels = ad5686_channels,
+ .num_channels = 4,
+ },
+ [ID_AD5686R] = {
+ .channels = ad5686_channels,
.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)
@@ -334,28 +302,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->channel;
- indio_dev->num_channels = AD5686_DAC_CHANNELS;
+ 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 = st->write(st, AD5686_CMD_INTERNAL_REFER_SETUP,
+ 0, !!voltage_uv);
if (ret)
goto error_disable_reg;
@@ -370,10 +335,11 @@ error_disable_reg:
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);
@@ -382,24 +348,7 @@ static int ad5686_remove(struct spi_device *spi)
return 0;
}
-
-static const struct spi_device_id ad5686_id[] = {
- {"ad5684", ID_AD5684},
- {"ad5685", ID_AD5685},
- {"ad5686", ID_AD5686},
- {}
-};
-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@blackfin.uclinux.org>");
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 000000000000..05f0ce9d2de1
--- /dev/null
+++ b/drivers/iio/dac/ad5686.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * This file is part of AD5686 DAC driver
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+
+#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 000000000000..275e0321bcf8
--- /dev/null
+++ b/drivers/iio/dac/ad5696-i2c.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * AD5671R, AD5675R, AD5694, AD5694R, AD5695R, AD5696, AD5696R
+ * Digital to analog converters driver
+ *
+ * Copyright 2018 Analog Devices Inc.
+ */
+
+#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, ad5686_i2c_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@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD5686 and similar multi-channel DACs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/dac/ti-dac5571.c b/drivers/iio/dac/ti-dac5571.c
new file mode 100644
index 000000000000..dd21eebed6a8
--- /dev/null
+++ b/drivers/iio/dac/ti-dac5571.c
@@ -0,0 +1,439 @@
+/*
+ * ti-dac5571.c - Texas Instruments 8/10/12-bit 1/4-channel DAC driver
+ *
+ * Copyright (C) 2018 Prevas A/S
+ *
+ * http://www.ti.com/lit/ds/symlink/dac5571.pdf
+ * http://www.ti.com/lit/ds/symlink/dac6571.pdf
+ * http://www.ti.com/lit/ds/symlink/dac7571.pdf
+ * http://www.ti.com/lit/ds/symlink/dac5574.pdf
+ * http://www.ti.com/lit/ds/symlink/dac6574.pdf
+ * http://www.ti.com/lit/ds/symlink/dac7574.pdf
+ * http://www.ti.com/lit/ds/symlink/dac5573.pdf
+ * http://www.ti.com/lit/ds/symlink/dac6573.pdf
+ * http://www.ti.com/lit/ds/symlink/dac7573.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (version 2) as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/iio/iio.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+enum chip_id {
+ single_8bit, single_10bit, single_12bit,
+ quad_8bit, quad_10bit, quad_12bit
+};
+
+struct dac5571_spec {
+ u8 num_channels;
+ u8 resolution;
+};
+
+static const struct dac5571_spec dac5571_spec[] = {
+ [single_8bit] = {.num_channels = 1, .resolution = 8},
+ [single_10bit] = {.num_channels = 1, .resolution = 10},
+ [single_12bit] = {.num_channels = 1, .resolution = 12},
+ [quad_8bit] = {.num_channels = 4, .resolution = 8},
+ [quad_10bit] = {.num_channels = 4, .resolution = 10},
+ [quad_12bit] = {.num_channels = 4, .resolution = 12},
+};
+
+struct dac5571_data {
+ struct i2c_client *client;
+ int id;
+ struct mutex lock;
+ struct regulator *vref;
+ u16 val[4];
+ bool powerdown;
+ u8 powerdown_mode;
+ struct dac5571_spec const *spec;
+ int (*dac5571_cmd)(struct dac5571_data *data, int channel, u16 val);
+ int (*dac5571_pwrdwn)(struct dac5571_data *data, int channel, u8 pwrdwn);
+ u8 buf[3] ____cacheline_aligned;
+};
+
+#define DAC5571_POWERDOWN(mode) ((mode) + 1)
+#define DAC5571_POWERDOWN_FLAG BIT(0)
+#define DAC5571_CHANNEL_SELECT 1
+#define DAC5571_LOADMODE_DIRECT BIT(4)
+#define DAC5571_SINGLE_PWRDWN_BITS 4
+#define DAC5571_QUAD_PWRDWN_BITS 6
+
+static int dac5571_cmd_single(struct dac5571_data *data, int channel, u16 val)
+{
+ unsigned int shift;
+
+ shift = 12 - data->spec->resolution;
+ data->buf[1] = val << shift;
+ data->buf[0] = val >> (8 - shift);
+
+ if (i2c_master_send(data->client, data->buf, 2) != 2)
+ return -EIO;
+
+ return 0;
+}
+
+static int dac5571_cmd_quad(struct dac5571_data *data, int channel, u16 val)
+{
+ unsigned int shift;
+
+ shift = 16 - data->spec->resolution;
+ data->buf[2] = val << shift;
+ data->buf[1] = (val >> (8 - shift));
+ data->buf[0] = (channel << DAC5571_CHANNEL_SELECT) |
+ DAC5571_LOADMODE_DIRECT;
+
+ if (i2c_master_send(data->client, data->buf, 3) != 3)
+ return -EIO;
+
+ return 0;
+}
+
+static int dac5571_pwrdwn_single(struct dac5571_data *data, int channel, u8 pwrdwn)
+{
+ unsigned int shift;
+
+ shift = 12 - data->spec->resolution;
+ data->buf[1] = 0;
+ data->buf[0] = pwrdwn << DAC5571_SINGLE_PWRDWN_BITS;
+
+ if (i2c_master_send(data->client, data->buf, 2) != 2)
+ return -EIO;
+
+ return 0;
+}
+
+static int dac5571_pwrdwn_quad(struct dac5571_data *data, int channel, u8 pwrdwn)
+{
+ unsigned int shift;
+
+ shift = 16 - data->spec->resolution;
+ data->buf[2] = 0;
+ data->buf[1] = pwrdwn << DAC5571_QUAD_PWRDWN_BITS;
+ data->buf[0] = (channel << DAC5571_CHANNEL_SELECT) |
+ DAC5571_LOADMODE_DIRECT | DAC5571_POWERDOWN_FLAG;
+
+ if (i2c_master_send(data->client, data->buf, 3) != 3)
+ return -EIO;
+
+ return 0;
+}
+
+static const char *const dac5571_powerdown_modes[] = {
+ "1kohm_to_gnd", "100kohm_to_gnd", "three_state",
+};
+
+static int dac5571_get_powerdown_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct dac5571_data *data = iio_priv(indio_dev);
+
+ return data->powerdown_mode;
+}
+
+static int dac5571_set_powerdown_mode(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ unsigned int mode)
+{
+ struct dac5571_data *data = iio_priv(indio_dev);
+ int ret = 0;
+
+ if (data->powerdown_mode == mode)
+ return 0;
+
+ mutex_lock(&data->lock);
+ if (data->powerdown) {
+ ret = data->dac5571_pwrdwn(data, chan->channel,
+ DAC5571_POWERDOWN(mode));
+ if (ret)
+ goto out;
+ }
+ data->powerdown_mode = mode;
+
+ out:
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static const struct iio_enum dac5571_powerdown_mode = {
+ .items = dac5571_powerdown_modes,
+ .num_items = ARRAY_SIZE(dac5571_powerdown_modes),
+ .get = dac5571_get_powerdown_mode,
+ .set = dac5571_set_powerdown_mode,
+};
+
+static ssize_t dac5571_read_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ char *buf)
+{
+ struct dac5571_data *data = iio_priv(indio_dev);
+
+ return sprintf(buf, "%d\n", data->powerdown);
+}
+
+static ssize_t dac5571_write_powerdown(struct iio_dev *indio_dev,
+ uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct dac5571_data *data = iio_priv(indio_dev);
+ bool powerdown;
+ int ret;
+
+ ret = strtobool(buf, &powerdown);
+ if (ret)
+ return ret;
+
+ if (data->powerdown == powerdown)
+ return len;
+
+ mutex_lock(&data->lock);
+ if (powerdown)
+ ret = data->dac5571_pwrdwn(data, chan->channel,
+ DAC5571_POWERDOWN(data->powerdown_mode));
+ else
+ ret = data->dac5571_cmd(data, chan->channel, data->val[0]);
+ if (ret)
+ goto out;
+
+ data->powerdown = powerdown;
+
+ out:
+ mutex_unlock(&data->lock);
+
+ return ret ? ret : len;
+}
+
+
+static const struct iio_chan_spec_ext_info dac5571_ext_info[] = {
+ {
+ .name = "powerdown",
+ .read = dac5571_read_powerdown,
+ .write = dac5571_write_powerdown,
+ .shared = IIO_SHARED_BY_TYPE,
+ },
+ IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, &dac5571_powerdown_mode),
+ IIO_ENUM_AVAILABLE("powerdown_mode", &dac5571_powerdown_mode),
+ {},
+};
+
+#define dac5571_CHANNEL(chan, name) { \
+ .type = IIO_VOLTAGE, \
+ .channel = (chan), \
+ .address = (chan), \
+ .indexed = true, \
+ .output = true, \
+ .datasheet_name = name, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .ext_info = dac5571_ext_info, \
+}
+
+static const struct iio_chan_spec dac5571_channels[] = {
+ dac5571_CHANNEL(0, "A"),
+ dac5571_CHANNEL(1, "B"),
+ dac5571_CHANNEL(2, "C"),
+ dac5571_CHANNEL(3, "D"),
+};
+
+static int dac5571_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct dac5571_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *val = data->val[chan->channel];
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = regulator_get_voltage(data->vref);
+ if (ret < 0)
+ return ret;
+
+ *val = ret / 1000;
+ *val2 = data->spec->resolution;
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int dac5571_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct dac5571_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (data->val[chan->channel] == val)
+ return 0;
+
+ if (val >= (1 << data->spec->resolution) || val < 0)
+ return -EINVAL;
+
+ if (data->powerdown)
+ return -EBUSY;
+
+ mutex_lock(&data->lock);
+ ret = data->dac5571_cmd(data, chan->channel, val);
+ if (ret == 0)
+ data->val[chan->channel] = val;
+ mutex_unlock(&data->lock);
+ return ret;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int dac5571_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ return IIO_VAL_INT;
+}
+
+static const struct iio_info dac5571_info = {
+ .read_raw = dac5571_read_raw,
+ .write_raw = dac5571_write_raw,
+ .write_raw_get_fmt = dac5571_write_raw_get_fmt,
+};
+
+static int dac5571_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ const struct dac5571_spec *spec;
+ struct dac5571_data *data;
+ struct iio_dev *indio_dev;
+ int ret, i;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ data->client = client;
+
+ indio_dev->dev.parent = dev;
+ indio_dev->dev.of_node = client->dev.of_node;
+ indio_dev->info = &dac5571_info;
+ indio_dev->name = id->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = dac5571_channels;
+
+ spec = &dac5571_spec[id->driver_data];
+ indio_dev->num_channels = spec->num_channels;
+ data->spec = spec;
+
+ data->vref = devm_regulator_get(dev, "vref");
+ if (IS_ERR(data->vref))
+ return PTR_ERR(data->vref);
+
+ ret = regulator_enable(data->vref);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&data->lock);
+
+ switch (spec->num_channels) {
+ case 1:
+ data->dac5571_cmd = dac5571_cmd_single;
+ data->dac5571_pwrdwn = dac5571_pwrdwn_single;
+ break;
+ case 4:
+ data->dac5571_cmd = dac5571_cmd_quad;
+ data->dac5571_pwrdwn = dac5571_pwrdwn_quad;
+ break;
+ default:
+ goto err;
+ }
+
+ for (i = 0; i < spec->num_channels; i++) {
+ ret = data->dac5571_cmd(data, i, 0);
+ if (ret) {
+ dev_err(dev, "failed to initialize channel %d to 0\n", i);
+ goto err;
+ }
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto err;
+
+ return 0;
+
+ err:
+ regulator_disable(data->vref);
+ return ret;
+}
+
+static int dac5571_remove(struct i2c_client *i2c)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(i2c);
+ struct dac5571_data *data = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ regulator_disable(data->vref);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dac5571_of_id[] = {
+ {.compatible = "ti,dac5571"},
+ {.compatible = "ti,dac6571"},
+ {.compatible = "ti,dac7571"},
+ {.compatible = "ti,dac5574"},
+ {.compatible = "ti,dac6574"},
+ {.compatible = "ti,dac7574"},
+ {.compatible = "ti,dac5573"},
+ {.compatible = "ti,dac6573"},
+ {.compatible = "ti,dac7573"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, dac5571_of_id);
+#endif
+
+static const struct i2c_device_id dac5571_id[] = {
+ {"dac5571", single_8bit},
+ {"dac6571", single_10bit},
+ {"dac7571", single_12bit},
+ {"dac5574", quad_8bit},
+ {"dac6574", quad_10bit},
+ {"dac7574", quad_12bit},
+ {"dac5573", quad_8bit},
+ {"dac6573", quad_10bit},
+ {"dac7573", quad_12bit},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, dac5571_id);
+
+static struct i2c_driver dac5571_driver = {
+ .driver = {
+ .name = "ti-dac5571",
+ },
+ .probe = dac5571_probe,
+ .remove = dac5571_remove,
+ .id_table = dac5571_id,
+};
+module_i2c_driver(dac5571_driver);
+
+MODULE_AUTHOR("Sean Nyekjaer <sean.nyekjaer@prevas.dk>");
+MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 1/4-channel DAC driver");
+MODULE_LICENSE("GPL v2");