Re: [PATCH] iio: Add support for LTC26xx I2C DAC

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

 



Hello,

> This driver adds support for the Linear LTC26x6, LTC26x7 and LTC26x9 I2C
> DAC chips. The driver exposes as iio device. It provides interface to all
> channels of the DAC plus an additional channel (channel 15) which allows
> writing to all channels simultanously.

currently there is no way in IIO to express such "virtual" channels

> The scale factor is calculated based on the reference voltage. The
> reference voltage is set through a regulator in the device-tree.

link to datasheet would be nice
 
> Binding documentation is provided in ltc26xx.txt.
> 
> Signed-off-by: Marc Andre <marc.andre@xxxxxxxxxx>
> ---
>  .../devicetree/bindings/iio/dac/ltc26xx.txt        |  50 +++
>  drivers/iio/dac/Kconfig                            |  12 +
>  drivers/iio/dac/Makefile                           |   1 +
>  drivers/iio/dac/ltc26xx.c                          | 457 +++++++++++++++++++++
>  4 files changed, 520 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/dac/ltc26xx.txt
>  create mode 100644 drivers/iio/dac/ltc26xx.c
> 
> diff --git a/Documentation/devicetree/bindings/iio/dac/ltc26xx.txt b/Documentation/devicetree/bindings/iio/dac/ltc26xx.txt
> new file mode 100644
> index 0000000..9b51aae
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/dac/ltc26xx.txt
> @@ -0,0 +1,50 @@
> +Driver for LTC2606, LTC2616, LTC2626, LTC2607, LTC2617, LTC2627,
> +LTC2609, LTC2619 and LTC2629 DAC with I2C interface
> +
> +Required properties:
> +- compatible: e.g. lltc,ltc2617
> +- reg: i2c address
> +
> +Required parameters for ltc26x6 and ltc26x7:
> +- vref-supply: Reference voltage supply
> +
> +Required parameters for ltc26x9:
> +- vref_a-supply: Reference voltage supply channel A
> +- vref_b-supply: Reference voltage supply channel B
> +- vref_c-supply: Reference voltage supply channel C
> +- vref_d-supply: Reference voltage supply channel D
> +
> +Available compatible strings. (defines the number of channels exposed)
> +lltc,ltc2606
> +lltc,ltc2616
> +lltc,ltc2626
> +lltc,ltc2607
> +lltc,ltc2617
> +lltc,ltc2627
> +lltc,ltc2609
> +lltc,ltc2619
> +lltc,ltc2629
> +
> +The driver registers a iio_device. The number of iio channels registered is

an iio_device

> +the number of channels available by the chip plus 1. The additional
> +channel (channel 15) can be used to write to all output simultanously.

outputs

it is not clear why channel 15 is the virtual channel; why 15?
MAX_CHANNEL is 4
is 15 an arbitraty number >= 4?

> +The scale parameter of channel 15 is always 0.

the value 1 might make more sense or no scale at all

> +
> +Example
> +	i2c@7000c400 {
> +		ltc2617: ltc2617@10 {
> +			compatible = "lltc,ltc2617";
> +			reg = <0x10>;
> +			vref-supply = <&vdd_ltc_vref>;
> +		};
> +	};
> +
> +	regulators {
> +		vdd_ltc_vref: regulator@100 {
> +			compatible = "regulator-fixed";
> +			reg = <100>;
> +			regulator-name = "LTC26xx VREF";
> +			regulator-min-microvolt = <2500000>;
> +			regulator-max-microvolt = <2500000>;
> +		};
> +	};
> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
> index e701e28..aa3e637 100644
> --- a/drivers/iio/dac/Kconfig
> +++ b/drivers/iio/dac/Kconfig
> @@ -142,6 +142,18 @@ config AD7303
>  	  To compile this driver as module choose M here: the module will be called
>  	  ad7303.
>  
> +config LTC26xx
> +	tristate "LTC26x6, LTC26x7 and LTC26x9 DAC driver"
> +	depends on I2C
> +	---help---
> +	  Say Y here if you want to build a driver for the Linear
> +	  LTC2606, LTC2616, LTC2626, LTC2607, LTC2617, LTC2627,
> +	  LTC2609, LTC2619 or LTC2629 digital-to-analog converter (DAC)
> +	  with I2C interface
> +
> +	  To compile this driver as a module chose M here: the module
> +	  will be called ltc26xx

often we have a full stop (.) at the end :)

> +
>  config M62332
>  	tristate "Mitsubishi M62332 DAC driver"
>  	depends on I2C
> diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
> index 63ae056..74bc8cc 100644
> --- a/drivers/iio/dac/Makefile
> +++ b/drivers/iio/dac/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_AD5764) += ad5764.o
>  obj-$(CONFIG_AD5791) += ad5791.o
>  obj-$(CONFIG_AD5686) += ad5686.o
>  obj-$(CONFIG_AD7303) += ad7303.o
> +obj-$(CONFIG_LTC26xx) += ltc26xx.o
>  obj-$(CONFIG_M62332) += m62332.o
>  obj-$(CONFIG_MAX517) += max517.o
>  obj-$(CONFIG_MAX5821) += max5821.o
> diff --git a/drivers/iio/dac/ltc26xx.c b/drivers/iio/dac/ltc26xx.c
> new file mode 100644
> index 0000000..4f9b381
> --- /dev/null
> +++ b/drivers/iio/dac/ltc26xx.c
> @@ -0,0 +1,457 @@
> +/*
> + *  ltc26xx.c - Support for LTC26x6, LTC26x7 and LTC26x9 I2C DAC chips
> + *
> + *  Copyright (C) 2015 Marc Andre <marc.andre@xxxxxxxxxx>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; either version 2 of the License, or
> + *  (at your option) any later version.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/jiffies.h>
> +#include <linux/i2c.h>
> +#include <linux/err.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +
> +#define LTC26xx_DRV_NAME	"ltc26xx"

XX maybe?

> +
> +#define MAX_CHANNEL		4

LTC26XX_ prefix please

> +
> +/* Commands to LTC26xx */
> +#define COMMAND_WR		0x00 /* Write only */
> +#define COMMAND_UP		0x01 /* Update only */
> +#define COMMAND_WR_UP		0x03 /* Write & Update */
> +#define COMMAND_PD		0x04 /* Power Down */

LTC26XX_ prefix please

> +
> +static int ltc26xx_suspend(struct device *dev);
> +static int ltc26xx_resume(struct device *dev);

I don't think these forward decls are needed

