[<prev] [next>] [day] [month] [year] [list]
Message-ID:
<BE1P281MB2420E8DFF47AD9754CEB3970EF972@BE1P281MB2420.DEUP281.PROD.OUTLOOK.COM>
Date: Fri, 30 Aug 2024 11:49:56 +0000
From: "Sperling, Tobias" <Tobias.Sperling@...ting.com>
To: "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
"linux-hwmon@...r.kernel.org" <linux-hwmon@...r.kernel.org>
CC: "jdelvare@...e.com" <jdelvare@...e.com>, "linux@...ck-us.net"
<linux@...ck-us.net>, "Sperling, Tobias" <Tobias.Sperling@...ting.com>
Subject: [PATCH 2/2] hwmon: Add driver for ADS71x8
>From 4b2836f3984ceee899b55448fc4b25cf27e5912e Mon Sep 17 00:00:00 2001
From: Tobias Sperling <tobias.sperling@...ting.com>
Date: Fri, 23 Aug 2024 12:07:50 +0200
Subject: [PATCH 2/2] hwmon: Add driver for ADS71x8
Add driver for ADS7128 and ADS7138 12-bit, 8-channel analog-to-digital
converters. These ADCs have a wide operating range and a wide feature
set. Communication is based on an I2C interface.
The driver provides the functionality of manually reading single channels
or sequentially reading all channels automatically.
Signed-off-by: Tobias Sperling <tobias.sperling@...ting.com>
---
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/ads71x8.c | 702 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 713 insertions(+)
create mode 100644 drivers/hwmon/ads71x8.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b60fe2e58ad6..062ff1dfc8fa 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2090,6 +2090,16 @@ config SENSORS_ADC128D818
This driver can also be built as a module. If so, the module
will be called adc128d818.
+config SENSORS_ADS71X8
+ tristate "Texas Instruments ADS7128 and ADS7138"
+ depends on I2C
+ help
+ If you say yes here you get support for Texas Instruments ADS7128 and
+ ADS7138 8-channel A/D converters with 12-bit resolution.
+
+ This driver can also be built as a module. If so, the module
+ will be called ads71x8.
+
config SENSORS_ADS7828
tristate "Texas Instruments ADS7828 and compatibles"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b1c7056c37db..e6488368b890 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
obj-$(CONFIG_SENSORS_ADM1177) += adm1177.o
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
+obj-$(CONFIG_SENSORS_ADS71X8) += ads71x8.o
obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o
obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o
obj-$(CONFIG_SENSORS_ADT7X10) += adt7x10.o
diff --git a/drivers/hwmon/ads71x8.c b/drivers/hwmon/ads71x8.c
new file mode 100644
index 000000000000..f9334ba7187c
--- /dev/null
+++ b/drivers/hwmon/ads71x8.c
@@ -0,0 +1,702 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ads71x8.c - driver for TI ADS71x8 8-channel A/D converter and compatibles
+ *
+ * For further information, see the Documentation/hwmon/ads71x8.rst file.
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+
+#define MODULE_NAME "ads71x8"
+
+/* AVDD (VREF) operating range */
+#define ADS71x8_AVDD_MV_MIN 2350
+#define ADS71x8_AVDD_MV_MAX 5500
+
+/* ADS71x8 operation codes */
+#define ADS71x8_OPCODE_WRITE 0x08
+#define ADS71x8_OPCODE_SET_BIT 0x18
+#define ADS71x8_OPCODE_BLOCK_WRITE 0x28
+#define ADS71x8_OPCODE_BLOCK_READ 0x30
+
+/* ADS71x8 registers */
+#define ADS71x8_REG_GENERAL_CFG 0x01
+#define ADS71x8_REG_OSR_CFG 0x03
+#define ADS71x8_REG_OPMODE_CFG 0x04
+#define ADS71x8_REG_SEQUENCE_CFG 0x10
+#define ADS71x8_REG_CHANNEL_SEL 0x11
+#define ADS71x8_REG_AUTO_SEQ_CH_SEL 0x12
+#define ADS71x8_REG_ALERT_CH_SEL 0x14
+#define ADS71x8_REG_EVENT_FLAG 0x18
+#define ADS71x8_REG_EVENT_HIGH_FLAG 0x1A
+#define ADS71x8_REG_EVENT_LOW_FLAG 0x1C
+#define ADS71x8_REG_HIGH_TH_CH0 0x21
+#define ADS71x8_REG_LOW_TH_CH0 0x23
+#define ADS71x8_REG_MAX_CH0_LSB 0x60
+#define ADS71x8_REG_MIN_CH0_LSB 0x80
+#define ADS71x8_REG_RECENT_CH0_LSB 0xA0
+
+/*
+ * Modes after ADS71x8_MODE_MAX can't be selected by configuration
+ * and are only intended for internal use of the driver.
+ */
+enum ads71x8_modes { ADS71x8_MODE_MANUAL, ADS71x8_MODE_AUTO,
+ ADS71x8_MODE_MAX, ADS71x8_MODE_AUTO_IRQ };
+
+/* Client specific data */
+struct ads71x8_data {
+ const struct i2c_device_id *id;
+ struct i2c_client *client;
+ struct device *hwmon_dev;
+ int vref; /* Reference voltage in mV */
+ struct mutex lock;
+ u8 mode;
+ u16 interval_us; /* Interval in us a new conversion is triggered */
+ long alarms; /* State of window comparator events */
+};
+
+struct ads71x8_val_map {
+ u16 val;
+ u8 bits;
+};
+
+static const struct ads71x8_val_map ads71x8_intervals_us[] = {
+ { 1, 0x0 }, { 2, 0x02 }, { 3, 0x03 }, { 4, 0x04 }, { 6, 0x05 },
+ { 8, 0x06 }, { 12, 0x07 }, { 16, 0x08 }, { 24, 0x09 }, { 32, 0x10 },
+ { 48, 0x11 }, { 64, 0x12 }, { 96, 0x13 }, { 128, 0x14 },
+ { 192, 0x15 }, { 256, 0x16 }, { 384, 0x17 }, { 512, 0x18 },
+ { 768, 0x19 }, { 1024, 0x1A }, { 1536, 0x1B }, { 2048, 0x1C },
+ { 3072, 0x1D }, { 4096, 0x1E }, { 6144, 0x1F }
+};
+
+/* List of supported devices */
+enum ads71x8_chips { ads7128, ads7138 };
+
+static const struct i2c_device_id ads71x8_device_ids[] = {
+ { "ads7128", ads7128 },
+ { "ads7138", ads7138 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ads71x8_device_ids);
+
+static const struct of_device_id __maybe_unused ads71x8_of_match[] = {
+ {
+ .compatible = "ti,ads7128",
+ .data = (void *)ads7128
+ },
+ {
+ .compatible = "ti,ads7138",
+ .data = (void *)ads7138
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ads71x8_of_match);
+
+static int ads71x8_i2c_write_block(const struct i2c_client *client, u8 reg,
+ u8 *values, u8 length)
+{
+ struct ads71x8_data *data = i2c_get_clientdata(client);
+ int ret;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = length + 2, /* "+ 2" for OPCODE and reg */
+ },
+ };
+
+ msgs[0].buf = kmalloc(msgs[0].len, GFP_KERNEL);
+ if (!msgs[0].buf)
+ return -ENOMEM;
+
+ msgs[0].buf[0] = ADS71x8_OPCODE_BLOCK_WRITE;
+ msgs[0].buf[1] = reg;
+ memcpy(&msgs[0].buf[2], values, length);
+
+ mutex_lock(&data->lock);
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ mutex_unlock(&data->lock);
+ kfree(msgs[0].buf);
+
+ return ret;
+}
+
+static int ads71x8_i2c_write(const struct i2c_client *client, u8 reg, u8 value)
+{
+ return ads71x8_i2c_write_block(client, reg, &value, sizeof(value));
+}
+
+static int ads71x8_i2c_set_bit(const struct i2c_client *client, u8 reg, u8 bits)
+{
+ struct ads71x8_data *data = i2c_get_clientdata(client);
+ int ret;
+ u8 buf[3] = {ADS71x8_OPCODE_SET_BIT, reg, bits};
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = ARRAY_SIZE(buf),
+ .buf = buf,
+ },
+ };
+
+ mutex_lock(&data->lock);
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int ads71x8_i2c_read_block(const struct i2c_client *client, u8 reg,
+ u8 *out_values, u8 length)
+{
+ struct ads71x8_data *data = i2c_get_clientdata(client);
+ int ret;
+ u8 buf[2] = {ADS71x8_OPCODE_BLOCK_READ, reg};
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = ARRAY_SIZE(buf),
+ .buf = buf,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = length,
+ .buf = out_values,
+ },
+ };
+
+ mutex_lock(&data->lock);
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ mutex_unlock(&data->lock);
+
+ return ret;
+}
+
+static int ads71x8_i2c_read(const struct i2c_client *client, u8 reg)
+{
+ u8 value;
+ int ret = ads71x8_i2c_read_block(client, reg, &value, sizeof(value));
+
+ if (ret < 0)
+ return ret;
+ return value;
+}
+
+static int ads71x8_i2c_read_manual(const struct i2c_client *client, u8 channel,
+ u16 *out_value)
+{
+ struct ads71x8_data *data = i2c_get_clientdata(client);
+ int ret;
+ u8 buf[3] = {ADS71x8_OPCODE_WRITE, ADS71x8_REG_CHANNEL_SEL, channel};
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = ARRAY_SIZE(buf),
+ .buf = buf,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = 2,
+ .buf = buf,
+ },
+ };
+
+ mutex_lock(&data->lock);
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ mutex_unlock(&data->lock);
+
+ /*
+ * For manual reading the order of LSB and MSB is swapped in comparison
+ * to the other registers.
+ */
+ *out_value = ((buf[0] << 8) | buf[1]);
+
+ return ret;
+}
+
+static int ads71x8_read_input_mv(struct ads71x8_data *data, u8 reg, long *val)
+{
+ u8 values[2];
+ int ret;
+
+ ret = ads71x8_i2c_read_block(data->client, reg, values,
+ ARRAY_SIZE(values));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Mask the lowest 4 bits, because it has to be masked for some
+ * registers and doesn't change anything in the result, anyway.
+ */
+ *val = ((values[1] << 8) | (values[0] & 0xF0));
+ /*
+ * Standard resolution is 12 bit, but can get 16 bit if oversampling is
+ * enabled. Therefore, use 16 bit all the time, because the registers
+ * are aligned like that anyway.
+ */
+ *val = DIV_ROUND_CLOSEST(*val * data->vref, (1 << 16));
+ return 0;
+}
+
+static int ads71x8_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct ads71x8_data *data = dev_get_drvdata(dev);
+ u8 reg, values[2];
+ u16 tmp_val;
+ int ret;
+
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_samples:
+ ret = ads71x8_i2c_read(data->client, ADS71x8_REG_OSR_CFG);
+ if (ret < 0)
+ return ret;
+ *val = (1 << (ret & 0x07));
+ return 0;
+ case hwmon_chip_update_interval:
+ *val = data->interval_us;
+ return 0;
+ case hwmon_chip_alarms:
+ *val = data->alarms;
+ /* Reset alarms after reading */
+ data->alarms = 0;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ if (data->mode == ADS71x8_MODE_MANUAL) {
+ ret = ads71x8_i2c_read_manual(data->client,
+ channel, &tmp_val);
+ *val = tmp_val;
+ } else {
+ reg = ADS71x8_REG_RECENT_CH0_LSB + (2 * channel);
+ ret = ads71x8_i2c_read_block(data->client, reg,
+ values, ARRAY_SIZE(values));
+ *val = ((values[1] << 8) | values[0]);
+ }
+ if (ret < 0)
+ return ret;
+ *val = DIV_ROUND_CLOSEST(*val * data->vref, (1 << 16));
+ return 0;
+ case hwmon_in_min:
+ reg = ADS71x8_REG_MIN_CH0_LSB + (2 * channel);
+ return ads71x8_read_input_mv(data, reg, val);
+ case hwmon_in_max:
+ reg = ADS71x8_REG_MAX_CH0_LSB + (2 * channel);
+ return ads71x8_read_input_mv(data, reg, val);
+ case hwmon_in_min_alarm:
+ reg = ADS71x8_REG_LOW_TH_CH0 - 1 + (4 * channel);
+ return ads71x8_read_input_mv(data, reg, val);
+ case hwmon_in_max_alarm:
+ reg = ADS71x8_REG_HIGH_TH_CH0 - 1 + (4 * channel);
+ return ads71x8_read_input_mv(data, reg, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static u32 get_closest_log2(u32 val)
+{
+ u32 down = ilog2(val);
+ u32 up = ilog2(roundup_pow_of_two(val));
+
+ return (val - (1 << down) < (1 << up) - val) ? down : up;
+}
+
+static int ads71x8_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct ads71x8_data *data = dev_get_drvdata(dev);
+ u8 reg, values[2];
+ int ret;
+
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_samples:
+ /* Number of samples can only be a power of 2 */
+ values[0] = get_closest_log2(clamp_val(val, 1, 128));
+ ret = ads71x8_i2c_write(data->client,
+ ADS71x8_REG_OSR_CFG, values[0]);
+ return ret < 0 ? ret : 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_min_alarm:
+ reg = ADS71x8_REG_LOW_TH_CH0 - 1 + (4 * channel);
+ val = DIV_ROUND_CLOSEST(val * (1 << 16), data->vref);
+ val = clamp_val(val, 0, 65535);
+ values[0] = (val & 0xF0);
+ values[1] = (val >> 8) & 0xFF;
+ ret = ads71x8_i2c_write_block(data->client, reg,
+ values, ARRAY_SIZE(values));
+ return ret < 0 ? ret : 0;
+ case hwmon_in_max_alarm:
+ reg = ADS71x8_REG_HIGH_TH_CH0 - 1 + (4 * channel);
+ val = DIV_ROUND_CLOSEST(val * (1 << 16), data->vref);
+ val = clamp_val(val, 0, 65535);
+ values[0] = (val & 0xF0);
+ values[1] = (val >> 8) & 0xFF;
+ ret = ads71x8_i2c_write_block(data->client, reg,
+ values, ARRAY_SIZE(values));
+ return ret < 0 ? ret : 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t ads71x8_is_visible(const void *_data,
+ enum hwmon_sensor_types type, u32 attr, int channel)
+{
+ u8 mode = ((struct ads71x8_data *)_data)->mode;
+
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_samples:
+ return 0644;
+ case hwmon_chip_update_interval:
+ return mode >= ADS71x8_MODE_AUTO ? 0444 : 0;
+ case hwmon_chip_alarms:
+ return mode >= ADS71x8_MODE_AUTO_IRQ ? 0444 : 0;
+ default:
+ return 0;
+ }
+
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ return 0444;
+ case hwmon_in_min:
+ case hwmon_in_max:
+ return mode >= ADS71x8_MODE_AUTO ? 0444 : 0;
+ case hwmon_in_min_alarm:
+ case hwmon_in_max_alarm:
+ return mode >= ADS71x8_MODE_AUTO_IRQ ? 0644 : 0;
+ default:
+ return 0;
+ }
+
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_channel_info *ads71x8_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_SAMPLES | HWMON_C_ALARMS | HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM,
+ HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM),
+ NULL
+};
+
+static const struct hwmon_ops ads71x8_hwmon_ops = {
+ .is_visible = ads71x8_is_visible,
+ .read = ads71x8_read,
+ .write = ads71x8_write,
+};
+
+static const struct hwmon_chip_info ads71x8_chip_info = {
+ .ops = &ads71x8_hwmon_ops,
+ .info = ads71x8_info,
+};
+
+static ssize_t ads71x8_cal_show(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct ads71x8_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = ads71x8_i2c_read(data->client, ADS71x8_REG_GENERAL_CFG);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", (ret & 0x02));
+}
+
+static ssize_t ads71x8_cal_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ads71x8_data *data = dev_get_drvdata(dev);
+ int ret;
+ long val;
+
+ ret = kstrtol(buf, 10, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val == 0)
+ return count;
+
+ ret = ads71x8_i2c_set_bit(data->client, ADS71x8_REG_GENERAL_CFG, 0x02);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static SENSOR_DEVICE_ATTR_RW(calibrate, ads71x8_cal, 0);
+
+static struct attribute *ads71x8_attrs[] = {
+ &sensor_dev_attr_calibrate.dev_attr.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ads71x8);
+
+static const struct ads71x8_val_map *get_closest_interval(u16 freq)
+{
+ const int idx_max = ARRAY_SIZE(ads71x8_intervals_us) - 1;
+ u16 cur, best = ads71x8_intervals_us[idx_max].val;
+ int i;
+
+ freq = clamp_val(freq, ads71x8_intervals_us[0].val,
+ ads71x8_intervals_us[idx_max].val);
+
+ for (i = 0; i <= idx_max; i++) {
+ cur = abs(ads71x8_intervals_us[i].val - freq);
+ if (cur > best)
+ return &ads71x8_intervals_us[i-1];
+ best = cur;
+ }
+ return &ads71x8_intervals_us[0];
+}
+
+static irqreturn_t ads71x8_irq_handler(int irq, void *_data)
+{
+ struct ads71x8_data *data = _data;
+ struct device *dev = &data->client->dev;
+ int ret;
+
+ ret = ads71x8_i2c_read(data->client, ADS71x8_REG_EVENT_FLAG);
+ if (ret <= 0)
+ return IRQ_NONE;
+
+ ret = ads71x8_i2c_read(data->client, ADS71x8_REG_EVENT_HIGH_FLAG);
+ if (ret < 0)
+ goto out;
+ data->alarms |= (ret << 8);
+
+ ret = ads71x8_i2c_read(data->client, ADS71x8_REG_EVENT_LOW_FLAG);
+ if (ret < 0)
+ goto out;
+ data->alarms |= (ret);
+
+ /* Clear all interrupt flags, so next interrupt can be captured */
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_EVENT_HIGH_FLAG, 0xFF);
+ if (ret < 0)
+ goto out;
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_EVENT_LOW_FLAG, 0xFF);
+ if (ret < 0)
+ goto out;
+
+ /* Notify poll/select in userspace. CONFIG_SYSFS must be set! */
+ sysfs_notify(&data->hwmon_dev->kobj, NULL, "alarms");
+ kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
+
+out:
+ if (ret < 0)
+ dev_warn(dev, "couldn't handle interrupt correctly: %d\n", ret);
+ return IRQ_HANDLED;
+}
+
+static int ads71x8_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ads71x8_data *data;
+ struct device *hwmon_dev;
+ struct regulator *regulator;
+ const struct ads71x8_val_map *interval = ads71x8_intervals_us;
+ int vref, ret, err = 0;
+
+ data = devm_kzalloc(dev, sizeof(struct ads71x8_data), GFP_KERNEL);
+ if (!data) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ data->client = client;
+ data->id = i2c_match_id(ads71x8_device_ids, client);
+ i2c_set_clientdata(client, data);
+ mutex_init(&data->lock);
+
+ /* Reset the chip to get a defined starting configuration */
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_GENERAL_CFG, 0x01);
+ if (ret < 0) {
+ dev_err(dev, "failed to reset\n");
+ err = ret;
+ goto cleanup_mutex;
+ }
+
+ /* Get AVDD (in mv) which is the analog supply and reference voltage */
+ regulator = devm_regulator_get(dev, "avdd");
+ if (IS_ERR(regulator)) {
+ err = PTR_ERR(regulator);
+ goto cleanup_mutex;
+ }
+
+ vref = regulator_get_voltage(regulator);
+ data->vref = DIV_ROUND_CLOSEST(vref, 1000);
+ if (data->vref < ADS71x8_AVDD_MV_MIN || data->vref > ADS71x8_AVDD_MV_MAX) {
+ dev_err(dev, "invalid value for AVDD %d\n", data->vref);
+ err = -EINVAL;
+ goto cleanup_mutex;
+ }
+
+ /*
+ * Try reading optional parameter 'ti,mode', otherwise keep current
+ * mode, which is manual mode.
+ */
+ if (of_property_read_u8(dev->of_node, "ti,mode", &data->mode) == 0) {
+ if (data->mode >= ADS71x8_MODE_MAX) {
+ dev_err(dev, "invalid operation mode %d\n", data->mode);
+ err = -EINVAL;
+ goto cleanup_mutex;
+ }
+ }
+
+ if (data->mode <= ADS71x8_MODE_MANUAL)
+ goto conf_manual;
+
+ /* Try reading optional parameter 'ti,interval' */
+ if (of_property_read_u16(dev->of_node, "ti,interval", &data->interval_us) == 0)
+ interval = get_closest_interval(data->interval_us);
+ data->interval_us = interval->val;
+
+ /* Check if interrupt is also configured */
+ if (!client->irq) {
+ dev_warn(dev, "interrupt not available, intended?\n");
+ goto conf_auto;
+ }
+
+ data->mode = ADS71x8_MODE_AUTO_IRQ;
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, ads71x8_irq_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED,
+ NULL, data);
+ if (ret) {
+ dev_err(dev, "unable to request IRQ %d\n", client->irq);
+ err = ret;
+ goto cleanup_mutex;
+ }
+
+ /* Enable possibility to trigger an alert/interrupt for all channels */
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_ALERT_CH_SEL, 0xFF);
+ if (ret < 0) {
+ err = ret;
+ goto cleanup_config;
+ }
+
+conf_auto:
+ /* Set to autonomous conversion and update interval */
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_OPMODE_CFG,
+ 0b00100000 | (interval->bits & 0x1F));
+ if (ret < 0) {
+ err = ret;
+ goto cleanup_config;
+ }
+
+ /* Enable statistics and digital window comparator */
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_GENERAL_CFG,
+ 0b00110000);
+ if (ret < 0) {
+ err = ret;
+ goto cleanup_config;
+ }
+
+ /* Enable all channels for auto sequencing */
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_AUTO_SEQ_CH_SEL,
+ 0xFF);
+ if (ret < 0) {
+ err = ret;
+ goto cleanup_config;
+ }
+
+ /* Set auto sequence mode and start sequencing */
+ ret = ads71x8_i2c_write(data->client, ADS71x8_REG_SEQUENCE_CFG,
+ 0b00010001);
+ if (ret < 0) {
+ err = ret;
+ goto cleanup_config;
+ }
+
+conf_manual:
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
+ data, &ads71x8_chip_info, ads71x8_groups);
+ if (IS_ERR_OR_NULL(hwmon_dev)) {
+ err = PTR_ERR_OR_ZERO(hwmon_dev);
+ goto cleanup_mutex;
+ }
+ data->hwmon_dev = hwmon_dev;
+
+ goto out;
+
+cleanup_config:
+ dev_err(dev, "failed to configure IC: %d\n", err);
+cleanup_mutex:
+ mutex_destroy(&data->lock);
+out:
+ return err;
+}
+
+static void ads71x8_remove(struct i2c_client *client)
+{
+ struct ads71x8_data *data = i2c_get_clientdata(client);
+
+ /* Reset the chip */
+ if (ads71x8_i2c_write(data->client, ADS71x8_REG_GENERAL_CFG, 0x01) < 0)
+ dev_err(&client->dev, "failed to reset\n");
+
+ mutex_destroy(&data->lock);
+}
+
+static struct i2c_driver ads71x8_driver = {
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(ads71x8_of_match),
+ },
+ .id_table = ads71x8_device_ids,
+ .probe = ads71x8_probe,
+ .remove = ads71x8_remove,
+};
+/* Cares about module_init and _exit */
+module_i2c_driver(ads71x8_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tobias Sperling <tobias.sperling@...ting.com>");
+MODULE_DESCRIPTION("Driver for TI ADS71x8 ADCs");
--
2.39.2
Powered by blists - more mailing lists