diff options
Diffstat (limited to 'drivers/iio/dac/ad5755.c')
| -rw-r--r-- | drivers/iio/dac/ad5755.c | 622 | 
1 files changed, 622 insertions, 0 deletions
diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c new file mode 100644 index 00000000000..a7c851f62d7 --- /dev/null +++ b/drivers/iio/dac/ad5755.c @@ -0,0 +1,622 @@ +/* + * AD5755, AD5755-1, AD5757, AD5735, AD5737 Digital to analog converters driver + * + * Copyright 2012 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/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/platform_data/ad5755.h> + +#define AD5755_NUM_CHANNELS 4 + +#define AD5755_ADDR(x)			((x) << 16) + +#define AD5755_WRITE_REG_DATA(chan)	(chan) +#define AD5755_WRITE_REG_GAIN(chan)	(0x08 | (chan)) +#define AD5755_WRITE_REG_OFFSET(chan)	(0x10 | (chan)) +#define AD5755_WRITE_REG_CTRL(chan)	(0x1c | (chan)) + +#define AD5755_READ_REG_DATA(chan)	(chan) +#define AD5755_READ_REG_CTRL(chan)	(0x4 | (chan)) +#define AD5755_READ_REG_GAIN(chan)	(0x8 | (chan)) +#define AD5755_READ_REG_OFFSET(chan)	(0xc | (chan)) +#define AD5755_READ_REG_CLEAR(chan)	(0x10 | (chan)) +#define AD5755_READ_REG_SLEW(chan)	(0x14 | (chan)) +#define AD5755_READ_REG_STATUS		0x18 +#define AD5755_READ_REG_MAIN		0x19 +#define AD5755_READ_REG_DC_DC		0x1a + +#define AD5755_CTRL_REG_SLEW	0x0 +#define AD5755_CTRL_REG_MAIN	0x1 +#define AD5755_CTRL_REG_DAC	0x2 +#define AD5755_CTRL_REG_DC_DC	0x3 +#define AD5755_CTRL_REG_SW	0x4 + +#define AD5755_READ_FLAG 0x800000 + +#define AD5755_NOOP 0x1CE000 + +#define AD5755_DAC_INT_EN			BIT(8) +#define AD5755_DAC_CLR_EN			BIT(7) +#define AD5755_DAC_OUT_EN			BIT(6) +#define AD5755_DAC_INT_CURRENT_SENSE_RESISTOR	BIT(5) +#define AD5755_DAC_DC_DC_EN			BIT(4) +#define AD5755_DAC_VOLTAGE_OVERRANGE_EN		BIT(3) + +#define AD5755_DC_DC_MAXV			0 +#define AD5755_DC_DC_FREQ_SHIFT			2 +#define AD5755_DC_DC_PHASE_SHIFT		4 +#define AD5755_EXT_DC_DC_COMP_RES		BIT(6) + +#define AD5755_SLEW_STEP_SIZE_SHIFT		0 +#define AD5755_SLEW_RATE_SHIFT			3 +#define AD5755_SLEW_ENABLE			BIT(12) + +/** + * struct ad5755_chip_info - chip specific information + * @channel_template:	channel specification + * @calib_shift:	shift for the calibration data registers + * @has_voltage_out:	whether the chip has voltage outputs + */ +struct ad5755_chip_info { +	const struct iio_chan_spec channel_template; +	unsigned int calib_shift; +	bool has_voltage_out; +}; + +/** + * struct ad5755_state - driver instance specific data + * @spi:	spi device the driver is attached to + * @chip_info:	chip model specific constants, available modes etc + * @pwr_down:	bitmask which contains  hether a channel is powered down or not + * @ctrl:	software shadow of the channel ctrl registers + * @channels:	iio channel spec for the device + * @data:	spi transfer buffers + */ +struct ad5755_state { +	struct spi_device		*spi; +	const struct ad5755_chip_info	*chip_info; +	unsigned int			pwr_down; +	unsigned int			ctrl[AD5755_NUM_CHANNELS]; +	struct iio_chan_spec		channels[AD5755_NUM_CHANNELS]; + +	/* +	 * 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 ad5755_type { +	ID_AD5755, +	ID_AD5757, +	ID_AD5735, +	ID_AD5737, +}; + +static int ad5755_write_unlocked(struct iio_dev *indio_dev, +	unsigned int reg, unsigned int val) +{ +	struct ad5755_state *st = iio_priv(indio_dev); + +	st->data[0].d32 = cpu_to_be32((reg << 16) | val); + +	return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5755_write_ctrl_unlocked(struct iio_dev *indio_dev, +	unsigned int channel, unsigned int reg, unsigned int val) +{ +	return ad5755_write_unlocked(indio_dev, +		AD5755_WRITE_REG_CTRL(channel), (reg << 13) | val); +} + +static int ad5755_write(struct iio_dev *indio_dev, unsigned int reg, +	unsigned int val) +{ +	int ret; + +	mutex_lock(&indio_dev->mlock); +	ret = ad5755_write_unlocked(indio_dev, reg, val); +	mutex_unlock(&indio_dev->mlock); + +	return ret; +} + +static int ad5755_write_ctrl(struct iio_dev *indio_dev, unsigned int channel, +	unsigned int reg, unsigned int val) +{ +	int ret; + +	mutex_lock(&indio_dev->mlock); +	ret = ad5755_write_ctrl_unlocked(indio_dev, channel, reg, val); +	mutex_unlock(&indio_dev->mlock); + +	return ret; +} + +static int ad5755_read(struct iio_dev *indio_dev, unsigned int addr) +{ +	struct ad5755_state *st = iio_priv(indio_dev); +	int ret; +	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[1].d8[1], +			.len = 3, +		}, +	}; + +	mutex_lock(&indio_dev->mlock); + +	st->data[0].d32 = cpu_to_be32(AD5755_READ_FLAG | (addr << 16)); +	st->data[1].d32 = cpu_to_be32(AD5755_NOOP); + +	ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); +	if (ret >= 0) +		ret = be32_to_cpu(st->data[1].d32) & 0xffff; + +	mutex_unlock(&indio_dev->mlock); + +	return ret; +} + +static int ad5755_update_dac_ctrl(struct iio_dev *indio_dev, +	unsigned int channel, unsigned int set, unsigned int clr) +{ +	struct ad5755_state *st = iio_priv(indio_dev); +	int ret; + +	st->ctrl[channel] |= set; +	st->ctrl[channel] &= ~clr; + +	ret = ad5755_write_ctrl_unlocked(indio_dev, channel, +		AD5755_CTRL_REG_DAC, st->ctrl[channel]); + +	return ret; +} + +static int ad5755_set_channel_pwr_down(struct iio_dev *indio_dev, +	unsigned int channel, bool pwr_down) +{ +	struct ad5755_state *st = iio_priv(indio_dev); +	unsigned int mask = BIT(channel); + +	mutex_lock(&indio_dev->mlock); + +	if ((bool)(st->pwr_down & mask) == pwr_down) +		goto out_unlock; + +	if (!pwr_down) { +		st->pwr_down &= ~mask; +		ad5755_update_dac_ctrl(indio_dev, channel, +			AD5755_DAC_INT_EN | AD5755_DAC_DC_DC_EN, 0); +		udelay(200); +		ad5755_update_dac_ctrl(indio_dev, channel, +			AD5755_DAC_OUT_EN, 0); +	} else { +		st->pwr_down |= mask; +		ad5755_update_dac_ctrl(indio_dev, channel, +			0, AD5755_DAC_INT_EN | AD5755_DAC_OUT_EN | +				AD5755_DAC_DC_DC_EN); +	} + +out_unlock: +	mutex_unlock(&indio_dev->mlock); + +	return 0; +} + +static const int ad5755_min_max_table[][2] = { +	[AD5755_MODE_VOLTAGE_0V_5V] = { 0, 5000 }, +	[AD5755_MODE_VOLTAGE_0V_10V] = { 0, 10000 }, +	[AD5755_MODE_VOLTAGE_PLUSMINUS_5V] = { -5000, 5000 }, +	[AD5755_MODE_VOLTAGE_PLUSMINUS_10V] = { -10000, 10000 }, +	[AD5755_MODE_CURRENT_4mA_20mA] = { 4, 20 }, +	[AD5755_MODE_CURRENT_0mA_20mA] = { 0, 20 }, +	[AD5755_MODE_CURRENT_0mA_24mA] = { 0, 24 }, +}; + +static void ad5755_get_min_max(struct ad5755_state *st, +	struct iio_chan_spec const *chan, int *min, int *max) +{ +	enum ad5755_mode mode = st->ctrl[chan->channel] & 7; +	*min = ad5755_min_max_table[mode][0]; +	*max = ad5755_min_max_table[mode][1]; +} + +static inline int ad5755_get_offset(struct ad5755_state *st, +	struct iio_chan_spec const *chan) +{ +	int min, max; + +	ad5755_get_min_max(st, chan, &min, &max); +	return (min * (1 << chan->scan_type.realbits)) / (max - min); +} + +static int ad5755_chan_reg_info(struct ad5755_state *st, +	struct iio_chan_spec const *chan, long info, bool write, +	unsigned int *reg, unsigned int *shift, unsigned int *offset) +{ +	switch (info) { +	case IIO_CHAN_INFO_RAW: +		if (write) +			*reg = AD5755_WRITE_REG_DATA(chan->address); +		else +			*reg = AD5755_READ_REG_DATA(chan->address); +		*shift = chan->scan_type.shift; +		*offset = 0; +		break; +	case IIO_CHAN_INFO_CALIBBIAS: +		if (write) +			*reg = AD5755_WRITE_REG_OFFSET(chan->address); +		else +			*reg = AD5755_READ_REG_OFFSET(chan->address); +		*shift = st->chip_info->calib_shift; +		*offset = 32768; +		break; +	case IIO_CHAN_INFO_CALIBSCALE: +		if (write) +			*reg =  AD5755_WRITE_REG_GAIN(chan->address); +		else +			*reg =  AD5755_READ_REG_GAIN(chan->address); +		*shift = st->chip_info->calib_shift; +		*offset = 0; +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int ad5755_read_raw(struct iio_dev *indio_dev, +	const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ +	struct ad5755_state *st = iio_priv(indio_dev); +	unsigned int reg, shift, offset; +	int min, max; +	int ret; + +	switch (info) { +	case IIO_CHAN_INFO_SCALE: +		ad5755_get_min_max(st, chan, &min, &max); +		*val = max - min; +		*val2 = chan->scan_type.realbits; +		return IIO_VAL_FRACTIONAL_LOG2; +	case IIO_CHAN_INFO_OFFSET: +		*val = ad5755_get_offset(st, chan); +		return IIO_VAL_INT; +	default: +		ret = ad5755_chan_reg_info(st, chan, info, false, +						®, &shift, &offset); +		if (ret) +			return ret; + +		ret = ad5755_read(indio_dev, reg); +		if (ret < 0) +			return ret; + +		*val = (ret - offset) >> shift; + +		return IIO_VAL_INT; +	} + +	return -EINVAL; +} + +static int ad5755_write_raw(struct iio_dev *indio_dev, +	const struct iio_chan_spec *chan, int val, int val2, long info) +{ +	struct ad5755_state *st = iio_priv(indio_dev); +	unsigned int shift, reg, offset; +	int ret; + +	ret = ad5755_chan_reg_info(st, chan, info, true, +					®, &shift, &offset); +	if (ret) +		return ret; + +	val <<= shift; +	val += offset; + +	if (val < 0 || val > 0xffff) +		return -EINVAL; + +	return ad5755_write(indio_dev, reg, val); +} + +static ssize_t ad5755_read_powerdown(struct iio_dev *indio_dev, uintptr_t priv, +	const struct iio_chan_spec *chan, char *buf) +{ +	struct ad5755_state *st = iio_priv(indio_dev); + +	return sprintf(buf, "%d\n", +		       (bool)(st->pwr_down & (1 << chan->channel))); +} + +static ssize_t ad5755_write_powerdown(struct iio_dev *indio_dev, uintptr_t priv, +	struct iio_chan_spec const *chan, const char *buf, size_t len) +{ +	bool pwr_down; +	int ret; + +	ret = strtobool(buf, &pwr_down); +	if (ret) +		return ret; + +	ret = ad5755_set_channel_pwr_down(indio_dev, chan->channel, pwr_down); +	return ret ? ret : len; +} + +static const struct iio_info ad5755_info = { +	.read_raw = ad5755_read_raw, +	.write_raw = ad5755_write_raw, +	.driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ad5755_ext_info[] = { +	{ +		.name = "powerdown", +		.read = ad5755_read_powerdown, +		.write = ad5755_write_powerdown, +		.shared = IIO_SEPARATE, +	}, +	{ }, +}; + +#define AD5755_CHANNEL(_bits) {					\ +	.indexed = 1,						\ +	.output = 1,						\ +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |		\ +		BIT(IIO_CHAN_INFO_SCALE) |			\ +		BIT(IIO_CHAN_INFO_OFFSET) |			\ +		BIT(IIO_CHAN_INFO_CALIBSCALE) |			\ +		BIT(IIO_CHAN_INFO_CALIBBIAS),			\ +	.scan_type = {						\ +		.sign = 'u',					\ +		.realbits = (_bits),				\ +		.storagebits = 16,				\ +		.shift = 16 - (_bits),				\ +	},							\ +	.ext_info = ad5755_ext_info,				\ +} + +static const struct ad5755_chip_info ad5755_chip_info_tbl[] = { +	[ID_AD5735] = { +		.channel_template = AD5755_CHANNEL(14), +		.has_voltage_out = true, +		.calib_shift = 4, +	}, +	[ID_AD5737] = { +		.channel_template = AD5755_CHANNEL(14), +		.has_voltage_out = false, +		.calib_shift = 4, +	}, +	[ID_AD5755] = { +		.channel_template = AD5755_CHANNEL(16), +		.has_voltage_out = true, +		.calib_shift = 0, +	}, +	[ID_AD5757] = { +		.channel_template = AD5755_CHANNEL(16), +		.has_voltage_out = false, +		.calib_shift = 0, +	}, +}; + +static bool ad5755_is_valid_mode(struct ad5755_state *st, enum ad5755_mode mode) +{ +	switch (mode) { +	case AD5755_MODE_VOLTAGE_0V_5V: +	case AD5755_MODE_VOLTAGE_0V_10V: +	case AD5755_MODE_VOLTAGE_PLUSMINUS_5V: +	case AD5755_MODE_VOLTAGE_PLUSMINUS_10V: +		return st->chip_info->has_voltage_out; +	case AD5755_MODE_CURRENT_4mA_20mA: +	case AD5755_MODE_CURRENT_0mA_20mA: +	case AD5755_MODE_CURRENT_0mA_24mA: +		return true; +	default: +		return false; +	} +} + +static int ad5755_setup_pdata(struct iio_dev *indio_dev, +			      const struct ad5755_platform_data *pdata) +{ +	struct ad5755_state *st = iio_priv(indio_dev); +	unsigned int val; +	unsigned int i; +	int ret; + +	if (pdata->dc_dc_phase > AD5755_DC_DC_PHASE_90_DEGREE || +		pdata->dc_dc_freq > AD5755_DC_DC_FREQ_650kHZ || +		pdata->dc_dc_maxv > AD5755_DC_DC_MAXV_29V5) +		return -EINVAL; + +	val = pdata->dc_dc_maxv << AD5755_DC_DC_MAXV; +	val |= pdata->dc_dc_freq << AD5755_DC_DC_FREQ_SHIFT; +	val |= pdata->dc_dc_phase << AD5755_DC_DC_PHASE_SHIFT; +	if (pdata->ext_dc_dc_compenstation_resistor) +		val |= AD5755_EXT_DC_DC_COMP_RES; + +	ret = ad5755_write_ctrl(indio_dev, 0, AD5755_CTRL_REG_DC_DC, val); +	if (ret < 0) +		return ret; + +	for (i = 0; i < ARRAY_SIZE(pdata->dac); ++i) { +		val = pdata->dac[i].slew.step_size << +			AD5755_SLEW_STEP_SIZE_SHIFT; +		val |= pdata->dac[i].slew.rate << +			AD5755_SLEW_RATE_SHIFT; +		if (pdata->dac[i].slew.enable) +			val |= AD5755_SLEW_ENABLE; + +		ret = ad5755_write_ctrl(indio_dev, i, +					AD5755_CTRL_REG_SLEW, val); +		if (ret < 0) +			return ret; +	} + +	for (i = 0; i < ARRAY_SIZE(pdata->dac); ++i) { +		if (!ad5755_is_valid_mode(st, pdata->dac[i].mode)) +			return -EINVAL; + +		val = 0; +		if (!pdata->dac[i].ext_current_sense_resistor) +			val |= AD5755_DAC_INT_CURRENT_SENSE_RESISTOR; +		if (pdata->dac[i].enable_voltage_overrange) +			val |= AD5755_DAC_VOLTAGE_OVERRANGE_EN; +		val |= pdata->dac[i].mode; + +		ret = ad5755_update_dac_ctrl(indio_dev, i, val, 0); +		if (ret < 0) +			return ret; +	} + +	return 0; +} + +static bool ad5755_is_voltage_mode(enum ad5755_mode mode) +{ +	switch (mode) { +	case AD5755_MODE_VOLTAGE_0V_5V: +	case AD5755_MODE_VOLTAGE_0V_10V: +	case AD5755_MODE_VOLTAGE_PLUSMINUS_5V: +	case AD5755_MODE_VOLTAGE_PLUSMINUS_10V: +		return true; +	default: +		return false; +	} +} + +static int ad5755_init_channels(struct iio_dev *indio_dev, +				const struct ad5755_platform_data *pdata) +{ +	struct ad5755_state *st = iio_priv(indio_dev); +	struct iio_chan_spec *channels = st->channels; +	unsigned int i; + +	for (i = 0; i < AD5755_NUM_CHANNELS; ++i) { +		channels[i] = st->chip_info->channel_template; +		channels[i].channel = i; +		channels[i].address = i; +		if (pdata && ad5755_is_voltage_mode(pdata->dac[i].mode)) +			channels[i].type = IIO_VOLTAGE; +		else +			channels[i].type = IIO_CURRENT; +	} + +	indio_dev->channels = channels; + +	return 0; +} + +#define AD5755_DEFAULT_DAC_PDATA { \ +		.mode = AD5755_MODE_CURRENT_4mA_20mA, \ +		.ext_current_sense_resistor = true, \ +		.enable_voltage_overrange = false, \ +		.slew = { \ +			.enable = false, \ +			.rate = AD5755_SLEW_RATE_64k, \ +			.step_size = AD5755_SLEW_STEP_SIZE_1, \ +		}, \ +	} + +static const struct ad5755_platform_data ad5755_default_pdata = { +	.ext_dc_dc_compenstation_resistor = false, +	.dc_dc_phase = AD5755_DC_DC_PHASE_ALL_SAME_EDGE, +	.dc_dc_freq = AD5755_DC_DC_FREQ_410kHZ, +	.dc_dc_maxv = AD5755_DC_DC_MAXV_23V, +	.dac = { +		[0] = AD5755_DEFAULT_DAC_PDATA, +		[1] = AD5755_DEFAULT_DAC_PDATA, +		[2] = AD5755_DEFAULT_DAC_PDATA, +		[3] = AD5755_DEFAULT_DAC_PDATA, +	}, +}; + +static int ad5755_probe(struct spi_device *spi) +{ +	enum ad5755_type type = spi_get_device_id(spi)->driver_data; +	const struct ad5755_platform_data *pdata = dev_get_platdata(&spi->dev); +	struct iio_dev *indio_dev; +	struct ad5755_state *st; +	int ret; + +	indio_dev = devm_iio_device_alloc(&spi->dev, 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 = &ad5755_chip_info_tbl[type]; +	st->spi = spi; +	st->pwr_down = 0xf; + +	indio_dev->dev.parent = &spi->dev; +	indio_dev->name = spi_get_device_id(spi)->name; +	indio_dev->info = &ad5755_info; +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->num_channels = AD5755_NUM_CHANNELS; + +	if (!pdata) +		pdata = &ad5755_default_pdata; + +	ret = ad5755_init_channels(indio_dev, pdata); +	if (ret) +		return ret; + +	ret = ad5755_setup_pdata(indio_dev, pdata); +	if (ret) +		return ret; + +	return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad5755_id[] = { +	{ "ad5755", ID_AD5755 }, +	{ "ad5755-1", ID_AD5755 }, +	{ "ad5757", ID_AD5757 }, +	{ "ad5735", ID_AD5735 }, +	{ "ad5737", ID_AD5737 }, +	{} +}; +MODULE_DEVICE_TABLE(spi, ad5755_id); + +static struct spi_driver ad5755_driver = { +	.driver = { +		.name = "ad5755", +		.owner = THIS_MODULE, +	}, +	.probe = ad5755_probe, +	.id_table = ad5755_id, +}; +module_spi_driver(ad5755_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5755/55-1/57/35/37 DAC"); +MODULE_LICENSE("GPL v2");  | 
