mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 08:44:41 +00:00 
			
		
		
		
	iio: potentiometer: Add driver support for AD5110
The AD5110/AD5112/AD5114 provide a nonvolatile solution for 128-/64-/32-position adjustment applications, offering guaranteed low resistor tolerance errors of ±8% and up to ±6 mA current density. Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD5110_5112_5114.pdf Signed-off-by: Mugilraj Dhavachelvan <dmugil2000@gmail.com> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com> Link: https://lore.kernel.org/r/20210814175607.48399-3-dmugil2000@gmail.com Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
This commit is contained in:
		
							parent
							
								
									88b6509b8d
								
							
						
					
					
						commit
						d03a74bfac
					
				
					 4 changed files with 361 additions and 0 deletions
				
			
		|  | @ -459,6 +459,12 @@ S:	Maintained | |||
| W:	https://parisc.wiki.kernel.org/index.php/AD1889 | ||||
| F:	sound/pci/ad1889.* | ||||
| 
 | ||||
| AD5110 ANALOG DEVICES DIGITAL POTENTIOMETERS DRIVER | ||||
| M:	Mugilraj Dhavachelvan <dmugil2000@gmail.com> | ||||
| L:	linux-iio@vger.kernel.org | ||||
| S:	Supported | ||||
| F:	drivers/iio/potentiometer/ad5110.c | ||||
| 
 | ||||
| AD525X ANALOG DEVICES DIGITAL POTENTIOMETERS DRIVER | ||||
| M:	Michael Hennerich <michael.hennerich@analog.com> | ||||
| S:	Supported | ||||
|  |  | |||
|  | @ -6,6 +6,16 @@ | |||
| 
 | ||||
| menu "Digital potentiometers" | ||||
| 
 | ||||
| config AD5110 | ||||
| 	tristate "Analog Devices AD5110 and similar Digital Potentiometer driver" | ||||
| 	depends on I2C | ||||
| 	help | ||||
| 	  Say yes here to build support for the Analog Devices AD5110, AD5112  | ||||
| 	  and AD5114 digital potentiometer chip. | ||||
| 
 | ||||
| 	  To compile this driver as a module, choose M here: the | ||||
| 	  module will be called ad5110. | ||||
| 
 | ||||
| config AD5272 | ||||
| 	tristate "Analog Devices AD5272 and similar Digital Potentiometer driver" | ||||
| 	depends on I2C | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| #
 | ||||
| 
 | ||||
| # When adding new entries keep the list in alphabetical order
 | ||||
| obj-$(CONFIG_AD5110) += ad5110.o | ||||
| obj-$(CONFIG_AD5272) += ad5272.o | ||||
| obj-$(CONFIG_DS1803) += ds1803.o | ||||
| obj-$(CONFIG_MAX5432) += max5432.o | ||||
|  |  | |||
							
								
								
									
										344
									
								
								drivers/iio/potentiometer/ad5110.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								drivers/iio/potentiometer/ad5110.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,344 @@ | |||
| // SPDX-License-Identifier: GPL-2.0+
 | ||||
| /*
 | ||||
|  * Analog Devices AD5110 digital potentiometer driver | ||||
|  * | ||||
|  * Copyright (C) 2021 Mugilraj Dhavachelvan <dmugil2000@gmail.com> | ||||
|  * | ||||
|  * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD5110_5112_5114.pdf
 | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/bitfield.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/device.h> | ||||
| #include <linux/i2c.h> | ||||
| #include <linux/module.h> | ||||
| 
 | ||||
| #include <linux/iio/iio.h> | ||||
| #include <linux/iio/sysfs.h> | ||||
| 
 | ||||
| /* AD5110 commands */ | ||||
| #define AD5110_EEPROM_WR	1 | ||||
| #define AD5110_RDAC_WR		2 | ||||
| #define AD5110_SHUTDOWN	3 | ||||
| #define AD5110_RESET		4 | ||||
| #define AD5110_RDAC_RD		5 | ||||
| #define AD5110_EEPROM_RD	6 | ||||
| 
 | ||||
| /* AD5110_EEPROM_RD data */ | ||||
| #define AD5110_WIPER_POS	0 | ||||
| #define AD5110_RESISTOR_TOL	1 | ||||
| 
 | ||||
| #define AD5110_WIPER_RESISTANCE	70 | ||||
| 
 | ||||
| struct ad5110_cfg { | ||||
| 	int max_pos; | ||||
| 	int kohms; | ||||
| 	int shift; | ||||
| }; | ||||
| 
 | ||||
| enum ad5110_type { | ||||
| 	AD5110_10, | ||||
| 	AD5110_80, | ||||
| 	AD5112_05, | ||||
| 	AD5112_10, | ||||
| 	AD5112_80, | ||||
| 	AD5114_10, | ||||
| 	AD5114_80, | ||||
| }; | ||||
| 
 | ||||
| static const struct ad5110_cfg ad5110_cfg[] = { | ||||
| 	[AD5110_10] = { .max_pos = 128, .kohms = 10 }, | ||||
| 	[AD5110_80] = { .max_pos = 128, .kohms = 80 }, | ||||
| 	[AD5112_05] = { .max_pos = 64, .kohms = 5, .shift = 1 }, | ||||
| 	[AD5112_10] = { .max_pos = 64, .kohms = 10, .shift = 1 }, | ||||
| 	[AD5112_80] = { .max_pos = 64, .kohms = 80, .shift = 1 }, | ||||
| 	[AD5114_10] = { .max_pos = 32, .kohms = 10, .shift = 2 }, | ||||
| 	[AD5114_80] = { .max_pos = 32, .kohms = 80, .shift = 2 }, | ||||
| }; | ||||
| 
 | ||||
| struct ad5110_data { | ||||
| 	struct i2c_client       *client; | ||||
| 	s16			tol;		/* resistor tolerance */ | ||||
| 	bool			enable; | ||||
| 	struct mutex            lock; | ||||
| 	const struct ad5110_cfg	*cfg; | ||||
| 	/*
 | ||||
| 	 * DMA (thus cache coherency maintenance) requires the | ||||
| 	 * transfer buffers to live in their own cache lines. | ||||
| 	 */ | ||||
| 	u8			buf[2] ____cacheline_aligned; | ||||
| }; | ||||
| 
 | ||||
| static const struct iio_chan_spec ad5110_channels[] = { | ||||
| 	{ | ||||
| 		.type = IIO_RESISTANCE, | ||||
| 		.output = 1, | ||||
| 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_OFFSET) | | ||||
| 					BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_ENABLE), | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| static int ad5110_read(struct ad5110_data *data, u8 cmd, int *val) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	mutex_lock(&data->lock); | ||||
| 	data->buf[0] = cmd; | ||||
| 	data->buf[1] = *val; | ||||
| 
 | ||||
| 	ret = i2c_master_send_dmasafe(data->client, data->buf, sizeof(data->buf)); | ||||
| 	if (ret < 0) { | ||||
| 		goto error; | ||||
| 	} else if (ret != sizeof(data->buf)) { | ||||
| 		ret = -EIO; | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = i2c_master_recv_dmasafe(data->client, data->buf, 1); | ||||
| 	if (ret < 0) { | ||||
| 		goto error; | ||||
| 	} else if (ret != 1) { | ||||
| 		ret = -EIO; | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	*val = data->buf[0]; | ||||
| 	ret = 0; | ||||
| 
 | ||||
| error: | ||||
| 	mutex_unlock(&data->lock); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int ad5110_write(struct ad5110_data *data, u8 cmd, u8 val) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	mutex_lock(&data->lock); | ||||
| 	data->buf[0] = cmd; | ||||
| 	data->buf[1] = val; | ||||
| 
 | ||||
| 	ret = i2c_master_send_dmasafe(data->client, data->buf, sizeof(data->buf)); | ||||
| 	if (ret < 0) { | ||||
| 		goto error; | ||||
| 	} else if (ret != sizeof(data->buf)) { | ||||
| 		ret = -EIO; | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = 0; | ||||
| 
 | ||||
| error: | ||||
| 	mutex_unlock(&data->lock); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int ad5110_resistor_tol(struct ad5110_data *data, u8 cmd, int val) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = ad5110_read(data, cmd, &val); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	data->tol = data->cfg->kohms * (val & GENMASK(6, 0)) * 10 / 8; | ||||
| 	if (!(val & BIT(7))) | ||||
| 		data->tol *= -1; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static ssize_t store_eeprom_show(struct device *dev, | ||||
| 				  struct device_attribute *attr, | ||||
| 				  char *buf) | ||||
| { | ||||
| 	struct iio_dev *indio_dev = dev_to_iio_dev(dev); | ||||
| 	struct ad5110_data *data = iio_priv(indio_dev); | ||||
| 	int val = AD5110_WIPER_POS; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = ad5110_read(data, AD5110_EEPROM_RD, &val); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	val = val >> data->cfg->shift; | ||||
| 	return iio_format_value(buf, IIO_VAL_INT, 1, &val); | ||||
| } | ||||
| 
 | ||||
| static ssize_t store_eeprom_store(struct device *dev, | ||||
| 				   struct device_attribute *attr, | ||||
| 				   const char *buf, size_t len) | ||||
| { | ||||
| 	struct iio_dev *indio_dev = dev_to_iio_dev(dev); | ||||
| 	struct ad5110_data *data = iio_priv(indio_dev); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = ad5110_write(data, AD5110_EEPROM_WR, 0); | ||||
| 	if (ret) { | ||||
| 		dev_err(&data->client->dev, "RDAC to EEPROM write failed\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	/* The storing of EEPROM data takes approximately 18 ms. */ | ||||
| 	msleep(20); | ||||
| 
 | ||||
| 	return len; | ||||
| } | ||||
| 
 | ||||
| static IIO_DEVICE_ATTR_RW(store_eeprom, 0); | ||||
| 
 | ||||
| static struct attribute *ad5110_attributes[] = { | ||||
| 	&iio_dev_attr_store_eeprom.dev_attr.attr, | ||||
| 	NULL | ||||
| }; | ||||
| 
 | ||||
| static const struct attribute_group ad5110_attribute_group = { | ||||
| 	.attrs = ad5110_attributes, | ||||
| }; | ||||
| 
 | ||||
| static int ad5110_read_raw(struct iio_dev *indio_dev, | ||||
| 			   struct iio_chan_spec const *chan, | ||||
| 			   int *val, int *val2, long mask) | ||||
| { | ||||
| 	struct ad5110_data *data = iio_priv(indio_dev); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	switch (mask) { | ||||
| 	case IIO_CHAN_INFO_RAW: | ||||
| 		ret = ad5110_read(data, AD5110_RDAC_RD, val); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 
 | ||||
| 		*val = *val >> data->cfg->shift; | ||||
| 		return IIO_VAL_INT; | ||||
| 	case IIO_CHAN_INFO_OFFSET: | ||||
| 		*val = AD5110_WIPER_RESISTANCE * data->cfg->max_pos; | ||||
| 		*val2 = 1000 * data->cfg->kohms + data->tol; | ||||
| 		return IIO_VAL_FRACTIONAL; | ||||
| 	case IIO_CHAN_INFO_SCALE: | ||||
| 		*val = 1000 * data->cfg->kohms + data->tol; | ||||
| 		*val2 = data->cfg->max_pos; | ||||
| 		return IIO_VAL_FRACTIONAL; | ||||
| 	case IIO_CHAN_INFO_ENABLE: | ||||
| 		*val = data->enable; | ||||
| 		return IIO_VAL_INT; | ||||
| 	default: | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int ad5110_write_raw(struct iio_dev *indio_dev, | ||||
| 			    struct iio_chan_spec const *chan, | ||||
| 			    int val, int val2, long mask) | ||||
| { | ||||
| 	struct ad5110_data *data = iio_priv(indio_dev); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	switch (mask) { | ||||
| 	case IIO_CHAN_INFO_RAW: | ||||
| 		if (val > data->cfg->max_pos || val < 0) | ||||
| 			return -EINVAL; | ||||
| 
 | ||||
| 		return ad5110_write(data, AD5110_RDAC_WR, val << data->cfg->shift); | ||||
| 	case IIO_CHAN_INFO_ENABLE: | ||||
| 		if (val < 0 || val > 1) | ||||
| 			return -EINVAL; | ||||
| 		if (data->enable == val) | ||||
| 			return 0; | ||||
| 		ret = ad5110_write(data, AD5110_SHUTDOWN, val ? 0 : 1); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 		data->enable = val; | ||||
| 		return 0; | ||||
| 	default: | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static const struct iio_info ad5110_info = { | ||||
| 	.read_raw = ad5110_read_raw, | ||||
| 	.write_raw = ad5110_write_raw, | ||||
| 	.attrs = &ad5110_attribute_group, | ||||
| }; | ||||
| 
 | ||||
| #define AD5110_COMPATIBLE(of_compatible, cfg) {	\ | ||||
| 			.compatible = of_compatible,	\ | ||||
| 			.data = &ad5110_cfg[cfg],	\ | ||||
| } | ||||
| 
 | ||||
| static const struct of_device_id ad5110_of_match[] = { | ||||
| 	AD5110_COMPATIBLE("adi,ad5110-10", AD5110_10), | ||||
| 	AD5110_COMPATIBLE("adi,ad5110-80", AD5110_80), | ||||
| 	AD5110_COMPATIBLE("adi,ad5112-05", AD5112_05), | ||||
| 	AD5110_COMPATIBLE("adi,ad5112-10", AD5112_10), | ||||
| 	AD5110_COMPATIBLE("adi,ad5112-80", AD5112_80), | ||||
| 	AD5110_COMPATIBLE("adi,ad5114-10", AD5114_10), | ||||
| 	AD5110_COMPATIBLE("adi,ad5114-80", AD5114_80), | ||||
| 	{ } | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(of, ad5110_of_match); | ||||
| 
 | ||||
| static const struct i2c_device_id ad5110_id[] = { | ||||
| 	{ "ad5110-10", AD5110_10 }, | ||||
| 	{ "ad5110-80", AD5110_80 }, | ||||
| 	{ "ad5112-05", AD5112_05 }, | ||||
| 	{ "ad5112-10", AD5112_10 }, | ||||
| 	{ "ad5112-80", AD5112_80 }, | ||||
| 	{ "ad5114-10", AD5114_10 }, | ||||
| 	{ "ad5114-80", AD5114_80 }, | ||||
| 	{ } | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(i2c, ad5110_id); | ||||
| 
 | ||||
| static int ad5110_probe(struct i2c_client *client) | ||||
| { | ||||
| 	struct device *dev = &client->dev; | ||||
| 	struct iio_dev *indio_dev; | ||||
| 	struct ad5110_data *data; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); | ||||
| 	if (!indio_dev) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	data = iio_priv(indio_dev); | ||||
| 	data->client = client; | ||||
| 	mutex_init(&data->lock); | ||||
| 	data->enable = 1; | ||||
| 	data->cfg = device_get_match_data(dev); | ||||
| 
 | ||||
| 	/* refresh RDAC register with EEPROM */ | ||||
| 	ret = ad5110_write(data, AD5110_RESET, 0); | ||||
| 	if (ret) { | ||||
| 		dev_err(dev, "Refresh RDAC with EEPROM failed\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = ad5110_resistor_tol(data, AD5110_EEPROM_RD, AD5110_RESISTOR_TOL); | ||||
| 	if (ret) { | ||||
| 		dev_err(dev, "Read resistor tolerance failed\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	indio_dev->modes = INDIO_DIRECT_MODE; | ||||
| 	indio_dev->info = &ad5110_info; | ||||
| 	indio_dev->channels = ad5110_channels; | ||||
| 	indio_dev->num_channels = ARRAY_SIZE(ad5110_channels); | ||||
| 	indio_dev->name = client->name; | ||||
| 
 | ||||
| 	return devm_iio_device_register(dev, indio_dev); | ||||
| } | ||||
| 
 | ||||
| static struct i2c_driver ad5110_driver = { | ||||
| 	.driver = { | ||||
| 		.name	= "ad5110", | ||||
| 		.of_match_table = ad5110_of_match, | ||||
| 	}, | ||||
| 	.probe_new	= ad5110_probe, | ||||
| 	.id_table	= ad5110_id, | ||||
| }; | ||||
| module_i2c_driver(ad5110_driver); | ||||
| 
 | ||||
| MODULE_AUTHOR("Mugilraj Dhavachelvan <dmugil2000@gmail.com>"); | ||||
| MODULE_DESCRIPTION("AD5110 digital potentiometer"); | ||||
| MODULE_LICENSE("GPL v2"); | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Mugilraj Dhavachelvan
						Mugilraj Dhavachelvan