From: Peter Rosin <peda@xxxxxxxxxx> Signed-off-by: Peter Rosin <peda@xxxxxxxxxx> --- Documentation/misc-devices/mcp4xxx_dpot.txt | 47 +++++ MAINTAINERS | 5 + drivers/misc/Kconfig | 15 ++ drivers/misc/Makefile | 1 + drivers/misc/mcp4xxx_dpot.c | 269 +++++++++++++++++++++++++++ drivers/misc/mcp4xxx_dpot.h | 44 +++++ 6 files changed, 381 insertions(+) create mode 100644 Documentation/misc-devices/mcp4xxx_dpot.txt create mode 100644 drivers/misc/mcp4xxx_dpot.c create mode 100644 drivers/misc/mcp4xxx_dpot.h This patch has two checkpatch errors but I really don't know how to fix them. The offending code was copied from drivers/misc/ad525x_dpot.c and the errors are: ERROR: Macros with complex values should be enclosed in parentheses #266: FILE: drivers/misc/mcp4xxx_dpot.c:129: +#define MCP4XXX_DPOT_DEVICE_SHOW_SET(name, reg) \ +MCP4XXX_DPOT_DEVICE_SHOW(name, reg) \ +MCP4XXX_DPOT_DEVICE_SET(name, reg) \ +static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, show_##name, set_##name) ERROR: Macros with complex values should be enclosed in parentheses #271: FILE: drivers/misc/mcp4xxx_dpot.c:134: +#define MCP4XXX_DPOT_DEVICE_SHOW_ONLY(name, reg) \ +MCP4XXX_DPOT_DEVICE_SHOW(name, reg) \ +static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, show_##name, NULL) I have tried to add various parentheses, but no luck. I am also unsure if I have modelled this on deprecated stuff. Is there a better place to add a digital potentiometer driver? Industrial IO perhaps? In any case, this is a sufficient implementation for my needs, and a more complex user-space api is only going to be a burden. Cheers, Peter diff --git a/Documentation/misc-devices/mcp4xxx_dpot.txt b/Documentation/misc-devices/mcp4xxx_dpot.txt new file mode 100644 index 000000000000..10ed02958775 --- /dev/null +++ b/Documentation/misc-devices/mcp4xxx_dpot.txt @@ -0,0 +1,47 @@ +--------------------------------- + MCP4XXX Digital Potentiometers +--------------------------------- + +The mcp4xxx_dpot driver exports a simple sysfs interface. This allows you to +work with the immediate resistance settings. + +--------- + Files +--------- + +Each dpot device will have its own rdac files. How many depends on the actual +part you have, as will the range of allowed values. + +This rdac file is used to program the immediate value of the device. + +----------- + Example +----------- + +Locate the device in your sysfs tree. This is probably easiest by going into +the common i2c directory and locating the device by the i2c slave address. + + # ls /sys/bus/i2c/devices/ + 0-0028 0-0050 + +So assuming the device in question is on the first i2c bus and has the slave +address of 0x28, we descend (unrelated sysfs entries have been trimmed). + + # ls /sys/bus/i2c/devices/0-0028/ + rdac0 rdac1 + +You can use simple reads/writes to access these files: + + # cd /sys/bus/i2c/devices/0-0028/ + + # cat rdac0 + 0 + # echo 128 > rdac0 + # cat rdac0 + 128 + + # cat rdac1 + 5 + # echo 35 > rdac1 + # cat rdac1 + 35 diff --git a/MAINTAINERS b/MAINTAINERS index b60e2b2369d2..c7bc2cc8051d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6600,6 +6600,11 @@ W: http://linuxtv.org S: Maintained F: drivers/media/radio/radio-maxiradio* +MPC4XXX MICROCHIP DIGITAL POTENTIOMETERS DRIVER +M: Peter Rosin <peda@xxxxxxxxxx> +S: Maintained +F: drivers/misc/mcp4xxx_dpot.* + MEDIA DRIVERS FOR RENESAS - VSP1 M: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx> L: linux-media@xxxxxxxxxxxxxxx diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 42c38525904b..a4e5e42b6b92 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -51,6 +51,21 @@ config AD525X_DPOT_SPI To compile this driver as a module, choose M here: the module will be called ad525x_dpot-spi. +config MCP4XXX_DPOT + tristate "Microchip Digital Potentiometer" + depends on I2C && SYSFS + help + If you say yes here, you get support for the Microchip + MCP4531, MCP4532, MCP4551, MCP4552, + MCP4631, MCP4632, MCP4651, MCP4652, + digital potentiometer chips. + + See Documentation/misc-devices/mcp4xxx_dpot.txt for the + userspace interface. + + This driver can also be built as a module. If so, the module + will be called mcp4xxx_dpot. + config ATMEL_TCLIB bool "Atmel AT32/AT91 Timer/Counter Library" depends on (AVR32 || ARCH_AT91) diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d056fb7186fe..bb8c2994fd98 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_BMP085_SPI) += bmp085-spi.o obj-$(CONFIG_DUMMY_IRQ) += dummy-irq.o obj-$(CONFIG_ICS932S401) += ics932s401.o obj-$(CONFIG_LKDTM) += lkdtm.o +obj-$(CONFIG_MCP4XXX_DPOT) += mcp4xxx_dpot.o obj-$(CONFIG_TIFM_CORE) += tifm_core.o obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o obj-$(CONFIG_PHANTOM) += phantom.o diff --git a/drivers/misc/mcp4xxx_dpot.c b/drivers/misc/mcp4xxx_dpot.c new file mode 100644 index 000000000000..abacae99ae42 --- /dev/null +++ b/drivers/misc/mcp4xxx_dpot.c @@ -0,0 +1,269 @@ +/* + * mcp4xxx_dpot: Driver for Microchip digital potentiometers + * Copyright (c) 2015 Axentia Technologies AB + * Author: Peter Rosin <peda@xxxxxxxxxx> + * + * DEVID #Wipers #Positions Resistor Options (kOhm) + * mcp4531 1 129 5, 10, 50, 100 + * mcp4532 1 129 5, 10, 50, 100 + * mcp4551 1 257 5, 10, 50, 100 + * mcp4552 1 257 5, 10, 50, 100 + * mcp4631 2 129 5, 10, 50, 100 + * mcp4632 2 129 5, 10, 50, 100 + * mcp4651 2 257 5, 10, 50, 100 + * mcp4652 2 257 5, 10, 50, 100 + * + * See Documentation/misc-devices/mcp4xxx_dpot.txt for more info. + * + * derived from ad525x.c + * Copyright (c) 2009-2010 Analog Devices, Inc. + * Author: Michael Hennerich <hennerich@xxxxxxxxxxxxxxxxxxxx> + * + * derived from ad5258.c + * Copyright (c) 2009 Cyber Switching, Inc. + * Author: Chris Verges <chrisv@xxxxxxxxxxxxxxxxxx> + * + * derived from ad5252.c + * Copyright (c) 2006-2011 Michael Hennerich <hennerich@xxxxxxxxxxxxxxxxxxxx> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include "mcp4xxx_dpot.h" + +/* + * Client data (each client gets its own) + */ + +struct mcp4xxx_dpot_data { + struct i2c_client *client; + struct mutex update_lock; + unsigned max_pos; + unsigned long devid; + unsigned uid; + unsigned wipers; +}; + +static s32 mcp4xxx_dpot_read(struct mcp4xxx_dpot_data *dpot, u8 reg) +{ + s32 value; + + value = i2c_smbus_read_word_data(dpot->client, (reg << 4) | 0xc); + if (value < 0) + return value; + return ((value >> 8) & 0xff) | ((value & 0xff) << 8); +} + +static s32 mcp4xxx_dpot_write(struct mcp4xxx_dpot_data *dpot, + u8 reg, u16 value) +{ + return i2c_smbus_write_byte_data(dpot->client, + (reg << 4) | value >> 8, + value & 0xff); +} + +/* sysfs functions */ + +static ssize_t mcp4xxx_sysfs_show_reg(struct device *dev, + struct device_attribute *attr, + char *buf, u32 reg) +{ + struct mcp4xxx_dpot_data *data = dev_get_drvdata(dev); + s32 value; + + mutex_lock(&data->update_lock); + value = mcp4xxx_dpot_read(data, reg); + mutex_unlock(&data->update_lock); + + if (value < 0) + return -EINVAL; + + return sprintf(buf, "%u\n", value); +} + +static ssize_t mcp4xxx_sysfs_set_reg(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, u32 reg) +{ + struct mcp4xxx_dpot_data *data = dev_get_drvdata(dev); + unsigned long value; + int err; + + err = kstrtoul(buf, 10, &value); + if (err) + return err; + + if (value >= data->max_pos) + value = data->max_pos - 1; + + mutex_lock(&data->update_lock); + mcp4xxx_dpot_write(data, reg, value); + mutex_unlock(&data->update_lock); + + return count; +} + +/* ------------------------------------------------------------------------- */ + +#define MCP4XXX_DPOT_DEVICE_SHOW(_name, _reg) \ +static ssize_t show_##_name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return mcp4xxx_sysfs_show_reg(dev, attr, buf, _reg); \ +} + +#define MCP4XXX_DPOT_DEVICE_SET(_name, _reg) \ +static ssize_t set_##_name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + return mcp4xxx_sysfs_set_reg(dev, attr, buf, count, _reg); \ +} + +#define MCP4XXX_DPOT_DEVICE_SHOW_SET(name, reg) \ +MCP4XXX_DPOT_DEVICE_SHOW(name, reg) \ +MCP4XXX_DPOT_DEVICE_SET(name, reg) \ +static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, show_##name, set_##name) + +#define MCP4XXX_DPOT_DEVICE_SHOW_ONLY(name, reg) \ +MCP4XXX_DPOT_DEVICE_SHOW(name, reg) \ +static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, show_##name, NULL) + +MCP4XXX_DPOT_DEVICE_SHOW_SET(rdac0, DPOT_ADDR_RDAC | DPOT_RDAC0); +MCP4XXX_DPOT_DEVICE_SHOW_SET(rdac1, DPOT_ADDR_RDAC | DPOT_RDAC1); + +static const struct attribute *mcp4xxx_dpot_attrib_wipers[] = { + &dev_attr_rdac0.attr, + &dev_attr_rdac1.attr, + NULL +}; + +/* ------------------------------------------------------------------------- */ + +static int mcp4xxx_dpot_add_files(struct device *dev, unsigned rdac) +{ + int err = sysfs_create_file(&dev->kobj, + mcp4xxx_dpot_attrib_wipers[rdac]); + + if (err) + dev_err(dev, "failed to register sysfs hooks for RDAC%d\n", + rdac); + + return err; +} + +static inline void mcp4xxx_dpot_remove_files(struct device *dev, unsigned rdac) +{ + sysfs_remove_file(&dev->kobj, + mcp4xxx_dpot_attrib_wipers[rdac]); +} + +static int mcp4xxx_dpot_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + unsigned long devid = id->driver_data; + const char *name = id->name; + struct mcp4xxx_dpot_data *data; + int i, err = 0; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "SMBUS Word Data not Supported\n"); + return -EIO; + } + + data = kzalloc(sizeof(struct mcp4xxx_dpot_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + dev_set_drvdata(dev, data); + mutex_init(&data->update_lock); + + data->client = client; + data->devid = devid; + + data->max_pos = (1 << DPOT_MAX_POS(devid)) + 1; + data->uid = DPOT_UID(devid); + data->wipers = DPOT_WIPERS(devid); + + for (i = DPOT_RDAC0; i < MAX_RDACS; i++) { + if (!(data->wipers & (1 << i))) + continue; + if (mcp4xxx_dpot_add_files(dev, i)) + goto exit_remove_files; + } + + dev_info(dev, "%s %d-Position Digital Potentiometer registered\n", + name, data->max_pos); + + return 0; + +exit_remove_files: + for (i = DPOT_RDAC0; i < MAX_RDACS; i++) { + if (!(data->wipers & (1 << i))) + continue; + mcp4xxx_dpot_remove_files(dev, i); + } + + kfree(data); + dev_set_drvdata(dev, NULL); +exit: + dev_err(dev, "failed to create client for %s ID 0x%lX\n", + name, devid); + return err; +} + +static int mcp4xxx_dpot_i2c_remove(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct mcp4xxx_dpot_data *data = dev_get_drvdata(dev); + int i; + + for (i = DPOT_RDAC0; i < MAX_RDACS; i++) { + if (!(data->wipers & (1 << i))) + continue; + mcp4xxx_dpot_remove_files(dev, i); + } + + kfree(data); + + return 0; +} + +static const struct i2c_device_id mcp4xxx_dpot_id[] = { + {"mcp4531", MCP4531_ID}, + {"mcp4532", MCP4532_ID}, + {"mcp4551", MCP4551_ID}, + {"mcp4552", MCP4552_ID}, + {"mcp4631", MCP4631_ID}, + {"mcp4632", MCP4632_ID}, + {"mcp4651", MCP4651_ID}, + {"mcp4652", MCP4652_ID}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mcp4xxx_dpot_id); + +static struct i2c_driver mcp4xxx_dpot_i2c_driver = { + .driver = { + .name = "mcp4xxx_dpot", + .owner = THIS_MODULE, + }, + .probe = mcp4xxx_dpot_i2c_probe, + .remove = mcp4xxx_dpot_i2c_remove, + .id_table = mcp4xxx_dpot_id, +}; + +module_i2c_driver(mcp4xxx_dpot_i2c_driver); + +MODULE_AUTHOR("Peter Rosin <peda@xxxxxxxxxx>"); +MODULE_DESCRIPTION("MCP4XXX digital potentiometer I2C bus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/mcp4xxx_dpot.h b/drivers/misc/mcp4xxx_dpot.h new file mode 100644 index 000000000000..34cf077b67ad --- /dev/null +++ b/drivers/misc/mcp4xxx_dpot.h @@ -0,0 +1,44 @@ +/* + * Driver for the Microchip digital potentiometers + * + * Copyright (C) 2015 Peter Rosin, Axentia Technologies AB + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _MCP4XXX_DPOT_H_ +#define _MCP4XXX_DPOT_H_ + +#include <linux/types.h> + +#define DPOT_CONF(wipers, max_pos, uid) \ + ((((wipers) & 0xff) << 10) | \ + ((max_pos & 0xf) << 6) | (uid & 0x3f)) + +#define DPOT_UID(conf) (conf & 0x3f) +#define DPOT_MAX_POS(conf) ((conf >> 6) & 0xf) +#define DPOT_WIPERS(conf) ((conf >> 10) & 0xff) + +#define BRDAC0 (1 << 0) +#define BRDAC1 (1 << 1) +#define MAX_RDACS 2 + +enum mcp4xxx_dpot_devid { + MCP4531_ID = DPOT_CONF(BRDAC0, 7, 0), + MCP4532_ID = DPOT_CONF(BRDAC0, 7, 1), + MCP4551_ID = DPOT_CONF(BRDAC0, 8, 2), + MCP4552_ID = DPOT_CONF(BRDAC0, 8, 3), + MCP4631_ID = DPOT_CONF(BRDAC0 | BRDAC1, 7, 4), + MCP4632_ID = DPOT_CONF(BRDAC0 | BRDAC1, 7, 5), + MCP4651_ID = DPOT_CONF(BRDAC0 | BRDAC1, 8, 6), + MCP4652_ID = DPOT_CONF(BRDAC0 | BRDAC1, 8, 7), +}; + +#define DPOT_RDAC0 0 +#define DPOT_RDAC1 1 + +#define DPOT_RDAC_MASK 0x1F + +#define DPOT_ADDR_RDAC 0 + +#endif -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html