> +
> +enum ltc26xx_device_ids {
> +	ID_LTC2606,
> +	ID_LTC2616,
> +	ID_LTC2626,
> +	ID_LTC2607,
> +	ID_LTC2617,
> +	ID_LTC2627,
> +	ID_LTC2609,
> +	ID_LTC2619,
> +	ID_LTC2629
> +};
> +
> +struct ltc26xx_data {
> +	struct i2c_client	*client;
> +	struct regulator	*reg_vref[MAX_CHANNEL];
> +	int channels;
> +};
> +
> +static long ltc26xx_get_scale_nv(struct ltc26xx_data *data,
> +				 int channel)
> +{
> +	long long vref_nv;
> +	long scale_nv;
> +
> +	/* The generic channel 15 always returns 0 scale */
> +	if (channel > MAX_CHANNEL)

should be >=?

> +		return 0;
> +
> +	if (data->reg_vref[channel]) {
> +		vref_nv = regulator_get_voltage(data->reg_vref[channel]);
> +	} else {
> +		/* no regulator for the channel. This means we have a single
> +		 * reference supply.
> +		 */
> +		vref_nv = regulator_get_voltage(data->reg_vref[0]);
> +	}
> +	vref_nv *= 1000;
> +
> +	/* Corresponds to Vref / 2^(bits) */
> +	scale_nv = vref_nv >> 16;
> +
> +	return scale_nv;
> +}
> +
> +/*

this is not a proper kerneldoc comment

> + * channel: channel no or 0xf for all channels
> + * command: Command to LTC26xx
> + */
> +static int ltc26xx_set_value(struct iio_dev *indio_dev,
> +			     long val, int channel, int command)
> +{
> +	struct ltc26xx_data *data = iio_priv(indio_dev);
> +	struct i2c_client *client = data->client;
> +	u8 outbuf[3];
> +	int res;
> +
> +	if (val < 0 || val >= 65536)
> +		return -EINVAL;
> +
> +	outbuf[0] = (channel & 0xf) | ((command & 0x0f) << 4);
> +	outbuf[1] = (val >> 8) & 0xff;
> +	outbuf[2] = (val & 0xff);
> +
> +	res = i2c_master_send(client, outbuf, 3);
> +	if (res < 0)
> +		return res;
> +	else if (res != 3)
> +		return -EIO;
> +	else
> +		return 0;
> +}
> +
> +static int ltc26xx_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan,
> +			    int *val,
> +			    int *val2,
> +			    long m)
> +{
> +	struct ltc26xx_data *data = iio_priv(indio_dev);
> +	unsigned long scale_nv;
> +	int channel = chan->channel;
> +
> +	switch (m) {

maybe support read on _RAW as well

> +	case IIO_CHAN_INFO_SCALE:
> +		scale_nv = ltc26xx_get_scale_nv(data, channel);
> +		*val =  scale_nv / 1000000000;
> +		*val2 = scale_nv % 1000000000;
> +		return IIO_VAL_INT_PLUS_NANO;
> +	default:
> +		break;
> +	}
> +	return -EINVAL;
> +}
> +
> +static int ltc26xx_write_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int val, int val2, long mask)
> +{
> +	int ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:

check that val2 is 0
maybe do the validation of val here as well...

> +		ret = ltc26xx_set_value(indio_dev, val, chan->channel,
> +					COMMAND_WR_UP);

just return, drop ret temporary variable

> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int ltc26xx_suspend(struct device *dev)
> +{
> +	u8 outbuf[3];
> +	int ret;
> +	int i;
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct iio_dev *indio_dev =
> +		(struct iio_dev *)i2c_get_clientdata(client);
> +	struct ltc26xx_data *data = iio_priv(indio_dev);
> +
> +	outbuf[0] = (COMMAND_PD << 4) | 0xf; /* Suspend all channels */
> +	outbuf[1] = 0;
> +	outbuf[2] = 0;
> +
> +	/* Send DAC into suspend */
> +	ret = i2c_master_send(client, outbuf, 3);
> +	if (ret != 3) {
> +		dev_err(dev, "Error sending suspend i2c command: %d", ret);
> +		goto exit;
> +	}
> +	ret = 0;
> +
> +	/* Disable vref regulators */
> +	for (i = 0; i < MAX_CHANNEL; i++) {
> +		if (data->reg_vref[i]) {
> +			ret = regulator_disable(data->reg_vref[i]);
> +			if (ret) {
> +				dev_err(dev,
> +					"Error disabling regulator %d: %d",
> +					i, ret);
> +				goto exit;
> +			}
> +		}
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +static int ltc26xx_resume(struct device *dev)
> +{
> +	u8 outbuf[3];
> +	int ret;
> +	int i;
> +	struct i2c_client *client = to_i2c_client(dev);
> +	struct iio_dev *indio_dev =
> +		(struct iio_dev *)i2c_get_clientdata(client);
> +	struct ltc26xx_data *data = iio_priv(indio_dev);
> +
> +	/* Enable all vref regulators */
> +	for (i = 0; i < MAX_CHANNEL; i++) {
> +		if (data->reg_vref[i]) {
> +			ret = regulator_enable(data->reg_vref[i]);
> +			if (ret) {
> +				dev_err(dev,
> +					"Error enabling regulator %d: %d",
> +					i, ret);
> +				goto exit;
> +			}
> +		}
> +	}
> +
> +	/* Resume DAC */
> +	outbuf[0] = (COMMAND_UP << 4) | 0xf; /* Resume all channels */
> +	outbuf[1] = 0;
> +	outbuf[2] = 0;
> +
> +	ret = i2c_master_send(to_i2c_client(dev), outbuf, 3);
> +	if (ret != 3) {
> +		dev_err(dev, "Error sending resume i2c command: %d", ret);
> +		goto exit;
> +	}
> +	ret = 0;
> +
> +exit:
> +	return ret;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(ltc26xx_pm_ops, ltc26xx_suspend, ltc26xx_resume);
> +#define LTC26xx_PM_OPS (&ltc26xx_pm_ops)
> +#else
> +#define LTC26xx_PM_OPS NULL
> +#endif
> +
> +static const struct iio_info ltc26xx_info = {
> +	.read_raw = ltc26xx_read_raw,
> +	.write_raw = ltc26xx_write_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define LTC26xx_CHANNEL(chan) {				\
> +	.type = IIO_VOLTAGE,				\
> +	.indexed = 1,					\
> +	.output = 1,					\
> +	.channel = (chan),				\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\
> +	BIT(IIO_CHAN_INFO_SCALE),			\
> +}
> +
> +static const struct iio_chan_spec ltc26xx_channels[] = {
> +	LTC26xx_CHANNEL(15), /* All channels simultanously */
> +	LTC26xx_CHANNEL(0),
> +	LTC26xx_CHANNEL(1),
> +	LTC26xx_CHANNEL(2),
> +	LTC26xx_CHANNEL(3),
> +};
> +
> +static int ltc26xx_get_regulators(struct i2c_client *client)
> +{
> +	int err = 0;
> +	int i;
> +	struct regulator *reg;
> +	struct iio_dev *indio_dev =
> +		(struct iio_dev *)i2c_get_clientdata(client);
> +	struct ltc26xx_data *data = iio_priv(indio_dev);
> +
> +	for (i = 0; i < MAX_CHANNEL; i++)
> +		data->reg_vref[i] = 0;
> +
> +	/* LTC26x6 and LTCx7 have only one reference voltage */
> +	if (data->channels == 1 || data->channels == 2) {

this could also go into a chip properties table

> +		reg = devm_regulator_get(&client->dev, "vref");

lot of duplicate code (_get, _enable, log) here, put in a separate 
function?

> +		if (IS_ERR(reg)) {
> +			dev_err(&client->dev,
> +				"Can't get regulator vref: %ld\n",
> +				PTR_ERR(reg));
> +			err = PTR_ERR(reg);
> +			goto exit_cleanup;
> +		}
> +
> +		err = regulator_enable(reg);
> +		if (err < 0) {
> +			dev_err(&client->dev,
> +				"Error enabling regulator vref: %d\n",
> +				err);
> +			goto exit_cleanup;
> +		}
> +
> +		dev_info(&client->dev,
> +			 "Using voltage reference %duV",
> +			 regulator_get_voltage(reg));
> +
> +		data->reg_vref[0] = reg;
> +	}
> +	/* LTC26x9 have 4 separate reference voltages */
> +	else {
> +		for (i = 0; i < 4; i++) {
> +			char *name = "vref_a";
> +
> +			name[5] = i + 'a';

efficielt, but a sprintf() might be clearer

> +			reg = devm_regulator_get(&client->dev, name);
> +			if (IS_ERR(reg)) {
> +				dev_err(&client->dev,
> +					"Can't get regulator %s: %ld\n",
> +					name,
> +					PTR_ERR(reg));
> +				err = PTR_ERR(reg);
> +				goto exit_cleanup;
> +			}
> +
> +			err = regulator_enable(reg);
> +			if (err < 0) {
> +				dev_err(&client->dev,
> +					"Error enabling regulator %s: %d\n",
> +					name,
> +					err);
> +				goto exit_cleanup;
> +			}
> +
> +			dev_info(&client->dev,
> +				 "Using voltage reference for %s: %duV",
> +				 name,
> +				regulator_get_voltage(reg));
> +
> +			data->reg_vref[i] = reg;
> +		}
> +	}
> +
> +	return 0;
> +
> +exit_cleanup:
> +	for (i = 0; i < MAX_CHANNEL; i++) {
> +		if (data->reg_vref[i]) {
> +			regulator_disable(data->reg_vref[i]);
> +			devm_regulator_put(data->reg_vref[i]);
> +		}
> +	}
> +	return err;
> +}
> +
> +static int ltc26xx_probe(struct i2c_client *client,
> +			 const struct i2c_device_id *id)
> +{
> +	struct ltc26xx_data *data;
> +	struct iio_dev *indio_dev;
> +	int err;
> +
> +	indio_dev = iio_device_alloc(sizeof(*data));

devm_iio_device_alloc()

> +	if (!indio_dev) {
> +		err = -ENOMEM;

just return?

> +		goto exit;
> +	}
> +	data = iio_priv(indio_dev);
> +	i2c_set_clientdata(client, indio_dev);
> +	data->client = client;
> +
> +	/* establish that the iio_dev is a child of the i2c device */
> +	indio_dev->dev.parent = &client->dev;
> +
> +	/* Define channel count based on DAC type */

please use a static const table to record chip properties and look up by 
ID

> +	if (id->driver_data == ID_LTC2606 ||
> +	    id->driver_data == ID_LTC2616 ||
> +	    id->driver_data == ID_LTC2626) {
> +		indio_dev->num_channels = 2;
> +		data->channels = 1;
> +	} else if (id->driver_data == ID_LTC2607 ||
> +		   id->driver_data == ID_LTC2617 ||
> +		   id->driver_data == ID_LTC2627) {
> +		indio_dev->num_channels = 3;
> +		data->channels = 2;
> +	} else {
> +		indio_dev->num_channels = 5;
> +		data->channels = 4;
> +	}
> +	indio_dev->channels = ltc26xx_channels;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->info = &ltc26xx_info;
> +
> +	err = ltc26xx_get_regulators(client);
> +	if (err) {
> +		dev_err(&client->dev,
> +			"Failed to initialized vref: %d. Aborting.",

drop "Aborting"

> +			err);
> +		goto exit_free_device;
> +	}
> +
> +	err = iio_device_register(indio_dev);
> +	if (err) {
> +		dev_err(&client->dev,
> +			"Failed to register iio device: %d. Aborting.",
> +			err);
> +		goto exit_free_device;
> +	}
> +
> +	dev_info(&client->dev, "LTC26xx DAC registered\n");
> +
> +	return 0;
> +
> +exit_free_device:
> +	iio_device_free(indio_dev);
> +exit:
> +	return err;
> +}
> +
> +static int ltc26xx_remove(struct i2c_client *client)
> +{
> +	iio_device_unregister(i2c_get_clientdata(client));
> +	iio_device_free(i2c_get_clientdata(client));
> +
> +	dev_dbg(&client->dev, "LTC26xx DAC unregistered\n");
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ltc26xx_id[] = {
> +	{ "ltc2606", ID_LTC2606 },
> +	{ "ltc2616", ID_LTC2616 },
> +	{ "ltc2626", ID_LTC2626 },
> +	{ "ltc2607", ID_LTC2607 },
> +	{ "ltc2617", ID_LTC2617 },
> +	{ "ltc2627", ID_LTC2627 },
> +	{ "ltc2609", ID_LTC2609 },
> +	{ "ltc2619", ID_LTC2619 },
> +	{ "ltc2629", ID_LTC2629 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ltc26xx_id);
> +
> +static const struct of_device_id ltc26xx_dt_ids[] = {
> +	{ .compatible = "lltc,ltc2606", .data = (void *)&ltc26xx_id[0] },

.data = ... is ugly, really needed?

> +	{ .compatible = "lltc,ltc2616", .data = (void *)&ltc26xx_id[1] },
> +	{ .compatible = "lltc,ltc2626", .data = (void *)&ltc26xx_id[2] },
> +	{ .compatible = "lltc,ltc2607", .data = (void *)&ltc26xx_id[3] },
> +	{ .compatible = "lltc,ltc2617", .data = (void *)&ltc26xx_id[4] },
> +	{ .compatible = "lltc,ltc2627", .data = (void *)&ltc26xx_id[5] },
> +	{ .compatible = "lltc,ltc2609", .data = (void *)&ltc26xx_id[6] },
> +	{ .compatible = "lltc,ltc2619", .data = (void *)&ltc26xx_id[7] },
> +	{ .compatible = "lltc,ltc2629", .data = (void *)&ltc26xx_id[8] },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ltc26xx_dt_ids);
> +
> +static struct i2c_driver ltc26xx_driver = {
> +	.driver = {
> +		.name	= LTC26xx_DRV_NAME,
> +		.pm	= LTC26xx_PM_OPS,
> +		.of_match_table = of_match_ptr(ltc26xx_dt_ids),
> +	},
> +	.probe		= ltc26xx_probe,
> +	.remove		= ltc26xx_remove,
> +	.id_table	= ltc26xx_id,
> +};
> +module_i2c_driver(ltc26xx_driver);
> +
> +MODULE_AUTHOR("Marc Andre <marc.andre@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("LTC26x6, LTC26x7 and LTC26x9 DAC driver");
> +MODULE_LICENSE("GPL");
> 

-- 

Peter Meerwald
+43-664-2444418 (mobile)
--
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