hwmon: New driver for Texas Instruments TPS2480/TPS2481
PATCH 1/1 hwmon: New driver for Texas Instruments TPS2480/TPS2481
Attached is a patch which adds hwmon support for Texas Instruments TPS2480/TPS2481 current sensor. The code follows the same architecture found in other hwmon drivers and should be pretty easy to understand.
The chip reports measured current as the voltage drop across a shunt resistor. This value is reported in the curr1_input attribute as 10 uV steps. It is then the responsibility of the user to convert back this value in amperes to have the correct value (for instance using sensors.conf file).
Many parameters affecting the chip operation are exported through sysfs interface and thus are available for configuration from userland. Even if it is not necessary to configure the chip before usage, there might be cases where a user needs to do so.
To apply the patch on the latest kernel, simply type (assuming filename <patch>):
git apply --check <patch>
To use the driver, enable the option "SENSORS_TPS2480", in your kernel configuration file. I2C/SMBus support is required in your kernel. The code has been written in an architecture-independent way, but have only been tested on PowerPC architecture (big-endian).
As this is my first patch submitted for the Linux kernel, it is possible that the approach I used is not correct, notably the way the driver reports current/voltage information and the exported chip parameters. I don't know if it is necessary to export that much information/configuration for a simple chip like this one, or if I should keep it simple, and use the chip in the default configuration.
Please let me know any feedback you have on this patch.
Best regards,
Hadrien Copponnex
R&D Engineer
AppearTV AS
www.appeartv.com
--- drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/tps2480.c | 386 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 397 insertions(+)
create mode 100644 drivers/hwmon/tps2480.c
drivers/hwmon/Kconfig +10 -0
@@ -1504,6 +1504,16 @@ config SENSORS_TMP421 This driver can also be built as a module. If so, the module will be called tmp421. +config SENSORS_TPS2480 + tristate "Texas Instruments TPS2480/2481 (EXPERIMENTAL)" + depends on I2C + help + If you say yes here you get support for Texas Instruments TPS2480 + and TPS2481 sensor chips. + + This driver can also be built as a module. If so, the module + will be called tps2480. + config SENSORS_TWL4030_MADC tristate "Texas Instruments TWL4030 MADC Hwmon" depends on TWL4030_MADC
drivers/hwmon/Makefile +1 -0
@@ -145,6 +145,7 @@ obj-$(CONFIG_SENSORS_TMP102) += tmp102.o obj-$(CONFIG_SENSORS_TMP103) += tmp103.o obj-$(CONFIG_SENSORS_TMP401) += tmp401.o obj-$(CONFIG_SENSORS_TMP421) += tmp421.o +obj-$(CONFIG_SENSORS_TPS2480) += tps2480.o obj-$(CONFIG_SENSORS_TWL4030_MADC)+= twl4030-madc-hwmon.o obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress.o obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o
drivers/hwmon/tps2480.c new file +112 -0
112 lines not on this page · raw message
const u16
+*p, const char *fmt)
+{
+ struct tps2480_data *data = tps2480_update_device(dev);
+ u16 value = data->registers[reg];
+ u8 valid = data->valid;
+
+ mutex_unlock(&data->lock);
+ if (!valid)
+ return -EIO;
+ value = (value >> p[1]) & p[0];
+ return snprintf(buf, PAGE_SIZE, fmt, value);
+}
+
+static ssize_t perform_store(struct device *dev, const char *buf, size_t count,
+u8 reg, const u16 *p)
+{
+ struct tps2480_data *data = tps2480_update_device(dev);
+ struct i2c_client *client = to_i2c_client(dev);
+ unsigned long val = 0;
+ ssize_t result = count;
+ s32 err;
+
+ if (kstrtoul(buf, 10, &val) || val > p[0]) {
+ result = -EINVAL;
+ goto finish;
+ }
+
+ data->registers[reg] = (val & p[0]) << p[1];
+
+ err = write_register(client, reg, data->registers[reg]);
+ if (err)
+ result = err;
+
+finish:
+ mutex_unlock(&data->lock);
+
+ return result;
+}
+
+/* Variable parameters
+ [0]: mask
+ [1]: shift */
+static const u16 in0[2] = {0xfff8, 0x01};
+static const u16 cu1[2] = {0xffff, 0x00};
+static const u16 cu2[2] = {0xffff, 0x00};
+static const u16 pow[2] = {0xffff, 0x00};
+static const u16 bvr[2] = {0x0001, 0x0d};
+static const u16 pga[2] = {0x0003, 0x0b};
+static const u16 bad[2] = {0x000f, 0x07};
+static const u16 sad[2] = {0x000f, 0x03};
+static const u16 mod[2] = {0x0007, 0x00};
+static const u16 cal[2] = {0xffff, 0x00};
+
+static ssize_t show_in0(struct device *dev, struct device_attribute *attr, char
+*buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_BUS_VOLTAGE, in0, "%hu\n");
+}
+
+static ssize_t show_curr1(struct device *dev, struct device_attribute *attr,
+char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_SHUNT_VOLTAGE, cu1, "%hd\n");
+}
+
+static ssize_t show_curr2(struct device *dev, struct device_attribute *attr,
+char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_CURRENT, cu2, "%hd\n");
+}
+
+static ssize_t show_power(struct device *dev, struct device_attribute *attr,
+char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_POWER, pow, "%hu\n");
+}
+
+static ssize_t store_reset(struct device *dev, struct device_attribute *attr,
+const char *buf, size_t count)
+{
+ s32 err = reset_chip(dev);
+
+ return err ? err : count;
+}
+
+static ssize_t show_bus_voltage_range(struct device *dev, struct +device_attribute *attr, char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_CONFIG, bvr, "%hu\n");
+}
+
+static ssize_t store_bus_voltage_range(struct device *dev, struct +device_attribute *attr, const char *buf, size_t count)
+{
+ return perform_store(dev, buf, count, TPS2480_REG_CONFIG, bvr);
+}
+
+static ssize_t show_pga(struct device *dev, struct device_attribute *attr, char
+*buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_CONFIG, pga, "%hu\n");
+}
+
+static ssize_t store_pga(struct device *dev, struct device_attribute *attr, +const char *buf, size_t count)
+{
+ return perform_store(dev, buf, count, TPS2480_REG_CONFIG, pga);
+}
+
+static ssize_t show_badc(struct device *dev, struct device_attribute *attr, +char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_CONFIG, bad, "%hu\n");
+}
+
+static ssize_t store_badc(struct device *dev, struct device_attribute *attr,
+const char *buf, size_t count)
+{
+ return perform_store(dev, buf, count, TPS2480_REG_CONFIG, bad);
+}
+
+static ssize_t show_sadc(struct device *dev, struct device_attribute *attr, +char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_CONFIG, sad, "%hu\n");
+}
+
+static ssize_t store_sadc(struct device *dev, struct device_attribute *attr,
+const char *buf, size_t count)
+{
+ return perform_store(dev, buf, count, TPS2480_REG_CONFIG, sad);
+}
+
+static ssize_t show_mode(struct device *dev, struct device_attribute *attr, +char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_CONFIG, mod, "%hu\n");
+}
+
+static ssize_t store_mode(struct device *dev, struct device_attribute *attr,
+const char *buf, size_t count)
+{
+ return perform_store(dev, buf, count, TPS2480_REG_CONFIG, mod);
+}
+
+static ssize_t show_calibration(struct device *dev, struct device_attribute +*attr, char *buf)
+{
+ return perform_show(dev, buf, TPS2480_REG_CALIBRATION, cal, "%hu\n");
+}
+
+static ssize_t store_calibration(struct device *dev, struct device_attribute
+*attr, const char *buf, size_t count)
+{
+ return perform_store(dev, buf, count, TPS2480_REG_CALIBRATION, cal);
+}
+
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0); /* Bus +voltage */
+static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, show_curr1, NULL, 1); /* Shunt
+voltage */
+static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, show_curr2, NULL, 1); /* +Current */
+static SENSOR_DEVICE_ATTR(power_input, S_IRUGO, show_power, NULL, 1); /* +Power */
+static SENSOR_DEVICE_ATTR(reset, S_IWUSR, NULL, store_reset, 0); /* Reset */
+static SENSOR_DEVICE_ATTR(bus_voltage_range, S_IRUGO | S_IWUSR, +show_bus_voltage_range, store_bus_voltage_range, 0); /* Bus voltage range */
+static SENSOR_DEVICE_ATTR(pga, S_IRUGO | S_IWUSR, show_pga, store_pga, 0); /*
+PGA */
+static SENSOR_DEVICE_ATTR(badc, S_IRUGO | S_IWUSR, show_badc, store_badc, 0);
+/* BADC */
+static SENSOR_DEVICE_ATTR(sadc, S_IRUGO | S_IWUSR, show_sadc, store_sadc, 0);
+/* SADC */
+static SENSOR_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, show_mode, store_mode, 0);
+/* Mode */
+static SENSOR_DEVICE_ATTR(calibration, S_IRUGO | S_IWUSR, show_calibration, +store_calibration, 0); /* Calibration */
+
+static struct attribute *tps2480_attrs[] = {
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_curr1_input.dev_attr.attr,
+ &sensor_dev_attr_curr2_input.dev_attr.attr,
+ &sensor_dev_attr_power_input.dev_attr.attr,
+ &sensor_dev_attr_reset.dev_attr.attr,
+ &sensor_dev_attr_bus_voltage_range.dev_attr.attr,
+ &sensor_dev_attr_pga.dev_attr.attr,
+ &sensor_dev_attr_badc.dev_attr.attr,
+ &sensor_dev_attr_sadc.dev_attr.attr,
+ &sensor_dev_attr_mode.dev_attr.attr,
+ &sensor_dev_attr_calibration.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group tps2480_attr_grp = {
+ .attrs = tps2480_attrs,
+};
+
+/*
+ * Driver interface functions
+ */
+
+static int tps2480_probe(struct i2c_client *client, const struct i2c_device_id
+*id)
+{
+ struct tps2480_data *data;
+ int err;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(&client->dev, "i2c adapter doesn't support smbus");
+ return -EOPNOTSUPP;
+ }
+
+ data = devm_kzalloc(&client->dev,
+ sizeof(struct tps2480_data),
+ GFP_KERNEL);
+ if (data = NULL) {
+ dev_err(&client->dev, "cannot allocate memory");
+ return -ENOMEM;
+ }
+ i2c_set_clientdata(client, data);
+
+ mutex_init(&data->lock);
+
+ err = sysfs_create_group(&client->dev.kobj, &tps2480_attr_grp);
+ if (err) {
+ dev_err(&client->dev, "error creating sysfs attribute group");
+ return err;
+ }
+
+ data->hwmon_dev = hwmon_device_register(&client->dev);
+ if (IS_ERR(data->hwmon_dev)) {
+ err = PTR_ERR(data->hwmon_dev);
+ dev_err(&client->dev, "error registering hwmon device");
+ sysfs_remove_group(&client->dev.kobj, &tps2480_attr_grp);
+ return err;
+ }
+
+ return 0;
+}
+
+static int tps2480_remove(struct i2c_client *client)
+{
+ struct tps2480_data *data = i2c_get_clientdata(client);
+
+ hwmon_device_unregister(data->hwmon_dev);
+ sysfs_remove_group(&client->dev.kobj, &tps2480_attr_grp);
+
+ return 0;
+}
+
+static const struct i2c_device_id tps2480_id[] = {
+ {"tps2480", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, tps2480_id);
+
+static struct i2c_driver tps2480_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "tps2480",
+ },
+ .probe = tps2480_probe,
+ .remove = tps2480_remove,
+ .id_table = tps2480_id,
+};
+
+module_i2c_driver(tps2480_driver);
+
+MODULE_AUTHOR("Hadrien Copponnex <[email protected]>"); +MODULE_DESCRIPTION("I2C/SMBus Driver for TI TPS2480/TPS2481"); +MODULE_LICENSE("GPL");
Signature · 8 lines
1.7.9.5 _______________________________________________ lm-sensors mailing list [email protected] http://lists.lm-sensors.org/mailman/listinfo/lm-sensors
Guenter Roeck
maintainerHi Hadrien,
Quoted 4 lines · Hadrien Copponnex
Attached is a patch which adds hwmon support for Texas Instruments TPS2480/TPS2481 current sensor. The code follows the same architecture found in other hwmon drivers and should be pretty easy to understand.
The chip reports measured current as the voltage drop across a shunt resistor. This value is reported in the
curr1_inputattribute as 10 uV steps. It is then the responsibility of the user to convert back this value in amperes to have the correct value (for instance using sensors.conf file).
The current is really reported in the current register. The voltage drop is just that, a voltage drop, and should be reported as voltage.
Quoted 11 lines
Many parameters affecting the chip operation are exported through sysfs interface and thus are available for configuration from userland. Even if it is not necessary to configure the chip before usage, there might be cases where a user needs to do so.
To apply the patch on the latest kernel, simply type (assuming filename <patch>):
git apply --check <patch>To use the driver, enable the option "
SENSORS_TPS2480", in your kernel configuration file. I2C/SMBus support is required in your kernel. The code has been written in an architecture-independent way, but have only been tested on PowerPC architecture (big-endian).As this is my first patch submitted for the Linux kernel, it is possible that the approach I used is not correct, notably the way the driver reports current/voltage information and the exported chip parameters. I don't know if it is necessary to export that much information/configuration for a simple chip like this one, or if I should keep it simple, and use the chip in the default configuration.
Please let me know any feedback you have on this patch.
Please read and follow Documentation/hwmon/submitting-patches, and consider what it says as overall feedback. For the most part, I won't comment on items covered by this document. Some more comments inline.
Thanks,
Guenter
Quoted 7 lines
Best regards,
Hadrien Copponnex
R&D EngineerAppearTV AS
www.appeartv.com
Quoted 6 lines
Signed-off-by Hadrien Copponnex <[email protected]>--- drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/tps2480.c | 386 +++++++++++++++++++++++++++++++++++++++++++++++
Please also provide Documentation/hwmon/tps2480.
Quoted 8 lines
3 files changed, 397 insertions(+)create mode 100644
drivers/hwmon/tps2480.cdrivers/hwmon/Kconfig +0
-0
drivers/hwmon/Kconfig config SENSORS_TMP421 · line 1513
Quoted 10 lines
drivers/hwmon/Kconfig +7
-0@@ -1504,6 +1504,16 @@ config SENSORS_TMP421 This driver can also be built as a module. If so, the module will be called tmp421. +config SENSORS_TPS2480 + tristate "Texas Instruments TPS2480/2481 (EXPERIMENTAL)" + depends on I2C + help + If you say yes here you get support for Texas Instruments TPS2480 + and TPS2481 sensor chips. +
Those are not sensor chips. "Hotswap controllers", maybe ?
Quoted 24 lines
drivers/hwmon/Kconfig +3
-0+ This driver can also be built as a module. If so, the module + will be called tps2480. + config SENSORS_TWL4030_MADC tristate "Texas Instruments TWL4030 MADC Hwmon" depends on TWL4030_MADCdrivers/hwmon/Makefile +1
-0@@ -145,6 +145,7 @@ obj-$(CONFIG_SENSORS_TMP102) += tmp102.o obj-$(CONFIG_SENSORS_TMP103) += tmp103.o obj-$(CONFIG_SENSORS_TMP401) += tmp401.o obj-$(CONFIG_SENSORS_TMP421) += tmp421.o +obj-$(CONFIG_SENSORS_TPS2480) += tps2480.o obj-$(CONFIG_SENSORS_TWL4030_MADC)+= twl4030-madc-hwmon.o obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress.o obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.odrivers/hwmon/tps2480.c new file +0
-0
drivers/hwmon/tps2480.c line 5
Quoted 5 lines
drivers/hwmon/tps2480.c new file +5
-0@@ -0,0 +1,386 @@ +/* +* Hardware monitoring driver for TI TPS2480 / TPS2481 +* +* Copyright (c) 2014 AppearTV AS +*
2015 ?
drivers/hwmon/tps2480.c line 8
Quoted 3 lines
drivers/hwmon/tps2480.c new file +3
-0+* Relevant documentation: +* http://www.ti.com/lit/ds/symlink/tps2480.pdf +*
Plus tps2481 ? it might be better to point to the datasheet in the documentation file. This way it only needs to be updated once.
Quoted 6 lines
drivers/hwmon/tps2480.c new file +6
-0+* Parts of this file are based from: +* ltc4151.c (tps2480_update_device function) +* +* 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
drivers/hwmon/tps2480.c line 24
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/kernel.h> +#include <linux/delay.h>
Is this needed ?
drivers/hwmon/tps2480.c line 33
Quoted 9 lines
drivers/hwmon/tps2480.c new file +9
-0+#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/jiffies.h> +
Please provide include files in alphabetical order.
drivers/hwmon/tps2480.c line 43
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+#define TPS2480_REG_CONFIG 0x00/* R/W */ +#define TPS2480_REG_SHUNT_VOLTAGE 0x01/* R */ +#define TPS2480_REG_BUS_VOLTAGE 0x02/* R */ +#define TPS2480_REG_POWER 0x03/* R */ +#define TPS2480_REG_CURRENT 0x04/* R */ +#define TPS2480_REG_CALIBRATION 0x05/* R/W */ +#define TPS2480_MAX_REGISTERS 6 + +struct tps2480_data { +struct device *hwmon_dev;
Not needed (see below).
drivers/hwmon/tps2480.c line 47
Quoted 4 lines
drivers/hwmon/tps2480.c new file +4
-0+struct mutex lock; +u16 registers[TPS2480_MAX_REGISTERS ]; +unsigned long last_updated; +u8 valid;
Please use bool.
drivers/hwmon/tps2480.c line 57
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+} tps2480_data; + +/* +* Chip operating functions +*/ + +static s32 write_register (struct i2c_client *client,u8 reg,u16 value) +{ +s32 err =i2c_smbus_write_word_data (client, reg,cpu_to_le16 (value)); +
i2c_smbus_write_word_data does not need cpu_to_le16(). If the byte order is wrong, you need to use i2c_smbus_write_word_swapped().
drivers/hwmon/tps2480.c line 61
Quoted 4 lines
drivers/hwmon/tps2480.c new file +4
-0+if (err) +dev_err (&client->dev,"cannot write i2c command" ); +return err; +}
Please just use i2c_smbus_write_word_data (or probably i2c_smbus_write_word_swapped) and return the error to use space.
drivers/hwmon/tps2480.c line 70
Quoted 9 lines
drivers/hwmon/tps2480.c new file +9
-0+ +static s32 reset_chip (struct device *dev) +{ +struct i2c_client *client =to_i2c_client (dev); +struct tps2480_data *data =i2c_get_clientdata (client); + +mutex_lock (&data->lock); + data->valid =0 ; +mutex_unlock (&data->lock);
This mutex does not protect anything.
drivers/hwmon/tps2480.c line 72
Quoted 2 lines
drivers/hwmon/tps2480.c new file +2
-0+ +return write_register (client,TPS2480_REG_CONFIG ,1 <<15 );
If really needed, this would probably leave the chip unconfigured. Is this what you want ? Also, are you sure this is needed ? It is quite unusual, and may result in a system reset.
If that is the idea, it would be better to register a restart handler. See register_restart_handler() / unregister_restart_handler().
drivers/hwmon/tps2480.c line 85
Quoted 13 lines
drivers/hwmon/tps2480.c new file +13
-0+} + +/* returns the driver data associated to the device +* in addition, reads the latest status from registers +* don't forget to release the lock when you have finished using the data +* structure +*/ +static struct tps2480_data *tps2480_update_device (struct device *dev) +{ +struct i2c_client *client =to_i2c_client (dev); +struct tps2480_data *data =i2c_get_clientdata (client); +u8 i; +
Please just use int. It generates better code on most architectures.
drivers/hwmon/tps2480.c line 88
Quoted 3 lines
drivers/hwmon/tps2480.c new file +3
-0+mutex_lock (&data->lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) ||
With this chip, it probably doesn't make much sense to use this much caching time. Worst case conversion time is 68ms. Given that, it might not even make sense to cache anything, and to just read the registers directly.
You might consider using regmap instead of direct i2c accesses. Regmap could be configured to cache the configuration and calibration registers permanently, which would probably make sense.
drivers/hwmon/tps2480.c line 98
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+ !data->valid) { +/* read data and update device structure */ +for (i =0 ; i <TPS2480_MAX_REGISTERS ; ++i) { +s32 val =i2c_smbus_read_word_data (client, i); + +if (val <0 ) { + data->valid =0 ; + goto finish; + } + data->registers[i] =le16_to_cpu ((u16 )val);
i2c_smbus_read_word_data returns data in host byte order. If the byte order is wrong, you would need to use use i2c_smbus_read_word_swapped().
drivers/hwmon/tps2480.c line 104
Quoted 6 lines
drivers/hwmon/tps2480.c new file +6
-0+ } + data->valid =1 ; + data->last_updated = jiffies; + } + +finish:
I don't see an unlock operation, which is highly unusual (more on that below).
drivers/hwmon/tps2480.c
Quoted 12 lines
drivers/hwmon/tps2480.c new file +12
-0+return data; +} + +/* +* Sysfs callbacks +*/ + +static ssize_t perform_show (struct device *dev,char *buf,u8 reg,const u16 +*p,const char *fmt) +{ +struct tps2480_data *data =tps2480_update_device (dev); +u16 value = data->registers[reg];
This is an unsigned short which may be displayed as short with %hd. Only reason for the compiler not to complain is probably that the format is passed as parameter. This results in the compiler not being able to do format checking, which is really nice to have. One more reason to use individual functions for the different attributes.
drivers/hwmon/tps2480.c line 118
Quoted 2 lines
drivers/hwmon/tps2480.c new file +2
-0+u8 valid = data->valid; +
Unnecessary variable.
drivers/hwmon/tps2480.c line 120
Quoted 2 lines
drivers/hwmon/tps2480.c new file +2
-0+mutex_unlock (&data->lock); +if (!valid)
Ah, this is where the unlock is hiding. Please don't do that, and keep the locking within one function. Please have tps2480_update_device() return the actual error, and return that error to user space.
drivers/hwmon/tps2480.c line 121
Quoted 1 line
drivers/hwmon/tps2480.c new file +1
-0+return -EIO ;
Replacing one error with another is never a good idea. Static checkers will complain about that if they catch it.
drivers/hwmon/tps2480.c line 122
Quoted 1 line
drivers/hwmon/tps2480.c new file +1
-0+ value = (value >> p[1 ]) & p[0 ];
The printed value must follow the ABI (mV, mA, uW). Given the different calculations necessary, it might be better to just use a separate function for each attribute.
Per the datasheets, the shunt voltage must include the programmed gain. Current and power depend on the calibration values, so you'll have to do some complex calculations to convert the raw values into mA and uW.
drivers/hwmon/tps2480.c line 64
Quoted 6 lines
drivers/hwmon/tps2480.c new file +6
-0+return snprintf (buf,PAGE_SIZE , fmt, value); +} + +static ssize_t perform_store (struct device *dev,const char *buf,size_t count, +u8 reg,const u16 *p) +{
As with the show command, I would suggest to use separate write functions for each (writable) attribute.
drivers/hwmon/tps2480.c line 129
Quoted 1 line
drivers/hwmon/tps2480.c new file +1
-0+struct tps2480_data *data =tps2480_update_device (dev);
Not needed here. You don't need to read anything from the chip to update one of its registers.
drivers/hwmon/tps2480.c line 136
Quoted 7 lines
drivers/hwmon/tps2480.c new file +7
-0+struct i2c_client *client =to_i2c_client (dev); +unsigned long val =0 ; +ssize_t result = count; +s32 err; + +if (kstrtoul (buf,10 , &val) || val > p[0 ]) { + result = -EINVAL ;
kstrtoul() returns an error, which you should return
drivers/hwmon/tps2480.c line 96
Quoted 1 line
drivers/hwmon/tps2480.c new file +1
-0+ goto finish;
directly without locking / goto.
drivers/hwmon/tps2480.c line 139
Quoted 2 lines
drivers/hwmon/tps2480.c new file +2
-0+ } +
Locking is needed from here
drivers/hwmon/tps2480.c line 142
Quoted 3 lines
drivers/hwmon/tps2480.c new file +3
-0+ data->registers[reg] = (val & p[0 ]) << p[1 ]; + + err =write_register (client, reg, data->registers[reg]);
to here.
drivers/hwmon/tps2480.c
Quoted 5 lines
drivers/hwmon/tps2480.c new file +5
-0+if (err) + result = err; + +finish: +mutex_unlock (&data->lock);
but not here.
Quoted 114 lines
drivers/hwmon/tps2480.c new file +114
-0+ +return result; +} + +/* Variable parameters +[0]: mask +[1]: shift */ +static const u16 in0[2 ] = {0xfff8 ,0x01 }; +static const u16 cu1[2 ] = {0xffff ,0x00 }; +static const u16 cu2[2 ] = {0xffff ,0x00 }; +static const u16 pow[2 ] = {0xffff ,0x00 }; +static const u16 bvr[2 ] = {0x0001 ,0x0d }; +static const u16 pga[2 ] = {0x0003 ,0x0b }; +static const u16 bad[2 ] = {0x000f ,0x07 }; +static const u16 sad[2 ] = {0x000f ,0x03 }; +static const u16 mod[2 ] = {0x0007 ,0x00 }; +static const u16 cal[2 ] = {0xffff ,0x00 }; + +static ssize_t show_in0 (struct device *dev,struct device_attribute *attr,char +*buf) +{ +return perform_show (dev, buf,TPS2480_REG_BUS_VOLTAGE , in0,"%hu\n" ); +} + +static ssize_t show_curr1 (struct device *dev,struct device_attribute *attr, +char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_SHUNT_VOLTAGE , cu1,"%hd\n" ); +} + +static ssize_t show_curr2 (struct device *dev,struct device_attribute *attr, +char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_CURRENT , cu2,"%hd\n" ); +} + +static ssize_t show_power (struct device *dev,struct device_attribute *attr, +char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_POWER , pow,"%hu\n" ); +} + +static ssize_t store_reset (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) +{ +s32 err =reset_chip (dev); + +return err ? err : count; +} + +static ssize_t show_bus_voltage_range (struct device *dev,struct +device_attribute *attr,char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_CONFIG , bvr,"%hu\n" ); +} + +static ssize_t store_bus_voltage_range (struct device *dev,struct +device_attribute *attr,const char *buf,size_t count) +{ +return perform_store (dev, buf, count,TPS2480_REG_CONFIG , bvr); +} + +static ssize_t show_pga (struct device *dev,struct device_attribute *attr,char +*buf) +{ +return perform_show (dev, buf,TPS2480_REG_CONFIG , pga,"%hu\n" ); +} + +static ssize_t store_pga (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) +{ +return perform_store (dev, buf, count,TPS2480_REG_CONFIG , pga); +} + +static ssize_t show_badc (struct device *dev,struct device_attribute *attr, +char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_CONFIG , bad,"%hu\n" ); +} + +static ssize_t store_badc (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) +{ +return perform_store (dev, buf, count,TPS2480_REG_CONFIG , bad); +} + +static ssize_t show_sadc (struct device *dev,struct device_attribute *attr, +char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_CONFIG , sad,"%hu\n" ); +} + +static ssize_t store_sadc (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) +{ +return perform_store (dev, buf, count,TPS2480_REG_CONFIG , sad); +} + +static ssize_t show_mode (struct device *dev,struct device_attribute *attr, +char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_CONFIG , mod,"%hu\n" ); +} + +static ssize_t store_mode (struct device *dev,struct device_attribute *attr, +const char *buf,size_t count) +{ +return perform_store (dev, buf, count,TPS2480_REG_CONFIG , mod); +} + +static ssize_t show_calibration (struct device *dev,struct device_attribute +*attr,char *buf) +{ +return perform_show (dev, buf,TPS2480_REG_CALIBRATION , cal,"%hu\n" );
drivers/hwmon/tps2480.c line 271
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+} + +static ssize_t store_calibration (struct device *dev,struct device_attribute +*attr,const char *buf,size_t count) +{ +return perform_store (dev, buf, count,TPS2480_REG_CALIBRATION , cal); +} + +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0); /* Bus +voltage */
You don't really use the parameter (last value), so providing it is quite useless.
the bus voltage has an alarm bit, so you could provide in0_alarm.
drivers/hwmon/tps2480.c line 273
Quoted 2 lines
drivers/hwmon/tps2480.c new file +2
-0+static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, show_curr1, NULL, 1); /* Shunt +voltage */
This is a voltage, not a current ??
Also, the shunt voltage can be negative per the datasheet.
drivers/hwmon/tps2480.c line 277
Quoted 4 lines
drivers/hwmon/tps2480.c new file +4
-0+static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, show_curr2, NULL, 1); /* +Current */ +static SENSOR_DEVICE_ATTR(power_input, S_IRUGO, show_power, NULL, 1); /* +Power */
power1_input
drivers/hwmon/tps2480.c line 278
Quoted 1 line
drivers/hwmon/tps2480.c new file +1
-0+static SENSOR_DEVICE_ATTR(reset, S_IWUSR, NULL, store_reset, 0); /* Reset */
I don't think this should be exported.
drivers/hwmon/tps2480.c line 280
Quoted 2 lines
drivers/hwmon/tps2480.c new file +2
-0+static SENSOR_DEVICE_ATTR(bus_voltage_range, S_IRUGO | S_IWUSR, +show_bus_voltage_range, store_bus_voltage_range, 0); /* Bus voltage range */
Maybe in1_max.
drivers/hwmon/tps2480.c line 291
Quoted 11 lines
drivers/hwmon/tps2480.c new file +11
-0+static SENSOR_DEVICE_ATTR(pga, S_IRUGO | S_IWUSR, show_pga, store_pga, 0); /* +PGA */ +static SENSOR_DEVICE_ATTR(badc, S_IRUGO | S_IWUSR, show_badc, store_badc, 0); +/* BADC */ +static SENSOR_DEVICE_ATTR(sadc, S_IRUGO | S_IWUSR, show_sadc, store_sadc, 0); +/* SADC */ +static SENSOR_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, show_mode, store_mode, 0); +/* Mode */ +static SENSOR_DEVICE_ATTR(calibration, S_IRUGO | S_IWUSR, show_calibration, +store_calibration, 0); /* Calibration */ +
Those are not standard attributes supported by the ABI. Where needed, they should be provided either with platform data or with devicetree data or both, but not with attributes. It might make sense to provide some abstraction instead of just raw register values.
Some of the parameters may be implementable as in[01]_max (for shunt and bus voltage range) and curr1_max (for the maximum supported current).
You could use the update_interval attribute to set the conversion time, though that is in milli-seconds so you'd only affect the number of samples taken per value.
Quoted 28 lines
drivers/hwmon/tps2480.c new file +28
-0+static struct attribute *tps2480_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_power_input.dev_attr.attr, + &sensor_dev_attr_reset.dev_attr.attr, + &sensor_dev_attr_bus_voltage_range.dev_attr.attr, + &sensor_dev_attr_pga.dev_attr.attr, + &sensor_dev_attr_badc.dev_attr.attr, + &sensor_dev_attr_sadc.dev_attr.attr, + &sensor_dev_attr_mode.dev_attr.attr, + &sensor_dev_attr_calibration.dev_attr.attr, +NULL +}; + +static struct attribute_group tps2480_attr_grp = { + .attrs = tps2480_attrs, +}; + +/* +* Driver interface functions +*/ + +static int tps2480_probe (struct i2c_client *client,const struct i2c_device_id +*id) +{ +struct tps2480_data *data; +int err;
drivers/hwmon/tps2480.c line 329
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+ +if (!i2c_check_functionality (client->adapter,I2C_FUNC_SMBUS_BYTE_DATA | +I2C_FUNC_SMBUS_WORD_DATA )) { +dev_err (&client->dev,"i2c adapter doesn't support smbus" ); +return -EOPNOTSUPP ; + } + + data =devm_kzalloc (&client->dev, +sizeof (struct tps2480_data ), +GFP_KERNEL );
Not all continuation lines are needed here.
drivers/hwmon/tps2480.c line 331
Quoted 2 lines
drivers/hwmon/tps2480.c new file +2
-0+if (data =NULL ) { +dev_err (&client->dev,"cannot allocate memory" );
Memory allocation failures already display an error message. Another one is not needed.
drivers/hwmon/tps2480.c line 344
Quoted 13 lines
drivers/hwmon/tps2480.c new file +13
-0+return -ENOMEM ; + } +i2c_set_clientdata (client, data); + +mutex_init (&data->lock); + + err =sysfs_create_group (&client->dev.kobj, &tps2480_attr_grp); +if (err) { +dev_err (&client->dev,"error creating sysfs attribute group" ); +return err; + } + + data->hwmon_dev =hwmon_device_register (&client->dev);
Please use devm_hwmon_device_register_with_groups(). You can then just return PTR_ERR_OR_ZERO(hwmon_dev), and you don't need to call sysfs_create_group().
drivers/hwmon/tps2480.c line 355
Quoted 11 lines
drivers/hwmon/tps2480.c new file +11
-0+if (IS_ERR (data->hwmon_dev)) { + err =PTR_ERR (data->hwmon_dev); +dev_err (&client->dev,"error registering hwmon device" ); +sysfs_remove_group (&client->dev.kobj, &tps2480_attr_grp); +return err; + } + +return 0 ; +} + +static int tps2480_remove (struct i2c_client *client)
Not needed if you use devm_hwmon_device_register_with_groups().
drivers/hwmon/tps2480.c
Quoted 11 lines
drivers/hwmon/tps2480.c new file +11
-0+{ +struct tps2480_data *data =i2c_get_clientdata (client); + +hwmon_device_unregister (data->hwmon_dev); +sysfs_remove_group (&client->dev.kobj, &tps2480_attr_grp); + +return 0 ; +} + +static const struct i2c_device_id tps2480_id[] = { + {"tps2480" ,0 },
It might make sense to also provide "tps2481" to simplify / support instantiation on systems with that chip.
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+ {} +}; + +MODULE_DEVICE_TABLE (i2c, tps2480_id); + +static struct i2c_driver tps2480_driver = { + .class =I2C_CLASS_HWMON , + .driver = { + .name ="tps2480" , + },
drivers/hwmon/tps2480.c line 386
Quoted 10 lines
drivers/hwmon/tps2480.c new file +10
-0+ .probe = tps2480_probe, + .remove = tps2480_remove, + .id_table = tps2480_id, +}; + +module_i2c_driver (tps2480_driver); + +MODULE_AUTHOR ("Hadrien Copponnex <[email protected]>" ); +MODULE_DESCRIPTION ("I2C/SMBus Driver for TI TPS2480/TPS2481" ); +MODULE_LICENSE ("GPL" );
_______________________________________________
lm-sensors mailing list
[email protected]
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors