From 686d303ee6301261b422ea51e64833d7909a2c36 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Fri, 25 Feb 2022 17:06:09 +0100 Subject: [PATCH 01/86] hwmon: (pmbus) Add mutex to regulator ops On PMBUS devices with multiple pages, the regulator ops need to be protected with the update mutex. This prevents accidentally changing the page in a separate thread while operating on the PMBUS_OPERATION register. Tested on Infineon xdpe11280 while a separate thread polls for sensor data. Signed-off-by: Patrick Rudolph Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/b991506bcbf665f7af185945f70bf9d5cf04637c.1645804976.git.sylv@sylv.io Fixes: ddbb4db4ced1b ("hwmon: (pmbus) Add regulator support") Cc: Alan Tull Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index ac2fbee1ba9c..b1386a4df4cc 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -2391,10 +2391,14 @@ static int pmbus_regulator_is_enabled(struct regulator_dev *rdev) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); int ret; + mutex_lock(&data->update_lock); ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION); + mutex_unlock(&data->update_lock); + if (ret < 0) return ret; @@ -2405,11 +2409,17 @@ static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); + int ret; - return pmbus_update_byte_data(client, page, PMBUS_OPERATION, - PB_OPERATION_CONTROL_ON, - enable ? PB_OPERATION_CONTROL_ON : 0); + mutex_lock(&data->update_lock); + ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION, + PB_OPERATION_CONTROL_ON, + enable ? PB_OPERATION_CONTROL_ON : 0); + mutex_unlock(&data->update_lock); + + return ret; } static int pmbus_regulator_enable(struct regulator_dev *rdev) From 7f3cc8f897634b7e2d79bc2b7105e9ae6eaf4ac2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 10 Jan 2022 14:56:25 -0800 Subject: [PATCH 02/86] hwmon: Report attribute name with udev events Up to now udev events only report the affected hwmon device if an alert is reported. This requires userspace to read all attributes if it wants to know what triggered the event. Provide the attribute name with the NAME property to help userspace find the attribute causing the event. Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 3ae961986fc3..0b79c4e1bf99 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -625,7 +625,9 @@ static const int __templates_size[] = { int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel) { + char event[MAX_SYSFS_ATTR_NAME_LENGTH + 5]; char sattr[MAX_SYSFS_ATTR_NAME_LENGTH]; + char *envp[] = { event, NULL }; const char * const *templates; const char *template; int base; @@ -641,8 +643,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, base = hwmon_attr_base(type); scnprintf(sattr, MAX_SYSFS_ATTR_NAME_LENGTH, template, base + channel); + scnprintf(event, sizeof(event), "NAME=%s", sattr); sysfs_notify(&dev->kobj, NULL, sattr); - kobject_uevent(&dev->kobj, KOBJ_CHANGE); + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); if (type == hwmon_temp) hwmon_thermal_notify(dev, channel); From 7c68c2c761d157203b64ebbb61dd7b5b6c32df61 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Dec 2021 11:42:38 -0800 Subject: [PATCH 03/86] hwmon: (lm83) Reorder include files to be in alphabetic order Reorder include files to be in alphabetic order to simplify driver maintenance. Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 74fd7aa373a3..44d720af2473 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -18,15 +18,15 @@ * http://www.national.com/pf/LM/LM82.html */ -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include #include +#include #include /* From 11e3377b9a439a5cf989bdb2b16d90e237542ec2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Dec 2021 11:49:04 -0800 Subject: [PATCH 04/86] hwmon: (lm83) Move lm83_id to avoid forward declaration There is no need to keep lm83_id at the end of the driver. Move it forward to where it is needed to avoid a forward declaration. Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 44d720af2473..2bb4bceef551 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -317,7 +317,12 @@ static int lm83_detect(struct i2c_client *new_client, return 0; } -static const struct i2c_device_id lm83_id[]; +static const struct i2c_device_id lm83_id[] = { + { "lm83", lm83 }, + { "lm82", lm82 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm83_id); static int lm83_probe(struct i2c_client *new_client) { @@ -352,13 +357,6 @@ static int lm83_probe(struct i2c_client *new_client) * Driver data (common to all clients) */ -static const struct i2c_device_id lm83_id[] = { - { "lm83", lm83 }, - { "lm82", lm82 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, lm83_id); - static struct i2c_driver lm83_driver = { .class = I2C_CLASS_HWMON, .driver = { From 81de0eea2bbc1b1334d40c4bb420219b603b4c45 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Dec 2021 16:23:31 -0800 Subject: [PATCH 05/86] hwmon: (lm83) Replace new_client with client It has no value to name a variable 'new_client' in probe and detect functions; it is obvious that the client is new. Use 'client' as variable name instead. Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 2bb4bceef551..fdd89cc481fa 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -272,10 +272,10 @@ static const struct attribute_group lm83_group_opt = { */ /* Return 0 if detection is successful, -ENODEV otherwise */ -static int lm83_detect(struct i2c_client *new_client, +static int lm83_detect(struct i2c_client *client, struct i2c_board_info *info) { - struct i2c_adapter *adapter = new_client->adapter; + struct i2c_adapter *adapter = client->adapter; const char *name; u8 man_id, chip_id; @@ -283,20 +283,20 @@ static int lm83_detect(struct i2c_client *new_client, return -ENODEV; /* Detection */ - if ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) & 0xA8) || - (i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) & 0x48) || - (i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) & 0x41)) { + if ((i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) & 0xA8) || + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) & 0x48) || + (i2c_smbus_read_byte_data(client, LM83_REG_R_CONFIG) & 0x41)) { dev_dbg(&adapter->dev, "LM83 detection failed at 0x%02x\n", - new_client->addr); + client->addr); return -ENODEV; } /* Identification */ - man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID); + man_id = i2c_smbus_read_byte_data(client, LM83_REG_R_MAN_ID); if (man_id != 0x01) /* National Semiconductor */ return -ENODEV; - chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID); + chip_id = i2c_smbus_read_byte_data(client, LM83_REG_R_CHIP_ID); switch (chip_id) { case 0x03: name = "lm83"; @@ -324,17 +324,17 @@ static const struct i2c_device_id lm83_id[] = { }; MODULE_DEVICE_TABLE(i2c, lm83_id); -static int lm83_probe(struct i2c_client *new_client) +static int lm83_probe(struct i2c_client *client) { struct device *hwmon_dev; struct lm83_data *data; - data = devm_kzalloc(&new_client->dev, sizeof(struct lm83_data), + data = devm_kzalloc(&client->dev, sizeof(struct lm83_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->client = new_client; + data->client = client; mutex_init(&data->update_lock); /* @@ -344,11 +344,11 @@ static int lm83_probe(struct i2c_client *new_client) * declare 1 and 3 common, and then 2 and 4 only for the LM83. */ data->groups[0] = &lm83_group; - if (i2c_match_id(lm83_id, new_client)->driver_data == lm83) + if (i2c_match_id(lm83_id, client)->driver_data == lm83) data->groups[1] = &lm83_group_opt; - hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev, - new_client->name, + hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, + client->name, data, data->groups); return PTR_ERR_OR_ZERO(hwmon_dev); } From 719af4f1a40bdf46cab7e4db216af7084a70897b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Dec 2021 16:22:00 -0800 Subject: [PATCH 06/86] hwmon: (lm83) Use regmap Using local caching in this driver had few benefits. It used cached values for two seconds and then re-read all registers from the chip even if the user only accessed a single attribute. On top of that, alarm attributes were stale for up to four seconds (the first status register read reports and clears an alarm, the second reports it cleared). Use regmap instead for caching. Do not re-read non-volatile registers, and do not cache volatile registers. As part of this change, handle register read and write address differences in regmap code. This is necessary to avoid problems with caching in the regmap core, and ultimately simplifies the code. Also, errors observed when reading from and writing to registers are no longer ignored. Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/lm83.c | 168 +++++++++++++++++++++++++++--------------- 2 files changed, 108 insertions(+), 61 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8df25f1079ba..01ab80a2cc4a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1288,6 +1288,7 @@ config SENSORS_LM80 config SENSORS_LM83 tristate "National Semiconductor LM83 and compatibles" depends on I2C + select REGMAP help If you say yes here you get support for National Semiconductor LM82 and LM83 sensor chips. diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index fdd89cc481fa..c9605957e400 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -21,11 +21,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -77,7 +77,7 @@ enum chips { lm83, lm82 }; (val) < 0 ? ((val) - 500) / 1000 : \ ((val) + 500) / 1000) -static const u8 LM83_REG_R_TEMP[] = { +static const u8 LM83_REG_TEMP[] = { LM83_REG_R_LOCAL_TEMP, LM83_REG_R_REMOTE1_TEMP, LM83_REG_R_REMOTE2_TEMP, @@ -89,62 +89,82 @@ static const u8 LM83_REG_R_TEMP[] = { LM83_REG_R_TCRIT, }; -static const u8 LM83_REG_W_HIGH[] = { - LM83_REG_W_LOCAL_HIGH, - LM83_REG_W_REMOTE1_HIGH, - LM83_REG_W_REMOTE2_HIGH, - LM83_REG_W_REMOTE3_HIGH, - LM83_REG_W_TCRIT, -}; - /* * Client data (each client gets its own) */ struct lm83_data { - struct i2c_client *client; + struct regmap *regmap; const struct attribute_group *groups[3]; - struct mutex update_lock; - bool valid; /* false until following fields are valid */ - unsigned long last_updated; /* in jiffies */ - - /* registers values */ - s8 temp[9]; /* 0..3: input 1-4, - 4..7: high limit 1-4, - 8 : critical limit */ - u16 alarms; /* bitvector, combined */ }; -static struct lm83_data *lm83_update_device(struct device *dev) +/* regmap code */ + +static int lm83_regmap_reg_read(void *context, unsigned int reg, unsigned int *val) { - struct lm83_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; + struct i2c_client *client = context; + int ret; - mutex_lock(&data->update_lock); + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return ret; - if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { - int nr; + *val = ret; + return 0; +} - dev_dbg(&client->dev, "Updating lm83 data.\n"); - for (nr = 0; nr < 9; nr++) { - data->temp[nr] = - i2c_smbus_read_byte_data(client, - LM83_REG_R_TEMP[nr]); - } - data->alarms = - i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) - + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) - << 8); +/* + * The regmap write function maps read register addresses to write register + * addresses. This is necessary for regmap register caching to work. + * An alternative would be to clear the regmap cache whenever a register is + * written, but that would be much more expensive. + */ +static int lm83_regmap_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; - data->last_updated = jiffies; - data->valid = true; + switch (reg) { + case LM83_REG_R_CONFIG: + case LM83_REG_R_LOCAL_HIGH: + case LM83_REG_R_REMOTE2_HIGH: + reg += 0x06; + break; + case LM83_REG_R_REMOTE1_HIGH: + case LM83_REG_R_REMOTE3_HIGH: + case LM83_REG_R_TCRIT: + reg += 0x18; + break; + default: + break; } - mutex_unlock(&data->update_lock); - - return data; + return i2c_smbus_write_byte_data(client, reg, val); } +static bool lm83_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LM83_REG_R_LOCAL_TEMP: + case LM83_REG_R_REMOTE1_TEMP: + case LM83_REG_R_REMOTE2_TEMP: + case LM83_REG_R_REMOTE3_TEMP: + case LM83_REG_R_STATUS1: + case LM83_REG_R_STATUS2: + return true; + default: + return false; + } +} + +static const struct regmap_config lm83_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = lm83_regmap_is_volatile, + .reg_read = lm83_regmap_reg_read, + .reg_write = lm83_regmap_reg_write, +}; + /* * Sysfs stuff */ @@ -153,8 +173,15 @@ static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm83_data *data = lm83_update_device(dev); - return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); + struct lm83_data *data = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, LM83_REG_TEMP[attr->index], ®val); + if (ret) + return ret; + + return sprintf(buf, "%d\n", TEMP_FROM_REG((s8)regval)); } static ssize_t temp_store(struct device *dev, @@ -163,38 +190,57 @@ static ssize_t temp_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct lm83_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; + unsigned int regval; long val; - int nr = attr->index; int err; err = kstrtol(buf, 10, &val); if (err < 0) return err; - mutex_lock(&data->update_lock); - data->temp[nr] = TEMP_TO_REG(val); - i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr - 4], - data->temp[nr]); - mutex_unlock(&data->update_lock); - return count; + regval = TEMP_TO_REG(val); + err = regmap_write(data->regmap, LM83_REG_TEMP[attr->index], regval); + return err ? : count; } static ssize_t alarms_show(struct device *dev, struct device_attribute *dummy, char *buf) { - struct lm83_data *data = lm83_update_device(dev); - return sprintf(buf, "%d\n", data->alarms); + struct lm83_data *data = dev_get_drvdata(dev); + unsigned int alarms, regval; + int err; + + err = regmap_read(data->regmap, LM83_REG_R_STATUS1, ®val); + if (err < 0) + return err; + alarms = regval; + err = regmap_read(data->regmap, LM83_REG_R_STATUS2, ®val); + if (err < 0) + return err; + alarms |= regval << 8; + + return sprintf(buf, "%u\n", alarms); } static ssize_t alarm_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm83_data *data = lm83_update_device(dev); + struct lm83_data *data = dev_get_drvdata(dev); int bitnr = attr->index; + unsigned int alarm; + int reg, err; - return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); + if (bitnr < 8) { + reg = LM83_REG_R_STATUS1; + } else { + reg = LM83_REG_R_STATUS2; + bitnr -= 8; + } + err = regmap_read(data->regmap, reg, &alarm); + if (err < 0) + return err; + return sprintf(buf, "%d\n", (alarm >> bitnr) & 1); } static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0); @@ -326,16 +372,17 @@ MODULE_DEVICE_TABLE(i2c, lm83_id); static int lm83_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct device *hwmon_dev; struct lm83_data *data; - data = devm_kzalloc(&client->dev, sizeof(struct lm83_data), - GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(struct lm83_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->client = client; - mutex_init(&data->update_lock); + data->regmap = devm_regmap_init(dev, NULL, client, &lm83_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); /* * Register sysfs hooks @@ -347,8 +394,7 @@ static int lm83_probe(struct i2c_client *client) if (i2c_match_id(lm83_id, client)->driver_data == lm83) data->groups[1] = &lm83_group_opt; - hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, - client->name, + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, data, data->groups); return PTR_ERR_OR_ZERO(hwmon_dev); } From 362c5663e8760b145a67adb3a7704de401f6ceab Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Dec 2021 19:03:04 -0800 Subject: [PATCH 07/86] hwmon: (lm83) Replace temperature conversion macros with standard functions Replace TEMP_FROM_REG with direct calculation and TEMP_TO_REG with standard functions/macros. Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index c9605957e400..434bd5b903d2 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -66,17 +66,6 @@ enum chips { lm83, lm82 }; #define LM83_REG_R_TCRIT 0x42 #define LM83_REG_W_TCRIT 0x5A -/* - * Conversions and various macros - * The LM83 uses signed 8-bit values with LSB = 1 degree Celsius. - */ - -#define TEMP_FROM_REG(val) ((val) * 1000) -#define TEMP_TO_REG(val) ((val) <= -128000 ? -128 : \ - (val) >= 127000 ? 127 : \ - (val) < 0 ? ((val) - 500) / 1000 : \ - ((val) + 500) / 1000) - static const u8 LM83_REG_TEMP[] = { LM83_REG_R_LOCAL_TEMP, LM83_REG_R_REMOTE1_TEMP, @@ -181,7 +170,7 @@ static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, if (ret) return ret; - return sprintf(buf, "%d\n", TEMP_FROM_REG((s8)regval)); + return sprintf(buf, "%d\n", (s8)regval * 1000); } static ssize_t temp_store(struct device *dev, @@ -198,7 +187,7 @@ static ssize_t temp_store(struct device *dev, if (err < 0) return err; - regval = TEMP_TO_REG(val); + regval = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); err = regmap_write(data->regmap, LM83_REG_TEMP[attr->index], regval); return err ? : count; } From 4d63c2d31a8f4c68458422fc5d0639a16237b426 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Dec 2021 23:49:59 -0800 Subject: [PATCH 08/86] hwmon: (lm83) Demote log message if chip identification fails There should be no message in the kernel function if the detect function fails to identify a chip; this is perfectly normal and does not warrant a kernel log entry. Demote message to debug. Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 434bd5b903d2..82d7ef264f6f 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -341,9 +341,9 @@ static int lm83_detect(struct i2c_client *client, break; default: /* identification failed */ - dev_info(&adapter->dev, - "Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n", - man_id, chip_id); + dev_dbg(&adapter->dev, + "Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n", + man_id, chip_id); return -ENODEV; } From 913ac02ade57493fae1fa030f5a33934ef80a254 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 24 Dec 2021 11:31:32 -0800 Subject: [PATCH 09/86] hwmon: (lm83) Explain why LM82 may be misdetected as LM83 According to the March 2013 revision of the LM82 datasheet, the latest LM82 die revision is 0x03. This was confirmed and observed with a real chip. Further details in this revision of the LM82 datasheet suggest that LM82 is now just a repackaged LM83. Such versions of LM82 will be detected as LM83. Add comment to the code explaining why this may happen. Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 82d7ef264f6f..d9ee01ca8aed 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -334,6 +334,14 @@ static int lm83_detect(struct i2c_client *client, chip_id = i2c_smbus_read_byte_data(client, LM83_REG_R_CHIP_ID); switch (chip_id) { case 0x03: + /* + * According to the LM82 datasheet dated March 2013, recent + * revisions of LM82 have a die revision of 0x03. This was + * confirmed with a real chip. Further details in this revision + * of the LM82 datasheet strongly suggest that LM82 is just a + * repackaged LM83. It is therefore impossible to distinguish + * those chips from LM83, and they will be misdetected as LM83. + */ name = "lm83"; break; case 0x01: From c291f612a813a1f295266a3de5cb418e48f41440 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 22 Dec 2021 20:24:02 -0800 Subject: [PATCH 10/86] hwmon: (lm83) Convert to use with_info API Use with_info API to reduce code size and simplify the code. Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 329 +++++++++++++++++++++++++------------------ 1 file changed, 190 insertions(+), 139 deletions(-) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index d9ee01ca8aed..12370dcefa6a 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -18,11 +18,11 @@ * http://www.national.com/pf/LM/LM82.html */ +#include #include #include #include #include -#include #include #include #include @@ -71,11 +71,30 @@ static const u8 LM83_REG_TEMP[] = { LM83_REG_R_REMOTE1_TEMP, LM83_REG_R_REMOTE2_TEMP, LM83_REG_R_REMOTE3_TEMP, +}; + +static const u8 LM83_REG_MAX[] = { LM83_REG_R_LOCAL_HIGH, LM83_REG_R_REMOTE1_HIGH, LM83_REG_R_REMOTE2_HIGH, LM83_REG_R_REMOTE3_HIGH, - LM83_REG_R_TCRIT, +}; + +/* alarm and fault registers and bits, indexed by channel */ +static const u8 LM83_ALARM_REG[] = { + LM83_REG_R_STATUS1, LM83_REG_R_STATUS2, LM83_REG_R_STATUS1, LM83_REG_R_STATUS2 +}; + +static const u8 LM83_MAX_ALARM_BIT[] = { + BIT(6), BIT(7), BIT(4), BIT(4) +}; + +static const u8 LM83_CRIT_ALARM_BIT[] = { + BIT(0), BIT(0), BIT(1), BIT(1) +}; + +static const u8 LM83_FAULT_BIT[] = { + 0, BIT(5), BIT(2), BIT(2) }; /* @@ -84,7 +103,7 @@ static const u8 LM83_REG_TEMP[] = { struct lm83_data { struct regmap *regmap; - const struct attribute_group *groups[3]; + enum chips type; }; /* regmap code */ @@ -154,158 +173,198 @@ static const struct regmap_config lm83_regmap_config = { .reg_write = lm83_regmap_reg_write, }; -/* - * Sysfs stuff - */ +/* hwmon API */ -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) +static int lm83_temp_read(struct device *dev, u32 attr, int channel, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct lm83_data *data = dev_get_drvdata(dev); unsigned int regval; - int ret; - - ret = regmap_read(data->regmap, LM83_REG_TEMP[attr->index], ®val); - if (ret) - return ret; - - return sprintf(buf, "%d\n", (s8)regval * 1000); -} - -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm83_data *data = dev_get_drvdata(dev); - unsigned int regval; - long val; int err; - err = kstrtol(buf, 10, &val); - if (err < 0) - return err; + switch (attr) { + case hwmon_temp_input: + err = regmap_read(data->regmap, LM83_REG_TEMP[channel], ®val); + if (err < 0) + return err; + *val = (s8)regval * 1000; + break; + case hwmon_temp_max: + err = regmap_read(data->regmap, LM83_REG_MAX[channel], ®val); + if (err < 0) + return err; + *val = (s8)regval * 1000; + break; + case hwmon_temp_crit: + err = regmap_read(data->regmap, LM83_REG_R_TCRIT, ®val); + if (err < 0) + return err; + *val = (s8)regval * 1000; + break; + case hwmon_temp_max_alarm: + err = regmap_read(data->regmap, LM83_ALARM_REG[channel], ®val); + if (err < 0) + return err; + *val = !!(regval & LM83_MAX_ALARM_BIT[channel]); + break; + case hwmon_temp_crit_alarm: + err = regmap_read(data->regmap, LM83_ALARM_REG[channel], ®val); + if (err < 0) + return err; + *val = !!(regval & LM83_CRIT_ALARM_BIT[channel]); + break; + case hwmon_temp_fault: + err = regmap_read(data->regmap, LM83_ALARM_REG[channel], ®val); + if (err < 0) + return err; + *val = !!(regval & LM83_FAULT_BIT[channel]); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int lm83_temp_write(struct device *dev, u32 attr, int channel, long val) +{ + struct lm83_data *data = dev_get_drvdata(dev); + unsigned int regval; + int err; regval = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); - err = regmap_write(data->regmap, LM83_REG_TEMP[attr->index], regval); - return err ? : count; + + switch (attr) { + case hwmon_temp_max: + err = regmap_write(data->regmap, LM83_REG_MAX[channel], regval); + if (err < 0) + return err; + break; + case hwmon_temp_crit: + err = regmap_write(data->regmap, LM83_REG_R_TCRIT, regval); + if (err < 0) + return err; + break; + default: + return -EOPNOTSUPP; + } + return 0; } -static ssize_t alarms_show(struct device *dev, struct device_attribute *dummy, - char *buf) +static int lm83_chip_read(struct device *dev, u32 attr, int channel, long *val) { struct lm83_data *data = dev_get_drvdata(dev); - unsigned int alarms, regval; + unsigned int regval; int err; - err = regmap_read(data->regmap, LM83_REG_R_STATUS1, ®val); - if (err < 0) - return err; - alarms = regval; - err = regmap_read(data->regmap, LM83_REG_R_STATUS2, ®val); - if (err < 0) - return err; - alarms |= regval << 8; - - return sprintf(buf, "%u\n", alarms); -} - -static ssize_t alarm_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm83_data *data = dev_get_drvdata(dev); - int bitnr = attr->index; - unsigned int alarm; - int reg, err; - - if (bitnr < 8) { - reg = LM83_REG_R_STATUS1; - } else { - reg = LM83_REG_R_STATUS2; - bitnr -= 8; + switch (attr) { + case hwmon_chip_alarms: + err = regmap_read(data->regmap, LM83_REG_R_STATUS1, ®val); + if (err < 0) + return err; + *val = regval; + err = regmap_read(data->regmap, LM83_REG_R_STATUS2, ®val); + if (err < 0) + return err; + *val |= regval << 8; + return 0; + default: + return -EOPNOTSUPP; } - err = regmap_read(data->regmap, reg, &alarm); - if (err < 0) - return err; - return sprintf(buf, "%d\n", (alarm >> bitnr) & 1); + + return 0; } -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0); -static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2); -static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3); -static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, 4); -static SENSOR_DEVICE_ATTR_RW(temp2_max, temp, 5); -static SENSOR_DEVICE_ATTR_RW(temp3_max, temp, 6); -static SENSOR_DEVICE_ATTR_RW(temp4_max, temp, 7); -static SENSOR_DEVICE_ATTR_RO(temp1_crit, temp, 8); -static SENSOR_DEVICE_ATTR_RO(temp2_crit, temp, 8); -static SENSOR_DEVICE_ATTR_RW(temp3_crit, temp, 8); -static SENSOR_DEVICE_ATTR_RO(temp4_crit, temp, 8); +static int lm83_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return lm83_chip_read(dev, attr, channel, val); + case hwmon_temp: + return lm83_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -/* Individual alarm files */ -static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 0); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_alarm, alarm, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_fault, alarm, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, alarm, 4); -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 6); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 8); -static SENSOR_DEVICE_ATTR_RO(temp4_crit_alarm, alarm, 9); -static SENSOR_DEVICE_ATTR_RO(temp4_fault, alarm, 10); -static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, alarm, 12); -static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 13); -static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 15); -/* Raw alarm file for compatibility */ -static DEVICE_ATTR_RO(alarms); +static int lm83_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_temp: + return lm83_temp_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static struct attribute *lm83_attributes[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp3_crit.dev_attr.attr, +static umode_t lm83_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct lm83_data *data = _data; - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_fault.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &dev_attr_alarms.attr, + /* + * LM82 only supports a single external channel, modeled as channel 2. + */ + if (data->type == lm82 && (channel == 1 || channel == 3)) + return 0; + + switch (type) { + case hwmon_chip: + if (attr == hwmon_chip_alarms) + return 0444; + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + return 0444; + case hwmon_temp_fault: + if (channel) + return 0444; + break; + case hwmon_temp_max: + return 0644; + case hwmon_temp_crit: + if (channel == 2) + return 0644; + return 0444; + default: + break; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_channel_info *lm83_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT + ), NULL }; -static const struct attribute_group lm83_group = { - .attrs = lm83_attributes, +static const struct hwmon_ops lm83_hwmon_ops = { + .is_visible = lm83_is_visible, + .read = lm83_read, + .write = lm83_write, }; -static struct attribute *lm83_attributes_opt[] = { - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp2_crit.dev_attr.attr, - &sensor_dev_attr_temp4_crit.dev_attr.attr, - - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_fault.dev_attr.attr, - &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - NULL +static const struct hwmon_chip_info lm83_chip_info = { + .ops = &lm83_hwmon_ops, + .info = lm83_info, }; -static const struct attribute_group lm83_group_opt = { - .attrs = lm83_attributes_opt, -}; - -/* - * Real code - */ - /* Return 0 if detection is successful, -ENODEV otherwise */ static int lm83_detect(struct i2c_client *client, struct i2c_board_info *info) @@ -381,18 +440,10 @@ static int lm83_probe(struct i2c_client *client) if (IS_ERR(data->regmap)) return PTR_ERR(data->regmap); - /* - * Register sysfs hooks - * The LM82 can only monitor one external diode which is - * at the same register as the LM83 temp3 entry - so we - * declare 1 and 3 common, and then 2 and 4 only for the LM83. - */ - data->groups[0] = &lm83_group; - if (i2c_match_id(lm83_id, client)->driver_data == lm83) - data->groups[1] = &lm83_group_opt; + data->type = i2c_match_id(lm83_id, client)->driver_data; - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, data->groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &lm83_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } From b68437ace4b8636c5c8686c0334cbe5d029557bc Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Thu, 13 Jan 2022 08:46:29 -0800 Subject: [PATCH 11/86] hwmon: (nct6775) add support for TSI temperature registers These registers report CPU temperatures (and, depending on the system, sometimes chipset temperatures) via the TSI interface on AMD systems. They're distinct from most of the other Super-IO temperature readings (CPUTIN, SYSTIN, etc.) in that they're not a selectable source for monitoring and are in a different (higher resolution) format, but can still provide useful temperature data. Signed-off-by: Zev Weiss Tested-by: Renze Nicolai Link: https://lore.kernel.org/r/20220113164629.21924-1-zev@bewilderbeest.net Tested-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 129 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 098d12b9ecda..63e15b5f93a0 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -308,6 +308,7 @@ static void superio_exit(struct nct6775_sio_data *sio_data) #define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/ #define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ +#define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */ #define NUM_REG_ALARM 7 /* Max number of alarm registers */ #define NUM_REG_BEEP 5 /* Max number of beep registers */ @@ -498,6 +499,8 @@ static const u16 NCT6775_REG_TEMP_CRIT[32] = { [11] = 0xa07 }; +static const u16 NCT6775_REG_TSI_TEMP[] = { 0x669 }; + /* NCT6776 specific data */ /* STEP_UP_TIME and STEP_DOWN_TIME regs are swapped for all chips but NCT6775 */ @@ -581,6 +584,9 @@ static const u16 NCT6776_REG_TEMP_CRIT[32] = { [12] = 0x70a, }; +static const u16 NCT6776_REG_TSI_TEMP[] = { + 0x409, 0x40b, 0x40d, 0x40f, 0x411, 0x413, 0x415, 0x417 }; + /* NCT6779 specific data */ static const u16 NCT6779_REG_IN[] = { @@ -864,6 +870,8 @@ static const char *const nct6796_temp_label[] = { #define NCT6796_TEMP_MASK 0xbfff0ffe #define NCT6796_VIRT_TEMP_MASK 0x80000c00 +static const u16 NCT6796_REG_TSI_TEMP[] = { 0x409, 0x40b }; + static const char *const nct6798_temp_label[] = { "", "SYSTIN", @@ -1005,6 +1013,8 @@ static const u16 NCT6106_REG_TEMP_CRIT[32] = { [12] = 0x205, }; +static const u16 NCT6106_REG_TSI_TEMP[] = { 0x59, 0x5b, 0x5d, 0x5f, 0x61, 0x63, 0x65, 0x67 }; + /* NCT6112D/NCT6114D/NCT6116D specific data */ static const u16 NCT6116_REG_FAN[] = { 0x20, 0x22, 0x24, 0x26, 0x28 }; @@ -1069,6 +1079,8 @@ static const s8 NCT6116_BEEP_BITS[] = { 34, -1 /* intrusion0, intrusion1 */ }; +static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b }; + static enum pwm_enable reg_to_pwm_enable(int pwm, int mode) { if (mode == 0 && pwm == 255) @@ -1169,6 +1181,12 @@ static inline u8 in_to_reg(u32 val, u8 nr) return clamp_val(DIV_ROUND_CLOSEST(val * 100, scale_in[nr]), 0, 255); } +/* TSI temperatures are in 8.3 format */ +static inline unsigned int tsi_temp_from_reg(unsigned int reg) +{ + return (reg >> 5) * 125; +} + /* * Data structures and manipulation thereof */ @@ -1179,7 +1197,7 @@ struct nct6775_data { enum kinds kind; const char *name; - const struct attribute_group *groups[6]; + const struct attribute_group *groups[7]; u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, * 3=temp_crit, 4=temp_lcrit @@ -1240,6 +1258,8 @@ struct nct6775_data { const u16 *REG_ALARM; const u16 *REG_BEEP; + const u16 *REG_TSI_TEMP; + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); @@ -1267,6 +1287,7 @@ struct nct6775_data { s8 temp_offset[NUM_TEMP_FIXED]; s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, * 3=temp_crit, 4=temp_lcrit */ + s16 tsi_temp[NUM_TSI_TEMP]; u64 alarms; u64 beeps; @@ -1315,6 +1336,7 @@ struct nct6775_data { u16 have_temp; u16 have_temp_fixed; + u16 have_tsi_temp; u16 have_in; /* Remember extra register values over suspend/resume */ @@ -1464,13 +1486,15 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) switch (data->kind) { case nct6106: return reg == 0x20 || reg == 0x22 || reg == 0x24 || + (reg >= 0x59 && reg < 0x69 && (reg & 1)) || reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0x111 || reg == 0x121 || reg == 0x131; case nct6116: return reg == 0x20 || reg == 0x22 || reg == 0x24 || - reg == 0x26 || reg == 0x28 || reg == 0xe0 || reg == 0xe2 || - reg == 0xe4 || reg == 0xe6 || reg == 0xe8 || reg == 0x111 || - reg == 0x121 || reg == 0x131 || reg == 0x191 || reg == 0x1a1; + reg == 0x26 || reg == 0x28 || reg == 0x59 || reg == 0x5b || + reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0xe6 || + reg == 0xe8 || reg == 0x111 || reg == 0x121 || reg == 0x131 || + reg == 0x191 || reg == 0x1a1; case nct6775: return (((reg & 0xff00) == 0x100 || (reg & 0xff00) == 0x200) && @@ -1479,7 +1503,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) (reg & 0x00ff) == 0x55)) || (reg & 0xfff0) == 0x630 || reg == 0x640 || reg == 0x642 || - reg == 0x662 || + reg == 0x662 || reg == 0x669 || ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || reg == 0x73 || reg == 0x75 || reg == 0x77; case nct6776: @@ -1490,6 +1514,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) (reg & 0x00ff) == 0x55)) || (reg & 0xfff0) == 0x630 || reg == 0x402 || + (reg >= 0x409 && reg < 0x419 && (reg & 1)) || reg == 0x640 || reg == 0x642 || ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || reg == 0x73 || reg == 0x75 || reg == 0x77; @@ -1504,6 +1529,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) return reg == 0x150 || reg == 0x153 || reg == 0x155 || (reg & 0xfff0) == 0x4c0 || reg == 0x402 || + (reg >= 0x409 && reg < 0x419 && (reg & 1)) || reg == 0x63a || reg == 0x63c || reg == 0x63e || reg == 0x640 || reg == 0x642 || reg == 0x64a || reg == 0x64c || @@ -1987,6 +2013,12 @@ static struct nct6775_data *nct6775_update_device(struct device *dev) data->REG_TEMP_OFFSET[i]); } + for (i = 0; i < NUM_TSI_TEMP; i++) { + if (!(data->have_tsi_temp & BIT(i))) + continue; + data->tsi_temp[i] = data->read_value(data, data->REG_TSI_TEMP[i]); + } + data->alarms = 0; for (i = 0; i < NUM_REG_ALARM; i++) { u8 alarm; @@ -2670,6 +2702,44 @@ static const struct sensor_template_group nct6775_temp_template_group = { .base = 1, }; +static ssize_t show_tsi_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sysfs_emit(buf, "%u\n", tsi_temp_from_reg(data->tsi_temp[sattr->index])); +} + +static ssize_t show_tsi_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sysfs_emit(buf, "TSI%d_TEMP\n", sattr->index); +} + +SENSOR_TEMPLATE(tsi_temp_input, "temp%d_input", 0444, show_tsi_temp, NULL, 0); +SENSOR_TEMPLATE(tsi_temp_label, "temp%d_label", 0444, show_tsi_temp_label, NULL, 0); + +static umode_t nct6775_tsi_temp_is_visible(struct kobject *kobj, struct attribute *attr, + int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int temp = index / 2; + + return (data->have_tsi_temp & BIT(temp)) ? attr->mode : 0; +} + +/* + * The index calculation in nct6775_tsi_temp_is_visible() must be kept in + * sync with the size of this array. + */ +static struct sensor_device_template *nct6775_tsi_temp_template[] = { + &sensor_dev_template_tsi_temp_input, + &sensor_dev_template_tsi_temp_label, + NULL +}; + static ssize_t show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf) { @@ -3948,10 +4018,11 @@ static int nct6775_probe(struct platform_device *pdev) const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config; const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit; const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; - int num_reg_temp, num_reg_temp_mon; + int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp; u8 cr2a; struct attribute_group *group; struct device *hwmon_dev; + struct sensor_template_group tsi_temp_tg; int num_attr_groups = 0; if (sio_data->access == access_direct) { @@ -4043,11 +4114,13 @@ static int nct6775_probe(struct platform_device *pdev) data->ALARM_BITS = NCT6106_ALARM_BITS; data->REG_BEEP = NCT6106_REG_BEEP; data->BEEP_BITS = NCT6106_BEEP_BITS; + data->REG_TSI_TEMP = NCT6106_REG_TSI_TEMP; reg_temp = NCT6106_REG_TEMP; reg_temp_mon = NCT6106_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6106_REG_TSI_TEMP); reg_temp_over = NCT6106_REG_TEMP_OVER; reg_temp_hyst = NCT6106_REG_TEMP_HYST; reg_temp_config = NCT6106_REG_TEMP_CONFIG; @@ -4116,11 +4189,13 @@ static int nct6775_probe(struct platform_device *pdev) data->ALARM_BITS = NCT6116_ALARM_BITS; data->REG_BEEP = NCT6106_REG_BEEP; data->BEEP_BITS = NCT6116_BEEP_BITS; + data->REG_TSI_TEMP = NCT6116_REG_TSI_TEMP; reg_temp = NCT6106_REG_TEMP; reg_temp_mon = NCT6106_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP); reg_temp_over = NCT6106_REG_TEMP_OVER; reg_temp_hyst = NCT6106_REG_TEMP_HYST; reg_temp_config = NCT6106_REG_TEMP_CONFIG; @@ -4191,11 +4266,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6775_REG_ALARM; data->REG_BEEP = NCT6775_REG_BEEP; + data->REG_TSI_TEMP = NCT6775_REG_TSI_TEMP; reg_temp = NCT6775_REG_TEMP; reg_temp_mon = NCT6775_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6775_REG_TSI_TEMP); reg_temp_over = NCT6775_REG_TEMP_OVER; reg_temp_hyst = NCT6775_REG_TEMP_HYST; reg_temp_config = NCT6775_REG_TEMP_CONFIG; @@ -4264,11 +4341,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6775_REG_ALARM; data->REG_BEEP = NCT6776_REG_BEEP; + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; reg_temp = NCT6775_REG_TEMP; reg_temp_mon = NCT6775_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); reg_temp_over = NCT6775_REG_TEMP_OVER; reg_temp_hyst = NCT6775_REG_TEMP_HYST; reg_temp_config = NCT6776_REG_TEMP_CONFIG; @@ -4341,11 +4420,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6779_REG_ALARM; data->REG_BEEP = NCT6776_REG_BEEP; + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; reg_temp = NCT6779_REG_TEMP; reg_temp_mon = NCT6779_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); reg_temp_over = NCT6779_REG_TEMP_OVER; reg_temp_hyst = NCT6779_REG_TEMP_HYST; reg_temp_config = NCT6779_REG_TEMP_CONFIG; @@ -4460,6 +4541,24 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_BEEP = NCT6776_REG_BEEP; else data->REG_BEEP = NCT6792_REG_BEEP; + switch (data->kind) { + case nct6791: + case nct6792: + case nct6793: + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); + break; + case nct6795: + case nct6796: + case nct6797: + case nct6798: + data->REG_TSI_TEMP = NCT6796_REG_TSI_TEMP; + num_reg_tsi_temp = ARRAY_SIZE(NCT6796_REG_TSI_TEMP); + break; + default: + num_reg_tsi_temp = 0; + break; + } reg_temp = NCT6779_REG_TEMP; num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); @@ -4659,6 +4758,12 @@ static int nct6775_probe(struct platform_device *pdev) } #endif /* USE_ALTERNATE */ + /* Check which TSIx_TEMP registers are active */ + for (i = 0; i < num_reg_tsi_temp; i++) { + if (data->read_value(data, data->REG_TSI_TEMP[i])) + data->have_tsi_temp |= BIT(i); + } + /* Initialize the chip */ nct6775_init_device(data); @@ -4766,6 +4871,18 @@ static int nct6775_probe(struct platform_device *pdev) return PTR_ERR(group); data->groups[num_attr_groups++] = group; + + if (data->have_tsi_temp) { + tsi_temp_tg.templates = nct6775_tsi_temp_template; + tsi_temp_tg.is_visible = nct6775_tsi_temp_is_visible; + tsi_temp_tg.base = fls(data->have_temp) + 1; + group = nct6775_create_attr_group(dev, &tsi_temp_tg, fls(data->have_tsi_temp)); + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[num_attr_groups++] = group; + } + data->groups[num_attr_groups++] = &nct6775_group_other; hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, From 32b9a19a1966fb3124d7ddd60ebd08688be2f3fa Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 10 Jan 2022 18:22:55 +0000 Subject: [PATCH 12/86] ABI: hwmon: Document "label" sysfs attribute Add the "label" sysfs attribute, which can contain a descriptive label that allows to uniquely identify a device within the system. Signed-off-by: Paul Cercueil Link: https://lore.kernel.org/r/20220110182256.30763-2-paul@crapouillou.net Signed-off-by: Guenter Roeck --- Documentation/ABI/testing/sysfs-class-hwmon | 8 ++++++++ Documentation/hwmon/sysfs-interface.rst | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon index 1f20687def44..653d4c75eddb 100644 --- a/Documentation/ABI/testing/sysfs-class-hwmon +++ b/Documentation/ABI/testing/sysfs-class-hwmon @@ -9,6 +9,14 @@ Description: RO +What: /sys/class/hwmon/hwmonX/label +Description: + A descriptive label that allows to uniquely identify a + device within the system. + The contents of the label are free-form. + + RO + What: /sys/class/hwmon/hwmonX/update_interval Description: The interval at which the chip will update readings. diff --git a/Documentation/hwmon/sysfs-interface.rst b/Documentation/hwmon/sysfs-interface.rst index 85652a6aaa3e..209626fb2405 100644 --- a/Documentation/hwmon/sysfs-interface.rst +++ b/Documentation/hwmon/sysfs-interface.rst @@ -99,6 +99,10 @@ Global attributes `name` The chip name. +`label` + A descriptive label that allows to uniquely identify a device + within the system. + `update_interval` The interval at which the chip will update readings. From e1c9d6d61ddf3f34f14d3de51d6eea68683b5841 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 10 Jan 2022 18:22:56 +0000 Subject: [PATCH 13/86] hwmon: Add "label" attribute If a label is defined in the device tree for this device add that to the device specific attributes. This is useful for userspace to be able to identify an individual device when multiple identical chips are present in the system. Signed-off-by: Paul Cercueil Tested-by: Cosmin Tanislav Link: https://lore.kernel.org/r/20220110182256.30763-3-paul@crapouillou.net Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 0b79c4e1bf99..6c3a8c65390d 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,7 @@ struct hwmon_device { const char *name; + const char *label; struct device dev; const struct hwmon_chip_info *chip; struct list_head tzdata; @@ -71,17 +73,29 @@ name_show(struct device *dev, struct device_attribute *attr, char *buf) } static DEVICE_ATTR_RO(name); +static ssize_t +label_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); +} +static DEVICE_ATTR_RO(label); + static struct attribute *hwmon_dev_attrs[] = { &dev_attr_name.attr, + &dev_attr_label.attr, NULL }; -static umode_t hwmon_dev_name_is_visible(struct kobject *kobj, +static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct device *dev = kobj_to_dev(kobj); + struct hwmon_device *hdev = to_hwmon_device(dev); - if (to_hwmon_device(dev)->name == NULL) + if (attr == &dev_attr_name.attr && hdev->name == NULL) + return 0; + + if (attr == &dev_attr_label.attr && hdev->label == NULL) return 0; return attr->mode; @@ -89,7 +103,7 @@ static umode_t hwmon_dev_name_is_visible(struct kobject *kobj, static const struct attribute_group hwmon_dev_attr_group = { .attrs = hwmon_dev_attrs, - .is_visible = hwmon_dev_name_is_visible, + .is_visible = hwmon_dev_attr_is_visible, }; static const struct attribute_group *hwmon_dev_attr_groups[] = { @@ -117,6 +131,7 @@ static void hwmon_dev_release(struct device *dev) if (hwdev->group.attrs) hwmon_free_attrs(hwdev->group.attrs); kfree(hwdev->groups); + kfree(hwdev->label); kfree(hwdev); } @@ -738,6 +753,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, const struct attribute_group **groups) { struct hwmon_device *hwdev; + const char *label; struct device *hdev; int i, err, id; @@ -793,6 +809,18 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, hdev->groups = groups; } + if (device_property_present(dev, "label")) { + err = device_property_read_string(dev, "label", &label); + if (err < 0) + goto free_hwmon; + + hwdev->label = kstrdup(label, GFP_KERNEL); + if (hwdev->label == NULL) { + err = -ENOMEM; + goto free_hwmon; + } + } + hwdev->name = name; hdev->class = &hwmon_class; hdev->parent = dev; From 23a8d76e5ec5fea267d5c822477b8b11681c2b45 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Wed, 12 Jan 2022 23:50:13 +0200 Subject: [PATCH 14/86] hwmon: (nct6775) add ASUS Pro B550M-C/PRIME B550M-A MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ASUS Pro B550M-C/PRIME B550M-A boards have got an nct6775 chip, but by default there's no use of it because of resource conflict with WMI method. This commit adds "Pro B550M-C" and "PRIME B550M-A" to the list of boards that can be monitored using ASUS WMI. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Tested-by: Gregory P. Smith Tested-by: Joel Wirāmu Tested-by: Jonathan Farrugia Link: https://lore.kernel.org/r/20220112215013.11694-1-pauk.denis@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 63e15b5f93a0..4ef468b413a1 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -5102,9 +5102,12 @@ static struct platform_device *pdev[2]; static const char * const asus_wmi_boards[] = { "ProArt X570-CREATOR WIFI", + "Pro B550M-C", "Pro WS X570-ACE", "PRIME B360-PLUS", "PRIME B460-PLUS", + "PRIME B550M-A", + "PRIME B550M-A (WI-FI)", "PRIME X570-PRO", "ROG CROSSHAIR VIII DARK HERO", "ROG CROSSHAIR VIII FORMULA", From de00c068c4ac9391f3c604ad2f06ac1e73005754 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Mon, 10 Jan 2022 05:47:12 +0300 Subject: [PATCH 15/86] hwmon: (nct6775) add PRIME B550-PLUS motherboard to whitelist Asus PRIME B550-PLUS motherboards have got an nct6775 chip. Its resource range is covered by the \AMW0.SHWM OpRegion, so the chip is unusable when using SIO. However ASUS WMI access works. Add PRIME B550-PLUS to the list of motherboards using ASUS WMI to read data. Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20220110024712.753492-1-dmitry.baryshkov@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 4ef468b413a1..fbf6266c7ba7 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -5106,6 +5106,7 @@ static const char * const asus_wmi_boards[] = { "Pro WS X570-ACE", "PRIME B360-PLUS", "PRIME B460-PLUS", + "PRIME B550-PLUS", "PRIME B550M-A", "PRIME B550M-A (WI-FI)", "PRIME X570-PRO", From 1b089084ec654c05df202896d54df8d92b16fc1e Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sun, 9 Jan 2022 22:42:46 +0100 Subject: [PATCH 16/86] Documentation: admin-guide: Update i8k driver name The driver should be called dell_smm_hwmon, i8k is only an alias now. Signed-off-by: Armin Wolf Acked-by: Randy Dunlap Link: https://lore.kernel.org/r/20220109214248.61759-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- .../admin-guide/kernel-parameters.txt | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index f5a27f067db9..7189402e9fc7 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -944,6 +944,24 @@ dump out devices still on the deferred probe list after retrying. + dell_smm_hwmon.ignore_dmi= + [HW] Continue probing hardware even if DMI data + indicates that the driver is running on unsupported + hardware. + + dell_smm_hwmon.force= + [HW] Activate driver even if SMM BIOS signature does + not match list of supported models and enable otherwise + blacklisted features. + + dell_smm_hwmon.power_status= + [HW] Report power status in /proc/i8k + (disabled by default). + + dell_smm_hwmon.restricted= + [HW] Allow controlling fans only if SYS_ADMIN + capability is set. + dfltcc= [HW,S390] Format: { on | off | def_only | inf_only | always } on: s390 zlib hardware support for compression on @@ -1703,17 +1721,6 @@ i810= [HW,DRM] - i8k.ignore_dmi [HW] Continue probing hardware even if DMI data - indicates that the driver is running on unsupported - hardware. - i8k.force [HW] Activate i8k driver even if SMM BIOS signature - does not match list of supported models. - i8k.power_status - [HW] Report power status in /proc/i8k - (disabled by default) - i8k.restricted [HW] Allow controlling fans only if SYS_ADMIN - capability is set. - i915.invert_brightness= [DRM] Invert the sense of the variable that is used to set the brightness of the panel backlight. Normally a From 99fdc5875b005b6014be2bd2a7c6aaf0abe2896d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sun, 9 Jan 2022 22:42:47 +0100 Subject: [PATCH 17/86] Documentation: admin-guide: Add Documentation for undocumented dell_smm_hwmon parameters Add documentation for fan_mult and fan_max. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220109214248.61759-3-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- Documentation/admin-guide/kernel-parameters.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 7189402e9fc7..d68053db21cc 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -962,6 +962,12 @@ [HW] Allow controlling fans only if SYS_ADMIN capability is set. + dell_smm_hwmon.fan_mult= + [HW] Factor to multiply fan speed with. + + dell_smm_hwmon.fan_max= + [HW] Maximum configurable fan speed. + dfltcc= [HW,S390] Format: { on | off | def_only | inf_only | always } on: s390 zlib hardware support for compression on From 34781a6bec382dd11254caff0d379c7be70576fd Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sun, 9 Jan 2022 22:42:48 +0100 Subject: [PATCH 18/86] Documentation: ABI: Add ABI file for legacy /proc/i8k interface Add ABI file for informing remaining users of the deprecation of the legacy /proc/i8k interface. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220109214248.61759-4-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- Documentation/ABI/obsolete/procfs-i8k | 10 ++++++++++ MAINTAINERS | 1 + 2 files changed, 11 insertions(+) create mode 100644 Documentation/ABI/obsolete/procfs-i8k diff --git a/Documentation/ABI/obsolete/procfs-i8k b/Documentation/ABI/obsolete/procfs-i8k new file mode 100644 index 000000000000..32df4d5bdd15 --- /dev/null +++ b/Documentation/ABI/obsolete/procfs-i8k @@ -0,0 +1,10 @@ +What: /proc/i8k +Date: November 2001 +KernelVersion: 2.4.14 +Contact: Pali Rohár +Description: Legacy interface for getting/setting sensor information like + fan speed, temperature, serial number, hotkey status etc + on Dell Laptops. + Since the driver is now using the standard hwmon sysfs interface, + the procfs interface is deprecated. +Users: https://github.com/vitorafsr/i8kutils diff --git a/MAINTAINERS b/MAINTAINERS index 1ba1e4af2cbc..293c925b6beb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5410,6 +5410,7 @@ F: drivers/platform/x86/dell/dell-rbtn.* DELL LAPTOP SMM DRIVER M: Pali Rohár S: Maintained +F: Documentation/ABI/obsolete/procfs-i8k F: drivers/hwmon/dell-smm-hwmon.c F: include/uapi/linux/i8k.h From 848da7b58796a2583e88dfa0c974bcaa7e40fbec Mon Sep 17 00:00:00 2001 From: Anthony DeRossi Date: Mon, 10 Jan 2022 21:18:42 -0800 Subject: [PATCH 19/86] hwmon: (asus_wmi_ec_sensors) Support T_Sensor on Prime X570-Pro Asus Prime X570-Pro motherboards have a T_Sensor header that can be connected to an optional temperature probe. Signed-off-by: Anthony DeRossi Link: https://lore.kernel.org/r/20220111051842.25634-1-ajderossi@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_wmi_ec_sensors.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/asus_wmi_ec_sensors.c b/drivers/hwmon/asus_wmi_ec_sensors.c index 22a1459305a7..a3a2f014dec0 100644 --- a/drivers/hwmon/asus_wmi_ec_sensors.c +++ b/drivers/hwmon/asus_wmi_ec_sensors.c @@ -112,7 +112,8 @@ struct asus_wmi_data { /* boards with EC support */ static struct asus_wmi_data sensors_board_PW_X570_P = { .known_board_sensors = { - SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, SENSOR_FAN_CHIPSET, SENSOR_MAX }, From 6dd0ea4c565f73dca55a20bc1ab1587a6587d687 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Wed, 12 Jan 2022 23:49:17 +0200 Subject: [PATCH 20/86] hwmon: (asus_wmi_sensors) add ASUS ROG STRIX B450-F GAMING II ASUS ROG STRIX B450-F GAMING II has support of the same WMI monitoring method as ASUS ROG STRIX B450-F GAMING. This commit adds "ASUS ROG STRIX B450-F GAMING II" to the list of boards that can be monitored using ASUS WMI. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Tested-by: Aleksa Savic Link: https://lore.kernel.org/r/20220112214917.11662-1-pauk.denis@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus_wmi_sensors.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c index c80eee874b6c..8fdcb62ae52d 100644 --- a/drivers/hwmon/asus_wmi_sensors.c +++ b/drivers/hwmon/asus_wmi_sensors.c @@ -77,6 +77,7 @@ static const struct dmi_system_id asus_wmi_dmi_table[] = { DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)"), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING"), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING II"), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING"), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING"), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING"), From e505e44fb09aa2024c7f1df0551e7c6b50b0f8db Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Mon, 17 Jan 2022 17:12:49 +0100 Subject: [PATCH 21/86] hwmon: (pmbus) Remove trailing whitespaces from Kconfig file Fix checkpatch issues by removing trailing whitespaces in Kconfig. Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/c984b88b136a1cde16ce52c5f818886653b0f84a.1642434222.git.sylv@sylv.io [groeck: Updated subject] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 41f6cbf96d3b..c96f7b7338bd 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -189,8 +189,8 @@ config SENSORS_LTC2978_REGULATOR depends on SENSORS_LTC2978 && REGULATOR help If you say yes here you get regulator support for Linear Technology - LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880, - LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686, + LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880, + LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686, and LTM4700. config SENSORS_LTC3815 From f86380b797834bed6cd55c6c1f49a590356ec828 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Tue, 18 Jan 2022 19:55:13 -0600 Subject: [PATCH 22/86] dt-bindings: hwmon: lm90: Drop Tegra specifics from example There's no need to complicate examples with a platform specific macro. It also complicates example parsing to figure out the number of interrupt cells in examples (based on bracketing). Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20220119015514.2441231-1-robh@kernel.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/national,lm90.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/national,lm90.yaml b/Documentation/devicetree/bindings/hwmon/national,lm90.yaml index 6e1d54ff5d5b..30db92977937 100644 --- a/Documentation/devicetree/bindings/hwmon/national,lm90.yaml +++ b/Documentation/devicetree/bindings/hwmon/national,lm90.yaml @@ -60,7 +60,6 @@ additionalProperties: false examples: - | - #include #include i2c { @@ -71,8 +70,7 @@ examples: compatible = "onnn,nct1008"; reg = <0x4c>; vcc-supply = <&palmas_ldo6_reg>; - interrupt-parent = <&gpio>; - interrupts = ; + interrupts = <4 IRQ_TYPE_LEVEL_LOW>; #thermal-sensor-cells = <1>; }; }; From 15b1c188f8cf2ff9f296c9781d1e4fc061aaf371 Mon Sep 17 00:00:00 2001 From: Michael Shych Date: Tue, 18 Jan 2022 09:56:09 +0200 Subject: [PATCH 23/86] hwmon: (powr1220) Cosmetic changes Update code alignments. Signed-off-by: Michael Shych Link: https://lore.kernel.org/r/20220118075611.10665-2-michaelsh@nvidia.com Signed-off-by: Guenter Roeck --- drivers/hwmon/powr1220.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c index 9e086338dcba..542e4a7b0234 100644 --- a/drivers/hwmon/powr1220.c +++ b/drivers/hwmon/powr1220.c @@ -111,7 +111,7 @@ static int powr1220_read_adc(struct device *dev, int ch_num) mutex_lock(&data->update_lock); if (time_after(jiffies, data->adc_last_updated[ch_num] + HZ) || - !data->adc_valid[ch_num]) { + !data->adc_valid[ch_num]) { /* * figure out if we need to use the attenuator for * high inputs or inputs that we don't yet have a measurement @@ -119,12 +119,12 @@ static int powr1220_read_adc(struct device *dev, int ch_num) * max reading. */ if (data->adc_maxes[ch_num] > ADC_MAX_LOW_MEASUREMENT_MV || - data->adc_maxes[ch_num] == 0) + data->adc_maxes[ch_num] == 0) adc_range = 1 << 4; /* set the attenuator and mux */ result = i2c_smbus_write_byte_data(data->client, ADC_MUX, - adc_range | ch_num); + adc_range | ch_num); if (result) goto exit; From 915d4664b7158d8d0f44da810186742c69300f02 Mon Sep 17 00:00:00 2001 From: Michael Shych Date: Tue, 18 Jan 2022 09:56:10 +0200 Subject: [PATCH 24/86] hwmon: (powr1220) Upgrade driver to support hwmon info infrastructure Reduce code by using devm_hwmon_device_register_with_groups() API by devm_hwmon_device_register_with_info() API. The motivation is to reduce code and to allow easy support for similar devices by the same driver. Signed-off-by: Michael Shych Reviewed-by: Vadim Pasternak Link: https://lore.kernel.org/r/20220118075611.10665-3-michaelsh@nvidia.com Signed-off-by: Guenter Roeck --- drivers/hwmon/powr1220.c | 209 +++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 116 deletions(-) diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c index 542e4a7b0234..0fa1a136eec8 100644 --- a/drivers/hwmon/powr1220.c +++ b/drivers/hwmon/powr1220.c @@ -167,135 +167,109 @@ exit: return result; } -/* Shows the voltage associated with the specified ADC channel */ -static ssize_t powr1220_voltage_show(struct device *dev, - struct device_attribute *dev_attr, - char *buf) +static umode_t +powr1220_is_visible(const void *data, enum hwmon_sensor_types type, u32 + attr, int channel) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); - int adc_val = powr1220_read_adc(dev, attr->index); + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_highest: + case hwmon_in_label: + return 0444; + default: + break; + } + break; + default: + break; + } - if (adc_val < 0) - return adc_val; - - return sprintf(buf, "%d\n", adc_val); + return 0; } -/* Shows the maximum setting associated with the specified ADC channel */ -static ssize_t powr1220_max_show(struct device *dev, - struct device_attribute *dev_attr, char *buf) +static int +powr1220_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_label: + *str = input_names[channel]; + return 0; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return -EOPNOTSUPP; +} + +static int +powr1220_read(struct device *dev, enum hwmon_sensor_types type, u32 + attr, int channel, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); struct powr1220_data *data = dev_get_drvdata(dev); + int ret; - return sprintf(buf, "%d\n", data->adc_maxes[attr->index]); + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + ret = powr1220_read_adc(dev, channel); + if (ret < 0) + return ret; + *val = ret; + break; + case hwmon_in_highest: + *val = data->adc_maxes[channel]; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; } -/* Shows the label associated with the specified ADC channel */ -static ssize_t powr1220_label_show(struct device *dev, - struct device_attribute *dev_attr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); - - return sprintf(buf, "%s\n", input_names[attr->index]); + return 0; } -static SENSOR_DEVICE_ATTR_RO(in0_input, powr1220_voltage, VMON1); -static SENSOR_DEVICE_ATTR_RO(in1_input, powr1220_voltage, VMON2); -static SENSOR_DEVICE_ATTR_RO(in2_input, powr1220_voltage, VMON3); -static SENSOR_DEVICE_ATTR_RO(in3_input, powr1220_voltage, VMON4); -static SENSOR_DEVICE_ATTR_RO(in4_input, powr1220_voltage, VMON5); -static SENSOR_DEVICE_ATTR_RO(in5_input, powr1220_voltage, VMON6); -static SENSOR_DEVICE_ATTR_RO(in6_input, powr1220_voltage, VMON7); -static SENSOR_DEVICE_ATTR_RO(in7_input, powr1220_voltage, VMON8); -static SENSOR_DEVICE_ATTR_RO(in8_input, powr1220_voltage, VMON9); -static SENSOR_DEVICE_ATTR_RO(in9_input, powr1220_voltage, VMON10); -static SENSOR_DEVICE_ATTR_RO(in10_input, powr1220_voltage, VMON11); -static SENSOR_DEVICE_ATTR_RO(in11_input, powr1220_voltage, VMON12); -static SENSOR_DEVICE_ATTR_RO(in12_input, powr1220_voltage, VCCA); -static SENSOR_DEVICE_ATTR_RO(in13_input, powr1220_voltage, VCCINP); - -static SENSOR_DEVICE_ATTR_RO(in0_highest, powr1220_max, VMON1); -static SENSOR_DEVICE_ATTR_RO(in1_highest, powr1220_max, VMON2); -static SENSOR_DEVICE_ATTR_RO(in2_highest, powr1220_max, VMON3); -static SENSOR_DEVICE_ATTR_RO(in3_highest, powr1220_max, VMON4); -static SENSOR_DEVICE_ATTR_RO(in4_highest, powr1220_max, VMON5); -static SENSOR_DEVICE_ATTR_RO(in5_highest, powr1220_max, VMON6); -static SENSOR_DEVICE_ATTR_RO(in6_highest, powr1220_max, VMON7); -static SENSOR_DEVICE_ATTR_RO(in7_highest, powr1220_max, VMON8); -static SENSOR_DEVICE_ATTR_RO(in8_highest, powr1220_max, VMON9); -static SENSOR_DEVICE_ATTR_RO(in9_highest, powr1220_max, VMON10); -static SENSOR_DEVICE_ATTR_RO(in10_highest, powr1220_max, VMON11); -static SENSOR_DEVICE_ATTR_RO(in11_highest, powr1220_max, VMON12); -static SENSOR_DEVICE_ATTR_RO(in12_highest, powr1220_max, VCCA); -static SENSOR_DEVICE_ATTR_RO(in13_highest, powr1220_max, VCCINP); - -static SENSOR_DEVICE_ATTR_RO(in0_label, powr1220_label, VMON1); -static SENSOR_DEVICE_ATTR_RO(in1_label, powr1220_label, VMON2); -static SENSOR_DEVICE_ATTR_RO(in2_label, powr1220_label, VMON3); -static SENSOR_DEVICE_ATTR_RO(in3_label, powr1220_label, VMON4); -static SENSOR_DEVICE_ATTR_RO(in4_label, powr1220_label, VMON5); -static SENSOR_DEVICE_ATTR_RO(in5_label, powr1220_label, VMON6); -static SENSOR_DEVICE_ATTR_RO(in6_label, powr1220_label, VMON7); -static SENSOR_DEVICE_ATTR_RO(in7_label, powr1220_label, VMON8); -static SENSOR_DEVICE_ATTR_RO(in8_label, powr1220_label, VMON9); -static SENSOR_DEVICE_ATTR_RO(in9_label, powr1220_label, VMON10); -static SENSOR_DEVICE_ATTR_RO(in10_label, powr1220_label, VMON11); -static SENSOR_DEVICE_ATTR_RO(in11_label, powr1220_label, VMON12); -static SENSOR_DEVICE_ATTR_RO(in12_label, powr1220_label, VCCA); -static SENSOR_DEVICE_ATTR_RO(in13_label, powr1220_label, VCCINP); - -static struct attribute *powr1220_attrs[] = { - &sensor_dev_attr_in0_input.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in3_input.dev_attr.attr, - &sensor_dev_attr_in4_input.dev_attr.attr, - &sensor_dev_attr_in5_input.dev_attr.attr, - &sensor_dev_attr_in6_input.dev_attr.attr, - &sensor_dev_attr_in7_input.dev_attr.attr, - &sensor_dev_attr_in8_input.dev_attr.attr, - &sensor_dev_attr_in9_input.dev_attr.attr, - &sensor_dev_attr_in10_input.dev_attr.attr, - &sensor_dev_attr_in11_input.dev_attr.attr, - &sensor_dev_attr_in12_input.dev_attr.attr, - &sensor_dev_attr_in13_input.dev_attr.attr, - - &sensor_dev_attr_in0_highest.dev_attr.attr, - &sensor_dev_attr_in1_highest.dev_attr.attr, - &sensor_dev_attr_in2_highest.dev_attr.attr, - &sensor_dev_attr_in3_highest.dev_attr.attr, - &sensor_dev_attr_in4_highest.dev_attr.attr, - &sensor_dev_attr_in5_highest.dev_attr.attr, - &sensor_dev_attr_in6_highest.dev_attr.attr, - &sensor_dev_attr_in7_highest.dev_attr.attr, - &sensor_dev_attr_in8_highest.dev_attr.attr, - &sensor_dev_attr_in9_highest.dev_attr.attr, - &sensor_dev_attr_in10_highest.dev_attr.attr, - &sensor_dev_attr_in11_highest.dev_attr.attr, - &sensor_dev_attr_in12_highest.dev_attr.attr, - &sensor_dev_attr_in13_highest.dev_attr.attr, - - &sensor_dev_attr_in0_label.dev_attr.attr, - &sensor_dev_attr_in1_label.dev_attr.attr, - &sensor_dev_attr_in2_label.dev_attr.attr, - &sensor_dev_attr_in3_label.dev_attr.attr, - &sensor_dev_attr_in4_label.dev_attr.attr, - &sensor_dev_attr_in5_label.dev_attr.attr, - &sensor_dev_attr_in6_label.dev_attr.attr, - &sensor_dev_attr_in7_label.dev_attr.attr, - &sensor_dev_attr_in8_label.dev_attr.attr, - &sensor_dev_attr_in9_label.dev_attr.attr, - &sensor_dev_attr_in10_label.dev_attr.attr, - &sensor_dev_attr_in11_label.dev_attr.attr, - &sensor_dev_attr_in12_label.dev_attr.attr, - &sensor_dev_attr_in13_label.dev_attr.attr, +static const struct hwmon_channel_info *powr1220_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL), NULL }; -ATTRIBUTE_GROUPS(powr1220); +static const struct hwmon_ops powr1220_hwmon_ops = { + .read = powr1220_read, + .read_string = powr1220_read_string, + .is_visible = powr1220_is_visible, +}; + +static const struct hwmon_chip_info powr1220_chip_info = { + .ops = &powr1220_hwmon_ops, + .info = powr1220_info, +}; static int powr1220_probe(struct i2c_client *client) { @@ -312,8 +286,11 @@ static int powr1220_probe(struct i2c_client *client) mutex_init(&data->update_lock); data->client = client; - hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, - client->name, data, powr1220_groups); + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, + client->name, + data, + &powr1220_chip_info, + NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } From 9f93aa1005fa1b960f10e0ee3ed8c4e697526053 Mon Sep 17 00:00:00 2001 From: Michael Shych Date: Tue, 18 Jan 2022 09:56:11 +0200 Subject: [PATCH 25/86] hwmon: (powr1220) Add support for Lattice's POWR1014 power manager IC This patch adds support for Lattice's POWR1014 power manager IC. Read access to all the ADCs on the chip are supported through the "hwmon" "sysfs" files. The main differences of POWR1014 compared to POWR1220 are amount of VMON input lines: 10 on POWR1014 and 12 lines on POWR1220 and number of output control signals: 14 on POWR1014 and 20 on POWR1220. Signed-off-by: Michael Shych Reviewed-by: Vadim Pasternak Link: https://lore.kernel.org/r/20220118075611.10665-4-michaelsh@nvidia.com Signed-off-by: Guenter Roeck --- drivers/hwmon/powr1220.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c index 0fa1a136eec8..f77dc6db31ac 100644 --- a/drivers/hwmon/powr1220.c +++ b/drivers/hwmon/powr1220.c @@ -22,6 +22,8 @@ #define ADC_STEP_MV 2 #define ADC_MAX_LOW_MEASUREMENT_MV 2000 +enum powr1xxx_chips { powr1014, powr1220 }; + enum powr1220_regs { VMON_STATUS0, VMON_STATUS1, @@ -74,6 +76,7 @@ enum powr1220_adc_values { struct powr1220_data { struct i2c_client *client; struct mutex update_lock; + u8 max_channels; bool adc_valid[MAX_POWR1220_ADC_VALUES]; /* the next value is in jiffies */ unsigned long adc_last_updated[MAX_POWR1220_ADC_VALUES]; @@ -171,6 +174,11 @@ static umode_t powr1220_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { + struct powr1220_data *chip_data = (struct powr1220_data *)data; + + if (channel >= chip_data->max_channels) + return 0; + switch (type) { case hwmon_in: switch (attr) { @@ -271,6 +279,8 @@ static const struct hwmon_chip_info powr1220_chip_info = { .info = powr1220_info, }; +static const struct i2c_device_id powr1220_ids[]; + static int powr1220_probe(struct i2c_client *client) { struct powr1220_data *data; @@ -283,6 +293,15 @@ static int powr1220_probe(struct i2c_client *client) if (!data) return -ENOMEM; + switch (i2c_match_id(powr1220_ids, client)->driver_data) { + case powr1014: + data->max_channels = 10; + break; + default: + data->max_channels = 12; + break; + } + mutex_init(&data->update_lock); data->client = client; @@ -296,7 +315,8 @@ static int powr1220_probe(struct i2c_client *client) } static const struct i2c_device_id powr1220_ids[] = { - { "powr1220", 0, }, + { "powr1014", powr1014, }, + { "powr1220", powr1220, }, { } }; From 07320c91565658e117f2f86a190eec9bb64abeb6 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Mon, 31 Jan 2022 16:27:40 +0000 Subject: [PATCH 26/86] hwmon: Fix possible NULL pointer The recent addition of the label attribute added some code that read the "label" device property, without checking first that "dev" was non-NULL. Fix this issue by first checking that "dev" is non-NULL. Fixes: ccd98cba6a18 ("hwmon: Add "label" attribute") Signed-off-by: Paul Cercueil Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 6c3a8c65390d..0d6c6809f26c 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -809,7 +809,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, hdev->groups = groups; } - if (device_property_present(dev, "label")) { + if (dev && device_property_present(dev, "label")) { err = device_property_read_string(dev, "label", &label); if (err < 0) goto free_hwmon; From 4db3c09228a0899cdd791b4e34cd102a3288fd8e Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 31 Jan 2022 22:19:32 +0100 Subject: [PATCH 27/86] hwmon: (sch56xx) Autoload modules on platform device creation Right now, when sch56xx-common has detected a SCH5627/SCH5636 superio chip, the corresponding module is not automatically loaded. Fix that by adding the necessary device tables to both modules. Tested on a Fujitsu Esprimo P720. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220131211935.3656-2-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/sch5627.c | 10 ++++++++++ drivers/hwmon/sch5636.c | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c index 8f1b569c69e7..72c3f6757e34 100644 --- a/drivers/hwmon/sch5627.c +++ b/drivers/hwmon/sch5627.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -456,11 +457,20 @@ static int sch5627_probe(struct platform_device *pdev) return 0; } +static const struct platform_device_id sch5627_device_id[] = { + { + .name = "sch5627", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, sch5627_device_id); + static struct platform_driver sch5627_driver = { .driver = { .name = DRVNAME, }, .probe = sch5627_probe, + .id_table = sch5627_device_id, }; module_platform_driver(sch5627_driver); diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c index 39ff1c9b1df5..269757bc3a9e 100644 --- a/drivers/hwmon/sch5636.c +++ b/drivers/hwmon/sch5636.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -501,12 +502,21 @@ error: return err; } +static const struct platform_device_id sch5636_device_id[] = { + { + .name = "sch5636", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, sch5636_device_id); + static struct platform_driver sch5636_driver = { .driver = { .name = DRVNAME, }, .probe = sch5636_probe, .remove = sch5636_remove, + .id_table = sch5636_device_id, }; module_platform_driver(sch5636_driver); From 393935baa45e5ccb9603cf7f9f020ed1bc0915f7 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 31 Jan 2022 22:19:33 +0100 Subject: [PATCH 28/86] hwmon: (sch56xx-common) Add automatic module loading on supported devices This patch enables the sch56xx-common module to get automatically loaded on supported machines. If a machine supports Fujitsu's SCH56XX-based hardware monitoring solutions, it contains a "Antiope"/" Antiope" dmi onboard device in case of the sch5627 or a "Theseus"/" Theseus" dmi onboard device in case of the sch5636. Since some machines like the Esprimo C700 have a seemingly faulty DMI table containing both onboard devices, the driver still needs to probe for the individual superio chip, which in presence of at least one DMI onboard device however can be considered safe. Also add a module parameter allowing for bypassing the DMI check. Tested on a Fujitsu Esprimo P720. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220131211935.3656-3-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/sch56xx-common.c | 40 ++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index 40cdadad35e5..0172aa16dc0c 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c @@ -7,8 +7,10 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include +#include #include #include #include @@ -19,7 +21,10 @@ #include #include "sch56xx-common.h" -/* Insmod parameters */ +static bool ignore_dmi; +module_param(ignore_dmi, bool, 0); +MODULE_PARM_DESC(ignore_dmi, "Omit DMI check for supported devices (default=0)"); + static bool nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" @@ -518,11 +523,42 @@ static int __init sch56xx_device_add(int address, const char *name) return PTR_ERR_OR_ZERO(sch56xx_pdev); } +/* For autoloading only */ +static const struct dmi_system_id sch56xx_dmi_table[] __initconst = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, sch56xx_dmi_table); + static int __init sch56xx_init(void) { - int address; const char *name = NULL; + int address; + if (!ignore_dmi) { + if (!dmi_check_system(sch56xx_dmi_table)) + return -ENODEV; + + /* + * Some machines like the Esprimo P720 and Esprimo C700 have + * onboard devices named " Antiope"/" Theseus" instead of + * "Antiope"/"Theseus", so we need to check for both. + */ + if (!dmi_find_device(DMI_DEV_TYPE_OTHER, "Antiope", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OTHER, " Antiope", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OTHER, "Theseus", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OTHER, " Theseus", NULL)) + return -ENODEV; + } + + /* + * Some devices like the Esprimo C700 have both onboard devices, + * so we still have to check manually + */ address = sch56xx_find(0x4e, &name); if (address < 0) address = sch56xx_find(0x2e, &name); From 799c3e1e5348effda4312764bb484a084d561949 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 31 Jan 2022 22:19:34 +0100 Subject: [PATCH 29/86] hwmon: (sch56xx-common) Replace msleep() with usleep_range() msleep(1) will often sleep more than 20ms, slowing down sensor and watchdog reads/writes. Use usleep_range() as recommended in timers-howto.rst to fix that. Tested on a Fujitsu Esprimo P720. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220131211935.3656-4-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/sch56xx-common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index 0172aa16dc0c..82602b74c7ed 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c @@ -139,7 +139,7 @@ static int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v) /* EM Interface Polling "Algorithm" */ for (i = 0; i < max_busy_polls + max_lazy_polls; i++) { if (i >= max_busy_polls) - msleep(1); + usleep_range(1000, 2000); /* Read Interrupt source Register */ val = inb(addr + 8); /* Write Clear the interrupt source bits */ From 647d6f09bea7dacf4cdb6d4ea7e3051883955297 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Mon, 31 Jan 2022 22:19:35 +0100 Subject: [PATCH 30/86] hwmon: (sch56xx-common) Replace WDOG_ACTIVE with WDOG_HW_RUNNING If the watchdog was already enabled by the BIOS after booting, the watchdog infrastructure needs to regularly send keepalives to prevent a unexpected reset. WDOG_ACTIVE only serves as an status indicator for userspace, we want to use WDOG_HW_RUNNING instead. Since my Fujitsu Esprimo P720 does not support the watchdog, this change is compile-tested only. Suggested-by: Guenter Roeck Fixes: fb551405c0f8 (watchdog: sch56xx: Use watchdog core) Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220131211935.3656-5-W_Armin@gmx.de Reviewed-by: Hans de Goede Signed-off-by: Guenter Roeck --- drivers/hwmon/sch56xx-common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index 82602b74c7ed..3ece53adabd6 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c @@ -427,7 +427,7 @@ void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision, data->wddev.max_timeout = 255 * 60; watchdog_set_nowayout(&data->wddev, nowayout); if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) - set_bit(WDOG_ACTIVE, &data->wddev.status); + set_bit(WDOG_HW_RUNNING, &data->wddev.status); /* Since the watchdog uses a downcounter there is no register to read the BIOS set timeout from (if any was set at all) -> From d0ddfd241e5719d696bc0b081e260db69d368668 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Mon, 24 Jan 2022 02:56:43 +0100 Subject: [PATCH 31/86] hwmon: (asus-ec-sensors) add driver for ASUS EC This driver provides the same data as the asus_wmi_ec_sensors driver (and gets it from the same source) but does not use WMI, polling the ACPI EC directly. That provides two enhancements: sensor reading became quicker (on some systems or kernel configuration it took almost a full second to read all the sensors, that transfers less than 15 bytes of data), the driver became more flexible. The driver now relies on ACPI mutex to lock access to the EC in the same way as the WMI code does. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220124015658.687309-2-eugene.shalygin@gmail.com Tested-by: Oleksandr Natalenko Tested-by: Denis Pauk Signed-off-by: Guenter Roeck --- MAINTAINERS | 6 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/asus-ec-sensors.c | 694 ++++++++++++++++++++++++++++++++ 4 files changed, 712 insertions(+) create mode 100644 drivers/hwmon/asus-ec-sensors.c diff --git a/MAINTAINERS b/MAINTAINERS index 293c925b6beb..27a87a664635 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3056,6 +3056,12 @@ L: linux-hwmon@vger.kernel.org S: Maintained F: drivers/hwmon/asus_wmi_ec_sensors.c +ASUS EC HARDWARE MONITOR DRIVER +M: Eugene Shalygin +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/asus-ec-sensors.c + ASUS WIRELESS RADIO CONTROL DRIVER M: João Paulo Rechi Vita L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 01ab80a2cc4a..b75ec220da5a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2263,6 +2263,17 @@ config SENSORS_ASUS_WMI_EC This driver can also be built as a module. If so, the module will be called asus_wmi_sensors_ec. +config SENSORS_ASUS_EC + tristate "ASUS EC Sensors" + help + If you say yes here you get support for the ACPI embedded controller + hardware monitoring interface found in ASUS motherboards. The driver + currently supports B550/X570 boards, although other ASUS boards might + provide this monitoring interface as well. + + This driver can also be built as a module. If so, the module + will be called asus_ec_sensors. + endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 185f946d698b..7bba3415ca3d 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o # APCI drivers obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_SENSORS_ASUS_EC) += asus-ec-sensors.o obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c new file mode 100644 index 000000000000..7285334c7d80 --- /dev/null +++ b/drivers/hwmon/asus-ec-sensors.c @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HWMON driver for ASUS motherboards that publish some sensor values + * via the embedded controller registers. + * + * Copyright (C) 2021 Eugene Shalygin + + * EC provides: + * - Chipset temperature + * - CPU temperature + * - Motherboard temperature + * - T_Sensor temperature + * - VRM temperature + * - Water In temperature + * - Water Out temperature + * - CPU Optional fan RPM + * - Chipset fan RPM + * - VRM Heat Sink fan RPM + * - Water Flow fan RPM + * - CPU current + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static char *mutex_path_override; + +/* Writing to this EC register switches EC bank */ +#define ASUS_EC_BANK_REGISTER 0xff +#define SENSOR_LABEL_LEN 16 + +/* + * Arbitrary set max. allowed bank number. Required for sorting banks and + * currently is overkill with just 2 banks used at max, but for the sake + * of alignment let's set it to a higher value. + */ +#define ASUS_EC_MAX_BANK 3 + +#define ACPI_LOCK_DELAY_MS 500 + +/* ACPI mutex for locking access to the EC for the firmware */ +#define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX" + +/* There are two variants of the vendor spelling */ +#define VENDOR_ASUS_UPPER_CASE "ASUSTeK COMPUTER INC." + +typedef union { + u32 value; + struct { + u8 index; + u8 bank; + u8 size; + u8 dummy; + } components; +} sensor_address; + +#define MAKE_SENSOR_ADDRESS(size, bank, index) { \ + .value = (size << 16) + (bank << 8) + index \ + } + +static u32 hwmon_attributes[hwmon_max] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +struct ec_sensor_info { + char label[SENSOR_LABEL_LEN]; + enum hwmon_sensor_types type; + sensor_address addr; +}; + +#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \ + .label = sensor_label, .type = sensor_type, \ + .addr = MAKE_SENSOR_ADDRESS(size, bank, index), \ + } + +enum ec_sensors { + /* chipset temperature [℃] */ + ec_sensor_temp_chipset, + /* CPU temperature [℃] */ + ec_sensor_temp_cpu, + /* motherboard temperature [℃] */ + ec_sensor_temp_mb, + /* "T_Sensor" temperature sensor reading [℃] */ + ec_sensor_temp_t_sensor, + /* VRM temperature [℃] */ + ec_sensor_temp_vrm, + /* CPU_Opt fan [RPM] */ + ec_sensor_fan_cpu_opt, + /* VRM heat sink fan [RPM] */ + ec_sensor_fan_vrm_hs, + /* Chipset fan [RPM] */ + ec_sensor_fan_chipset, + /* Water flow sensor reading [RPM] */ + ec_sensor_fan_water_flow, + /* CPU current [A] */ + ec_sensor_curr_cpu, + /* "Water_In" temperature sensor reading [℃] */ + ec_sensor_temp_water_in, + /* "Water_Out" temperature sensor reading [℃] */ + ec_sensor_temp_water_out, +}; + +#define SENSOR_TEMP_CHIPSET BIT(ec_sensor_temp_chipset) +#define SENSOR_TEMP_CPU BIT(ec_sensor_temp_cpu) +#define SENSOR_TEMP_MB BIT(ec_sensor_temp_mb) +#define SENSOR_TEMP_T_SENSOR BIT(ec_sensor_temp_t_sensor) +#define SENSOR_TEMP_VRM BIT(ec_sensor_temp_vrm) +#define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt) +#define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs) +#define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset) +#define SENSOR_FAN_WATER_FLOW BIT(ec_sensor_fan_water_flow) +#define SENSOR_CURR_CPU BIT(ec_sensor_curr_cpu) +#define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in) +#define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out) + +/* All the known sensors for ASUS EC controllers */ +static const struct ec_sensor_info known_ec_sensors[] = { + [ec_sensor_temp_chipset] = + EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [ec_sensor_temp_mb] = + EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [ec_sensor_fan_vrm_hs] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [ec_sensor_fan_chipset] = + EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc), + [ec_sensor_curr_cpu] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [ec_sensor_temp_water_in] = + EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), + [ec_sensor_temp_water_out] = + EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), +}; + +/* Shortcuts for common combinations */ +#define SENSOR_SET_TEMP_CHIPSET_CPU_MB \ + (SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB) +#define SENSOR_SET_TEMP_WATER (SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT) + +#define DMI_EXACT_MATCH_BOARD(vendor, name, sensors) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, vendor), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ + .driver_data = (void *)(sensors), \ +} + +static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "PRIME X570-PRO", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII DARK HERO", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII FORMULA", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII HERO", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII IMPACT", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-E GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-I GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS | SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-E GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-F GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-I GAMING", + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + {} +}; + +struct ec_sensor { + unsigned int info_index; + u32 cached_value; +}; + +struct ec_sensors_data { + unsigned long board_sensors; + struct ec_sensor *sensors; + /* EC registers to read from */ + u16 *registers; + u8 *read_buffer; + /* sorted list of unique register banks */ + u8 banks[ASUS_EC_MAX_BANK + 1]; + /* in jiffies */ + unsigned long last_updated; + acpi_handle aml_mutex; + /* number of board EC sensors */ + u8 nr_sensors; + /* + * number of EC registers to read + * (sensor might span more than 1 register) + */ + u8 nr_registers; + /* number of unique register banks */ + u8 nr_banks; +}; + +static u8 register_bank(u16 reg) +{ + return reg >> 8; +} + +static u8 register_index(u16 reg) +{ + return reg & 0x00ff; +} + +static const struct ec_sensor_info * +get_sensor_info(const struct ec_sensors_data *state, int index) +{ + return &known_ec_sensors[state->sensors[index].info_index]; +} + +static int find_ec_sensor_index(const struct ec_sensors_data *ec, + enum hwmon_sensor_types type, int channel) +{ + unsigned int i; + + for (i = 0; i < ec->nr_sensors; i++) { + if (get_sensor_info(ec, i)->type == type) { + if (channel == 0) + return i; + channel--; + } + } + return -ENOENT; +} + +static int __init bank_compare(const void *a, const void *b) +{ + return *((const s8 *)a) - *((const s8 *)b); +} + +static int __init board_sensors_count(unsigned long sensors) +{ + return hweight_long(sensors); +} + +static void __init setup_sensor_data(struct ec_sensors_data *ec) +{ + struct ec_sensor *s = ec->sensors; + bool bank_found; + int i, j; + u8 bank; + + ec->nr_banks = 0; + ec->nr_registers = 0; + + for_each_set_bit(i, &ec->board_sensors, + BITS_PER_TYPE(ec->board_sensors)) { + s->info_index = i; + s->cached_value = 0; + ec->nr_registers += + known_ec_sensors[s->info_index].addr.components.size; + bank_found = false; + bank = known_ec_sensors[s->info_index].addr.components.bank; + for (j = 0; j < ec->nr_banks; j++) { + if (ec->banks[j] == bank) { + bank_found = true; + break; + } + } + if (!bank_found) { + ec->banks[ec->nr_banks++] = bank; + } + s++; + } + sort(ec->banks, ec->nr_banks, 1, bank_compare, NULL); +} + +static void __init fill_ec_registers(struct ec_sensors_data *ec) +{ + const struct ec_sensor_info *si; + unsigned int i, j, register_idx = 0; + + for (i = 0; i < ec->nr_sensors; ++i) { + si = get_sensor_info(ec, i); + for (j = 0; j < si->addr.components.size; ++j, ++register_idx) { + ec->registers[register_idx] = + (si->addr.components.bank << 8) + + si->addr.components.index + j; + } + } +} + +static acpi_handle __init asus_hw_access_mutex(struct device *dev) +{ + const char *mutex_path; + acpi_handle res; + int status; + + mutex_path = mutex_path_override ? + mutex_path_override : ASUS_HW_ACCESS_MUTEX_ASMX; + + status = acpi_get_handle(NULL, (acpi_string)mutex_path, &res); + if (ACPI_FAILURE(status)) { + dev_err(dev, + "Could not get hardware access guard mutex '%s': error %d", + mutex_path, status); + return NULL; + } + return res; +} + +static int asus_ec_bank_switch(u8 bank, u8 *old) +{ + int status = 0; + + if (old) { + status = ec_read(ASUS_EC_BANK_REGISTER, old); + } + if (status || (old && (*old == bank))) + return status; + return ec_write(ASUS_EC_BANK_REGISTER, bank); +} + +static int asus_ec_block_read(const struct device *dev, + struct ec_sensors_data *ec) +{ + int ireg, ibank, status; + u8 bank, reg_bank, prev_bank; + + bank = 0; + status = asus_ec_bank_switch(bank, &prev_bank); + if (status) { + dev_warn(dev, "EC bank switch failed"); + return status; + } + + if (prev_bank) { + /* oops... somebody else is working with the EC too */ + dev_warn(dev, + "Concurrent access to the ACPI EC detected.\nRace condition possible."); + } + + /* read registers minimizing bank switches. */ + for (ibank = 0; ibank < ec->nr_banks; ibank++) { + if (bank != ec->banks[ibank]) { + bank = ec->banks[ibank]; + if (asus_ec_bank_switch(bank, NULL)) { + dev_warn(dev, "EC bank switch to %d failed", + bank); + break; + } + } + for (ireg = 0; ireg < ec->nr_registers; ireg++) { + reg_bank = register_bank(ec->registers[ireg]); + if (reg_bank < bank) { + continue; + } + ec_read(register_index(ec->registers[ireg]), + ec->read_buffer + ireg); + } + } + + status = asus_ec_bank_switch(prev_bank, NULL); + return status; +} + +static inline u32 get_sensor_value(const struct ec_sensor_info *si, u8 *data) +{ + switch (si->addr.components.size) { + case 1: + return *data; + case 2: + return get_unaligned_be16(data); + case 4: + return get_unaligned_be32(data); + default: + return 0; + } +} + +static void update_sensor_values(struct ec_sensors_data *ec, u8 *data) +{ + const struct ec_sensor_info *si; + struct ec_sensor *s; + + for (s = ec->sensors; s != ec->sensors + ec->nr_sensors; s++) { + si = &known_ec_sensors[s->info_index]; + s->cached_value = get_sensor_value(si, data); + data += si->addr.components.size; + } +} + +static int update_ec_sensors(const struct device *dev, + struct ec_sensors_data *ec) +{ + int status; + + /* + * ASUS DSDT does not specify that access to the EC has to be guarded, + * but firmware does access it via ACPI + */ + if (ACPI_FAILURE(acpi_acquire_mutex(ec->aml_mutex, NULL, + ACPI_LOCK_DELAY_MS))) { + dev_err(dev, "Failed to acquire AML mutex"); + status = -EBUSY; + goto cleanup; + } + + status = asus_ec_block_read(dev, ec); + + if (!status) { + update_sensor_values(ec, ec->read_buffer); + } + if (ACPI_FAILURE(acpi_release_mutex(ec->aml_mutex, NULL))) { + dev_err(dev, "Failed to release AML mutex"); + } +cleanup: + return status; +} + +static int scale_sensor_value(u32 value, int data_type) +{ + switch (data_type) { + case hwmon_curr: + case hwmon_temp: + case hwmon_in: + return value * MILLI; + default: + return value; + } +} + +static int get_cached_value_or_update(const struct device *dev, + int sensor_index, + struct ec_sensors_data *state, u32 *value) +{ + if (time_after(jiffies, state->last_updated + HZ)) { + if (update_ec_sensors(dev, state)) { + dev_err(dev, "update_ec_sensors() failure\n"); + return -EIO; + } + + state->last_updated = jiffies; + } + + *value = state->sensors[sensor_index].cached_value; + return 0; +} + +/* + * Now follow the functions that implement the hwmon interface + */ + +static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + u32 value = 0; + + struct ec_sensors_data *state = dev_get_drvdata(dev); + int sidx = find_ec_sensor_index(state, type, channel); + + if (sidx < 0) { + return sidx; + } + + ret = get_cached_value_or_update(dev, sidx, state, &value); + if (!ret) { + *val = scale_sensor_value(value, + get_sensor_info(state, sidx)->type); + } + + return ret; +} + +static int asus_ec_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct ec_sensors_data *state = dev_get_drvdata(dev); + int sensor_index = find_ec_sensor_index(state, type, channel); + *str = get_sensor_info(state, sensor_index)->label; + + return 0; +} + +static umode_t asus_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct ec_sensors_data *state = drvdata; + + return find_ec_sensor_index(state, type, channel) >= 0 ? S_IRUGO : 0; +} + +static int __init +asus_ec_hwmon_add_chan_info(struct hwmon_channel_info *asus_ec_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + int i; + u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + + if (!cfg) + return -ENOMEM; + + asus_ec_hwmon_chan->type = type; + asus_ec_hwmon_chan->config = cfg; + for (i = 0; i < num; i++, cfg++) + *cfg = config; + + return 0; +} + +static const struct hwmon_ops asus_ec_hwmon_ops = { + .is_visible = asus_ec_hwmon_is_visible, + .read = asus_ec_hwmon_read, + .read_string = asus_ec_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_ec_chip_info = { + .ops = &asus_ec_hwmon_ops, +}; + +static unsigned long __init +get_board_sensors(const struct device *dev) +{ + const struct dmi_system_id *dmi_entry; + + dmi_entry = dmi_first_match(asus_ec_dmi_table); + if (!dmi_entry) { + dev_info(dev, "Unsupported board"); + return 0; + } + + return (unsigned long)dmi_entry->driver_data; +} + +static int __init configure_sensor_setup(struct device *dev) +{ + struct ec_sensors_data *ec_data = dev_get_drvdata(dev); + int nr_count[hwmon_max] = { 0 }, nr_types = 0; + struct device *hwdev; + struct hwmon_channel_info *asus_ec_hwmon_chan; + const struct hwmon_channel_info **ptr_asus_ec_ci; + const struct hwmon_chip_info *chip_info; + const struct ec_sensor_info *si; + enum hwmon_sensor_types type; + unsigned int i; + + ec_data->board_sensors = get_board_sensors(dev); + if (!ec_data->board_sensors) { + return -ENODEV; + } + + ec_data->nr_sensors = board_sensors_count(ec_data->board_sensors); + ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors, + sizeof(struct ec_sensor), GFP_KERNEL); + + setup_sensor_data(ec_data); + ec_data->registers = devm_kcalloc(dev, ec_data->nr_registers, + sizeof(u16), GFP_KERNEL); + ec_data->read_buffer = devm_kcalloc(dev, ec_data->nr_registers, + sizeof(u8), GFP_KERNEL); + + if (!ec_data->registers || !ec_data->read_buffer) { + return -ENOMEM; + } + + fill_ec_registers(ec_data); + + ec_data->aml_mutex = asus_hw_access_mutex(dev); + + for (i = 0; i < ec_data->nr_sensors; ++i) { + si = get_sensor_info(ec_data, i); + if (!nr_count[si->type]) + ++nr_types; + ++nr_count[si->type]; + } + + if (nr_count[hwmon_temp]) + nr_count[hwmon_chip]++, nr_types++; + + asus_ec_hwmon_chan = devm_kcalloc( + dev, nr_types, sizeof(*asus_ec_hwmon_chan), GFP_KERNEL); + if (!asus_ec_hwmon_chan) + return -ENOMEM; + + ptr_asus_ec_ci = devm_kcalloc(dev, nr_types + 1, + sizeof(*ptr_asus_ec_ci), GFP_KERNEL); + if (!ptr_asus_ec_ci) + return -ENOMEM; + + asus_ec_chip_info.info = ptr_asus_ec_ci; + chip_info = &asus_ec_chip_info; + + for (type = 0; type < hwmon_max; ++type) { + if (!nr_count[type]) + continue; + + asus_ec_hwmon_add_chan_info(asus_ec_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + *ptr_asus_ec_ci++ = asus_ec_hwmon_chan++; + } + + dev_info(dev, "board has %d EC sensors that span %d registers", + ec_data->nr_sensors, ec_data->nr_registers); + + hwdev = devm_hwmon_device_register_with_info(dev, "asusec", + ec_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int __init asus_ec_probe(struct platform_device *pdev) +{ + struct asus_ec_sensors *state; + int status = 0; + + state = devm_kzalloc(&pdev->dev, sizeof(struct ec_sensors_data), + GFP_KERNEL); + + if (!state) { + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, state); + status = configure_sensor_setup(&pdev->dev); + return status; +} + +static const struct acpi_device_id acpi_ec_ids[] = { + /* Embedded Controller Device */ + { "PNP0C09", 0 }, + {} +}; + +static struct platform_driver asus_ec_sensors_platform_driver = { + .driver = { + .name = "asus-ec-sensors", + .acpi_match_table = acpi_ec_ids, + }, +}; + +MODULE_DEVICE_TABLE(dmi, asus_ec_dmi_table); +module_platform_driver_probe(asus_ec_sensors_platform_driver, asus_ec_probe); + +module_param_named(mutex_path, mutex_path_override, charp, 0); +MODULE_PARM_DESC(mutex_path, + "Override ACPI mutex path used to guard access to hardware"); + +MODULE_AUTHOR("Eugene Shalygin "); +MODULE_DESCRIPTION( + "HWMON driver for sensors accessible via ACPI EC in ASUS motherboards"); +MODULE_LICENSE("GPL"); From 0314c6ac9e98d7551554fe422128726cd2849a8e Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Mon, 24 Jan 2022 02:56:44 +0100 Subject: [PATCH 32/86] hwmon: (asus-ec-sensors) update documentation Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220124015658.687309-3-eugene.shalygin@gmail.com Tested-by: Oleksandr Natalenko Tested-by: Denis Pauk [groeck: update index.rst, do not drop asus_wmi_ec_sensors.rst] Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 52 +++++++++++++++++++++++++ Documentation/hwmon/index.rst | 1 + 2 files changed, 53 insertions(+) create mode 100644 Documentation/hwmon/asus_ec_sensors.rst diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst new file mode 100644 index 000000000000..b12ac7ebeb1a --- /dev/null +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -0,0 +1,52 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_ec_sensors +================================= + +Supported boards: + * PRIME X570-PRO, + * Pro WS X570-ACE, + * ROG CROSSHAIR VIII DARK HERO, + * ROG CROSSHAIR VIII FORMULA, + * ROG CROSSHAIR VIII HERO, + * ROG CROSSHAIR VIII IMPACT, + * ROG STRIX B550-E GAMING, + * ROG STRIX B550-I GAMING, + * ROG STRIX X570-E GAMING, + * ROG STRIX X570-F GAMING, + * ROG STRIX X570-I GAMING + +Authors: + - Eugene Shalygin + +Description: +------------ +ASUS mainboards publish hardware monitoring information via Super I/O +chip and the ACPI embedded controller (EC) registers. Some of the sensors +are only available via the EC. + +The driver is aware of and reads the following sensors: + +1. Chipset (PCH) temperature +2. CPU package temperature +3. Motherboard temperature +4. Readings from the T_Sensor header +5. VRM temperature +6. CPU_Opt fan RPM +7. VRM heatsink fan RPM +8. Chipset fan RPM +9. Readings from the "Water flow meter" header (RPM) +10. Readings from the "Water In" and "Water Out" temperature headers +11. CPU current + +Sensor values are read from EC registers, and to avoid race with the board +firmware the driver acquires ACPI mutex, the one used by the WMI when its +methods access the EC. + +Module Parameters +----------------- + * mutex_path: string + The driver holds path to the ACPI mutex for each board (actually, + the path is mostly identical for them). If ASUS changes this path + in a future BIOS update, this parameter can be used to override + the stored in the driver value until it gets updated. diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index df20022c741f..b69fdaf1af82 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -43,6 +43,7 @@ Hardware Monitoring Kernel Drivers asb100 asc7621 aspeed-pwm-tacho + asus_ec_sensors asus_wmi_ec_sensors asus_wmi_sensors bcm54140 From 4408d3600f5af8abc3258900526ae8c16b106e81 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Mon, 24 Jan 2022 02:56:45 +0100 Subject: [PATCH 33/86] hwmon: deprecate asis_wmi_ec_sensors driver Deprecate the asus_wmi_ec_sensors driver in favor of the asus_ec_sensors Signed-off-by: Eugene Shalygin Reviewed-by: Oleksandr Natalenko Link: https://lore.kernel.org/r/20220124015658.687309-4-eugene.shalygin@gmail.com Tested-by: Oleksandr Natalenko Tested-by: Denis Pauk Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b75ec220da5a..5f1506cca68f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2253,13 +2253,16 @@ config SENSORS_ASUS_WMI config SENSORS_ASUS_WMI_EC tristate "ASUS WMI B550/X570" - depends on ACPI_WMI + depends on ACPI_WMI && SENSORS_ASUS_EC=n help If you say yes here you get support for the ACPI embedded controller hardware monitoring interface found in B550/X570 ASUS motherboards. This driver will provide readings of fans, voltages and temperatures through the system firmware. + This driver is deprecated in favor of the ASUS EC Sensors driver + which provides fully compatible output. + This driver can also be built as a module. If so, the module will be called asus_wmi_sensors_ec. From da74944d3a469ffc0e8229520afbf41ad01219b6 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Wed, 26 Jan 2022 16:18:25 +0200 Subject: [PATCH 34/86] hwmon: (mlxreg-fan) Use pwm attribute for setting fan speed low limit Recently 'cur_state' user space 'sysfs' interface 'sysfs' has been deprecated. This interface is used in Nvidia systems for setting fan speed limit. Currently fan speed limit is set from the user space by setting 'sysfs' 'cur_state' attribute to 'max_state + n', where 'n' is required limit, for example: 15 for 50% speed limit, 20 for full fan speed enforcement. The purpose of this feature is to provides ability to limit fan speed according to some system wise considerations, like absence of some replaceable units (PSU or line cards), high system ambient temperature, unreliable transceivers temperature sensing or some other factors which indirectly impacts system's airflow. The motivation is to support fan low limit feature through 'hwmon' interface. Use 'hwmon' 'pwm' attribute for setting low limit for fan speed in case 'thermal' subsystem is configured in kernel. In this case setting fan speed through 'hwmon' will never let the 'thermal' subsystem to select a lower duty cycle than the duty cycle selected with the 'pwm' attribute. From other side, fan speed is to be updated in hardware through 'pwm' only in case the requested fan speed is above last speed set by 'thermal' subsystem, otherwise requested fan speed will be just stored with no PWM update. Signed-off-by: Vadim Pasternak Link: https://lore.kernel.org/r/20220126141825.13545-1-vadimp@nvidia.com Signed-off-by: Guenter Roeck --- drivers/hwmon/mlxreg-fan.c | 84 ++++++++++++-------------------------- 1 file changed, 27 insertions(+), 57 deletions(-) diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c index 4a8becdb0d58..b48bd7c961d6 100644 --- a/drivers/hwmon/mlxreg-fan.c +++ b/drivers/hwmon/mlxreg-fan.c @@ -18,15 +18,6 @@ #define MLXREG_FAN_MAX_STATE 10 #define MLXREG_FAN_MIN_DUTY 51 /* 20% */ #define MLXREG_FAN_MAX_DUTY 255 /* 100% */ -/* - * Minimum and maximum FAN allowed speed in percent: from 20% to 100%. Values - * MLXREG_FAN_MAX_STATE + x, where x is between 2 and 10 are used for - * setting FAN speed dynamic minimum. For example, if value is set to 14 (40%) - * cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to - * introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100. - */ -#define MLXREG_FAN_SPEED_MIN (MLXREG_FAN_MAX_STATE + 2) -#define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2) #define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ #define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44 #define MLXREG_FAN_TACHO_DIV_MIN 283 @@ -87,13 +78,16 @@ struct mlxreg_fan_tacho { * @connected: indicates if PWM is connected; * @reg: register offset; * @cooling: cooling device levels; + * @last_hwmon_state: last cooling state set by hwmon subsystem; + * @last_thermal_state: last cooling state set by thermal subsystem; * @cdev: cooling device; */ struct mlxreg_fan_pwm { struct mlxreg_fan *fan; bool connected; u32 reg; - u8 cooling_levels[MLXREG_FAN_MAX_STATE + 1]; + unsigned long last_hwmon_state; + unsigned long last_thermal_state; struct thermal_cooling_device *cdev; }; @@ -119,6 +113,9 @@ struct mlxreg_fan { int divider; }; +static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state); + static int mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) @@ -213,6 +210,18 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, val > MLXREG_FAN_MAX_DUTY) return -EINVAL; pwm = &fan->pwm[channel]; + /* If thermal is configured - handle PWM limit setting. */ + if (IS_REACHABLE(CONFIG_THERMAL)) { + pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(val); + /* + * Update PWM only in case requested state is not less than the + * last thermal state. + */ + if (pwm->last_hwmon_state >= pwm->last_thermal_state) + return mlxreg_fan_set_cur_state(pwm->cdev, + pwm->last_hwmon_state); + return 0; + } return regmap_write(fan->regmap, pwm->reg, val); default: return -EOPNOTSUPP; @@ -338,58 +347,22 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, { struct mlxreg_fan_pwm *pwm = cdev->devdata; struct mlxreg_fan *fan = pwm->fan; - unsigned long cur_state; - int i, config = 0; - u32 regval; int err; - /* - * Verify if this request is for changing allowed FAN dynamical - * minimum. If it is - update cooling levels accordingly and update - * state, if current state is below the newly requested minimum state. - * For example, if current state is 5, and minimal state is to be - * changed from 4 to 6, fan->cooling_levels[0 to 5] will be changed all - * from 4 to 6. And state 5 (fan->cooling_levels[4]) should be - * overwritten. - */ - if (state >= MLXREG_FAN_SPEED_MIN && state <= MLXREG_FAN_SPEED_MAX) { - /* - * This is configuration change, which is only supported through sysfs. - * For configuration non-zero value is to be returned to avoid thermal - * statistics update. - */ - config = 1; - state -= MLXREG_FAN_MAX_STATE; - for (i = 0; i < state; i++) - pwm->cooling_levels[i] = state; - for (i = state; i <= MLXREG_FAN_MAX_STATE; i++) - pwm->cooling_levels[i] = i; - - err = regmap_read(fan->regmap, pwm->reg, ®val); - if (err) { - dev_err(fan->dev, "Failed to query PWM duty\n"); - return err; - } - - cur_state = MLXREG_FAN_PWM_DUTY2STATE(regval); - if (state < cur_state) - return config; - - state = cur_state; - } - if (state > MLXREG_FAN_MAX_STATE) return -EINVAL; - /* Normalize the state to the valid speed range. */ - state = pwm->cooling_levels[state]; + /* Save thermal state. */ + pwm->last_thermal_state = state; + + state = max_t(unsigned long, state, pwm->last_hwmon_state); err = regmap_write(fan->regmap, pwm->reg, MLXREG_FAN_PWM_STATE2DUTY(state)); if (err) { dev_err(fan->dev, "Failed to write PWM duty\n"); return err; } - return config; + return 0; } static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = { @@ -564,7 +537,7 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan, static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan) { - int i, j; + int i; for (i = 0; i < MLXREG_FAN_MAX_PWM; i++) { struct mlxreg_fan_pwm *pwm = &fan->pwm[i]; @@ -579,11 +552,8 @@ static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan) return PTR_ERR(pwm->cdev); } - /* Init cooling levels per PWM state. */ - for (j = 0; j < MLXREG_FAN_SPEED_MIN_LEVEL; j++) - pwm->cooling_levels[j] = MLXREG_FAN_SPEED_MIN_LEVEL; - for (j = MLXREG_FAN_SPEED_MIN_LEVEL; j <= MLXREG_FAN_MAX_STATE; j++) - pwm->cooling_levels[j] = j; + /* Set minimal PWM speed. */ + pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY); } return 0; From 06f7d6e75d1232fefc2c3c8567159963c38ec628 Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Thu, 27 Jan 2022 17:17:27 +0100 Subject: [PATCH 35/86] hwmon: (max6639) Update Datasheet URL The old Datasheet does not exist anymore. Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/76025f40d2684dc0d3ec02c8899b726b07a0e7da.1643299570.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- Documentation/hwmon/max6639.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/max6639.rst b/Documentation/hwmon/max6639.rst index 3da54225f83c..c85d285a3489 100644 --- a/Documentation/hwmon/max6639.rst +++ b/Documentation/hwmon/max6639.rst @@ -9,7 +9,7 @@ Supported chips: Addresses scanned: I2C 0x2c, 0x2e, 0x2f - Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6639.pdf + Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX6639-MAX6639F.pdf Authors: - He Changqing From 4e2271ea275193a24ea1c905a38fa000b0f8b822 Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Thu, 27 Jan 2022 17:17:28 +0100 Subject: [PATCH 36/86] hwmon: (max6639) Add regulator support Add regulator support for boards where the fan-supply have to be powered up before it can be used. Signed-off-by: Patrick Rudolph Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/2cb9ed600fb43cdc604799746fbde2e2942cdca6.1643299570.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- drivers/hwmon/max6639.c | 62 +++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c index ccc0f047bd44..14bb7726f8d7 100644 --- a/drivers/hwmon/max6639.c +++ b/drivers/hwmon/max6639.c @@ -87,6 +87,9 @@ struct max6639_data { /* Register values initialized only once */ u8 ppr; /* Pulses per rotation 0..3 for 1..4 ppr */ u8 rpm_range; /* Index in above rpm_ranges table */ + + /* Optional regulator for FAN supply */ + struct regulator *reg; }; static struct max6639_data *max6639_update_device(struct device *dev) @@ -516,6 +519,11 @@ static int max6639_detect(struct i2c_client *client, return 0; } +static void max6639_regulator_disable(void *data) +{ + regulator_disable(data); +} + static int max6639_probe(struct i2c_client *client) { struct device *dev = &client->dev; @@ -528,6 +536,28 @@ static int max6639_probe(struct i2c_client *client) return -ENOMEM; data->client = client; + + data->reg = devm_regulator_get_optional(dev, "fan"); + if (IS_ERR(data->reg)) { + if (PTR_ERR(data->reg) != -ENODEV) + return PTR_ERR(data->reg); + + data->reg = NULL; + } else { + /* Spin up fans */ + err = regulator_enable(data->reg); + if (err) { + dev_err(dev, "Failed to enable fan supply: %d\n", err); + return err; + } + err = devm_add_action_or_reset(dev, max6639_regulator_disable, + data->reg); + if (err) { + dev_err(dev, "Failed to register action: %d\n", err); + return err; + } + } + mutex_init(&data->update_lock); /* Initialize the max6639 chip */ @@ -545,23 +575,39 @@ static int max6639_probe(struct i2c_client *client) static int max6639_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); - if (data < 0) - return data; + struct max6639_data *data = dev_get_drvdata(dev); + int ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + + if (ret < 0) + return ret; + + if (data->reg) + regulator_disable(data->reg); return i2c_smbus_write_byte_data(client, - MAX6639_REG_GCONFIG, data | MAX6639_GCONFIG_STANDBY); + MAX6639_REG_GCONFIG, ret | MAX6639_GCONFIG_STANDBY); } static int max6639_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); - if (data < 0) - return data; + struct max6639_data *data = dev_get_drvdata(dev); + int ret; + + if (data->reg) { + ret = regulator_enable(data->reg); + if (ret) { + dev_err(dev, "Failed to enable fan supply: %d\n", ret); + return ret; + } + } + + ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (ret < 0) + return ret; return i2c_smbus_write_byte_data(client, - MAX6639_REG_GCONFIG, data & ~MAX6639_GCONFIG_STANDBY); + MAX6639_REG_GCONFIG, ret & ~MAX6639_GCONFIG_STANDBY); } #endif /* CONFIG_PM_SLEEP */ From 2f66cb5bf35265a8c9413a019166a0c29823ce57 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Thu, 3 Feb 2022 21:30:52 +0100 Subject: [PATCH 37/86] hwmon: (asus-ec-sensors) Add Crosshair VIII Hero WiFi The Wi-Fi variant of Crosshair VIII Hero provides the same sensors, which was tested by a Libre Hardware Monitor user [1]. [1] https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/pull/453#issuecomment-1028398487 Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220203203052.441712-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index b12ac7ebeb1a..22de1b037cfb 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -7,6 +7,7 @@ Supported boards: * PRIME X570-PRO, * Pro WS X570-ACE, * ROG CROSSHAIR VIII DARK HERO, + * ROG CROSSHAIR VIII HERO (WI-FI) * ROG CROSSHAIR VIII FORMULA, * ROG CROSSHAIR VIII HERO, * ROG CROSSHAIR VIII IMPACT, diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 7285334c7d80..05244209c0c6 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -188,6 +188,12 @@ static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII HERO (WI-FI)", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII IMPACT", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | From 88846ff7422089f576e29b68153034babbc68ce6 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sat, 5 Feb 2022 12:20:15 +0300 Subject: [PATCH 38/86] hwmon: (asus-ec-sensors) fix a typo in asus_ec_probe() There is no such struct as "asus_ec_sensors", it was supposed to be "ec_sensors_data". This typo does not affect either build or runtime. Fixes: c4b1687d6897 ("hwmon: (asus-ec-sensors) add driver for ASUS EC") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20220205092015.GA612@kili Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 05244209c0c6..7330cce46b14 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -659,7 +659,7 @@ static int __init configure_sensor_setup(struct device *dev) static int __init asus_ec_probe(struct platform_device *pdev) { - struct asus_ec_sensors *state; + struct ec_sensors_data *state; int status = 0; state = devm_kzalloc(&pdev->dev, sizeof(struct ec_sensors_data), From 339f8a998f58e8693f73429e7597f2ffc9b93ec5 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Fri, 4 Feb 2022 17:30:45 +0100 Subject: [PATCH 39/86] hwmon: (asus-ec-sensors) read sensors as signed ints Temperature sensor readings are signed, which is hinted by their blank value (oxd8, 216 as unsigned and -40 as signed). T_Sensor, Crosshair VIII Hero, and a freezer were used to confirm that. Here we read fan sensors as signed too, because with their typical values and 2-byte width, I can't tell a difference between signed and unsigned, as I don't have a high speed chipset fan. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220204163045.576903-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 7330cce46b14..dd7b207d062f 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -221,7 +221,7 @@ static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { struct ec_sensor { unsigned int info_index; - u32 cached_value; + s32 cached_value; }; struct ec_sensors_data { @@ -408,15 +408,15 @@ static int asus_ec_block_read(const struct device *dev, return status; } -static inline u32 get_sensor_value(const struct ec_sensor_info *si, u8 *data) +static inline s32 get_sensor_value(const struct ec_sensor_info *si, u8 *data) { switch (si->addr.components.size) { case 1: - return *data; + return (s8)*data; case 2: - return get_unaligned_be16(data); + return (s16)get_unaligned_be16(data); case 4: - return get_unaligned_be32(data); + return (s32)get_unaligned_be32(data); default: return 0; } @@ -462,7 +462,7 @@ cleanup: return status; } -static int scale_sensor_value(u32 value, int data_type) +static long scale_sensor_value(s32 value, int data_type) { switch (data_type) { case hwmon_curr: @@ -476,7 +476,7 @@ static int scale_sensor_value(u32 value, int data_type) static int get_cached_value_or_update(const struct device *dev, int sensor_index, - struct ec_sensors_data *state, u32 *value) + struct ec_sensors_data *state, s32 *value) { if (time_after(jiffies, state->last_updated + HZ)) { if (update_ec_sensors(dev, state)) { @@ -499,7 +499,7 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { int ret; - u32 value = 0; + s32 value = 0; struct ec_sensors_data *state = dev_get_drvdata(dev); int sidx = find_ec_sensor_index(state, type, channel); From 7979a30ddc560b372c0b9549f0587f56365cf6bf Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Mon, 7 Feb 2022 23:48:15 +0200 Subject: [PATCH 40/86] hwmon: (nct6775) add ASUS ROG STRIX Z390/Z490/X570-* / PRIME X570-P Boards such as * PRIME X570-P, * ROG STRIX B550-F GAMING WIFI II, * ROG STRIX B550-XE GAMING (WI-FI), * ROG STRIX X570-E GAMING, * ROG STRIX Z390-F GAMING, * ROG STRIX Z390-H GAMING, * ROG STRIX Z390-I GAMING, * ROG STRIX Z490-A GAMING, * ROG STRIX Z490-E GAMING, * ROG STRIX Z490-F GAMING, * ROG STRIX Z490-G GAMING, * ROG STRIX Z490-G GAMING (WI-FI), * ROG STRIX Z490-H GAMING have got a nct6775 chip, but by default there's no use of it because of resource conflict with WMI method. This commit adds such boards to the WMI monitoring list. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Tested-by: Per Melin Tested-by: Jaap de Haan Link: https://lore.kernel.org/r/20220207214815.10995-1-pauk.denis@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index fbf6266c7ba7..2b91f7e05126 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -5109,6 +5109,7 @@ static const char * const asus_wmi_boards[] = { "PRIME B550-PLUS", "PRIME B550M-A", "PRIME B550M-A (WI-FI)", + "PRIME X570-P", "PRIME X570-PRO", "ROG CROSSHAIR VIII DARK HERO", "ROG CROSSHAIR VIII FORMULA", @@ -5118,10 +5119,22 @@ static const char * const asus_wmi_boards[] = { "ROG STRIX B550-E GAMING", "ROG STRIX B550-F GAMING", "ROG STRIX B550-F GAMING (WI-FI)", + "ROG STRIX B550-F GAMING WIFI II", "ROG STRIX B550-I GAMING", + "ROG STRIX B550-XE GAMING (WI-FI)", + "ROG STRIX X570-E GAMING", "ROG STRIX X570-F GAMING", "ROG STRIX X570-I GAMING", "ROG STRIX Z390-E GAMING", + "ROG STRIX Z390-F GAMING", + "ROG STRIX Z390-H GAMING", + "ROG STRIX Z390-I GAMING", + "ROG STRIX Z490-A GAMING", + "ROG STRIX Z490-E GAMING", + "ROG STRIX Z490-F GAMING", + "ROG STRIX Z490-G GAMING", + "ROG STRIX Z490-G GAMING (WI-FI)", + "ROG STRIX Z490-H GAMING", "ROG STRIX Z490-I GAMING", "TUF GAMING B550M-PLUS", "TUF GAMING B550M-PLUS (WI-FI)", From f53207017fd99aaeecef6e61b4a3d8e868f53756 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 23 Dec 2021 10:32:05 -0800 Subject: [PATCH 41/86] hwmon: (adt7x10) Convert to use regmap Using regmap lets us use the regmap subsystem for SPI vs. I2C register accesses. It lets us hide access differences in backend code and lets the common code just access registers without knowing their size. We can also use regmap for register caching. Tested-by: Cosmin Tanislav Reviewed-by: Cosmin Tanislav Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/adt7310.c | 88 +++++++++++++--- drivers/hwmon/adt7410.c | 79 ++++++++++---- drivers/hwmon/adt7x10.c | 227 +++++++++++++++------------------------- drivers/hwmon/adt7x10.h | 10 +- 5 files changed, 215 insertions(+), 190 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5f1506cca68f..387253ae6c14 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -174,6 +174,7 @@ config SENSORS_ADM9240 config SENSORS_ADT7X10 tristate + select REGMAP help This module contains common code shared by the ADT7310/ADT7320 and ADT7410/ADT7420 temperature monitoring chip drivers. diff --git a/drivers/hwmon/adt7310.c b/drivers/hwmon/adt7310.c index c40cac16af68..a83092470bce 100644 --- a/drivers/hwmon/adt7310.c +++ b/drivers/hwmon/adt7310.c @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -38,16 +39,13 @@ static const u8 adt7310_reg_table[] = { #define AD7310_COMMAND(reg) (adt7310_reg_table[(reg)] << ADT7310_CMD_REG_OFFSET) -static int adt7310_spi_read_word(struct device *dev, u8 reg) +static int adt7310_spi_read_word(struct spi_device *spi, u8 reg) { - struct spi_device *spi = to_spi_device(dev); - return spi_w8r16be(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ); } -static int adt7310_spi_write_word(struct device *dev, u8 reg, u16 data) +static int adt7310_spi_write_word(struct spi_device *spi, u8 reg, u16 data) { - struct spi_device *spi = to_spi_device(dev); u8 buf[3]; buf[0] = AD7310_COMMAND(reg); @@ -56,17 +54,13 @@ static int adt7310_spi_write_word(struct device *dev, u8 reg, u16 data) return spi_write(spi, buf, sizeof(buf)); } -static int adt7310_spi_read_byte(struct device *dev, u8 reg) +static int adt7310_spi_read_byte(struct spi_device *spi, u8 reg) { - struct spi_device *spi = to_spi_device(dev); - return spi_w8r8(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ); } -static int adt7310_spi_write_byte(struct device *dev, u8 reg, - u8 data) +static int adt7310_spi_write_byte(struct spi_device *spi, u8 reg, u8 data) { - struct spi_device *spi = to_spi_device(dev); u8 buf[2]; buf[0] = AD7310_COMMAND(reg); @@ -75,17 +69,77 @@ static int adt7310_spi_write_byte(struct device *dev, u8 reg, return spi_write(spi, buf, sizeof(buf)); } -static const struct adt7x10_ops adt7310_spi_ops = { - .read_word = adt7310_spi_read_word, - .write_word = adt7310_spi_write_word, - .read_byte = adt7310_spi_read_byte, - .write_byte = adt7310_spi_write_byte, +static bool adt7310_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_STATUS: + return true; + default: + return false; + } +} + +static int adt7310_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct spi_device *spi = context; + int regval; + + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + regval = adt7310_spi_read_word(spi, reg); + break; + default: + regval = adt7310_spi_read_byte(spi, reg); + break; + } + if (regval < 0) + return regval; + *val = regval; + return 0; +} + +static int adt7310_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct spi_device *spi = context; + int ret; + + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + ret = adt7310_spi_write_word(spi, reg, val); + break; + default: + ret = adt7310_spi_write_byte(spi, reg, val); + break; + } + return ret; +} + +static const struct regmap_config adt7310_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = adt7310_regmap_is_volatile, + .reg_read = adt7310_reg_read, + .reg_write = adt7310_reg_write, }; static int adt7310_spi_probe(struct spi_device *spi) { + struct regmap *regmap; + + regmap = devm_regmap_init(&spi->dev, NULL, spi, &adt7310_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + return adt7x10_probe(&spi->dev, spi_get_device_id(spi)->name, spi->irq, - &adt7310_spi_ops); + regmap); } static int adt7310_spi_remove(struct spi_device *spi) diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c index 973db057427b..b1f4497dca05 100644 --- a/drivers/hwmon/adt7410.c +++ b/drivers/hwmon/adt7410.c @@ -9,43 +9,82 @@ #include #include #include +#include #include "adt7x10.h" -static int adt7410_i2c_read_word(struct device *dev, u8 reg) +static bool adt7410_regmap_is_volatile(struct device *dev, unsigned int reg) { - return i2c_smbus_read_word_swapped(to_i2c_client(dev), reg); + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_STATUS: + return true; + default: + return false; + } } -static int adt7410_i2c_write_word(struct device *dev, u8 reg, u16 data) +static int adt7410_reg_read(void *context, unsigned int reg, unsigned int *val) { - return i2c_smbus_write_word_swapped(to_i2c_client(dev), reg, data); + struct i2c_client *client = context; + int regval; + + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + regval = i2c_smbus_read_word_swapped(client, reg); + break; + default: + regval = i2c_smbus_read_byte_data(client, reg); + break; + } + if (regval < 0) + return regval; + *val = regval; + return 0; } -static int adt7410_i2c_read_byte(struct device *dev, u8 reg) +static int adt7410_reg_write(void *context, unsigned int reg, unsigned int val) { - return i2c_smbus_read_byte_data(to_i2c_client(dev), reg); + struct i2c_client *client = context; + int ret; + + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + ret = i2c_smbus_write_word_swapped(client, reg, val); + break; + default: + ret = i2c_smbus_write_byte_data(client, reg, val); + break; + } + return ret; } -static int adt7410_i2c_write_byte(struct device *dev, u8 reg, u8 data) -{ - return i2c_smbus_write_byte_data(to_i2c_client(dev), reg, data); -} - -static const struct adt7x10_ops adt7410_i2c_ops = { - .read_word = adt7410_i2c_read_word, - .write_word = adt7410_i2c_write_word, - .read_byte = adt7410_i2c_read_byte, - .write_byte = adt7410_i2c_write_byte, +static const struct regmap_config adt7410_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = ADT7X10_ID, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = adt7410_regmap_is_volatile, + .reg_read = adt7410_reg_read, + .reg_write = adt7410_reg_write, }; static int adt7410_i2c_probe(struct i2c_client *client) { - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; + struct regmap *regmap; - return adt7x10_probe(&client->dev, NULL, client->irq, &adt7410_i2c_ops); + regmap = devm_regmap_init(&client->dev, NULL, client, + &adt7410_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return adt7x10_probe(&client->dev, NULL, client->irq, regmap); } static int adt7410_i2c_remove(struct i2c_client *client) diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index e9d33aa78a19..05dd48b707b4 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "adt7x10.h" @@ -53,46 +54,15 @@ /* Each client has this additional data */ struct adt7x10_data { - const struct adt7x10_ops *ops; + struct regmap *regmap; const char *name; struct device *hwmon_dev; struct mutex update_lock; u8 config; u8 oldconfig; - bool valid; /* true if registers valid */ - unsigned long last_updated; /* In jiffies */ - s16 temp[4]; /* Register values, - 0 = input - 1 = high - 2 = low - 3 = critical */ - u8 hyst; /* hysteresis offset */ + bool valid; /* true if temperature valid */ }; -static int adt7x10_read_byte(struct device *dev, u8 reg) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->read_byte(dev, reg); -} - -static int adt7x10_write_byte(struct device *dev, u8 reg, u8 data) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->write_byte(dev, reg, data); -} - -static int adt7x10_read_word(struct device *dev, u8 reg) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->read_word(dev, reg); -} - -static int adt7x10_write_word(struct device *dev, u8 reg, u16 data) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->write_word(dev, reg, data); -} - static const u8 ADT7X10_REG_TEMP[4] = { ADT7X10_TEMPERATURE, /* input */ ADT7X10_T_ALARM_HIGH, /* high */ @@ -103,10 +73,12 @@ static const u8 ADT7X10_REG_TEMP[4] = { static irqreturn_t adt7x10_irq_handler(int irq, void *private) { struct device *dev = private; - int status; + struct adt7x10_data *d = dev_get_drvdata(dev); + unsigned int status; + int ret; - status = adt7x10_read_byte(dev, ADT7X10_STATUS); - if (status < 0) + ret = regmap_read(d->regmap, ADT7X10_STATUS, &status); + if (ret < 0) return IRQ_HANDLED; if (status & ADT7X10_STAT_T_HIGH) @@ -119,14 +91,15 @@ static irqreturn_t adt7x10_irq_handler(int irq, void *private) return IRQ_HANDLED; } -static int adt7x10_temp_ready(struct device *dev) +static int adt7x10_temp_ready(struct regmap *regmap) { - int i, status; + unsigned int status; + int i, ret; for (i = 0; i < 6; i++) { - status = adt7x10_read_byte(dev, ADT7X10_STATUS); - if (status < 0) - return status; + ret = regmap_read(regmap, ADT7X10_STATUS, &status); + if (ret < 0) + return ret; if (!(status & ADT7X10_STAT_NOT_RDY)) return 0; msleep(60); @@ -134,71 +107,10 @@ static int adt7x10_temp_ready(struct device *dev) return -ETIMEDOUT; } -static int adt7x10_update_temp(struct device *dev) -{ - struct adt7x10_data *data = dev_get_drvdata(dev); - int ret = 0; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + HZ + HZ / 2) - || !data->valid) { - int temp; - - dev_dbg(dev, "Starting update\n"); - - ret = adt7x10_temp_ready(dev); /* check for new value */ - if (ret) - goto abort; - - temp = adt7x10_read_word(dev, ADT7X10_REG_TEMP[0]); - if (temp < 0) { - ret = temp; - dev_dbg(dev, "Failed to read value: reg %d, error %d\n", - ADT7X10_REG_TEMP[0], ret); - goto abort; - } - data->temp[0] = temp; - data->last_updated = jiffies; - data->valid = true; - } - -abort: - mutex_unlock(&data->update_lock); - return ret; -} - -static int adt7x10_fill_cache(struct device *dev) -{ - struct adt7x10_data *data = dev_get_drvdata(dev); - int ret; - int i; - - for (i = 1; i < ARRAY_SIZE(data->temp); i++) { - ret = adt7x10_read_word(dev, ADT7X10_REG_TEMP[i]); - if (ret < 0) { - dev_dbg(dev, "Failed to read value: reg %d, error %d\n", - ADT7X10_REG_TEMP[i], ret); - return ret; - } - data->temp[i] = ret; - } - - ret = adt7x10_read_byte(dev, ADT7X10_T_HYST); - if (ret < 0) { - dev_dbg(dev, "Failed to read value: reg %d, error %d\n", - ADT7X10_T_HYST, ret); - return ret; - } - data->hyst = ret; - - return 0; -} - static s16 ADT7X10_TEMP_TO_REG(long temp) { return DIV_ROUND_CLOSEST(clamp_val(temp, ADT7X10_TEMP_MIN, - ADT7X10_TEMP_MAX) * 128, 1000); + ADT7X10_TEMP_MAX) * 128, 1000); } static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg) @@ -222,18 +134,26 @@ static ssize_t adt7x10_temp_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct adt7x10_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret; - - if (attr->index == 0) { - int ret; - - ret = adt7x10_update_temp(dev); - if (ret) + mutex_lock(&data->update_lock); + if (attr->index == 0 && !data->valid) { + /* wait for valid temperature */ + ret = adt7x10_temp_ready(data->regmap); + if (ret) { + mutex_unlock(&data->update_lock); return ret; + } + data->valid = true; } + mutex_unlock(&data->update_lock); - return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, - data->temp[attr->index])); + ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[attr->index], &val); + if (ret) + return ret; + + return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, val)); } static ssize_t adt7x10_temp_store(struct device *dev, @@ -251,12 +171,10 @@ static ssize_t adt7x10_temp_store(struct device *dev, return ret; mutex_lock(&data->update_lock); - data->temp[nr] = ADT7X10_TEMP_TO_REG(temp); - ret = adt7x10_write_word(dev, ADT7X10_REG_TEMP[nr], data->temp[nr]); - if (ret) - count = ret; + ret = regmap_write(data->regmap, ADT7X10_REG_TEMP[nr], + ADT7X10_TEMP_TO_REG(temp)); mutex_unlock(&data->update_lock); - return count; + return ret ? : count; } static ssize_t adt7x10_t_hyst_show(struct device *dev, @@ -265,9 +183,21 @@ static ssize_t adt7x10_t_hyst_show(struct device *dev, struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct adt7x10_data *data = dev_get_drvdata(dev); int nr = attr->index; - int hyst; + int hyst, temp, ret; - hyst = (data->hyst & ADT7X10_T_HYST_MASK) * 1000; + mutex_lock(&data->update_lock); + ret = regmap_read(data->regmap, ADT7X10_T_HYST, &hyst); + if (ret) { + mutex_unlock(&data->update_lock); + return ret; + } + + ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[nr], &temp); + mutex_unlock(&data->update_lock); + if (ret) + return ret; + + hyst = (hyst & ADT7X10_T_HYST_MASK) * 1000; /* * hysteresis is stored as a 4 bit offset in the device, convert it @@ -275,8 +205,8 @@ static ssize_t adt7x10_t_hyst_show(struct device *dev, */ if (nr == 2) /* min has positive offset, others have negative */ hyst = -hyst; - return sprintf(buf, "%d\n", - ADT7X10_REG_TO_TEMP(data, data->temp[nr]) - hyst); + + return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, temp) - hyst); } static ssize_t adt7x10_t_hyst_store(struct device *dev, @@ -284,35 +214,45 @@ static ssize_t adt7x10_t_hyst_store(struct device *dev, const char *buf, size_t count) { struct adt7x10_data *data = dev_get_drvdata(dev); + unsigned int regval; int limit, ret; long hyst; ret = kstrtol(buf, 10, &hyst); - if (ret) - return ret; - /* convert absolute hysteresis value to a 4 bit delta value */ - limit = ADT7X10_REG_TO_TEMP(data, data->temp[1]); - hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX); - data->hyst = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), - 0, ADT7X10_T_HYST_MASK); - ret = adt7x10_write_byte(dev, ADT7X10_T_HYST, data->hyst); if (ret) return ret; - return count; + mutex_lock(&data->update_lock); + + /* convert absolute hysteresis value to a 4 bit delta value */ + ret = regmap_read(data->regmap, ADT7X10_T_ALARM_HIGH, ®val); + if (ret < 0) + goto abort; + + limit = ADT7X10_REG_TO_TEMP(data, regval); + + hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX); + regval = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), 0, + ADT7X10_T_HYST_MASK); + ret = regmap_write(data->regmap, ADT7X10_T_HYST, regval); +abort: + mutex_unlock(&data->update_lock); + return ret ? : count; } static ssize_t adt7x10_alarm_show(struct device *dev, struct device_attribute *da, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct adt7x10_data *data = dev_get_drvdata(dev); + unsigned int status; int ret; - ret = adt7x10_read_byte(dev, ADT7X10_STATUS); + ret = regmap_read(data->regmap, ADT7X10_STATUS, &status); if (ret < 0) return ret; - return sprintf(buf, "%d\n", !!(ret & attr->index)); + return sprintf(buf, "%d\n", !!(status & attr->index)); } static ssize_t name_show(struct device *dev, struct device_attribute *da, @@ -357,28 +297,29 @@ static const struct attribute_group adt7x10_group = { }; int adt7x10_probe(struct device *dev, const char *name, int irq, - const struct adt7x10_ops *ops) + struct regmap *regmap) { struct adt7x10_data *data; + unsigned int config; int ret; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->ops = ops; + data->regmap = regmap; data->name = name; dev_set_drvdata(dev, data); mutex_init(&data->update_lock); /* configure as specified */ - ret = adt7x10_read_byte(dev, ADT7X10_CONFIG); + ret = regmap_read(regmap, ADT7X10_CONFIG, &config); if (ret < 0) { dev_dbg(dev, "Can't read config? %d\n", ret); return ret; } - data->oldconfig = ret; + data->oldconfig = config; /* * Set to 16 bit resolution, continous conversion and comparator mode. @@ -389,16 +330,12 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, data->config |= ADT7X10_FULL | ADT7X10_RESOLUTION | ADT7X10_EVENT_MODE; if (data->config != data->oldconfig) { - ret = adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); + ret = regmap_write(regmap, ADT7X10_CONFIG, data->config); if (ret) return ret; } dev_dbg(dev, "Config %02x\n", data->config); - ret = adt7x10_fill_cache(dev); - if (ret) - goto exit_restore; - /* Register sysfs hooks */ ret = sysfs_create_group(&dev->kobj, &adt7x10_group); if (ret) @@ -439,7 +376,7 @@ exit_remove_name: exit_remove: sysfs_remove_group(&dev->kobj, &adt7x10_group); exit_restore: - adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); + regmap_write(regmap, ADT7X10_CONFIG, data->oldconfig); return ret; } EXPORT_SYMBOL_GPL(adt7x10_probe); @@ -456,7 +393,7 @@ void adt7x10_remove(struct device *dev, int irq) device_remove_file(dev, &dev_attr_name); sysfs_remove_group(&dev->kobj, &adt7x10_group); if (data->oldconfig != data->config) - adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); + regmap_write(data->regmap, ADT7X10_CONFIG, data->oldconfig); } EXPORT_SYMBOL_GPL(adt7x10_remove); @@ -466,15 +403,15 @@ static int adt7x10_suspend(struct device *dev) { struct adt7x10_data *data = dev_get_drvdata(dev); - return adt7x10_write_byte(dev, ADT7X10_CONFIG, - data->config | ADT7X10_PD); + return regmap_write(data->regmap, ADT7X10_CONFIG, + data->config | ADT7X10_PD); } static int adt7x10_resume(struct device *dev) { struct adt7x10_data *data = dev_get_drvdata(dev); - return adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); + return regmap_write(data->regmap, ADT7X10_CONFIG, data->config); } SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume); diff --git a/drivers/hwmon/adt7x10.h b/drivers/hwmon/adt7x10.h index a1ae682eb32e..55ff08bfe946 100644 --- a/drivers/hwmon/adt7x10.h +++ b/drivers/hwmon/adt7x10.h @@ -17,15 +17,9 @@ struct device; -struct adt7x10_ops { - int (*read_byte)(struct device *, u8 reg); - int (*write_byte)(struct device *, u8 reg, u8 data); - int (*read_word)(struct device *, u8 reg); - int (*write_word)(struct device *, u8 reg, u16 data); -}; - int adt7x10_probe(struct device *dev, const char *name, int irq, - const struct adt7x10_ops *ops); + struct regmap *regmap); + void adt7x10_remove(struct device *dev, int irq); #ifdef CONFIG_PM_SLEEP From af910e92886c35461ccc5ea234e17f448a46b876 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Tue, 21 Dec 2021 23:58:36 +0200 Subject: [PATCH 42/86] hwmon: (adt7x10) Add device managed action for restoring config To simplify the core driver remove function. Signed-off-by: Cosmin Tanislav Link: https://lore.kernel.org/r/20211221215841.2641417-3-demonsingur@gmail.com [groeck: Adjust to use regmap; only register restore function if needed] Tested-by: Cosmin Tanislav Reviewed-by: Cosmin Tanislav Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7x10.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index 05dd48b707b4..47ce1a88a03f 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -296,6 +296,13 @@ static const struct attribute_group adt7x10_group = { .attrs = adt7x10_attributes, }; +static void adt7x10_restore_config(void *private) +{ + struct adt7x10_data *data = private; + + regmap_write(data->regmap, ADT7X10_CONFIG, data->oldconfig); +} + int adt7x10_probe(struct device *dev, const char *name, int irq, struct regmap *regmap) { @@ -333,13 +340,16 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, ret = regmap_write(regmap, ADT7X10_CONFIG, data->config); if (ret) return ret; + ret = devm_add_action_or_reset(dev, adt7x10_restore_config, data); + if (ret) + return ret; } dev_dbg(dev, "Config %02x\n", data->config); /* Register sysfs hooks */ ret = sysfs_create_group(&dev->kobj, &adt7x10_group); if (ret) - goto exit_restore; + return ret; /* * The I2C device will already have it's own 'name' attribute, but for @@ -375,8 +385,6 @@ exit_remove_name: device_remove_file(dev, &dev_attr_name); exit_remove: sysfs_remove_group(&dev->kobj, &adt7x10_group); -exit_restore: - regmap_write(regmap, ADT7X10_CONFIG, data->oldconfig); return ret; } EXPORT_SYMBOL_GPL(adt7x10_probe); @@ -392,8 +400,6 @@ void adt7x10_remove(struct device *dev, int irq) if (data->name) device_remove_file(dev, &dev_attr_name); sysfs_remove_group(&dev->kobj, &adt7x10_group); - if (data->oldconfig != data->config) - regmap_write(data->regmap, ADT7X10_CONFIG, data->oldconfig); } EXPORT_SYMBOL_GPL(adt7x10_remove); From a748d30c37bb7d238e5869f4f4d6037367493ebe Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Tue, 21 Dec 2021 23:58:37 +0200 Subject: [PATCH 43/86] hwmon: (adt7x10) Use devm_hwmon_device_register_with_info Describe the only available channel, implement read, write and is_visible callbacks. Also, pass name to core driver for the i2c device so that it can be used to register hwmon device. Signed-off-by: Cosmin Tanislav Link: https://lore.kernel.org/r/20211221215841.2641417-4-demonsingur@gmail.com [groeck: Adjusted to use regmap] Tested-by: Cosmin Tanislav Reviewed-by: Cosmin Tanislav Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7410.c | 2 +- drivers/hwmon/adt7x10.c | 247 ++++++++++++++++++++-------------------- 2 files changed, 124 insertions(+), 125 deletions(-) diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c index b1f4497dca05..81950a079c2f 100644 --- a/drivers/hwmon/adt7410.c +++ b/drivers/hwmon/adt7410.c @@ -84,7 +84,7 @@ static int adt7410_i2c_probe(struct i2c_client *client) if (IS_ERR(regmap)) return PTR_ERR(regmap); - return adt7x10_probe(&client->dev, NULL, client->irq, regmap); + return adt7x10_probe(&client->dev, client->name, client->irq, regmap); } static int adt7410_i2c_remove(struct i2c_client *client) diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index 47ce1a88a03f..9482fd8fff41 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -8,12 +8,12 @@ * and adt7410.c from iio-staging by Sonic Zhang */ +#include #include #include #include #include #include -#include #include #include #include @@ -55,19 +55,24 @@ /* Each client has this additional data */ struct adt7x10_data { struct regmap *regmap; - const char *name; - struct device *hwmon_dev; struct mutex update_lock; u8 config; u8 oldconfig; bool valid; /* true if temperature valid */ }; -static const u8 ADT7X10_REG_TEMP[4] = { - ADT7X10_TEMPERATURE, /* input */ - ADT7X10_T_ALARM_HIGH, /* high */ - ADT7X10_T_ALARM_LOW, /* low */ - ADT7X10_T_CRIT, /* critical */ +enum { + adt7x10_temperature = 0, + adt7x10_t_alarm_high, + adt7x10_t_alarm_low, + adt7x10_t_crit, +}; + +static const u8 ADT7X10_REG_TEMP[] = { + [adt7x10_temperature] = ADT7X10_TEMPERATURE, /* input */ + [adt7x10_t_alarm_high] = ADT7X10_T_ALARM_HIGH, /* high */ + [adt7x10_t_alarm_low] = ADT7X10_T_ALARM_LOW, /* low */ + [adt7x10_t_crit] = ADT7X10_T_CRIT, /* critical */ }; static irqreturn_t adt7x10_irq_handler(int irq, void *private) @@ -127,18 +132,13 @@ static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg) /*-----------------------------------------------------------------------*/ -/* sysfs attributes for hwmon */ - -static ssize_t adt7x10_temp_show(struct device *dev, - struct device_attribute *da, char *buf) +static int adt7x10_temp_read(struct adt7x10_data *data, int index, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct adt7x10_data *data = dev_get_drvdata(dev); - unsigned int val; + unsigned int regval; int ret; mutex_lock(&data->update_lock); - if (attr->index == 0 && !data->valid) { + if (index == adt7x10_temperature && !data->valid) { /* wait for valid temperature */ ret = adt7x10_temp_ready(data->regmap); if (ret) { @@ -149,40 +149,27 @@ static ssize_t adt7x10_temp_show(struct device *dev, } mutex_unlock(&data->update_lock); - ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[attr->index], &val); + ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], ®val); if (ret) return ret; - return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, val)); + *val = ADT7X10_REG_TO_TEMP(data, regval); + return 0; } -static ssize_t adt7x10_temp_store(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +static int adt7x10_temp_write(struct adt7x10_data *data, int index, long temp) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct adt7x10_data *data = dev_get_drvdata(dev); - int nr = attr->index; - long temp; int ret; - ret = kstrtol(buf, 10, &temp); - if (ret) - return ret; - mutex_lock(&data->update_lock); - ret = regmap_write(data->regmap, ADT7X10_REG_TEMP[nr], + ret = regmap_write(data->regmap, ADT7X10_REG_TEMP[index], ADT7X10_TEMP_TO_REG(temp)); mutex_unlock(&data->update_lock); - return ret ? : count; + return ret; } -static ssize_t adt7x10_t_hyst_show(struct device *dev, - struct device_attribute *da, char *buf) +static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct adt7x10_data *data = dev_get_drvdata(dev); - int nr = attr->index; int hyst, temp, ret; mutex_lock(&data->update_lock); @@ -192,7 +179,7 @@ static ssize_t adt7x10_t_hyst_show(struct device *dev, return ret; } - ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[nr], &temp); + ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], &temp); mutex_unlock(&data->update_lock); if (ret) return ret; @@ -203,24 +190,18 @@ static ssize_t adt7x10_t_hyst_show(struct device *dev, * hysteresis is stored as a 4 bit offset in the device, convert it * to an absolute value */ - if (nr == 2) /* min has positive offset, others have negative */ + /* min has positive offset, others have negative */ + if (index == adt7x10_t_alarm_low) hyst = -hyst; - return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, temp) - hyst); + *val = ADT7X10_REG_TO_TEMP(data, temp) - hyst; + return 0; } -static ssize_t adt7x10_t_hyst_store(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +static int adt7x10_hyst_write(struct adt7x10_data *data, long hyst) { - struct adt7x10_data *data = dev_get_drvdata(dev); unsigned int regval; int limit, ret; - long hyst; - - ret = kstrtol(buf, 10, &hyst); - if (ret) - return ret; mutex_lock(&data->update_lock); @@ -237,14 +218,11 @@ static ssize_t adt7x10_t_hyst_store(struct device *dev, ret = regmap_write(data->regmap, ADT7X10_T_HYST, regval); abort: mutex_unlock(&data->update_lock); - return ret ? : count; + return ret; } -static ssize_t adt7x10_alarm_show(struct device *dev, - struct device_attribute *da, char *buf) +static int adt7x10_alarm_read(struct adt7x10_data *data, int index, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct adt7x10_data *data = dev_get_drvdata(dev); unsigned int status; int ret; @@ -252,48 +230,102 @@ static ssize_t adt7x10_alarm_show(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", !!(status & attr->index)); + *val = !!(status & index); + + return 0; } -static ssize_t name_show(struct device *dev, struct device_attribute *da, - char *buf) +static umode_t adt7x10_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (attr) { + case hwmon_temp_max: + case hwmon_temp_min: + case hwmon_temp_crit: + case hwmon_temp_max_hyst: + return 0644; + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_min_hyst: + case hwmon_temp_crit_hyst: + return 0444; + default: + break; + } + + return 0; +} + +static int adt7x10_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) { struct adt7x10_data *data = dev_get_drvdata(dev); - return sprintf(buf, "%s\n", data->name); + switch (attr) { + case hwmon_temp_input: + return adt7x10_temp_read(data, adt7x10_temperature, val); + case hwmon_temp_max: + return adt7x10_temp_read(data, adt7x10_t_alarm_high, val); + case hwmon_temp_min: + return adt7x10_temp_read(data, adt7x10_t_alarm_low, val); + case hwmon_temp_crit: + return adt7x10_temp_read(data, adt7x10_t_crit, val); + case hwmon_temp_max_hyst: + return adt7x10_hyst_read(data, adt7x10_t_alarm_high, val); + case hwmon_temp_min_hyst: + return adt7x10_hyst_read(data, adt7x10_t_alarm_low, val); + case hwmon_temp_crit_hyst: + return adt7x10_hyst_read(data, adt7x10_t_crit, val); + case hwmon_temp_min_alarm: + return adt7x10_alarm_read(data, ADT7X10_STAT_T_LOW, val); + case hwmon_temp_max_alarm: + return adt7x10_alarm_read(data, ADT7X10_STAT_T_HIGH, val); + case hwmon_temp_crit_alarm: + return adt7x10_alarm_read(data, ADT7X10_STAT_T_CRIT, val); + default: + return -EOPNOTSUPP; + } } -static SENSOR_DEVICE_ATTR_RO(temp1_input, adt7x10_temp, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_max, adt7x10_temp, 1); -static SENSOR_DEVICE_ATTR_RW(temp1_min, adt7x10_temp, 2); -static SENSOR_DEVICE_ATTR_RW(temp1_crit, adt7x10_temp, 3); -static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, adt7x10_t_hyst, 1); -static SENSOR_DEVICE_ATTR_RO(temp1_min_hyst, adt7x10_t_hyst, 2); -static SENSOR_DEVICE_ATTR_RO(temp1_crit_hyst, adt7x10_t_hyst, 3); -static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, adt7x10_alarm, - ADT7X10_STAT_T_LOW); -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, adt7x10_alarm, - ADT7X10_STAT_T_HIGH); -static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, adt7x10_alarm, - ADT7X10_STAT_T_CRIT); -static DEVICE_ATTR_RO(name); +static int adt7x10_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); -static struct attribute *adt7x10_attributes[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - NULL + switch (attr) { + case hwmon_temp_max: + return adt7x10_temp_write(data, adt7x10_t_alarm_high, val); + case hwmon_temp_min: + return adt7x10_temp_write(data, adt7x10_t_alarm_low, val); + case hwmon_temp_crit: + return adt7x10_temp_write(data, adt7x10_t_crit, val); + case hwmon_temp_max_hyst: + return adt7x10_hyst_write(data, val); + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info *adt7x10_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_MIN_HYST | + HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM), + NULL, }; -static const struct attribute_group adt7x10_group = { - .attrs = adt7x10_attributes, +static const struct hwmon_ops adt7x10_hwmon_ops = { + .is_visible = adt7x10_is_visible, + .read = adt7x10_read, + .write = adt7x10_write, +}; + +static const struct hwmon_chip_info adt7x10_chip_info = { + .ops = &adt7x10_hwmon_ops, + .info = adt7x10_info, }; static void adt7x10_restore_config(void *private) @@ -308,6 +340,7 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, { struct adt7x10_data *data; unsigned int config; + struct device *hdev; int ret; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); @@ -315,7 +348,6 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, return -ENOMEM; data->regmap = regmap; - data->name = name; dev_set_drvdata(dev, data); mutex_init(&data->update_lock); @@ -346,60 +378,27 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, } dev_dbg(dev, "Config %02x\n", data->config); - /* Register sysfs hooks */ - ret = sysfs_create_group(&dev->kobj, &adt7x10_group); - if (ret) - return ret; - - /* - * The I2C device will already have it's own 'name' attribute, but for - * the SPI device we need to register it. name will only be non NULL if - * the device doesn't register the 'name' attribute on its own. - */ - if (name) { - ret = device_create_file(dev, &dev_attr_name); - if (ret) - goto exit_remove; - } - - data->hwmon_dev = hwmon_device_register(dev); - if (IS_ERR(data->hwmon_dev)) { - ret = PTR_ERR(data->hwmon_dev); - goto exit_remove_name; - } + hdev = devm_hwmon_device_register_with_info(dev, name, data, + &adt7x10_chip_info, NULL); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); if (irq > 0) { ret = request_threaded_irq(irq, NULL, adt7x10_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(dev), dev); if (ret) - goto exit_hwmon_device_unregister; + return ret; } return 0; - -exit_hwmon_device_unregister: - hwmon_device_unregister(data->hwmon_dev); -exit_remove_name: - if (name) - device_remove_file(dev, &dev_attr_name); -exit_remove: - sysfs_remove_group(&dev->kobj, &adt7x10_group); - return ret; } EXPORT_SYMBOL_GPL(adt7x10_probe); void adt7x10_remove(struct device *dev, int irq) { - struct adt7x10_data *data = dev_get_drvdata(dev); - if (irq > 0) free_irq(irq, dev); - - hwmon_device_unregister(data->hwmon_dev); - if (data->name) - device_remove_file(dev, &dev_attr_name); - sysfs_remove_group(&dev->kobj, &adt7x10_group); } EXPORT_SYMBOL_GPL(adt7x10_remove); From f691adc3e95ecd6ae01830f7f6140dce3bd730a2 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Tue, 21 Dec 2021 23:58:38 +0200 Subject: [PATCH 44/86] hwmon: (adt7x10) Use devm_request_threaded_irq To simplify the core driver remove function. Signed-off-by: Cosmin Tanislav Link: https://lore.kernel.org/r/20211221215841.2641417-5-demonsingur@gmail.com Tested-by: Cosmin Tanislav Reviewed-by: Cosmin Tanislav Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7x10.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index 9482fd8fff41..147c28b24167 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -384,9 +384,11 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, return PTR_ERR(hdev); if (irq > 0) { - ret = request_threaded_irq(irq, NULL, adt7x10_irq_handler, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(dev), dev); + ret = devm_request_threaded_irq(dev, irq, NULL, + adt7x10_irq_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(dev), dev); if (ret) return ret; } @@ -397,8 +399,6 @@ EXPORT_SYMBOL_GPL(adt7x10_probe); void adt7x10_remove(struct device *dev, int irq) { - if (irq > 0) - free_irq(irq, dev); } EXPORT_SYMBOL_GPL(adt7x10_remove); From 8331585ab3700662c0979244fb06522aa0d36fbc Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Tue, 21 Dec 2021 23:58:39 +0200 Subject: [PATCH 45/86] hwmon: (adt7x10) Remove empty driver removal callback Not used to do anything anymore. Signed-off-by: Cosmin Tanislav Link: https://lore.kernel.org/r/20211221215841.2641417-6-demonsingur@gmail.com Tested-by: Cosmin Tanislav Reviewed-by: Cosmin Tanislav Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7310.c | 7 ------- drivers/hwmon/adt7410.c | 7 ------- drivers/hwmon/adt7x10.c | 5 ----- drivers/hwmon/adt7x10.h | 2 -- 4 files changed, 21 deletions(-) diff --git a/drivers/hwmon/adt7310.c b/drivers/hwmon/adt7310.c index a83092470bce..1efc0bdcceab 100644 --- a/drivers/hwmon/adt7310.c +++ b/drivers/hwmon/adt7310.c @@ -142,12 +142,6 @@ static int adt7310_spi_probe(struct spi_device *spi) regmap); } -static int adt7310_spi_remove(struct spi_device *spi) -{ - adt7x10_remove(&spi->dev, spi->irq); - return 0; -} - static const struct spi_device_id adt7310_id[] = { { "adt7310", 0 }, { "adt7320", 0 }, @@ -161,7 +155,6 @@ static struct spi_driver adt7310_driver = { .pm = ADT7X10_DEV_PM_OPS, }, .probe = adt7310_spi_probe, - .remove = adt7310_spi_remove, .id_table = adt7310_id, }; module_spi_driver(adt7310_driver); diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c index 81950a079c2f..aede5baca7b9 100644 --- a/drivers/hwmon/adt7410.c +++ b/drivers/hwmon/adt7410.c @@ -87,12 +87,6 @@ static int adt7410_i2c_probe(struct i2c_client *client) return adt7x10_probe(&client->dev, client->name, client->irq, regmap); } -static int adt7410_i2c_remove(struct i2c_client *client) -{ - adt7x10_remove(&client->dev, client->irq); - return 0; -} - static const struct i2c_device_id adt7410_ids[] = { { "adt7410", 0 }, { "adt7420", 0 }, @@ -107,7 +101,6 @@ static struct i2c_driver adt7410_driver = { .pm = ADT7X10_DEV_PM_OPS, }, .probe_new = adt7410_i2c_probe, - .remove = adt7410_i2c_remove, .id_table = adt7410_ids, .address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b), }; diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index 147c28b24167..ea8cd918bc22 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -397,11 +397,6 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, } EXPORT_SYMBOL_GPL(adt7x10_probe); -void adt7x10_remove(struct device *dev, int irq) -{ -} -EXPORT_SYMBOL_GPL(adt7x10_remove); - #ifdef CONFIG_PM_SLEEP static int adt7x10_suspend(struct device *dev) diff --git a/drivers/hwmon/adt7x10.h b/drivers/hwmon/adt7x10.h index 55ff08bfe946..ba22c32c8355 100644 --- a/drivers/hwmon/adt7x10.h +++ b/drivers/hwmon/adt7x10.h @@ -20,8 +20,6 @@ struct device; int adt7x10_probe(struct device *dev, const char *name, int irq, struct regmap *regmap); -void adt7x10_remove(struct device *dev, int irq); - #ifdef CONFIG_PM_SLEEP extern const struct dev_pm_ops adt7x10_dev_pm_ops; #define ADT7X10_DEV_PM_OPS (&adt7x10_dev_pm_ops) From a7a5731a09521a1053bb409647bf3e52cdf350de Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Tue, 21 Dec 2021 23:58:41 +0200 Subject: [PATCH 46/86] hwmon: (adt7x10) Use hwmon_notify_event The hwmon subsystem provides means of notifying userspace about events. Use it. Signed-off-by: Cosmin Tanislav Link: https://lore.kernel.org/r/20211221215841.2641417-8-demonsingur@gmail.com [groeck: Pass hwmon device to interrupt handler] Tested-by: Cosmin Tanislav Reviewed-by: Cosmin Tanislav Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7x10.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index ea8cd918bc22..ce54bffab2ec 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -87,11 +87,11 @@ static irqreturn_t adt7x10_irq_handler(int irq, void *private) return IRQ_HANDLED; if (status & ADT7X10_STAT_T_HIGH) - sysfs_notify(&dev->kobj, NULL, "temp1_max_alarm"); + hwmon_notify_event(dev, hwmon_temp, hwmon_temp_max_alarm, 0); if (status & ADT7X10_STAT_T_LOW) - sysfs_notify(&dev->kobj, NULL, "temp1_min_alarm"); + hwmon_notify_event(dev, hwmon_temp, hwmon_temp_min_alarm, 0); if (status & ADT7X10_STAT_T_CRIT) - sysfs_notify(&dev->kobj, NULL, "temp1_crit_alarm"); + hwmon_notify_event(dev, hwmon_temp, hwmon_temp_crit_alarm, 0); return IRQ_HANDLED; } @@ -388,7 +388,7 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, adt7x10_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(dev), dev); + dev_name(dev), hdev); if (ret) return ret; } From f545a2fd473606809cb4a5045919a10194d86d36 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Tue, 8 Feb 2022 10:42:43 +0100 Subject: [PATCH 47/86] hwmon: (asus-ec-sensors) add CPU core voltage A user discovered [1] the CPU Core voltage sensor, which spans 2 registers and provides output in mV. Althroug the discovery was made with a X470 chipset, the sensor is present in X570 (tested with C8H). For now simply add it to each board with the CPU current sensor present. [1] https://github.com/zeule/asus-ec-sensors/issues/12 Signed-off-by: Eugene Shalygin Tested-by: Oleksandr Natalenko Tested-by: Denis Pauk Link: https://lore.kernel.org/r/20220208094244.1106312-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 29 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 22de1b037cfb..e7e8f1640f45 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -39,6 +39,7 @@ The driver is aware of and reads the following sensors: 9. Readings from the "Water flow meter" header (RPM) 10. Readings from the "Water In" and "Water Out" temperature headers 11. CPU current +12. CPU core voltage Sensor values are read from EC registers, and to avoid race with the board firmware the driver acquires ACPI mutex, the one used by the WMI when its diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index dd7b207d062f..bfac08a5dc57 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -18,6 +18,7 @@ * - VRM Heat Sink fan RPM * - Water Flow fan RPM * - CPU current + * - CPU core voltage */ #include @@ -100,6 +101,8 @@ enum ec_sensors { ec_sensor_temp_t_sensor, /* VRM temperature [℃] */ ec_sensor_temp_vrm, + /* CPU Core voltage [mV] */ + ec_sensor_in_cpu_core, /* CPU_Opt fan [RPM] */ ec_sensor_fan_cpu_opt, /* VRM heat sink fan [RPM] */ @@ -121,6 +124,7 @@ enum ec_sensors { #define SENSOR_TEMP_MB BIT(ec_sensor_temp_mb) #define SENSOR_TEMP_T_SENSOR BIT(ec_sensor_temp_t_sensor) #define SENSOR_TEMP_VRM BIT(ec_sensor_temp_vrm) +#define SENSOR_IN_CPU_CORE BIT(ec_sensor_in_cpu_core) #define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt) #define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs) #define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset) @@ -139,6 +143,8 @@ static const struct ec_sensor_info known_ec_sensors[] = { [ec_sensor_temp_t_sensor] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [ec_sensor_in_cpu_core] = + EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2), [ec_sensor_fan_cpu_opt] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), [ec_sensor_fan_vrm_hs] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), @@ -172,32 +178,34 @@ static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | - SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII DARK HERO", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU), + SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII FORMULA", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU), + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII HERO", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU), + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII HERO (WI-FI)", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU), + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII IMPACT", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-E GAMING", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | @@ -205,17 +213,19 @@ static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-I GAMING", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS | SENSOR_CURR_CPU), + SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-E GAMING", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-F GAMING", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-I GAMING", SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | - SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU), + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), {} }; @@ -467,7 +477,6 @@ static long scale_sensor_value(s32 value, int data_type) switch (data_type) { case hwmon_curr: case hwmon_temp: - case hwmon_in: return value * MILLI; default: return value; From a7a6f65a39a75a5821eb9aad157326c30f8bbb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Jo=C5=84czyk?= Date: Sat, 12 Feb 2022 13:56:53 +0100 Subject: [PATCH 48/86] x86/Kconfig: move and modify CONFIG_I8K MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Kconfig, inside the "Processor type and features" menu, there is the CONFIG_I8K option: "Dell i8k legacy laptop support". This is very confusing - enabling CONFIG_I8K is not required for the kernel to support old Dell laptops. This option is specific to the dell-smm-hwmon driver, which mostly exports some hardware monitoring information and allows the user to change fan speed. This option is misplaced, so move CONFIG_I8K to drivers/hwmon/Kconfig, where it belongs. Also, modify the dependency order - change select SENSORS_DELL_SMM to depends on SENSORS_DELL_SMM as it is just a configuration option of dell-smm-hwmon. This includes changing the option type from tristate to bool. It was tristate because it could select CONFIG_SENSORS_DELL_SMM=m . When running "make oldconfig" on configurations with CONFIG_SENSORS_DELL_SMM enabled , this change will result in an additional question (which could be printed several times during bisecting). I think that tidying up the configuration is worth it, though. Next patch tweaks the description of CONFIG_I8K. Signed-off-by: Mateusz Jończyk Cc: Pali Rohár Cc: Thomas Gleixner Cc: Ingo Molnar Cc: Dave Hansen Cc: Jean Delvare Cc: Guenter Roeck Cc: Mark Gross Reviewed-by: Hans de Goede Reviewed-by: Randy Dunlap Acked-by: Borislav Petkov Link: https://lore.kernel.org/r/20220212125654.357408-1-mat.jonczyk@o2.pl Signed-off-by: Guenter Roeck --- arch/x86/Kconfig | 17 ----------------- drivers/hwmon/Kconfig | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 9f5bd41bf660..71d4ddd48c02 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -1275,23 +1275,6 @@ config TOSHIBA Say Y if you intend to run this kernel on a Toshiba portable. Say N otherwise. -config I8K - tristate "Dell i8k legacy laptop support" - depends on HWMON - depends on PROC_FS - select SENSORS_DELL_SMM - help - This option enables legacy /proc/i8k userspace interface in hwmon - dell-smm-hwmon driver. Character file /proc/i8k reports bios version, - temperature and allows controlling fan speeds of Dell laptops via - System Management Mode. For old Dell laptops (like Dell Inspiron 8000) - it reports also power and hotkey status. For fan speed control is - needed userspace package i8kutils. - - Say Y if you intend to run this kernel on old Dell laptops or want to - use userspace package i8kutils. - Say N otherwise. - config X86_REBOOTFIXUPS bool "Enable X86 board specific fixups for reboot" depends on X86_32 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 387253ae6c14..fceea537101d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -506,6 +506,22 @@ config SENSORS_DELL_SMM When option I8K is also enabled this driver provides legacy /proc/i8k userspace interface for i8kutils package. +config I8K + bool "Dell i8k legacy laptop support" + depends on SENSORS_DELL_SMM + depends on PROC_FS + help + This option enables legacy /proc/i8k userspace interface in hwmon + dell-smm-hwmon driver. Character file /proc/i8k reports bios version, + temperature and allows controlling fan speeds of Dell laptops via + System Management Mode. For old Dell laptops (like Dell Inspiron 8000) + it reports also power and hotkey status. For fan speed control is + needed userspace package i8kutils. + + Say Y if you intend to run this kernel on old Dell laptops or want to + use userspace package i8kutils. + Say N otherwise. + config SENSORS_DA9052_ADC tristate "Dialog DA9052/DA9053 ADC" depends on PMIC_DA9052 From 5f86cce61c1d12219b2ea25b9404bf263a77dd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Jo=C5=84czyk?= Date: Sat, 12 Feb 2022 13:56:54 +0100 Subject: [PATCH 49/86] hwmon: (dell-smm) rewrite CONFIG_I8K description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is not the laptops, but the /proc/i8k interface that is legacy (or so I think was the intention of the help text author). The old description was confusing, fix this. The phrase "Say Y if you intend to run this kernel on old Dell laptops or want to use userspace package i8kutils." was introduced in 2015, in commit 039ae58503f3 ("hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k") I think that "old laptops" was about hotkey and Fn key support - this driver in the 2.4 kernels' era apparently had these capabilities (see: https://github.com/vitorafsr/i8kutils , description of "repeat_rate" kernel module parameter). Signed-off-by: Mateusz Jończyk Cc: Pali Rohár Cc: Jean Delvare Cc: Guenter Roeck Cc: Mark Gross Reviewed-by: Hans de Goede Reviewed-by: Randy Dunlap Link: https://lore.kernel.org/r/20220212125654.357408-2-mat.jonczyk@o2.pl Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index fceea537101d..ce9149e0648f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -507,19 +507,18 @@ config SENSORS_DELL_SMM userspace interface for i8kutils package. config I8K - bool "Dell i8k legacy laptop support" + bool "Legacy /proc/i8k interface of Dell laptop SMM BIOS hwmon driver" depends on SENSORS_DELL_SMM depends on PROC_FS help - This option enables legacy /proc/i8k userspace interface in hwmon - dell-smm-hwmon driver. Character file /proc/i8k reports bios version, - temperature and allows controlling fan speeds of Dell laptops via - System Management Mode. For old Dell laptops (like Dell Inspiron 8000) - it reports also power and hotkey status. For fan speed control is - needed userspace package i8kutils. + This option enables the legacy /proc/i8k userspace interface of the + dell-smm-hwmon driver. The character file /proc/i8k exposes the BIOS + version, temperatures and allows control of fan speeds of some Dell + laptops. Sometimes it also reports power and hotkey status. - Say Y if you intend to run this kernel on old Dell laptops or want to - use userspace package i8kutils. + This interface is required to run programs from the i8kutils package. + + Say Y if you intend to run userspace programs that use this interface. Say N otherwise. config SENSORS_DA9052_ADC From 99cb5e9f7a78857657220f65533dce550331d629 Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Sun, 13 Feb 2022 01:47:33 +0100 Subject: [PATCH 50/86] hwmon: (tc654) Add thermal_cooling device support Adds thermal_cooling device support to the tc654/tc655 driver. This make it possible to integrate it into a device-tree supported thermal-zone node as a cooling device. I have been using this patch as part of the Netgear WNDR4700 Centria NAS Router support within OpenWrt since 2016. Signed-off-by: Christian Lamparter Link: https://lore.kernel.org/r/20220213004733.2421193-1-chunkeey@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/tc654.c | 104 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c index a52ca72af120..54cd33d09688 100644 --- a/drivers/hwmon/tc654.c +++ b/drivers/hwmon/tc654.c @@ -15,6 +15,7 @@ #include #include #include +#include #include enum tc654_regs { @@ -379,28 +380,20 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *da, return sprintf(buf, "%d\n", pwm); } -static ssize_t pwm_store(struct device *dev, struct device_attribute *da, - const char *buf, size_t count) +static int _set_pwm(struct tc654_data *data, unsigned long val) { - struct tc654_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; - unsigned long val; int ret; - if (kstrtoul(buf, 10, &val)) - return -EINVAL; - if (val > 255) - return -EINVAL; - mutex_lock(&data->update_lock); - if (val == 0) + if (val == 0) { data->config |= TC654_REG_CONFIG_SDM; - else + data->duty_cycle = 0; + } else { data->config &= ~TC654_REG_CONFIG_SDM; - - data->duty_cycle = find_closest(val, tc654_pwm_map, - ARRAY_SIZE(tc654_pwm_map)); + data->duty_cycle = val - 1; + } ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); if (ret < 0) @@ -411,6 +404,24 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *da, out: mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t pwm_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct tc654_data *data = dev_get_drvdata(dev); + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + if (val > 255) + return -EINVAL; + if (val > 0) + val = find_closest(val, tc654_pwm_map, ARRAY_SIZE(tc654_pwm_map)) + 1; + + ret = _set_pwm(data, val); return ret < 0 ? ret : count; } @@ -442,6 +453,58 @@ static struct attribute *tc654_attrs[] = { ATTRIBUTE_GROUPS(tc654); +/* + * thermal cooling device functions + * + * Account for the "ShutDown Mode (SDM)" state by offsetting + * the 16 PWM duty cycle states by 1. + * + * State 0 = 0% PWM | Shutdown - Fan(s) are off + * State 1 = 30% PWM | duty_cycle = 0 + * State 2 = ~35% PWM | duty_cycle = 1 + * [...] + * State 15 = ~95% PWM | duty_cycle = 14 + * State 16 = 100% PWM | duty_cycle = 15 + */ +#define TC654_MAX_COOLING_STATE 16 + +static int tc654_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) +{ + *state = TC654_MAX_COOLING_STATE; + return 0; +} + +static int tc654_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) +{ + struct tc654_data *data = tc654_update_client(cdev->devdata); + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->config & TC654_REG_CONFIG_SDM) + *state = 0; /* FAN is off */ + else + *state = data->duty_cycle + 1; /* offset PWM States by 1 */ + + return 0; +} + +static int tc654_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +{ + struct tc654_data *data = tc654_update_client(cdev->devdata); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return _set_pwm(data, clamp_val(state, 0, TC654_MAX_COOLING_STATE)); +} + +static const struct thermal_cooling_device_ops tc654_fan_cool_ops = { + .get_max_state = tc654_get_max_state, + .get_cur_state = tc654_get_cur_state, + .set_cur_state = tc654_set_cur_state, +}; + /* * device probe and removal */ @@ -472,7 +535,18 @@ static int tc654_probe(struct i2c_client *client) hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, data, tc654_groups); - return PTR_ERR_OR_ZERO(hwmon_dev); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + if (IS_ENABLED(CONFIG_THERMAL)) { + struct thermal_cooling_device *cdev; + + cdev = devm_thermal_of_cooling_device_register(dev, dev->of_node, client->name, + hwmon_dev, &tc654_fan_cool_ops); + return PTR_ERR_OR_ZERO(cdev); + } + + return 0; } static const struct i2c_device_id tc654_id[] = { From 8aba9ca62677570abf8c6b62611adb85bf1580ca Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Fri, 11 Feb 2022 17:48:55 +0100 Subject: [PATCH 51/86] hwmon: (asus-ec-sensors) deduce sensor signedness from its type Reading DSDT code for ASUS X470-based boards (the ones served by the asus_wmi_Sensors driver), where ASUS put hardware monitoring functions into the WMI code, reveals that fan and current sensors data is unsigned. For the current sensor that was confirmed by a user who showed high enough current value for overflow. Thus let's assume that the signedness of the sensors is determined by its type and that only temperature ones provide signed numbers. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220211164855.265698-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 40 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index bfac08a5dc57..d2b84578d2af 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -266,6 +266,15 @@ static u8 register_index(u16 reg) return reg & 0x00ff; } +static bool is_sensor_data_signed(const struct ec_sensor_info *si) +{ + /* + * guessed from WMI functions in DSDT code for boards + * of the X470 generation + */ + return si->type == hwmon_temp; +} + static const struct ec_sensor_info * get_sensor_info(const struct ec_sensors_data *state, int index) { @@ -420,15 +429,28 @@ static int asus_ec_block_read(const struct device *dev, static inline s32 get_sensor_value(const struct ec_sensor_info *si, u8 *data) { - switch (si->addr.components.size) { - case 1: - return (s8)*data; - case 2: - return (s16)get_unaligned_be16(data); - case 4: - return (s32)get_unaligned_be32(data); - default: - return 0; + if (is_sensor_data_signed(si)) { + switch (si->addr.components.size) { + case 1: + return (s8)*data; + case 2: + return (s16)get_unaligned_be16(data); + case 4: + return (s32)get_unaligned_be32(data); + default: + return 0; + } + } else { + switch (si->addr.components.size) { + case 1: + return *data; + case 2: + return get_unaligned_be16(data); + case 4: + return get_unaligned_be32(data); + default: + return 0; + } } } From 1298184b387fd71fe68ecb7dc2001cb63ffa17c5 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Wed, 16 Feb 2022 20:19:58 +0100 Subject: [PATCH 52/86] hwmon: (asus-ec-sensors) merge setup functions Merge configure_sensor_setup() into probe(). Changes: - v2: add local struct device *dev = &pdev->dev; - v3: initialize dev at declaration - v4: fix checkpatch warning - v5: fix formatting - v6: code style fixes Signed-off-by: Eugene Shalygin Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 43 +++++++++++++-------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index d2b84578d2af..0701ade16227 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -611,23 +611,31 @@ get_board_sensors(const struct device *dev) return (unsigned long)dmi_entry->driver_data; } -static int __init configure_sensor_setup(struct device *dev) +static int __init asus_ec_probe(struct platform_device *pdev) { - struct ec_sensors_data *ec_data = dev_get_drvdata(dev); - int nr_count[hwmon_max] = { 0 }, nr_types = 0; - struct device *hwdev; - struct hwmon_channel_info *asus_ec_hwmon_chan; const struct hwmon_channel_info **ptr_asus_ec_ci; + int nr_count[hwmon_max] = { 0 }, nr_types = 0; + struct hwmon_channel_info *asus_ec_hwmon_chan; const struct hwmon_chip_info *chip_info; + struct device *dev = &pdev->dev; + struct ec_sensors_data *ec_data; const struct ec_sensor_info *si; enum hwmon_sensor_types type; + unsigned long board_sensors; + struct device *hwdev; unsigned int i; - ec_data->board_sensors = get_board_sensors(dev); - if (!ec_data->board_sensors) { + board_sensors = get_board_sensors(dev); + if (!board_sensors) return -ENODEV; - } + ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), + GFP_KERNEL); + if (!ec_data) + return -ENOMEM; + + dev_set_drvdata(dev, ec_data); + ec_data->board_sensors = board_sensors; ec_data->nr_sensors = board_sensors_count(ec_data->board_sensors); ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors, sizeof(struct ec_sensor), GFP_KERNEL); @@ -638,9 +646,8 @@ static int __init configure_sensor_setup(struct device *dev) ec_data->read_buffer = devm_kcalloc(dev, ec_data->nr_registers, sizeof(u8), GFP_KERNEL); - if (!ec_data->registers || !ec_data->read_buffer) { + if (!ec_data->registers || !ec_data->read_buffer) return -ENOMEM; - } fill_ec_registers(ec_data); @@ -688,22 +695,6 @@ static int __init configure_sensor_setup(struct device *dev) return PTR_ERR_OR_ZERO(hwdev); } -static int __init asus_ec_probe(struct platform_device *pdev) -{ - struct ec_sensors_data *state; - int status = 0; - - state = devm_kzalloc(&pdev->dev, sizeof(struct ec_sensors_data), - GFP_KERNEL); - - if (!state) { - return -ENOMEM; - } - - dev_set_drvdata(&pdev->dev, state); - status = configure_sensor_setup(&pdev->dev); - return status; -} static const struct acpi_device_id acpi_ec_ids[] = { /* Embedded Controller Device */ From 034dadfbd329231fc771984d4897c71f73a4434d Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Thu, 17 Feb 2022 08:32:38 +0100 Subject: [PATCH 53/86] hwmon: (asus-ec-sensors) depend on X86 in KConfig All the supported mainboards are for the X86 platform Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220217073238.2479005-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index ce9149e0648f..b3597ba66ddb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2284,6 +2284,7 @@ config SENSORS_ASUS_WMI_EC config SENSORS_ASUS_EC tristate "ASUS EC Sensors" + depends on X86 help If you say yes here you get support for the ACPI embedded controller hardware monitoring interface found in ASUS motherboards. The driver From e0f0307ac16fe69c6f9dcc8d14b532bc608fc989 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Fri, 18 Feb 2022 16:03:59 -0800 Subject: [PATCH 54/86] hwmon: (pmbus) Add get_error_flags support to regulator ops The various PMBus status bits don't all map perfectly to the more limited set of REGULATOR_ERROR_* flags, but there's a reasonable number where they correspond well enough. Signed-off-by: Zev Weiss Link: https://lore.kernel.org/r/20220219000359.19985-1-zev@bewilderbeest.net [groeck: Added missing locking] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 114 +++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index b1386a4df4cc..8eed7968a50e 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -2432,10 +2432,124 @@ static int pmbus_regulator_disable(struct regulator_dev *rdev) return _pmbus_regulator_on_off(rdev, 0); } +/* A PMBus status flag and the corresponding REGULATOR_ERROR_* flag */ +struct pmbus_regulator_status_assoc { + int pflag, rflag; +}; + +/* PMBus->regulator bit mappings for a PMBus status register */ +struct pmbus_regulator_status_category { + int func; + int reg; + const struct pmbus_regulator_status_assoc *bits; /* zero-terminated */ +}; + +static const struct pmbus_regulator_status_category pmbus_regulator_flag_map[] = { + { + .func = PMBUS_HAVE_STATUS_VOUT, + .reg = PMBUS_STATUS_VOUT, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_VOLTAGE_UV_WARNING, REGULATOR_ERROR_UNDER_VOLTAGE_WARN }, + { PB_VOLTAGE_UV_FAULT, REGULATOR_ERROR_UNDER_VOLTAGE }, + { PB_VOLTAGE_OV_WARNING, REGULATOR_ERROR_OVER_VOLTAGE_WARN }, + { PB_VOLTAGE_OV_FAULT, REGULATOR_ERROR_REGULATION_OUT }, + { }, + }, + }, { + .func = PMBUS_HAVE_STATUS_IOUT, + .reg = PMBUS_STATUS_IOUT, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_IOUT_OC_WARNING, REGULATOR_ERROR_OVER_CURRENT_WARN }, + { PB_IOUT_OC_FAULT, REGULATOR_ERROR_OVER_CURRENT }, + { PB_IOUT_OC_LV_FAULT, REGULATOR_ERROR_OVER_CURRENT }, + { }, + }, + }, { + .func = PMBUS_HAVE_STATUS_TEMP, + .reg = PMBUS_STATUS_TEMPERATURE, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_TEMP_OT_WARNING, REGULATOR_ERROR_OVER_TEMP_WARN }, + { PB_TEMP_OT_FAULT, REGULATOR_ERROR_OVER_TEMP }, + { }, + }, + }, +}; + +static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags) +{ + int i, status; + const struct pmbus_regulator_status_category *cat; + const struct pmbus_regulator_status_assoc *bit; + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + u8 page = rdev_get_id(rdev); + int func = data->info->func[page]; + + *flags = 0; + + mutex_lock(&data->update_lock); + + for (i = 0; i < ARRAY_SIZE(pmbus_regulator_flag_map); i++) { + cat = &pmbus_regulator_flag_map[i]; + if (!(func & cat->func)) + continue; + + status = pmbus_read_byte_data(client, page, cat->reg); + if (status < 0) { + mutex_unlock(&data->update_lock); + return status; + } + + for (bit = cat->bits; bit->pflag; bit++) { + if (status & bit->pflag) + *flags |= bit->rflag; + } + } + + /* + * Map what bits of STATUS_{WORD,BYTE} we can to REGULATOR_ERROR_* + * bits. Some of the other bits are tempting (especially for cases + * where we don't have the relevant PMBUS_HAVE_STATUS_* + * functionality), but there's an unfortunate ambiguity in that + * they're defined as indicating a fault *or* a warning, so we can't + * easily determine whether to report REGULATOR_ERROR_ or + * REGULATOR_ERROR__WARN. + */ + status = pmbus_get_status(client, page, PMBUS_STATUS_WORD); + mutex_unlock(&data->update_lock); + if (status < 0) + return status; + + if (pmbus_regulator_is_enabled(rdev) && (status & PB_STATUS_OFF)) + *flags |= REGULATOR_ERROR_FAIL; + + /* + * Unlike most other status bits, PB_STATUS_{IOUT_OC,VOUT_OV} are + * defined strictly as fault indicators (not warnings). + */ + if (status & PB_STATUS_IOUT_OC) + *flags |= REGULATOR_ERROR_OVER_CURRENT; + if (status & PB_STATUS_VOUT_OV) + *flags |= REGULATOR_ERROR_REGULATION_OUT; + + /* + * If we haven't discovered any thermal faults or warnings via + * PMBUS_STATUS_TEMPERATURE, map PB_STATUS_TEMPERATURE to a warning as + * a (conservative) best-effort interpretation. + */ + if (!(*flags & (REGULATOR_ERROR_OVER_TEMP | REGULATOR_ERROR_OVER_TEMP_WARN)) && + (status & PB_STATUS_TEMPERATURE)) + *flags |= REGULATOR_ERROR_OVER_TEMP_WARN; + + return 0; +} + const struct regulator_ops pmbus_regulator_ops = { .enable = pmbus_regulator_enable, .disable = pmbus_regulator_disable, .is_enabled = pmbus_regulator_is_enabled, + .get_error_flags = pmbus_regulator_get_error_flags, }; EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS); From da78ad2b6b9b7308c5835428442fef8d73cea2e3 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Fri, 18 Feb 2022 16:07:42 -0800 Subject: [PATCH 55/86] hwmon: (pmbus/lm25066) Add regulator support While these chips aren't strictly advertised as voltage regulators per se, they (aside from the lm25056) support the PMBus OPERATION command to enable and disable their outputs and have status bits for reporting various warnings and faults, and can hence usefully support all the pmbus_regulator_ops operations. Signed-off-by: Zev Weiss Link: https://lore.kernel.org/r/20220219000742.20126-1-zev@bewilderbeest.net Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 7 +++++++ drivers/hwmon/pmbus/lm25066.c | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index c96f7b7338bd..c73aa50c7615 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -174,6 +174,13 @@ config SENSORS_LM25066 This driver can also be built as a module. If so, the module will be called lm25066. +config SENSORS_LM25066_REGULATOR + bool "Regulator support for LM25066 and compatibles" + depends on SENSORS_LM25066 && REGULATOR + help + If you say yes here you get regulator support for National + Semiconductor LM25066, LM5064, and LM5066. + config SENSORS_LTC2978 tristate "Linear Technologies LTC2978 and compatibles" help diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 8402b41520eb..09792cd03d9f 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -435,6 +435,12 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, return ret; } +#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) +static const struct regulator_desc lm25066_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif + static const struct i2c_device_id lm25066_id[] = { {"lm25056", lm25056}, {"lm25066", lm25066}, @@ -545,6 +551,14 @@ static int lm25066_probe(struct i2c_client *client) info->m[PSC_CURRENT_IN] = info->m[PSC_CURRENT_IN] * shunt / 1000; info->m[PSC_POWER] = info->m[PSC_POWER] * shunt / 1000; +#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) + /* LM25056 doesn't support OPERATION */ + if (data->id != lm25056) { + info->num_regulators = ARRAY_SIZE(lm25066_reg_desc); + info->reg_desc = lm25066_reg_desc; + } +#endif + return pmbus_do_probe(client, info); } From 567e77a2c213f0103912378891f25442b665cbfb Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Thu, 17 Feb 2022 20:43:18 +0100 Subject: [PATCH 56/86] hwmon: (asus-ec-sensors) do not print from .probe() Remove the call to dev_info() from the board detection function, which is called from probe(), not only to be in line with hwmon driver rules, but also because the message duplicates the error code returned from probe() for that case (ENODEV). Changes in: - v2: add missing newline (style). Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220217194318.2960472-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 0701ade16227..b5cf0136360c 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -597,18 +597,12 @@ static struct hwmon_chip_info asus_ec_chip_info = { .ops = &asus_ec_hwmon_ops, }; -static unsigned long __init -get_board_sensors(const struct device *dev) +static unsigned long __init get_board_sensors(void) { - const struct dmi_system_id *dmi_entry; + const struct dmi_system_id *dmi_entry = + dmi_first_match(asus_ec_dmi_table); - dmi_entry = dmi_first_match(asus_ec_dmi_table); - if (!dmi_entry) { - dev_info(dev, "Unsupported board"); - return 0; - } - - return (unsigned long)dmi_entry->driver_data; + return dmi_entry ? (unsigned long)dmi_entry->driver_data : 0; } static int __init asus_ec_probe(struct platform_device *pdev) @@ -625,7 +619,7 @@ static int __init asus_ec_probe(struct platform_device *pdev) struct device *hwdev; unsigned int i; - board_sensors = get_board_sensors(dev); + board_sensors = get_board_sensors(); if (!board_sensors) return -ENODEV; From 6109c3e1905c3f8d0a3909c5f6a9ad5186822b2b Mon Sep 17 00:00:00 2001 From: Eddie James Date: Tue, 15 Feb 2022 09:10:19 -0600 Subject: [PATCH 57/86] hwmon: (occ) Add sysfs entry for IPS (Idle Power Saver) status BMC control applications need to check the Idle Power Saver status byte returned by the OCC poll response, so export it in sysfs with the other OCC-specific data. Signed-off-by: Eddie James Reviewed-by: Joel Stanley Link: https://lore.kernel.org/r/20220215151022.7498-2-eajames@linux.ibm.com Signed-off-by: Guenter Roeck --- drivers/hwmon/occ/common.h | 1 + drivers/hwmon/occ/sysfs.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 5020117be740..a88c66d36e38 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -119,6 +119,7 @@ struct occ { u8 prev_stat; u8 prev_ext_stat; u8 prev_occs_present; + u8 prev_ips_status; }; int occ_setup(struct occ *occ, const char *name); diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index 03b16abef67f..6dc69c9aa4c2 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -63,6 +63,9 @@ static ssize_t occ_sysfs_show(struct device *dev, else val = 1; break; + case 8: + val = header->ips_status; + break; default: return -EINVAL; } @@ -88,6 +91,7 @@ static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4); static SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5); static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6); static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7); +static SENSOR_DEVICE_ATTR(occ_ips_status, 0444, occ_sysfs_show, NULL, 8); static DEVICE_ATTR_RO(occ_error); static struct attribute *occ_attributes[] = { @@ -99,6 +103,7 @@ static struct attribute *occ_attributes[] = { &sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr, &sensor_dev_attr_occ_state.dev_attr.attr, &sensor_dev_attr_occs_present.dev_attr.attr, + &sensor_dev_attr_occ_ips_status.dev_attr.attr, &dev_attr_occ_error.attr, NULL }; @@ -162,6 +167,11 @@ void occ_sysfs_poll_done(struct occ *occ) sysfs_notify(&occ->bus_dev->kobj, NULL, name); } + if (header->ips_status != occ->prev_ips_status) { + name = sensor_dev_attr_occ_ips_status.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + if (occ->error && occ->error != occ->prev_error) { name = dev_attr_occ_error.attr.name; sysfs_notify(&occ->bus_dev->kobj, NULL, name); @@ -174,6 +184,7 @@ done: occ->prev_stat = header->status; occ->prev_ext_stat = header->ext_status; occ->prev_occs_present = header->occs_present; + occ->prev_ips_status = header->ips_status; } int occ_setup_sysfs(struct occ *occ) From a25126fc32890a2a03ba5ddd1189aa8a4bcf4591 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Tue, 15 Feb 2022 09:10:20 -0600 Subject: [PATCH 58/86] hwmon: (occ) Add sysfs entry for OCC mode BMC control applications need to check the OCC mode returned by the OCC poll response, so export it in sysfs with the other OCC-specific data. Signed-off-by: Eddie James Reviewed-by: Joel Stanley Link: https://lore.kernel.org/r/20220215151022.7498-3-eajames@linux.ibm.com Signed-off-by: Guenter Roeck --- drivers/hwmon/occ/common.h | 1 + drivers/hwmon/occ/sysfs.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index a88c66d36e38..2dd4a4d240c0 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -120,6 +120,7 @@ struct occ { u8 prev_ext_stat; u8 prev_occs_present; u8 prev_ips_status; + u8 prev_mode; }; int occ_setup(struct occ *occ, const char *name); diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index 6dc69c9aa4c2..88f655887c95 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -66,6 +66,9 @@ static ssize_t occ_sysfs_show(struct device *dev, case 8: val = header->ips_status; break; + case 9: + val = header->mode; + break; default: return -EINVAL; } @@ -92,6 +95,7 @@ static SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5); static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6); static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7); static SENSOR_DEVICE_ATTR(occ_ips_status, 0444, occ_sysfs_show, NULL, 8); +static SENSOR_DEVICE_ATTR(occ_mode, 0444, occ_sysfs_show, NULL, 9); static DEVICE_ATTR_RO(occ_error); static struct attribute *occ_attributes[] = { @@ -104,6 +108,7 @@ static struct attribute *occ_attributes[] = { &sensor_dev_attr_occ_state.dev_attr.attr, &sensor_dev_attr_occs_present.dev_attr.attr, &sensor_dev_attr_occ_ips_status.dev_attr.attr, + &sensor_dev_attr_occ_mode.dev_attr.attr, &dev_attr_occ_error.attr, NULL }; @@ -172,6 +177,11 @@ void occ_sysfs_poll_done(struct occ *occ) sysfs_notify(&occ->bus_dev->kobj, NULL, name); } + if (header->mode != occ->prev_mode) { + name = sensor_dev_attr_occ_mode.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + if (occ->error && occ->error != occ->prev_error) { name = dev_attr_occ_error.attr.name; sysfs_notify(&occ->bus_dev->kobj, NULL, name); @@ -185,6 +195,7 @@ done: occ->prev_ext_stat = header->ext_status; occ->prev_occs_present = header->occs_present; occ->prev_ips_status = header->ips_status; + occ->prev_mode = header->mode; } int occ_setup_sysfs(struct occ *occ) From a03d8969887325167d5fe47ead99d280bbbc6b06 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Tue, 15 Feb 2022 09:10:21 -0600 Subject: [PATCH 59/86] hwmon: (occ) Add sysfs entries for additional extended status bits Add sysfs entries for DVFS due to a VRM Vdd over-temperature condition, and add the GPU throttling condition bits (such that if bit 1 is set, GPU1 is throttling). Signed-off-by: Eddie James Reviewed-by: Joel Stanley Link: https://lore.kernel.org/r/20220215151022.7498-4-eajames@linux.ibm.com Signed-off-by: Guenter Roeck --- drivers/hwmon/occ/sysfs.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index 88f655887c95..b2f788a77746 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -19,6 +19,8 @@ #define OCC_EXT_STAT_DVFS_POWER BIT(6) #define OCC_EXT_STAT_MEM_THROTTLE BIT(5) #define OCC_EXT_STAT_QUICK_DROP BIT(4) +#define OCC_EXT_STAT_DVFS_VDD BIT(3) +#define OCC_EXT_STAT_GPU_THROTTLE GENMASK(2, 0) static ssize_t occ_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -69,6 +71,12 @@ static ssize_t occ_sysfs_show(struct device *dev, case 9: val = header->mode; break; + case 10: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD); + break; + case 11: + val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE; + break; default: return -EINVAL; } @@ -96,6 +104,8 @@ static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6); static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7); static SENSOR_DEVICE_ATTR(occ_ips_status, 0444, occ_sysfs_show, NULL, 8); static SENSOR_DEVICE_ATTR(occ_mode, 0444, occ_sysfs_show, NULL, 9); +static SENSOR_DEVICE_ATTR(occ_dvfs_vdd, 0444, occ_sysfs_show, NULL, 10); +static SENSOR_DEVICE_ATTR(occ_gpu_throttle, 0444, occ_sysfs_show, NULL, 11); static DEVICE_ATTR_RO(occ_error); static struct attribute *occ_attributes[] = { @@ -109,6 +119,8 @@ static struct attribute *occ_attributes[] = { &sensor_dev_attr_occs_present.dev_attr.attr, &sensor_dev_attr_occ_ips_status.dev_attr.attr, &sensor_dev_attr_occ_mode.dev_attr.attr, + &sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr, + &sensor_dev_attr_occ_gpu_throttle.dev_attr.attr, &dev_attr_occ_error.attr, NULL }; @@ -166,6 +178,18 @@ void occ_sysfs_poll_done(struct occ *occ) sysfs_notify(&occ->bus_dev->kobj, NULL, name); } + if ((header->ext_status & OCC_EXT_STAT_DVFS_VDD) != + (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_VDD)) { + name = sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + + if ((header->ext_status & OCC_EXT_STAT_GPU_THROTTLE) != + (occ->prev_ext_stat & OCC_EXT_STAT_GPU_THROTTLE)) { + name = sensor_dev_attr_occ_gpu_throttle.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + if ((header->status & OCC_STAT_MASTER) && header->occs_present != occ->prev_occs_present) { name = sensor_dev_attr_occs_present.dev_attr.attr.name; From 7cd682b027ee1ed8bcf1e79e208c2b6b1a4d31b7 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 15 Feb 2022 20:11:13 +0100 Subject: [PATCH 60/86] hwmon: (dell-smm) Reword and mark parameter "force" as unsafe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When enabling said module parameter, the driver ignores all feature blacklists on relevant models, which has the potential for strange side effects. Also there seems to be a slight chance for unsupported devices to behave badly when probed for features. In such cases, the kernel should be tainted to inform people that these issues might have been caused by the dell_smm_hwmon driver with "force" enabled. Also reword the parameter description to remind users that enabling "force" also enables blacklisted features. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20220215191113.16640-8-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 9949eeb79378..1fd672610aeb 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -86,8 +86,8 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("i8k"); static bool force; -module_param(force, bool, 0); -MODULE_PARM_DESC(force, "Force loading without checking for supported models"); +module_param_unsafe(force, bool, 0); +MODULE_PARM_DESC(force, "Force loading without checking for supported models and features"); static bool ignore_dmi; module_param(ignore_dmi, bool, 0); From ec3db1ecf63e02265b4e93ddb1ba5cc6858c3972 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 15 Feb 2022 20:11:12 +0100 Subject: [PATCH 61/86] hwmon: (dell-smm) Add SMM interface documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the SMM interface as requested by Pali Rohar. Since Dell does not offer any offical documentation regarding the SMM interface, the necessary information was extracted from the dell_smm_hwmon driver and other sources. Suggested-by: Pali Rohár Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20220215191113.16640-7-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- Documentation/hwmon/dell-smm-hwmon.rst | 180 +++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/Documentation/hwmon/dell-smm-hwmon.rst b/Documentation/hwmon/dell-smm-hwmon.rst index beec88491171..d3323a96665d 100644 --- a/Documentation/hwmon/dell-smm-hwmon.rst +++ b/Documentation/hwmon/dell-smm-hwmon.rst @@ -165,3 +165,183 @@ obtain the same information and to control the fan status. The ioctl interface can be accessed from C programs or from shell using the i8kctl utility. See the source file of ``i8kutils`` for more information on how to use the ioctl interface. + +SMM Interface +------------- + +.. warning:: The SMM interface was reverse-engineered by trial-and-error + since Dell did not provide any Documentation, + please keep that in mind. + +The driver uses the SMM interface to send commands to the system BIOS. +This interface is normally used by Dell's 32-bit diagnostic program or +on newer notebook models by the buildin BIOS diagnostics. +The SMM is triggered by writing to the special ioports ``0xb2`` and ``0x84``, +and may cause short hangs when the BIOS code is taking too long to +execute. + +The SMM handler inside the system BIOS looks at the contents of the +``eax``, ``ebx``, ``ecx``, ``edx``, ``esi`` and ``edi`` registers. +Each register has a special purpose: + +=============== ================================== +Register Purpose +=============== ================================== +eax Holds the command code before SMM, + holds the first result after SMM. +ebx Holds the arguments. +ecx Unknown, set to 0. +edx Holds the second result after SMM. +esi Unknown, set to 0. +edi Unknown, set to 0. +=============== ================================== + +The SMM handler can signal a failure by either: + +- setting the lower sixteen bits of ``eax`` to ``0xffff`` +- not modifying ``eax`` at all +- setting the carry flag + +SMM command codes +----------------- + +=============== ======================= ================================================ +Command Code Command Name Description +=============== ======================= ================================================ +``0x0025`` Get Fn key status Returns the Fn key pressed after SMM: + + - 9th bit in ``eax`` indicates Volume up + - 10th bit in ``eax`` indicates Volume down + - both bits indicate Volume mute + +``0xa069`` Get power status Returns current power status after SMM: + + - 1st bit in ``eax`` indicates Battery connected + - 3th bit in ``eax`` indicates AC connected + +``0x00a3`` Get fan state Returns current fan state after SMM: + + - 1st byte in ``eax`` holds the current + fan state (0 - 2 or 3) + +``0x01a3`` Set fan state Sets the fan speed: + + - 1st byte in ``ebx`` holds the fan number + - 2nd byte in ``ebx`` holds the desired + fan state (0 - 2 or 3) + +``0x02a3`` Get fan speed Returns the current fan speed in RPM: + + - 1st byte in ``ebx`` holds the fan number + - 1st word in ``eax`` holds the current + fan speed in RPM (after SMM) + +``0x03a3`` Get fan type Returns the fan type: + + - 1st byte in ``ebx`` holds the fan number + - 1st byte in ``eax`` holds the + fan type (after SMM): + + - 5th bit indicates docking fan + - 1 indicates Processor fan + - 2 indicates Motherboard fan + - 3 indicates Video fan + - 4 indicates Power supply fan + - 5 indicates Chipset fan + - 6 indicates other fan type + +``0x04a3`` Get nominal fan speed Returns the nominal RPM in each fan state: + + - 1st byte in ``ebx`` holds the fan number + - 2nd byte in ``ebx`` holds the fan state + in question (0 - 2 or 3) + - 1st word in ``eax`` holds the nominal + fan speed in RPM (after SMM) + +``0x05a3`` Get fan speed tolerance Returns the speed tolerance for each fan state: + + - 1st byte in ``ebx`` holds the fan number + - 2nd byte in ``ebx`` holds the fan state + in question (0 - 2 or 3) + - 1st byte in ``eax`` returns the speed + tolerance + +``0x10a3`` Get sensor temperature Returns the measured temperature: + + - 1st byte in ``ebx`` holds the sensor number + - 1st byte in ``eax`` holds the measured + temperature (after SMM) + +``0x11a3`` Get sensor type Returns the sensor type: + + - 1st byte in ``ebx`` holds the sensor number + - 1st byte in ``eax`` holds the + temperature type (after SMM): + + - 1 indicates CPU sensor + - 2 indicates GPU sensor + - 3 indicates SODIMM sensor + - 4 indicates other sensor type + - 5 indicates Ambient sensor + - 6 indicates other sensor type + +``0xfea3`` Get SMM signature Returns Dell signature if interface + is supported (after SMM): + + - ``eax`` holds 1145651527 + (0x44494147 or "DIAG") + - ``edx`` holds 1145392204 + (0x44454c4c or "DELL") + +``0xffa3`` Get SMM signature Same as ``0xfea3``, check both. +=============== ======================= ================================================ + +There are additional commands for enabling (``0x31a3`` or ``0x35a3``) and +disabling (``0x30a3`` or ``0x34a3``) automatic fan speed control. +The commands are however causing severe sideeffects on many machines, so +they are not used by default. + +On several machines (Inspiron 3505, Precision 490, Vostro 1720, ...), the +fans supports a 4th "magic" state, which signals the BIOS that automatic +fan control should be enabled for a specific fan. +However there are also some machines who do support a 4th regular fan state too, +but in case of the "magic" state, the nominal RPM reported for this state is a +placeholder value, which however is not always detectable. + +Firmware Bugs +------------- + +The SMM calls can behave erratic on some machines: + +======================================================= ================= +Firmware Bug Affected Machines +======================================================= ================= +Reading of fan states return spurious errors. Precision 490 + +Reading of fan types causes erratic fan behaviour. Studio XPS 8000 + + Studio XPS 8100 + + Inspiron 580 + +Fan-related SMM calls take too long (about 500ms). Inspiron 7720 + + Vostro 3360 + + XPS 13 9333 + + XPS 15 L502X +======================================================= ================= + +In case you experience similar issues on your Dell machine, please +submit a bugreport on bugzilla to we can apply workarounds. + +Limitations +----------- + +The SMM calls can take too long to execute on some machines, causing +short hangs and/or audio glitches. +Also the fan state needs to be restored after suspend, as well as +the automatic mode settings. +When reading a temperature sensor, values above 127 degrees indicate +a BIOS read error or a deactivated sensor. From 4d9983dee5d4839008f7d1a75e866bccaa5877e8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 15 Feb 2022 20:11:09 +0100 Subject: [PATCH 62/86] hwmon: (dell-smm) Make fan/temp sensor number a u8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right now, we only use bits 0 to 7 of the fan/temp sensor number by doing number & 0xff. Passing the value as a u8 makes this step unnecessary. Also add checks to the ioctl handler since users might get confused when passing 0x00000101 does the same as passing 0x00000001. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20220215191113.16640-4-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 68 ++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 1fd672610aeb..1cde005ff4e4 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -250,46 +251,52 @@ static int i8k_smm(struct smm_regs *regs) /* * Read the fan status. */ -static int i8k_get_fan_status(const struct dell_smm_data *data, int fan) +static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan) { - struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_FAN, + .ebx = fan, + }; if (data->disallow_fan_support) return -EINVAL; - regs.ebx = fan & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } /* * Read the fan speed in RPM. */ -static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan) +static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan) { - struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_SPEED, + .ebx = fan, + }; if (data->disallow_fan_support) return -EINVAL; - regs.ebx = fan & 0xff; return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; } /* * Read the fan type. */ -static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan) +static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan) { - struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_FAN_TYPE, + .ebx = fan, + }; if (data->disallow_fan_support || data->disallow_fan_type_call) return -EINVAL; - regs.ebx = fan & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } -static int i8k_get_fan_type(struct dell_smm_data *data, int fan) +static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan) { /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ if (data->fan_type[fan] == INT_MIN) @@ -301,14 +308,16 @@ static int i8k_get_fan_type(struct dell_smm_data *data, int fan) /* * Read the fan nominal rpm for specific fan speed. */ -static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed) +static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed) { - struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_NOM_SPEED, + .ebx = fan | (speed << 8), + }; if (data->disallow_fan_support) return -EINVAL; - regs.ebx = (fan & 0xff) | (speed << 8); return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; } @@ -329,7 +338,7 @@ static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enabl /* * Set the fan speed (off, low, high, ...). */ -static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed) +static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; @@ -337,33 +346,35 @@ static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed) return -EINVAL; speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed); - regs.ebx = (fan & 0xff) | (speed << 8); + regs.ebx = fan | (speed << 8); return i8k_smm(®s); } -static int __init i8k_get_temp_type(int sensor) +static int __init i8k_get_temp_type(u8 sensor) { - struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_TEMP_TYPE, + .ebx = sensor, + }; - regs.ebx = sensor & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } /* * Read the cpu temperature. */ -static int _i8k_get_temp(int sensor) +static int _i8k_get_temp(u8 sensor) { struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP, - .ebx = sensor & 0xff, + .ebx = sensor, }; return i8k_smm(®s) ? : regs.eax & 0xff; } -static int i8k_get_temp(int sensor) +static int i8k_get_temp(u8 sensor) { int temp = _i8k_get_temp(sensor); @@ -496,6 +507,9 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; + if (val > U8_MAX || val < 0) + return -EINVAL; + val = i8k_get_fan_speed(data, val); break; @@ -503,6 +517,9 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; + if (val > U8_MAX || val < 0) + return -EINVAL; + val = i8k_get_fan_status(data, val); break; @@ -513,6 +530,9 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; + if (val > U8_MAX || val < 0) + return -EINVAL; + if (copy_from_user(&speed, argp + 1, sizeof(int))) return -EFAULT; @@ -920,7 +940,8 @@ static int __init dell_smm_init_hwmon(struct device *dev) { struct dell_smm_data *data = dev_get_drvdata(dev); struct device *dell_smm_hwmon_dev; - int i, state, err; + int state, err; + u8 i; for (i = 0; i < DELL_SMM_NO_TEMP; i++) { data->temp_type[i] = i8k_get_temp_type(i); @@ -1236,7 +1257,8 @@ static int __init dell_smm_probe(struct platform_device *pdev) { struct dell_smm_data *data; const struct dmi_system_id *id, *fan_control; - int fan, ret; + int ret; + u8 fan; data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL); if (!data) From c82fdd42fb50d281f6c7268e136178f096af6c69 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 15 Feb 2022 20:11:10 +0100 Subject: [PATCH 63/86] hwmon: (dell-smm) Improve temperature sensors detection On the Dell Inspiron 3505, three temperature sensors are available through the SMM interface. However since they do not have an associated type, they are not detected. Probe for those sensors in case no type was detected. _i8k_get_temp() is used instead of i8k_get_temp() since it is sometimes faster and the result is easier to check (no -ENODATA) since we do not care about the actual temp value. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220215191113.16640-5-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 1cde005ff4e4..c5939e68586d 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -651,6 +651,11 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types case hwmon_temp: switch (attr) { case hwmon_temp_input: + /* _i8k_get_temp() is fine since we do not care about the actual value */ + if (data->temp_type[channel] >= 0 || _i8k_get_temp(channel) >= 0) + return 0444; + + break; case hwmon_temp_label: if (data->temp_type[channel] >= 0) return 0444; From 54cc3dbfc10dc3db7cb1cf49aee4477a8398fbde Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Mon, 21 Feb 2022 12:09:56 +0100 Subject: [PATCH 64/86] hwmon: (pmbus) Add regulator supply into macro Add regulator supply into PWBUS_REGULATOR macro. This makes it optional to define a vin-supply in DT. Not defining a supply will add a dummy regulator supply instead and only cause the following debug output: ``` Looking up vin-supply property in node [...] failed ``` Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/58f2ff7b90233fad3d7ae2e9d66d5192e2c1ac01.1645437439.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index e0aa8aa46d8c..38f049d68d32 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -464,6 +464,7 @@ extern const struct regulator_ops pmbus_regulator_ops; #define PMBUS_REGULATOR(_name, _id) \ [_id] = { \ .name = (_name # _id), \ + .supply_name = "vin", \ .id = (_id), \ .of_match = of_match_ptr(_name # _id), \ .regulators_node = of_match_ptr("regulators"), \ From 84dc9e8a7eec2cdff00728baedf0fb35fc7c11e8 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Tue, 15 Feb 2022 09:10:22 -0600 Subject: [PATCH 65/86] hwmon: (occ) Add soft minimum power cap attribute Export the power caps data for the soft minimum power cap through hwmon. Signed-off-by: Eddie James Reviewed-by: Joel Stanley Link: https://lore.kernel.org/r/20220215151022.7498-5-eajames@linux.ibm.com Signed-off-by: Guenter Roeck --- drivers/hwmon/occ/common.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 0cb4a0a6cbc1..f00cd59f1d19 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -674,6 +674,9 @@ static ssize_t occ_show_caps_3(struct device *dev, case 7: val = caps->user_source; break; + case 8: + val = get_unaligned_be16(&caps->soft_min) * 1000000ULL; + break; default: return -EINVAL; } @@ -835,12 +838,13 @@ static int occ_setup_sensor_attrs(struct occ *occ) case 1: num_attrs += (sensors->caps.num_sensors * 7); break; - case 3: - show_caps = occ_show_caps_3; - fallthrough; case 2: num_attrs += (sensors->caps.num_sensors * 8); break; + case 3: + show_caps = occ_show_caps_3; + num_attrs += (sensors->caps.num_sensors * 9); + break; default: sensors->caps.num_sensors = 0; } @@ -1047,6 +1051,15 @@ static int occ_setup_sensor_attrs(struct occ *occ) attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, 7, 0); attr++; + + if (sensors->caps.version > 2) { + snprintf(attr->name, sizeof(attr->name), + "power%d_cap_min_soft", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, + 8, 0); + attr++; + } } } From 4b1dd41cf211c2c1b93cab77aebbb0a01157d3ff Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Mon, 21 Feb 2022 10:42:04 +0100 Subject: [PATCH 66/86] dt-bindings: vendor-prefixes: add Vicor Corporation Add vendor prefix for Vicor Corporation. Signed-off-by: Marcello Sylvester Bauer Acked-by: Rob Herring Link: https://lore.kernel.org/r/5b487a0b68d58b64022662f1e527ac80a6ad81ef.1645435888.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 294093d45a23..047a83a089ce 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1298,6 +1298,8 @@ patternProperties: description: Vertexcom Technologies, Inc. "^via,.*": description: VIA Technologies, Inc. + "^vicor,.*": + description: Vicor Corporation "^videostrong,.*": description: Videostrong Technology Co., Ltd. "^virtio,.*": From b7b94f15e55223e75b588863075a10ae79445db4 Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Mon, 21 Feb 2022 10:42:05 +0100 Subject: [PATCH 67/86] dt-bindings:trivial-devices: Add pli1209bc Add trivial device entry for PLI1209BC Digital Supervisor from Vicor Corporation. Signed-off-by: Marcello Sylvester Bauer Acked-by: Rob Herring Link: https://lore.kernel.org/r/15a9fcfb5b9592c6d87f12c2a4c77fd069f5cfff.1645435888.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 091792ba993e..d03d90360aa0 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -354,6 +354,8 @@ properties: - ti,tps544c25 # Winbond/Nuvoton H/W Monitor - winbond,w83793 + # Vicor Corporation Digital Supervisor + - vicor,pli1209bc # i2c trusted platform module (TPM) - winbond,wpct301 From d0cd978513f2e37c353ec2dcfbd863f97edb1dcd Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Mon, 21 Feb 2022 10:42:06 +0100 Subject: [PATCH 68/86] hwmon: (pmbus) Add support for pli1209bc PLI1209BC is a Digital Supervisor from Vicor Corporation. Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/4e016e66275bc46c90974aec18b150c874e64787.1645435888.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/pli1209bc.rst | 75 +++++++++++++++++++ drivers/hwmon/pmbus/Kconfig | 9 +++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/pli1209bc.c | 115 ++++++++++++++++++++++++++++++ 5 files changed, 201 insertions(+) create mode 100644 Documentation/hwmon/pli1209bc.rst create mode 100644 drivers/hwmon/pmbus/pli1209bc.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b69fdaf1af82..075d1defcd20 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -161,6 +161,7 @@ Hardware Monitoring Kernel Drivers pc87427 pcf8591 pim4328 + pli1209bc pm6764tr pmbus powr1220 diff --git a/Documentation/hwmon/pli1209bc.rst b/Documentation/hwmon/pli1209bc.rst new file mode 100644 index 000000000000..ea5b3f68a515 --- /dev/null +++ b/Documentation/hwmon/pli1209bc.rst @@ -0,0 +1,75 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver pli1209bc +======================= + +Supported chips: + + * Digital Supervisor PLI1209BC + + Prefix: 'pli1209bc' + + Addresses scanned: 0x50 - 0x5F + + Datasheet: https://www.vicorpower.com/documents/datasheets/ds-PLI1209BCxyzz-VICOR.pdf + +Authors: + - Marcello Sylvester Bauer + +Description +----------- + +The Vicor PLI1209BC is an isolated digital power system supervisor that provides +a communication interface between a host processor and one Bus Converter Module +(BCM). The PLI communicates with a system controller via a PMBus compatible +interface over an isolated UART interface. Through the PLI, the host processor +can configure, set protection limits, and monitor the BCM. + +Sysfs entries +------------- + +======================= ======================================================== +in1_label "vin2" +in1_input Input voltage. +in1_rated_min Minimum rated input voltage. +in1_rated_max Maximum rated input voltage. +in1_max Maximum input voltage. +in1_max_alarm Input voltage high alarm. +in1_crit Critical input voltage. +in1_crit_alarm Input voltage critical alarm. + +in2_label "vout2" +in2_input Output voltage. +in2_rated_min Minimum rated output voltage. +in2_rated_max Maximum rated output voltage. +in2_alarm Output voltage alarm + +curr1_label "iin2" +curr1_input Input current. +curr1_max Maximum input current. +curr1_max_alarm Maximum input current high alarm. +curr1_crit Critical input current. +curr1_crit_alarm Input current critical alarm. + +curr2_label "iout2" +curr2_input Output current. +curr2_crit Critical output current. +curr2_crit_alarm Output current critical alarm. +curr2_max Maximum output current. +curr2_max_alarm Output current high alarm. + +power1_label "pin2" +power1_input Input power. +power1_alarm Input power alarm. + +power2_label "pout2" +power2_input Output power. +power2_rated_max Maximum rated output power. + +temp1_input Die temperature. +temp1_alarm Die temperature alarm. +temp1_max Maximum die temperature. +temp1_max_alarm Die temperature high alarm. +temp1_crit Critical die temperature. +temp1_crit_alarm Die temperature critical alarm. +======================= ======================================================== diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index c73aa50c7615..f18f67a94697 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -317,6 +317,15 @@ config SENSORS_PIM4328 This driver can also be built as a module. If so, the module will be called pim4328. +config SENSORS_PLI1209BC + tristate "Vicor PLI1209BC" + help + If you say yes here you get hardware monitoring support for Vicor + PLI1209BC Digital Supervisor. + + This driver can also be built as a module. If so, the module will + be called pli1209bc. + config SENSORS_PM6764TR tristate "ST PM6764TR" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index e5935f70c9e0..a4a96ac71de7 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_MP2888) += mp2888.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o obj-$(CONFIG_SENSORS_MP5023) += mp5023.o +obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o diff --git a/drivers/hwmon/pmbus/pli1209bc.c b/drivers/hwmon/pmbus/pli1209bc.c new file mode 100644 index 000000000000..5f8847307e55 --- /dev/null +++ b/drivers/hwmon/pmbus/pli1209bc.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Vicor PLI1209BC Digital Supervisor + * + * Copyright (c) 2022 9elements GmbH + */ + +#include +#include +#include +#include "pmbus.h" + +/* + * The capability command is only supported at page 0. Probing the device while + * the page register is set to 1 will falsely enable PEC support. Disable + * capability probing accordingly, since the PLI1209BC does not have any + * additional capabilities. + */ +static struct pmbus_platform_data pli1209bc_plat_data = { + .flags = PMBUS_NO_CAPABILITY, +}; + +static int pli1209bc_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int data; + + switch (reg) { + /* PMBUS_READ_POUT uses a direct format with R=0 */ + case PMBUS_READ_POUT: + data = pmbus_read_word_data(client, page, phase, reg); + if (data < 0) + return data; + data = sign_extend32(data, 15) * 10; + return clamp_val(data, -32768, 32767) & 0xffff; + default: + return -ENODATA; + } +} + +static struct pmbus_driver_info pli1209bc_info = { + .pages = 2, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 1, + .m[PSC_CURRENT_IN] = 1, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 1, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + /* + * Page 0 sums up all attributes except voltage readings. + * The pli1209 digital supervisor only contains a single BCM, making + * page 0 redundant. + */ + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT + | PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT, + .read_word_data = pli1209bc_read_word_data, +}; + +static int pli1209bc_probe(struct i2c_client *client) +{ + client->dev.platform_data = &pli1209bc_plat_data; + return pmbus_do_probe(client, &pli1209bc_info); +} + +static const struct i2c_device_id pli1209bc_id[] = { + {"pli1209bc", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pli1209bc_id); + +#ifdef CONFIG_OF +static const struct of_device_id pli1209bc_of_match[] = { + { .compatible = "vicor,pli1209bc" }, + { }, +}; +MODULE_DEVICE_TABLE(of, pli1209bc_of_match); +#endif + +static struct i2c_driver pli1209bc_driver = { + .driver = { + .name = "pli1209bc", + .of_match_table = of_match_ptr(pli1209bc_of_match), + }, + .probe_new = pli1209bc_probe, + .id_table = pli1209bc_id, +}; + +module_i2c_driver(pli1209bc_driver); + +MODULE_AUTHOR("Marcello Sylvester Bauer "); +MODULE_DESCRIPTION("PMBus driver for Vicor PLI1209BC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); From ba1d263af1c034baf479bca14d6f715b713214b1 Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Mon, 21 Feb 2022 10:42:07 +0100 Subject: [PATCH 69/86] hwmon: (pmbus/pli1209bc) Add regulator support Add regulator support for PLI1209BC Digital Supervisor. Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/21b0cdb6dd72654effa451d3b1636ecd07b160e9.1645435888.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 7 +++++++ drivers/hwmon/pmbus/pli1209bc.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index f18f67a94697..6552467c588d 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -326,6 +326,13 @@ config SENSORS_PLI1209BC This driver can also be built as a module. If so, the module will be called pli1209bc. +config SENSORS_PLI1209BC_REGULATOR + bool "Regulator support for PLI1209BC" + depends on SENSORS_PLI1209BC && REGULATOR + help + If you say yes here you get regulator support for Vicor PLI1209BC + Digital Supervisor. + config SENSORS_PM6764TR tristate "ST PM6764TR" help diff --git a/drivers/hwmon/pmbus/pli1209bc.c b/drivers/hwmon/pmbus/pli1209bc.c index 5f8847307e55..05b4ee35ba27 100644 --- a/drivers/hwmon/pmbus/pli1209bc.c +++ b/drivers/hwmon/pmbus/pli1209bc.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "pmbus.h" /* @@ -33,11 +34,37 @@ static int pli1209bc_read_word_data(struct i2c_client *client, int page, return data; data = sign_extend32(data, 15) * 10; return clamp_val(data, -32768, 32767) & 0xffff; + /* + * PMBUS_READ_VOUT and PMBUS_READ_TEMPERATURE_1 return invalid data + * when the BCM is turned off. Since it is not possible to return + * ENODATA error, return zero instead. + */ + case PMBUS_READ_VOUT: + case PMBUS_READ_TEMPERATURE_1: + data = pmbus_read_word_data(client, page, phase, + PMBUS_STATUS_WORD); + if (data < 0) + return data; + if (data & PB_STATUS_POWER_GOOD_N) + return 0; + return pmbus_read_word_data(client, page, phase, reg); default: return -ENODATA; } } +#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) +static const struct regulator_desc pli1209bc_reg_desc = { + .name = "vout2", + .id = 1, + .of_match = of_match_ptr("vout2"), + .regulators_node = of_match_ptr("regulators"), + .ops = &pmbus_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; +#endif + static struct pmbus_driver_info pli1209bc_info = { .pages = 2, .format[PSC_VOLTAGE_IN] = direct, @@ -75,6 +102,10 @@ static struct pmbus_driver_info pli1209bc_info = { | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT, .read_word_data = pli1209bc_read_word_data, +#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) + .num_regulators = 1, + .reg_desc = &pli1209bc_reg_desc, +#endif }; static int pli1209bc_probe(struct i2c_client *client) From 64b631fb0c6f7e5fbbe0d641556e07e7a8a272ef Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Fri, 18 Feb 2022 10:06:42 +0100 Subject: [PATCH 70/86] dt-bindings: Add ti,tmp125 temperature sensor binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the freely available Texas Instruments' TMP125 datasheet: "The TMP125 is an SPI-compatible temperature sensor available in the tiny SOT23-6 package. Requiring no external components, the TMP125 is capable of measuring temperatures within 2 degree C of accuracy over a temperature range of −25 degree C to +85 degree C and 2.5 degree C of accuracy over −40 degree C to +125 degree C." The TMP125 is very similar to the TMP121/TMP122 series of familiar chips. Signed-off-by: Christian Lamparter Acked-by: Krzysztof Kozlowski Acked-by: Rob Herring Link: https://lore.kernel.org/r/d3538ba9beededfe3a9ad5dab4903a6a01834822.1645175187.git.chunkeey@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index d03d90360aa0..1c49bbc654ee 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -337,6 +337,7 @@ properties: # Thermometer with SPI interface - ti,tmp121 - ti,tmp122 + - ti,tmp125 # Digital Temperature Sensor - ti,tmp275 # TI DC-DC converter on PMBus From cd929672a9ef644aca12de59a75de5f061d5983d Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Fri, 18 Feb 2022 10:06:43 +0100 Subject: [PATCH 71/86] hwmon: (lm70) Add ti,tmp125 support The TMP125 is a 2 degree Celsius accurate Digital Temperature Sensor with a SPI interface. The temperature register is a 16-bit, read-only register. The MSB (Bit 15) is a leading zero and never set. Bits 14 to 5 are the 1+9 temperature data bits in a two's complement format. Bits 4 to 0 are useless copies of Bit 5 value and therefore ignored. This was tested on a Aerohive HiveAP-350. Bonus: lm70 supports TMP122/TMP124 as well. I added them to the Kconfig module description. Signed-off-by: Christian Lamparter Link: https://lore.kernel.org/r/43b19cbd4e7f51e9509e561b02b5d8d0e7079fac.1645175187.git.chunkeey@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm70.rst | 7 +++++++ drivers/hwmon/Kconfig | 4 ++-- drivers/hwmon/lm70.c | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/lm70.rst b/Documentation/hwmon/lm70.rst index 6ddc5b67ccb5..11303a7e16a8 100644 --- a/Documentation/hwmon/lm70.rst +++ b/Documentation/hwmon/lm70.rst @@ -15,6 +15,10 @@ Supported chips: Information: https://www.ti.com/product/tmp122 + * Texas Instruments TMP125 + + Information: https://www.ti.com/product/tmp125 + * National Semiconductor LM71 Datasheet: https://www.ti.com/product/LM71 @@ -53,6 +57,9 @@ The LM74 and TMP121/TMP122/TMP123/TMP124 are very similar; main difference is The TMP122/TMP124 also feature configurable temperature thresholds. +The TMP125 is less accurate and provides 10-bit temperature data +with 0.25 degrees Celsius resolution. + The LM71 is also very similar; main difference is 14-bit temperature data (0.03125 degrees celsius resolution). diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b3597ba66ddb..463d5b110a85 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1224,8 +1224,8 @@ config SENSORS_LM70 depends on SPI_MASTER help If you say yes here you get support for the National Semiconductor - LM70, LM71, LM74 and Texas Instruments TMP121/TMP123 digital tempera- - ture sensor chips. + LM70, LM71, LM74 and Texas Instruments TMP121/TMP123, TMP122/TMP124, + TMP125 digital temperature sensor chips. This driver can also be built as a module. If so, the module will be called lm70. diff --git a/drivers/hwmon/lm70.c b/drivers/hwmon/lm70.c index d2a60de5b8de..c20a749fc7f2 100644 --- a/drivers/hwmon/lm70.c +++ b/drivers/hwmon/lm70.c @@ -34,6 +34,7 @@ #define LM70_CHIP_LM71 2 /* NS LM71 */ #define LM70_CHIP_LM74 3 /* NS LM74 */ #define LM70_CHIP_TMP122 4 /* TI TMP122/TMP124 */ +#define LM70_CHIP_TMP125 5 /* TI TMP125 */ struct lm70 { struct spi_device *spi; @@ -87,6 +88,12 @@ static ssize_t temp1_input_show(struct device *dev, * LM71: * 14 bits of 2's complement data, discard LSB 2 bits, * resolution 0.0312 degrees celsius. + * + * TMP125: + * MSB/D15 is a leading zero. D14 is the sign-bit. This is + * followed by 9 temperature bits (D13..D5) in 2's complement + * data format with a resolution of 0.25 degrees celsius per unit. + * LSB 5 bits (D4..D0) share the same value as D5 and get discarded. */ switch (p_lm70->chip) { case LM70_CHIP_LM70: @@ -102,6 +109,10 @@ static ssize_t temp1_input_show(struct device *dev, case LM70_CHIP_LM71: val = ((int)raw / 4) * 3125 / 100; break; + + case LM70_CHIP_TMP125: + val = (sign_extend32(raw, 14) / 32) * 250; + break; } status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */ @@ -135,6 +146,10 @@ static const struct of_device_id lm70_of_ids[] = { .compatible = "ti,tmp122", .data = (void *) LM70_CHIP_TMP122, }, + { + .compatible = "ti,tmp125", + .data = (void *) LM70_CHIP_TMP125, + }, { .compatible = "ti,lm71", .data = (void *) LM70_CHIP_LM71, @@ -184,6 +199,7 @@ static const struct spi_device_id lm70_ids[] = { { "lm70", LM70_CHIP_LM70 }, { "tmp121", LM70_CHIP_TMP121 }, { "tmp122", LM70_CHIP_TMP122 }, + { "tmp125", LM70_CHIP_TMP125 }, { "lm71", LM70_CHIP_LM71 }, { "lm74", LM70_CHIP_LM74 }, { }, From e75d16e58467c5703821e12536c7dc438f3c425d Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 24 Feb 2022 07:12:09 +0100 Subject: [PATCH 72/86] hwmon: (core) Add support for pwm auto channels attribute pwm[1-*]_auto_channels_temp is documented as an official hwmon sysfs attribute, yet there is no support for it in the new with_info-API. Fix that. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220224061210.16452-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 1 + include/linux/hwmon.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 0d6c6809f26c..989e2c8496dd 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -604,6 +604,7 @@ static const char * const hwmon_pwm_attr_templates[] = { [hwmon_pwm_enable] = "pwm%d_enable", [hwmon_pwm_mode] = "pwm%d_mode", [hwmon_pwm_freq] = "pwm%d_freq", + [hwmon_pwm_auto_channels_temp] = "pwm%d_auto_channels_temp", }; static const char * const hwmon_intrusion_attr_templates[] = { diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index fad1f1df26df..eba380b76d15 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -332,12 +332,14 @@ enum hwmon_pwm_attributes { hwmon_pwm_enable, hwmon_pwm_mode, hwmon_pwm_freq, + hwmon_pwm_auto_channels_temp, }; #define HWMON_PWM_INPUT BIT(hwmon_pwm_input) #define HWMON_PWM_ENABLE BIT(hwmon_pwm_enable) #define HWMON_PWM_MODE BIT(hwmon_pwm_mode) #define HWMON_PWM_FREQ BIT(hwmon_pwm_freq) +#define HWMON_PWM_AUTO_CHANNELS_TEMP BIT(hwmon_pwm_auto_channels_temp) enum hwmon_intrusion_attributes { hwmon_intrusion_alarm, From aa9f833dfc128169a1162261c5641aa516b4a231 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 24 Feb 2022 07:12:10 +0100 Subject: [PATCH 73/86] hwmon: (sch5627) Add pwmX_auto_channels_temp support After doing some research, it seems that Fujitsu's hardware monitoring solution exports data describing which temperature sensors affect which fans, similar to the data in fan_source of the ftsteutates driver. Writing 0 into these registers forces the fans to full speed. Export this data with standard attributes. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220224061210.16452-3-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- Documentation/hwmon/sch5627.rst | 4 +++ drivers/hwmon/sch5627.c | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/Documentation/hwmon/sch5627.rst b/Documentation/hwmon/sch5627.rst index 187682e99114..ecb4fc84d045 100644 --- a/Documentation/hwmon/sch5627.rst +++ b/Documentation/hwmon/sch5627.rst @@ -20,6 +20,10 @@ Description SMSC SCH5627 Super I/O chips include complete hardware monitoring capabilities. They can monitor up to 5 voltages, 4 fans and 8 temperatures. +In addition, the SCH5627 exports data describing which temperature sensors +affect the speed of each fan. Setting pwmX_auto_channels_temp to 0 forces +the corresponding fan to full speed until another value is written. + The SMSC SCH5627 hardware monitoring part also contains an integrated watchdog. In order for this watchdog to function some motherboard specific initialization most be done by the BIOS, so if the watchdog is not enabled diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c index 72c3f6757e34..25fbbd4c9a2b 100644 --- a/drivers/hwmon/sch5627.c +++ b/drivers/hwmon/sch5627.c @@ -52,6 +52,9 @@ static const u16 SCH5627_REG_FAN[SCH5627_NO_FANS] = { static const u16 SCH5627_REG_FAN_MIN[SCH5627_NO_FANS] = { 0x62, 0x64, 0x66, 0x68 }; +static const u16 SCH5627_REG_PWM_MAP[SCH5627_NO_FANS] = { + 0xA0, 0xA1, 0xA2, 0xA3 }; + static const u16 SCH5627_REG_IN_MSB[SCH5627_NO_IN] = { 0x22, 0x23, 0x24, 0x25, 0x189 }; static const u16 SCH5627_REG_IN_LSN[SCH5627_NO_IN] = { @@ -223,6 +226,9 @@ static int reg_to_rpm(u16 reg) static umode_t sch5627_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) { + if (type == hwmon_pwm && attr == hwmon_pwm_auto_channels_temp) + return 0644; + return 0444; } @@ -278,6 +284,23 @@ static int sch5627_read(struct device *dev, enum hwmon_sensor_types type, u32 at break; } break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_auto_channels_temp: + mutex_lock(&data->update_lock); + ret = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel]); + mutex_unlock(&data->update_lock); + + if (ret < 0) + return ret; + + *val = ret; + + return 0; + default: + break; + } + break; case hwmon_in: ret = sch5627_update_in(data); if (ret < 0) @@ -318,10 +341,42 @@ static int sch5627_read_string(struct device *dev, enum hwmon_sensor_types type, return -EOPNOTSUPP; } +static int sch5627_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + struct sch5627_data *data = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_auto_channels_temp: + /* registers are 8 bit wide */ + if (val > U8_MAX || val < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = sch56xx_write_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel], + val); + mutex_unlock(&data->update_lock); + + return ret; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + static const struct hwmon_ops sch5627_ops = { .is_visible = sch5627_is_visible, .read = sch5627_read, .read_string = sch5627_read_string, + .write = sch5627_write, }; static const struct hwmon_channel_info *sch5627_info[] = { @@ -342,6 +397,12 @@ static const struct hwmon_channel_info *sch5627_info[] = { HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT, HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP + ), HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, From 2fd3eec19c6e0a2c218853db9df27d4e74921673 Mon Sep 17 00:00:00 2001 From: Aleksa Savic Date: Sun, 27 Feb 2022 21:56:25 +0100 Subject: [PATCH 74/86] hwmon: (aquacomputer_d5next) Add support for Aquacomputer Farbwerk 360 Extend aquacomputer_d5next driver to expose hardware temperature sensors of the Aquacomputer Farbwerk 360 RGB controller, which communicates through a proprietary USB HID protocol. Four temperature sensors are available. Additionally, serial number and firmware version are exposed through debugfs. This driver has been tested on x86_64. Signed-off-by: Aleksa Savic Signed-off-by: Guenter Roeck --- Documentation/hwmon/aquacomputer_d5next.rst | 49 ++- drivers/hwmon/aquacomputer_d5next.c | 381 +++++++++++++------- 2 files changed, 279 insertions(+), 151 deletions(-) diff --git a/Documentation/hwmon/aquacomputer_d5next.rst b/Documentation/hwmon/aquacomputer_d5next.rst index 1f4bb4ba2e4b..3373e27b707d 100644 --- a/Documentation/hwmon/aquacomputer_d5next.rst +++ b/Documentation/hwmon/aquacomputer_d5next.rst @@ -6,22 +6,21 @@ Kernel driver aquacomputer-d5next Supported devices: * Aquacomputer D5 Next watercooling pump +* Aquacomputer Farbwerk 360 RGB controller Author: Aleksa Savic Description ----------- -This driver exposes hardware sensors of the Aquacomputer D5 Next watercooling -pump, which communicates through a proprietary USB HID protocol. +This driver exposes hardware sensors of listed Aquacomputer devices, which +communicate through proprietary USB HID protocols. -Available sensors are pump and fan speed, power, voltage and current, as -well as coolant temperature. Also available through debugfs are the serial -number, firmware version and power-on count. - -Attaching a fan is optional and allows it to be controlled using temperature -curves directly from the pump. If it's not connected, the fan-related sensors -will report zeroes. +For the D5 Next pump, available sensors are pump and fan speed, power, voltage +and current, as well as coolant temperature. Also available through debugfs are +the serial number, firmware version and power-on count. Attaching a fan to it is +optional and allows it to be controlled using temperature curves directly from the +pump. If it's not connected, the fan-related sensors will report zeroes. The pump can be configured either through software or via its physical interface. Configuring the pump through this driver is not implemented, as it @@ -29,33 +28,31 @@ seems to require sending it a complete configuration. That includes addressable RGB LEDs, for which there is no standard sysfs interface. Thus, that task is better suited for userspace tools. +The Farbwerk 360 exposes four temperature sensors. Depending on the device, +not all sysfs and debugfs entries will be available. + Usage notes ----------- -The pump communicates via HID reports. The driver is loaded automatically by +The devices communicate via HID reports. The driver is loaded automatically by the kernel and supports hotswapping. Sysfs entries ------------- -============ ============================================= -temp1_input Coolant temperature (in millidegrees Celsius) -fan1_input Pump speed (in RPM) -fan2_input Fan speed (in RPM) -power1_input Pump power (in micro Watts) -power2_input Fan power (in micro Watts) -in0_input Pump voltage (in milli Volts) -in1_input Fan voltage (in milli Volts) -in2_input +5V rail voltage (in milli Volts) -curr1_input Pump current (in milli Amperes) -curr2_input Fan current (in milli Amperes) -============ ============================================= +================ ============================================= +temp[1-4]_input Temperature sensors (in millidegrees Celsius) +fan[1-2]_input Pump/fan speed (in RPM) +power[1-2]_input Pump/fan power (in micro Watts) +in[0-2]_input Pump/fan voltage (in milli Volts) +curr[1-2]_input Pump/fan current (in milli Amperes) +================ ============================================= Debugfs entries --------------- -================ =============================================== -serial_number Serial number of the pump +================ ================================================= +serial_number Serial number of the device firmware_version Version of installed firmware -power_cycles Count of how many times the pump was powered on -================ =============================================== +power_cycles Count of how many times the device was powered on +================ ================================================= diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index fb9341a53051..525809cf7c95 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -1,32 +1,41 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for Aquacomputer D5 Next watercooling pump + * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360) * - * The D5 Next sends HID reports (with ID 0x01) every second to report sensor values - * (coolant temperature, pump and fan speed, voltage, current and power). It responds to - * Get_Report requests, but returns a dummy value of no use. + * Aquacomputer devices send HID reports (with ID 0x01) every second to report + * sensor values. * * Copyright 2021 Aleksa Savic */ -#include #include #include #include #include #include #include +#include -#define DRIVER_NAME "aquacomputer-d5next" +#define USB_VENDOR_ID_AQUACOMPUTER 0x0c70 +#define USB_PRODUCT_ID_D5NEXT 0xf00e +#define USB_PRODUCT_ID_FARBWERK360 0xf010 -#define D5NEXT_STATUS_REPORT_ID 0x01 -#define D5NEXT_STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */ +enum kinds { d5next, farbwerk360 }; + +static const char *const aqc_device_names[] = { + [d5next] = "d5next", + [farbwerk360] = "farbwerk360" +}; + +#define DRIVER_NAME "aquacomputer_d5next" + +#define STATUS_REPORT_ID 0x01 +#define STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */ +#define SERIAL_FIRST_PART 3 +#define SERIAL_SECOND_PART 5 +#define FIRMWARE_VERSION 13 /* Register offsets for the D5 Next pump */ - -#define D5NEXT_SERIAL_FIRST_PART 3 -#define D5NEXT_SERIAL_SECOND_PART 5 -#define D5NEXT_FIRMWARE_VERSION 13 #define D5NEXT_POWER_CYCLES 24 #define D5NEXT_COOLANT_TEMP 87 @@ -44,76 +53,118 @@ #define D5NEXT_PUMP_CURRENT 112 #define D5NEXT_FAN_CURRENT 99 -/* Labels for provided values */ +/* Register offsets for the Farbwerk 360 RGB controller */ +#define FARBWERK360_NUM_SENSORS 4 +#define FARBWERK360_SENSOR_START 0x32 +#define FARBWERK360_SENSOR_SIZE 0x02 +#define FARBWERK360_SENSOR_DISCONNECTED 0x7FFF -#define L_COOLANT_TEMP "Coolant temp" +/* Labels for D5 Next */ +#define L_D5NEXT_COOLANT_TEMP "Coolant temp" -#define L_PUMP_SPEED "Pump speed" -#define L_FAN_SPEED "Fan speed" - -#define L_PUMP_POWER "Pump power" -#define L_FAN_POWER "Fan power" - -#define L_PUMP_VOLTAGE "Pump voltage" -#define L_FAN_VOLTAGE "Fan voltage" -#define L_5V_VOLTAGE "+5V voltage" - -#define L_PUMP_CURRENT "Pump current" -#define L_FAN_CURRENT "Fan current" - -static const char *const label_speeds[] = { - L_PUMP_SPEED, - L_FAN_SPEED, +static const char *const label_d5next_speeds[] = { + "Pump speed", + "Fan speed" }; -static const char *const label_power[] = { - L_PUMP_POWER, - L_FAN_POWER, +static const char *const label_d5next_power[] = { + "Pump power", + "Fan power" }; -static const char *const label_voltages[] = { - L_PUMP_VOLTAGE, - L_FAN_VOLTAGE, - L_5V_VOLTAGE, +static const char *const label_d5next_voltages[] = { + "Pump voltage", + "Fan voltage", + "+5V voltage" }; -static const char *const label_current[] = { - L_PUMP_CURRENT, - L_FAN_CURRENT, +static const char *const label_d5next_current[] = { + "Pump current", + "Fan current" }; -struct d5next_data { +/* Labels for Farbwerk 360 temperature sensors */ +static const char *const label_temp_sensors[] = { + "Sensor 1", + "Sensor 2", + "Sensor 3", + "Sensor 4" +}; + +struct aqc_data { struct hid_device *hdev; struct device *hwmon_dev; struct dentry *debugfs; - s32 temp_input; + enum kinds kind; + const char *name; + + /* General info, same across all devices */ + u32 serial_number[2]; + u16 firmware_version; + + /* D5 Next specific - how many times the device was powered on */ + u32 power_cycles; + + /* Sensor values */ + s32 temp_input[4]; u16 speed_input[2]; u32 power_input[2]; u16 voltage_input[3]; u16 current_input[2]; - u32 serial_number[2]; - u16 firmware_version; - u32 power_cycles; /* How many times the device was powered on */ + unsigned long updated; }; -static umode_t d5next_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, - int channel) +static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) { - return 0444; + const struct aqc_data *priv = data; + + switch (type) { + case hwmon_temp: + switch (priv->kind) { + case d5next: + if (channel == 0) + return 0444; + break; + case farbwerk360: + return 0444; + default: + break; + } + break; + case hwmon_fan: + case hwmon_power: + case hwmon_in: + case hwmon_curr: + switch (priv->kind) { + case d5next: + return 0444; + default: + break; + } + break; + default: + break; + } + + return 0; } -static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, - long *val) +static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) { - struct d5next_data *priv = dev_get_drvdata(dev); + struct aqc_data *priv = dev_get_drvdata(dev); - if (time_after(jiffies, priv->updated + D5NEXT_STATUS_UPDATE_INTERVAL)) + if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL)) return -ENODATA; switch (type) { case hwmon_temp: - *val = priv->temp_input; + if (priv->temp_input[channel] == -ENODATA) + return -ENODATA; + + *val = priv->temp_input[channel]; break; case hwmon_fan: *val = priv->speed_input[channel]; @@ -134,24 +185,59 @@ static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 att return 0; } -static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, - int channel, const char **str) +static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) { + struct aqc_data *priv = dev_get_drvdata(dev); + switch (type) { case hwmon_temp: - *str = L_COOLANT_TEMP; + switch (priv->kind) { + case d5next: + *str = L_D5NEXT_COOLANT_TEMP; + break; + case farbwerk360: + *str = label_temp_sensors[channel]; + break; + default: + break; + } break; case hwmon_fan: - *str = label_speeds[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_speeds[channel]; + break; + default: + break; + } break; case hwmon_power: - *str = label_power[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_power[channel]; + break; + default: + break; + } break; case hwmon_in: - *str = label_voltages[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_voltages[channel]; + break; + default: + break; + } break; case hwmon_curr: - *str = label_current[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_current[channel]; + break; + default: + break; + } break; default: return -EOPNOTSUPP; @@ -160,60 +246,89 @@ static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, return 0; } -static const struct hwmon_ops d5next_hwmon_ops = { - .is_visible = d5next_is_visible, - .read = d5next_read, - .read_string = d5next_read_string, +static const struct hwmon_ops aqc_hwmon_ops = { + .is_visible = aqc_is_visible, + .read = aqc_read, + .read_string = aqc_read_string, }; -static const struct hwmon_channel_info *d5next_info[] = { - HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), - HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL), - HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL), - HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, +static const struct hwmon_channel_info *aqc_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL), - HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL), NULL }; -static const struct hwmon_chip_info d5next_chip_info = { - .ops = &d5next_hwmon_ops, - .info = d5next_info, +static const struct hwmon_chip_info aqc_chip_info = { + .ops = &aqc_hwmon_ops, + .info = aqc_info, }; -static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) { - struct d5next_data *priv; + int i, sensor_value; + struct aqc_data *priv; - if (report->id != D5NEXT_STATUS_REPORT_ID) + if (report->id != STATUS_REPORT_ID) return 0; priv = hid_get_drvdata(hdev); /* Info provided with every report */ - - priv->serial_number[0] = get_unaligned_be16(data + D5NEXT_SERIAL_FIRST_PART); - priv->serial_number[1] = get_unaligned_be16(data + D5NEXT_SERIAL_SECOND_PART); - - priv->firmware_version = get_unaligned_be16(data + D5NEXT_FIRMWARE_VERSION); - priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES); + priv->serial_number[0] = get_unaligned_be16(data + SERIAL_FIRST_PART); + priv->serial_number[1] = get_unaligned_be16(data + SERIAL_SECOND_PART); + priv->firmware_version = get_unaligned_be16(data + FIRMWARE_VERSION); /* Sensor readings */ + switch (priv->kind) { + case d5next: + priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES); - priv->temp_input = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10; + priv->temp_input[0] = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10; - priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED); - priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED); + priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED); + priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED); - priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000; - priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000; + priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000; + priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000; - priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10; - priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10; - priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10; + priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10; + priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10; + priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10; - priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT); - priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT); + priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT); + priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT); + break; + case farbwerk360: + /* Temperature sensor readings */ + for (i = 0; i < FARBWERK360_NUM_SENSORS; i++) { + sensor_value = get_unaligned_be16(data + FARBWERK360_SENSOR_START + + i * FARBWERK360_SENSOR_SIZE); + if (sensor_value == FARBWERK360_SENSOR_DISCONNECTED) + priv->temp_input[i] = -ENODATA; + else + priv->temp_input[i] = sensor_value * 10; + } + break; + default: + break; + } priv->updated = jiffies; @@ -224,7 +339,7 @@ static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, static int serial_number_show(struct seq_file *seqf, void *unused) { - struct d5next_data *priv = seqf->private; + struct aqc_data *priv = seqf->private; seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]); @@ -234,7 +349,7 @@ DEFINE_SHOW_ATTRIBUTE(serial_number); static int firmware_version_show(struct seq_file *seqf, void *unused) { - struct d5next_data *priv = seqf->private; + struct aqc_data *priv = seqf->private; seq_printf(seqf, "%u\n", priv->firmware_version); @@ -244,7 +359,7 @@ DEFINE_SHOW_ATTRIBUTE(firmware_version); static int power_cycles_show(struct seq_file *seqf, void *unused) { - struct d5next_data *priv = seqf->private; + struct aqc_data *priv = seqf->private; seq_printf(seqf, "%u\n", priv->power_cycles); @@ -252,29 +367,32 @@ static int power_cycles_show(struct seq_file *seqf, void *unused) } DEFINE_SHOW_ATTRIBUTE(power_cycles); -static void d5next_debugfs_init(struct d5next_data *priv) +static void aqc_debugfs_init(struct aqc_data *priv) { - char name[32]; + char name[64]; - scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev)); + scnprintf(name, sizeof(name), "%s_%s-%s", "aquacomputer", priv->name, + dev_name(&priv->hdev->dev)); priv->debugfs = debugfs_create_dir(name, NULL); debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops); debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); - debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); + + if (priv->kind == d5next) + debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); } #else -static void d5next_debugfs_init(struct d5next_data *priv) +static void aqc_debugfs_init(struct aqc_data *priv) { } #endif -static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id) +static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) { - struct d5next_data *priv; + struct aqc_data *priv; int ret; priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); @@ -284,7 +402,7 @@ static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->hdev = hdev; hid_set_drvdata(hdev, priv); - priv->updated = jiffies - D5NEXT_STATUS_UPDATE_INTERVAL; + priv->updated = jiffies - STATUS_UPDATE_INTERVAL; ret = hid_parse(hdev); if (ret) @@ -298,15 +416,28 @@ static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto fail_and_stop; - priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "d5next", priv, - &d5next_chip_info, NULL); + switch (hdev->product) { + case USB_PRODUCT_ID_D5NEXT: + priv->kind = d5next; + break; + case USB_PRODUCT_ID_FARBWERK360: + priv->kind = farbwerk360; + break; + default: + break; + } + + priv->name = aqc_device_names[priv->kind]; + + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv, + &aqc_chip_info, NULL); if (IS_ERR(priv->hwmon_dev)) { ret = PTR_ERR(priv->hwmon_dev); goto fail_and_close; } - d5next_debugfs_init(priv); + aqc_debugfs_init(priv); return 0; @@ -317,9 +448,9 @@ fail_and_stop: return ret; } -static void d5next_remove(struct hid_device *hdev) +static void aqc_remove(struct hid_device *hdev) { - struct d5next_data *priv = hid_get_drvdata(hdev); + struct aqc_data *priv = hid_get_drvdata(hdev); debugfs_remove_recursive(priv->debugfs); hwmon_device_unregister(priv->hwmon_dev); @@ -328,36 +459,36 @@ static void d5next_remove(struct hid_device *hdev) hid_hw_stop(hdev); } -static const struct hid_device_id d5next_table[] = { - { HID_USB_DEVICE(0x0c70, 0xf00e) }, /* Aquacomputer D5 Next */ - {}, +static const struct hid_device_id aqc_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_D5NEXT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) }, + { } }; -MODULE_DEVICE_TABLE(hid, d5next_table); +MODULE_DEVICE_TABLE(hid, aqc_table); -static struct hid_driver d5next_driver = { +static struct hid_driver aqc_driver = { .name = DRIVER_NAME, - .id_table = d5next_table, - .probe = d5next_probe, - .remove = d5next_remove, - .raw_event = d5next_raw_event, + .id_table = aqc_table, + .probe = aqc_probe, + .remove = aqc_remove, + .raw_event = aqc_raw_event, }; -static int __init d5next_init(void) +static int __init aqc_init(void) { - return hid_register_driver(&d5next_driver); + return hid_register_driver(&aqc_driver); } -static void __exit d5next_exit(void) +static void __exit aqc_exit(void) { - hid_unregister_driver(&d5next_driver); + hid_unregister_driver(&aqc_driver); } /* Request to initialize after the HID bus to ensure it's not being loaded before */ - -late_initcall(d5next_init); -module_exit(d5next_exit); +late_initcall(aqc_init); +module_exit(aqc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Aleksa Savic "); -MODULE_DESCRIPTION("Hwmon driver for Aquacomputer D5 Next pump"); +MODULE_DESCRIPTION("Hwmon driver for Aquacomputer devices"); From ff4670ed6e8137642b0760aa198bc04188451b80 Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Wed, 2 Mar 2022 10:49:19 +0100 Subject: [PATCH 75/86] dt-bindings: trivial-devices: Add xdpe11280 Add Infineon Multi-phase Digital Controller XDPE11280. Signed-off-by: Marcello Sylvester Bauer Acked-by: Rob Herring Link: https://lore.kernel.org/r/81c0523ba790965a371d267bb1944652d80f5f72.1646214248.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 1c49bbc654ee..da929cb08463 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -137,6 +137,8 @@ properties: - infineon,slb9645tt # Infineon TLV493D-A1B6 I2C 3D Magnetic Sensor - infineon,tlv493d-a1b6 + # Infineon Multi-phase Digital VR Controller xdpe11280 + - infineon,xdpe11280 # Infineon Multi-phase Digital VR Controller xdpe12254 - infineon,xdpe12254 # Infineon Multi-phase Digital VR Controller xdpe12284 From efdab64d88ab693bdcadc23780e347cc4185d4be Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Wed, 2 Mar 2022 10:49:20 +0100 Subject: [PATCH 76/86] hwmon: (xdpe12284) Add support for xdpe11280 Add support for another Infineon Multi-phase controller chip. The xdpe11280 uses linear instead of vid data format for VOUT. Detect VOUT_MODE format during identification and skip the xdpe122 related adaptions in case it is linear. Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/fa6a4b636a05ecb337d132824efca2545188a2a2.1646214248.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- Documentation/hwmon/xdpe12284.rst | 12 ++++++++---- drivers/hwmon/pmbus/xdpe12284.c | 21 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/Documentation/hwmon/xdpe12284.rst b/Documentation/hwmon/xdpe12284.rst index 67d1f87808e5..a224dc74ad35 100644 --- a/Documentation/hwmon/xdpe12284.rst +++ b/Documentation/hwmon/xdpe12284.rst @@ -5,6 +5,10 @@ Kernel driver xdpe122 Supported chips: + * Infineon XDPE11280 + + Prefix: 'xdpe11280' + * Infineon XDPE12254 Prefix: 'xdpe12254' @@ -20,10 +24,10 @@ Authors: Description ----------- -This driver implements support for Infineon Multi-phase XDPE122 family -dual loop voltage regulators. -The family includes XDPE12284 and XDPE12254 devices. -The devices from this family complaint with: +This driver implements support for Infineon Multi-phase XDPE112 and XDPE122 +family dual loop voltage regulators. +These families include XDPE11280, XDPE12284 and XDPE12254 devices. +The devices from this family compliant with: - Intel VR13 and VR13HC rev 1.3, IMVP8 rev 1.2 and IMPVP9 rev 1.3 DC-DC converter specification. diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c index b07da06a40c9..b979c88d2e87 100644 --- a/drivers/hwmon/pmbus/xdpe12284.c +++ b/drivers/hwmon/pmbus/xdpe12284.c @@ -76,7 +76,22 @@ static int xdpe122_identify(struct i2c_client *client, struct pmbus_driver_info *info) { u8 vout_params; - int i, ret; + int i, ret, vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + switch (vout_mode >> 5) { + case 0: + info->format[PSC_VOLTAGE_OUT] = linear; + return 0; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + info->read_word_data = xdpe122_read_word_data; + break; + default: + return -ENODEV; + } + } for (i = 0; i < XDPE122_PAGE_NUM; i++) { /* Read the register with VOUT scaling value.*/ @@ -110,7 +125,6 @@ static int xdpe122_identify(struct i2c_client *client, static struct pmbus_driver_info xdpe122_info = { .pages = XDPE122_PAGE_NUM, .format[PSC_VOLTAGE_IN] = linear, - .format[PSC_VOLTAGE_OUT] = vid, .format[PSC_TEMPERATURE] = linear, .format[PSC_CURRENT_IN] = linear, .format[PSC_CURRENT_OUT] = linear, @@ -124,7 +138,6 @@ static struct pmbus_driver_info xdpe122_info = { PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, .identify = xdpe122_identify, - .read_word_data = xdpe122_read_word_data, }; static int xdpe122_probe(struct i2c_client *client) @@ -140,6 +153,7 @@ static int xdpe122_probe(struct i2c_client *client) } static const struct i2c_device_id xdpe122_id[] = { + {"xdpe11280", 0}, {"xdpe12254", 0}, {"xdpe12284", 0}, {} @@ -148,6 +162,7 @@ static const struct i2c_device_id xdpe122_id[] = { MODULE_DEVICE_TABLE(i2c, xdpe122_id); static const struct of_device_id __maybe_unused xdpe122_of_match[] = { + {.compatible = "infineon,xdpe11280"}, {.compatible = "infineon,xdpe12254"}, {.compatible = "infineon,xdpe12284"}, {} From f53bfe4d698430bd602c37042db4021dc2603b8d Mon Sep 17 00:00:00 2001 From: Marcello Sylvester Bauer Date: Wed, 2 Mar 2022 10:49:21 +0100 Subject: [PATCH 77/86] hwmon: (xdpe12284) Add regulator support Add simple on/off regulator support for xdpe12284 and other pmbus parts supported by the xdpe12284 driver. Signed-off-by: Marcello Sylvester Bauer Link: https://lore.kernel.org/r/f69b8e7fa32cd2bad9516d8fa590abb87c7e4869.1646214248.git.sylv@sylv.io Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 6 ++++++ drivers/hwmon/pmbus/xdpe12284.c | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 6552467c588d..a2ea1d5a8765 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -417,6 +417,12 @@ config SENSORS_XDPE122 This driver can also be built as a module. If so, the module will be called xdpe12284. +config SENSORS_XDPE122_REGULATOR + bool "Regulator support for XDPE122 and compatibles" + depends on SENSORS_XDPE122 && REGULATOR + help + Uses the xdpe12284 or compatible as regulator. + config SENSORS_ZL6100 tristate "Intersil ZL6100 and compatibles" help diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c index b979c88d2e87..18fffc5d749b 100644 --- a/drivers/hwmon/pmbus/xdpe12284.c +++ b/drivers/hwmon/pmbus/xdpe12284.c @@ -10,6 +10,8 @@ #include #include #include +#include + #include "pmbus.h" #define XDPE122_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ @@ -122,6 +124,11 @@ static int xdpe122_identify(struct i2c_client *client, return 0; } +static const struct regulator_desc xdpe122_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), +}; + static struct pmbus_driver_info xdpe122_info = { .pages = XDPE122_PAGE_NUM, .format[PSC_VOLTAGE_IN] = linear, @@ -138,6 +145,10 @@ static struct pmbus_driver_info xdpe122_info = { PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, .identify = xdpe122_identify, +#if IS_ENABLED(CONFIG_SENSORS_XDPE122_REGULATOR) + .num_regulators = 2, + .reg_desc = xdpe122_reg_desc, +#endif }; static int xdpe122_probe(struct i2c_client *client) From a3cd66d7cbadcc0c29884f25b754fd22699c719c Mon Sep 17 00:00:00 2001 From: Potin Lai Date: Wed, 2 Mar 2022 20:38:16 +0800 Subject: [PATCH 78/86] hwmon: (adm1275) Allow setting sample averaging Current driver assume PWR_AVG and VI_AVG as 1 by default, and user needs to set sample averaging via sysfs manually. This patch parses the properties "adi,power-sample-average" and "adi,volt-curr-sample-average" from device tree, and setting sample averaging during probe. Input value must be one of value in the list [1, 2, 4, 8, 16, 32, 64, 128]. Signed-off-by: Potin Lai Link: https://lore.kernel.org/r/20220302123817.27025-2-potin.lai@quantatw.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1275.c | 40 ++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index d311e0557401..3b07bfb43e93 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -475,6 +475,7 @@ static int adm1275_probe(struct i2c_client *client) int vindex = -1, voindex = -1, cindex = -1, pindex = -1; int tindex = -1; u32 shunt; + u32 avg; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA @@ -687,7 +688,7 @@ static int adm1275_probe(struct i2c_client *client) if ((config & (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) != (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) { config |= ADM1278_VOUT_EN | ADM1278_TEMP1_EN; - ret = i2c_smbus_write_byte_data(client, + ret = i2c_smbus_write_word_data(client, ADM1275_PMON_CONFIG, config); if (ret < 0) { @@ -756,6 +757,43 @@ static int adm1275_probe(struct i2c_client *client) return -ENODEV; } + if (data->have_power_sampling && + of_property_read_u32(client->dev.of_node, + "adi,power-sample-average", &avg) == 0) { + if (!avg || avg > ADM1275_SAMPLES_AVG_MAX || + BIT(__fls(avg)) != avg) { + dev_err(&client->dev, + "Invalid number of power samples"); + return -EINVAL; + } + ret = adm1275_write_pmon_config(data, client, true, + ilog2(avg)); + if (ret < 0) { + dev_err(&client->dev, + "Setting power sample averaging failed with error %d", + ret); + return ret; + } + } + + if (of_property_read_u32(client->dev.of_node, + "adi,volt-curr-sample-average", &avg) == 0) { + if (!avg || avg > ADM1275_SAMPLES_AVG_MAX || + BIT(__fls(avg)) != avg) { + dev_err(&client->dev, + "Invalid number of voltage/current samples"); + return -EINVAL; + } + ret = adm1275_write_pmon_config(data, client, false, + ilog2(avg)); + if (ret < 0) { + dev_err(&client->dev, + "Setting voltage and current sample averaging failed with error %d", + ret); + return ret; + } + } + if (voindex < 0) voindex = vindex; if (vindex >= 0) { From a113870165b862440a31a8614fa6905a85033486 Mon Sep 17 00:00:00 2001 From: Potin Lai Date: Wed, 2 Mar 2022 20:38:17 +0800 Subject: [PATCH 79/86] dt-bindings: hwmon: Add sample averaging properties for ADM1275 Add documentation of new properties for sample averaging in PMON_CONFIG register. New properties: - adi,volt-curr-sample-average - adi,power-sample-average Signed-off-by: Potin Lai Reviewed-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20220302123817.27025-3-potin.lai@quantatw.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/adi,adm1275.yaml | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml index 223393d7cafd..ab87f51c5aef 100644 --- a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml @@ -37,6 +37,72 @@ properties: description: Shunt resistor value in micro-Ohm. + adi,volt-curr-sample-average: + description: | + Number of samples to be used to report voltage and current values. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 8, 16, 32, 64, 128] + + adi,power-sample-average: + description: | + Number of samples to be used to report power values. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 8, 16, 32, 64, 128] + +allOf: + - if: + properties: + compatible: + contains: + enum: + - adi,adm1075 + - adi,adm1276 + then: + properties: + adi,volt-curr-sample-average: + default: 128 + adi,power-sample-average: false + + - if: + properties: + compatible: + contains: + enum: + - adi,adm1275 + then: + properties: + adi,volt-curr-sample-average: + default: 16 + adi,power-sample-average: false + + - if: + properties: + compatible: + contains: + enum: + - adi,adm1272 + then: + properties: + adi,volt-curr-sample-average: + default: 128 + adi,power-sample-average: + default: 128 + + - if: + properties: + compatible: + contains: + enum: + - adi,adm1278 + - adi,adm1293 + - adi,adm1294 + then: + properties: + adi,volt-curr-sample-average: + default: 128 + adi,power-sample-average: + default: 1 + required: - compatible - reg @@ -53,5 +119,7 @@ examples: compatible = "adi,adm1272"; reg = <0x10>; shunt-resistor-micro-ohms = <500>; + adi,volt-curr-sample-average = <128>; + adi,power-sample-average = <128>; }; }; From b4fa042e92e17f243bdfa2c53e3cd4c8b3dfb56c Mon Sep 17 00:00:00 2001 From: Agathe Porte Date: Tue, 22 Feb 2022 14:36:09 -0800 Subject: [PATCH 80/86] dt-bindings: hwmon: add tmp464.yaml Add basic description of the tmp464 driver DT bindings. Signed-off-by: Agathe Porte Cc: Krzysztof Adamski Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20220222223610.23098-1-linux@roeck-us.net Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ti,tmp464.yaml | 114 ++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 121 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml diff --git a/Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml b/Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml new file mode 100644 index 000000000000..801ca9ba7d34 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/ti,tmp464.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TMP464 and TMP468 temperature sensors + +maintainers: + - Agathe Porte + +description: | + ±0.0625°C Remote and Local temperature sensor + https://www.ti.com/lit/ds/symlink/tmp464.pdf + https://www.ti.com/lit/ds/symlink/tmp468.pdf + +properties: + compatible: + enum: + - ti,tmp464 + - ti,tmp468 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +required: + - compatible + - reg + +additionalProperties: false + +patternProperties: + "^channel@([0-8])$": + type: object + description: | + Represents channels of the device and their specific configuration. + + properties: + reg: + description: | + The channel number. 0 is local channel, 1-8 are remote channels. + items: + minimum: 0 + maximum: 8 + + label: + description: | + A descriptive name for this channel, like "ambient" or "psu". + + ti,n-factor: + description: | + The value (two's complement) to be programmed in the channel specific N correction register. + For remote channels only. + $ref: /schemas/types.yaml#/definitions/int32 + items: + minimum: -128 + maximum: 127 + + required: + - reg + + additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@4b { + compatible = "ti,tmp464"; + reg = <0x4b>; + }; + }; + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@4b { + compatible = "ti,tmp464"; + reg = <0x4b>; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0x0>; + label = "local"; + }; + + channel@1 { + reg = <0x1>; + ti,n-factor = <(-10)>; + label = "external"; + }; + + channel@2 { + reg = <0x2>; + ti,n-factor = <0x10>; + label = "somelabel"; + }; + + channel@3 { + reg = <0x3>; + status = "disabled"; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 27a87a664635..3766875f4f2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19496,6 +19496,13 @@ S: Maintained F: Documentation/hwmon/tmp401.rst F: drivers/hwmon/tmp401.c +TMP464 HARDWARE MONITOR DRIVER +M: Agathe Porte +M: Guenter Roeck +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml + TMP513 HARDWARE MONITOR DRIVER M: Eric Tremblay L: linux-hwmon@vger.kernel.org From 007e433cf0373334a2bef1b0c9831647184906ba Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 22 Feb 2022 14:36:10 -0800 Subject: [PATCH 81/86] hwmon: Add driver for Texas Instruments TMP464 and TMP468 Add support for Texas Instruments TMP464 and TMP468 temperature sensor ICs. TI's TMP464 is an I2C temperature sensor chip. This chip is similar to TI's TMP421 chip, but with 16bit-wide registers (instead of 8bit-wide registers). The chip has one local sensor and four remote sensors. TMP468 is similar to TMP464 but has one local and eight remote sensors. Originally-from: Agathe Porte Cc: Agathe Porte Cc: Krzysztof Adamski Tested-by: Agathe Porte Link: https://lore.kernel.org/r/20220222223610.23098-2-linux@roeck-us.net Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/tmp464.rst | 73 ++++ MAINTAINERS | 2 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/tmp464.c | 712 +++++++++++++++++++++++++++++++++ 6 files changed, 800 insertions(+) create mode 100644 Documentation/hwmon/tmp464.rst create mode 100644 drivers/hwmon/tmp464.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 075d1defcd20..9d2787a12a69 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -195,6 +195,7 @@ Hardware Monitoring Kernel Drivers tmp108 tmp401 tmp421 + tmp464 tmp513 tps23861 tps40422 diff --git a/Documentation/hwmon/tmp464.rst b/Documentation/hwmon/tmp464.rst new file mode 100644 index 000000000000..7596e7623d06 --- /dev/null +++ b/Documentation/hwmon/tmp464.rst @@ -0,0 +1,73 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver tmp464 +==================== + +Supported chips: + + * Texas Instruments TMP464 + + Prefix: 'tmp464' + + Addresses scanned: I2C 0x48, 0x49, 0x4a and 0x4b + + Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp464.html + + * Texas Instruments TMP468 + + Prefix: 'tmp468' + + Addresses scanned: I2C 0x48, 0x49, 0x4a and 0x4b + + Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp468.html + +Authors: + + Agathe Porte + Guenter Roeck + +Description +----------- + +This driver implements support for Texas Instruments TMP464 and TMP468 +temperature sensor chips. TMP464 provides one local and four remote +sensors. TMP468 provides one local and eight remote sensors. +Temperature is measured in degrees Celsius. The chips are wired over +I2C/SMBus and specified over a temperature range of -40 to +125 degrees +Celsius. Resolution for both the local and remote channels is 0.0625 +degree C. + +The chips support only temperature measurements. The driver exports +temperature values, limits, and alarms via the following sysfs files: + +**temp[1-9]_input** + +**temp[1-9]_max** + +**temp[1-9]_max_hyst** + +**temp[1-9]_max_alarm** + +**temp[1-9]_crit** + +**temp[1-9]_crit_alarm** + +**temp[1-9]_crit_hyst** + +**temp[2-9]_offset** + +**temp[2-9]_fault** + +Each sensor can be individually disabled via Devicetree or from sysfs +via: + +**temp[1-9]_enable** + +If labels were specified in Devicetree, additional sysfs files will +be present: + +**temp[1-9]_label** + +The update interval is configurable with the following sysfs attribute. + +**update_interval** diff --git a/MAINTAINERS b/MAINTAINERS index 3766875f4f2a..a9c9ee378f93 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19502,6 +19502,8 @@ M: Guenter Roeck L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml +F: Documentation/hwmon/tmp464.rst +F: drivers/hwmon/tmp464.c TMP513 HARDWARE MONITOR DRIVER M: Eric Tremblay diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 463d5b110a85..9ab4e9b3d27b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1996,6 +1996,17 @@ config SENSORS_TMP421 This driver can also be built as a module. If so, the module will be called tmp421. +config SENSORS_TMP464 + tristate "Texas Instruments TMP464 and compatible" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments TMP464 + and TMP468 temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp464. + config SENSORS_TMP513 tristate "Texas Instruments TMP513 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 7bba3415ca3d..4ed138d0621f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -195,6 +195,7 @@ obj-$(CONFIG_SENSORS_TMP103) += tmp103.o obj-$(CONFIG_SENSORS_TMP108) += tmp108.o obj-$(CONFIG_SENSORS_TMP401) += tmp401.o obj-$(CONFIG_SENSORS_TMP421) += tmp421.o +obj-$(CONFIG_SENSORS_TMP464) += tmp464.o obj-$(CONFIG_SENSORS_TMP513) += tmp513.o obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o diff --git a/drivers/hwmon/tmp464.c b/drivers/hwmon/tmp464.c new file mode 100644 index 000000000000..7814f39bd1a3 --- /dev/null +++ b/drivers/hwmon/tmp464.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* Driver for the Texas Instruments TMP464 SMBus temperature sensor IC. + * Supported models: TMP464, TMP468 + + * Copyright (C) 2022 Agathe Porte + * Preliminary support by: + * Lionel Pouliquen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, I2C_CLIENT_END }; + +#define TMP464_NUM_CHANNELS 5 /* chan 0 is internal, 1-4 are remote */ +#define TMP468_NUM_CHANNELS 9 /* chan 0 is internal, 1-8 are remote */ + +#define MAX_CHANNELS 9 + +#define TMP464_TEMP_REG(channel) (channel) +#define TMP464_TEMP_OFFSET_REG(channel) (0x40 + ((channel) - 1) * 8) +#define TMP464_N_FACTOR_REG(channel) (0x41 + ((channel) - 1) * 8) + +static const u8 TMP464_THERM_LIMIT[MAX_CHANNELS] = { + 0x39, 0x42, 0x4A, 0x52, 0x5A, 0x62, 0x6a, 0x72, 0x7a }; +static const u8 TMP464_THERM2_LIMIT[MAX_CHANNELS] = { + 0x3A, 0x43, 0x4B, 0x53, 0x5B, 0x63, 0x6b, 0x73, 0x7b }; + +#define TMP464_THERM_STATUS_REG 0x21 +#define TMP464_THERM2_STATUS_REG 0x22 +#define TMP464_REMOTE_OPEN_REG 0x23 +#define TMP464_CONFIG_REG 0x30 +#define TMP464_TEMP_HYST_REG 0x38 +#define TMP464_LOCK_REG 0xc4 + +/* Identification */ +#define TMP464_MANUFACTURER_ID_REG 0xFE +#define TMP464_DEVICE_ID_REG 0xFF + +/* Flags */ +#define TMP464_CONFIG_SHUTDOWN BIT(5) +#define TMP464_CONFIG_RANGE 0x04 +#define TMP464_CONFIG_REG_REN(x) (BIT(7 + (x))) +#define TMP464_CONFIG_REG_REN_MASK GENMASK(15, 7) +#define TMP464_CONFIG_CONVERSION_RATE_B0 2 +#define TMP464_CONFIG_CONVERSION_RATE_B2 4 +#define TMP464_CONFIG_CONVERSION_RATE_MASK GENMASK(TMP464_CONFIG_CONVERSION_RATE_B2, \ + TMP464_CONFIG_CONVERSION_RATE_B0) + +#define TMP464_UNLOCK_VAL 0xeb19 +#define TMP464_LOCK_VAL 0x5ca6 +#define TMP464_LOCKED 0x8000 + +/* Manufacturer / Device ID's */ +#define TMP464_MANUFACTURER_ID 0x5449 +#define TMP464_DEVICE_ID 0x1468 +#define TMP468_DEVICE_ID 0x0468 + +static const struct i2c_device_id tmp464_id[] = { + { "tmp464", TMP464_NUM_CHANNELS }, + { "tmp468", TMP468_NUM_CHANNELS }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp464_id); + +static const struct of_device_id __maybe_unused tmp464_of_match[] = { + { + .compatible = "ti,tmp464", + .data = (void *)TMP464_NUM_CHANNELS + }, + { + .compatible = "ti,tmp468", + .data = (void *)TMP468_NUM_CHANNELS + }, + {}, +}; +MODULE_DEVICE_TABLE(of, tmp464_of_match); + +struct tmp464_channel { + const char *label; + bool enabled; +}; + +struct tmp464_data { + struct regmap *regmap; + struct mutex update_lock; + int channels; + s16 config_orig; + u16 open_reg; + unsigned long last_updated; + bool valid; + int update_interval; + struct tmp464_channel channel[MAX_CHANNELS]; +}; + +static int temp_from_reg(s16 reg) +{ + return DIV_ROUND_CLOSEST((reg >> 3) * 625, 10); +} + +static s16 temp_to_limit_reg(long temp) +{ + return DIV_ROUND_CLOSEST(temp, 500) << 6; +} + +static s16 temp_to_offset_reg(long temp) +{ + return DIV_ROUND_CLOSEST(temp * 10, 625) << 3; +} + +static int tmp464_enable_channels(struct tmp464_data *data) +{ + struct regmap *regmap = data->regmap; + u16 enable = 0; + int i; + + for (i = 0; i < data->channels; i++) + if (data->channel[i].enabled) + enable |= TMP464_CONFIG_REG_REN(i); + + return regmap_update_bits(regmap, TMP464_CONFIG_REG, TMP464_CONFIG_REG_REN_MASK, enable); +} + +static int tmp464_chip_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + *val = data->update_interval; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + unsigned int regval, regval2; + int err = 0; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_temp_max_alarm: + err = regmap_read(regmap, TMP464_THERM_STATUS_REG, ®val); + if (err < 0) + break; + *val = !!(regval & BIT(channel + 7)); + break; + case hwmon_temp_crit_alarm: + err = regmap_read(regmap, TMP464_THERM2_STATUS_REG, ®val); + if (err < 0) + break; + *val = !!(regval & BIT(channel + 7)); + break; + case hwmon_temp_fault: + /* + * The chip clears TMP464_REMOTE_OPEN_REG after it is read + * and only updates it after the next measurement cycle is + * complete. That means we have to cache the value internally + * for one measurement cycle and report the cached value. + */ + if (!data->valid || time_after(jiffies, data->last_updated + + msecs_to_jiffies(data->update_interval))) { + err = regmap_read(regmap, TMP464_REMOTE_OPEN_REG, ®val); + if (err < 0) + break; + data->open_reg = regval; + data->last_updated = jiffies; + data->valid = true; + } + *val = !!(data->open_reg & BIT(channel + 7)); + break; + case hwmon_temp_max_hyst: + err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], ®val); + if (err < 0) + break; + err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); + if (err < 0) + break; + regval -= regval2; + *val = temp_from_reg(regval); + break; + case hwmon_temp_max: + err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_crit_hyst: + err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val); + if (err < 0) + break; + err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); + if (err < 0) + break; + regval -= regval2; + *val = temp_from_reg(regval); + break; + case hwmon_temp_crit: + err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_offset: + err = regmap_read(regmap, TMP464_TEMP_OFFSET_REG(channel), ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_input: + if (!data->channel[channel].enabled) { + err = -ENODATA; + break; + } + err = regmap_read(regmap, TMP464_TEMP_REG(channel), ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_enable: + *val = data->channel[channel].enabled; + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return err; +} + +static int tmp464_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return tmp464_chip_read(dev, attr, channel, val); + case hwmon_temp: + return tmp464_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int tmp464_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + + *str = data->channel[channel].label; + + return 0; +} + +static int tmp464_set_convrate(struct tmp464_data *data, long interval) +{ + int rate; + + /* + * For valid rates, interval in milli-seconds can be calculated as + * interval = 125 << (7 - rate); + * or + * interval = (1 << (7 - rate)) * 125; + * The rate is therefore + * rate = 7 - __fls(interval / 125); + * and the rounded rate is + * rate = 7 - __fls(interval * 4 / (125 * 3)); + * Use clamp_val() to avoid overflows, and to ensure valid input + * for __fls. + */ + interval = clamp_val(interval, 125, 16000); + rate = 7 - __fls(interval * 4 / (125 * 3)); + data->update_interval = 125 << (7 - rate); + + return regmap_update_bits(data->regmap, TMP464_CONFIG_REG, + TMP464_CONFIG_CONVERSION_RATE_MASK, + rate << TMP464_CONFIG_CONVERSION_RATE_B0); +} + +static int tmp464_chip_write(struct tmp464_data *data, u32 attr, int channel, long val) +{ + switch (attr) { + case hwmon_chip_update_interval: + return tmp464_set_convrate(data, val); + default: + return -EOPNOTSUPP; + } +} + +static int tmp464_temp_write(struct tmp464_data *data, u32 attr, int channel, long val) +{ + struct regmap *regmap = data->regmap; + unsigned int regval; + int err = 0; + + switch (attr) { + case hwmon_temp_max_hyst: + err = regmap_read(regmap, TMP464_THERM_LIMIT[0], ®val); + if (err < 0) + break; + val = clamp_val(val, -256000, 256000); /* prevent overflow/underflow */ + val = clamp_val(temp_from_reg(regval) - val, 0, 255000); + err = regmap_write(regmap, TMP464_TEMP_HYST_REG, + DIV_ROUND_CLOSEST(val, 1000) << 7); + break; + case hwmon_temp_max: + val = temp_to_limit_reg(clamp_val(val, -255000, 255500)); + err = regmap_write(regmap, TMP464_THERM_LIMIT[channel], val); + break; + case hwmon_temp_crit: + val = temp_to_limit_reg(clamp_val(val, -255000, 255500)); + err = regmap_write(regmap, TMP464_THERM2_LIMIT[channel], val); + break; + case hwmon_temp_offset: + val = temp_to_offset_reg(clamp_val(val, -128000, 127937)); + err = regmap_write(regmap, TMP464_TEMP_OFFSET_REG(channel), val); + break; + case hwmon_temp_enable: + data->channel[channel].enabled = !!val; + err = tmp464_enable_channels(data); + break; + default: + err = -EOPNOTSUPP; + break; + } + + return err; +} + +static int tmp464_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + int err; + + mutex_lock(&data->update_lock); + + switch (type) { + case hwmon_chip: + err = tmp464_chip_write(data, attr, channel, val); + break; + case hwmon_temp: + err = tmp464_temp_write(data, attr, channel, val); + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return err; +} + +static umode_t tmp464_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct tmp464_data *data = _data; + + if (channel >= data->channels) + return 0; + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) + return 0644; + return 0; + } + + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_crit_hyst: + return 0444; + case hwmon_temp_enable: + case hwmon_temp_max: + case hwmon_temp_crit: + return 0644; + case hwmon_temp_max_hyst: + if (!channel) + return 0644; + return 0444; + case hwmon_temp_label: + if (data->channel[channel].label) + return 0444; + return 0; + case hwmon_temp_fault: + if (channel) + return 0444; + return 0; + case hwmon_temp_offset: + if (channel) + return 0644; + return 0; + default: + return 0; + } +} + +static void tmp464_restore_lock(void *regmap) +{ + regmap_write(regmap, TMP464_LOCK_REG, TMP464_LOCK_VAL); +} + +static void tmp464_restore_config(void *_data) +{ + struct tmp464_data *data = _data; + + regmap_write(data->regmap, TMP464_CONFIG_REG, data->config_orig); +} + +static int tmp464_init_client(struct device *dev, struct tmp464_data *data) +{ + struct regmap *regmap = data->regmap; + unsigned int regval; + int err; + + err = regmap_read(regmap, TMP464_LOCK_REG, ®val); + if (err) + return err; + if (regval == TMP464_LOCKED) { + /* Explicitly unlock chip if it is locked */ + err = regmap_write(regmap, TMP464_LOCK_REG, TMP464_UNLOCK_VAL); + if (err) + return err; + /* and lock it again when unloading the driver */ + err = devm_add_action_or_reset(dev, tmp464_restore_lock, regmap); + if (err) + return err; + } + + err = regmap_read(regmap, TMP464_CONFIG_REG, ®val); + if (err) + return err; + data->config_orig = regval; + err = devm_add_action_or_reset(dev, tmp464_restore_config, data); + if (err) + return err; + + /* Default to 500 ms update interval */ + err = regmap_update_bits(regmap, TMP464_CONFIG_REG, + TMP464_CONFIG_CONVERSION_RATE_MASK | TMP464_CONFIG_SHUTDOWN, + BIT(TMP464_CONFIG_CONVERSION_RATE_B0) | + BIT(TMP464_CONFIG_CONVERSION_RATE_B2)); + if (err) + return err; + + data->update_interval = 500; + + return tmp464_enable_channels(data); +} + +static int tmp464_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + char *name, *chip; + int reg; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + reg = i2c_smbus_read_word_swapped(client, TMP464_MANUFACTURER_ID_REG); + if (reg < 0) + return reg; + if (reg != TMP464_MANUFACTURER_ID) + return -ENODEV; + + /* Check for "always return zero" bits */ + reg = i2c_smbus_read_word_swapped(client, TMP464_THERM_STATUS_REG); + if (reg < 0) + return reg; + if (reg & 0x1f) + return -ENODEV; + reg = i2c_smbus_read_word_swapped(client, TMP464_THERM2_STATUS_REG); + if (reg < 0) + return reg; + if (reg & 0x1f) + return -ENODEV; + + reg = i2c_smbus_read_word_swapped(client, TMP464_DEVICE_ID_REG); + if (reg < 0) + return reg; + switch (reg) { + case TMP464_DEVICE_ID: + name = "tmp464"; + chip = "TMP464"; + break; + case TMP468_DEVICE_ID: + name = "tmp468"; + chip = "TMP468"; + break; + default: + return -ENODEV; + } + + strscpy(info->type, name, I2C_NAME_SIZE); + dev_info(&adapter->dev, "Detected TI %s chip at 0x%02x\n", chip, client->addr); + + return 0; +} + +static int tmp464_probe_child_from_dt(struct device *dev, + struct device_node *child, + struct tmp464_data *data) + +{ + struct regmap *regmap = data->regmap; + u32 channel; + s32 nfactor; + int err; + + err = of_property_read_u32(child, "reg", &channel); + if (err) { + dev_err(dev, "missing reg property of %pOFn\n", child); + return err; + } + + if (channel >= data->channels) { + dev_err(dev, "invalid reg %d of %pOFn\n", channel, child); + return -EINVAL; + } + + of_property_read_string(child, "label", &data->channel[channel].label); + + data->channel[channel].enabled = of_device_is_available(child); + + err = of_property_read_s32(child, "ti,n-factor", &nfactor); + if (err && err != -EINVAL) + return err; + if (!err) { + if (channel == 0) { + dev_err(dev, "n-factor can't be set for internal channel\n"); + return -EINVAL; + } + if (nfactor > 127 || nfactor < -128) { + dev_err(dev, "n-factor for channel %d invalid (%d)\n", + channel, nfactor); + return -EINVAL; + } + err = regmap_write(regmap, TMP464_N_FACTOR_REG(channel), + (nfactor << 8) & 0xff00); + if (err) + return err; + } + + return 0; +} + +static int tmp464_probe_from_dt(struct device *dev, struct tmp464_data *data) +{ + const struct device_node *np = dev->of_node; + struct device_node *child; + int err; + + for_each_child_of_node(np, child) { + if (strcmp(child->name, "channel")) + continue; + + err = tmp464_probe_child_from_dt(dev, child, data); + if (err) { + of_node_put(child); + return err; + } + } + + return 0; +} + +static const struct hwmon_ops tmp464_ops = { + .is_visible = tmp464_is_visible, + .read = tmp464_read, + .read_string = tmp464_read_string, + .write = tmp464_write, +}; + +static const struct hwmon_channel_info *tmp464_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_CRIT | + HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE), + NULL +}; + +static const struct hwmon_chip_info tmp464_chip_info = { + .ops = &tmp464_ops, + .info = tmp464_info, +}; + +/* regmap */ + +static bool tmp464_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg < TMP464_TEMP_REG(TMP468_NUM_CHANNELS) || + reg == TMP464_THERM_STATUS_REG || + reg == TMP464_THERM2_STATUS_REG || + reg == TMP464_REMOTE_OPEN_REG); +} + +static const struct regmap_config tmp464_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = TMP464_DEVICE_ID_REG, + .volatile_reg = tmp464_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static int tmp464_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct tmp464_data *data; + int i, err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "i2c functionality check failed\n"); + return -ENODEV; + } + data = devm_kzalloc(dev, sizeof(struct tmp464_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->update_lock); + + if (dev->of_node) + data->channels = (int)(unsigned long)of_device_get_match_data(&client->dev); + else + data->channels = i2c_match_id(tmp464_id, client)->driver_data; + + data->regmap = devm_regmap_init_i2c(client, &tmp464_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + for (i = 0; i < data->channels; i++) + data->channel[i].enabled = true; + + err = tmp464_init_client(dev, data); + if (err) + return err; + + if (dev->of_node) { + err = tmp464_probe_from_dt(dev, data); + if (err) + return err; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &tmp464_chip_info, NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct i2c_driver tmp464_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "tmp464", + .of_match_table = of_match_ptr(tmp464_of_match), + }, + .probe_new = tmp464_probe, + .id_table = tmp464_id, + .detect = tmp464_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(tmp464_driver); + +MODULE_AUTHOR("Agathe Porte "); +MODULE_DESCRIPTION("Texas Instruments TMP464 temperature sensor driver"); +MODULE_LICENSE("GPL"); From 92abe50490d36ac2ee56f63db24adee219ed63c5 Mon Sep 17 00:00:00 2001 From: "Minghao Chi (CGEL ZTE)" Date: Mon, 7 Mar 2022 03:36:31 +0000 Subject: [PATCH 82/86] hwmon: (vexpress-hwmon) Use of_device_get_match_data() Use of_device_get_match_data() to simplify the code. Reported-by: Zeal Robot Signed-off-by: Minghao Chi (CGEL ZTE) Link: https://lore.kernel.org/r/20220307033631.2075160-1-chi.minghao@zte.com.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/vexpress-hwmon.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/vexpress-hwmon.c b/drivers/hwmon/vexpress-hwmon.c index 44d798be3d59..2ac5fb96bba4 100644 --- a/drivers/hwmon/vexpress-hwmon.c +++ b/drivers/hwmon/vexpress-hwmon.c @@ -207,7 +207,6 @@ MODULE_DEVICE_TABLE(of, vexpress_hwmon_of_match); static int vexpress_hwmon_probe(struct platform_device *pdev) { - const struct of_device_id *match; struct vexpress_hwmon_data *data; const struct vexpress_hwmon_type *type; @@ -216,10 +215,9 @@ static int vexpress_hwmon_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, data); - match = of_match_device(vexpress_hwmon_of_match, &pdev->dev); - if (!match) + type = of_device_get_match_data(&pdev->dev); + if (!type) return -ENODEV; - type = match->data; data->reg = devm_regmap_init_vexpress_config(&pdev->dev); if (IS_ERR(data->reg)) From 5d4a2ea96b79e7e11f1551130bb9166258b71243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 8 Mar 2022 14:54:08 +0100 Subject: [PATCH 83/86] hwmon: (axi-fan-control) Use hwmon_notify_event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of directly accessing kobj directly from the driver, use the hwmon notify API. Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20220308135408.440744-1-nuno.sa@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index d2092c17d993..96c4a5c45291 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -339,7 +339,8 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data) ctl->update_tacho_params = true; } else { ctl->hw_pwm_req = false; - sysfs_notify(&ctl->hdev->kobj, NULL, "pwm1"); + hwmon_notify_event(ctl->hdev, hwmon_pwm, + hwmon_pwm_input, 0); } } From fd6ca3f5b80f6dda381fe84211be3b491f28bf0f Mon Sep 17 00:00:00 2001 From: Minghao Chi Date: Tue, 15 Mar 2022 02:34:12 +0000 Subject: [PATCH 84/86] hwmon: (scpi-hwmon): Use of_device_get_match_data() Use of_device_get_match_data() to simplify the code. Reported-by: Zeal Robot Signed-off-by: Minghao Chi Reviewed-by: Jean Delvare Link: https://lore.kernel.org/r/20220315023412.2118415-1-chi.minghao@zte.com.cn Signed-off-by: Guenter Roeck --- drivers/hwmon/scpi-hwmon.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c index 919877970ae3..5187c6dd5a4f 100644 --- a/drivers/hwmon/scpi-hwmon.c +++ b/drivers/hwmon/scpi-hwmon.c @@ -141,7 +141,6 @@ static int scpi_hwmon_probe(struct platform_device *pdev) struct scpi_ops *scpi_ops; struct device *hwdev, *dev = &pdev->dev; struct scpi_sensors *scpi_sensors; - const struct of_device_id *of_id; int idx, ret; scpi_ops = get_scpi_ops(); @@ -171,12 +170,11 @@ static int scpi_hwmon_probe(struct platform_device *pdev) scpi_sensors->scpi_ops = scpi_ops; - of_id = of_match_device(scpi_of_match, &pdev->dev); - if (!of_id) { + scale = of_device_get_match_data(&pdev->dev); + if (!scale) { dev_err(&pdev->dev, "Unable to initialize scpi-hwmon data\n"); return -ENODEV; } - scale = of_id->data; for (i = 0, idx = 0; i < nr_sensors; i++) { struct sensor_data *sensor = &scpi_sensors->data[idx]; From a5436af598779219b375c1977555c82def1c35d0 Mon Sep 17 00:00:00 2001 From: Brandon Wyman Date: Thu, 17 Mar 2022 23:21:23 +0000 Subject: [PATCH 85/86] hwmon: (pmbus) Add Vin unit off handling If there is an input undervoltage fault, reported in STATUS_INPUT command response, there is quite likely a "Unit Off For Insufficient Input Voltage" condition as well. Add a constant for bit 3 of STATUS_INPUT. Update the Vin limit attributes to include both bits in the mask for clearing faults. If an input undervoltage fault occurs, causing a unit off for insufficient input voltage, but the unit is off bit is not cleared, the STATUS_WORD will not be updated to clear the input fault condition. Including the unit is off bit (bit 3) allows for the input fault condition to completely clear. Signed-off-by: Brandon Wyman Link: https://lore.kernel.org/r/20220317232123.2103592-1-bjwyman@gmail.com Fixes: b4ce237b7f7d3 ("hwmon: (pmbus) Introduce infrastructure to detect sensors and limit registers") [groeck: Dropped unnecessary ()] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 1 + drivers/hwmon/pmbus/pmbus_core.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 38f049d68d32..e74b6ef070f3 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -319,6 +319,7 @@ enum pmbus_fan_mode { percent = 0, rpm }; /* * STATUS_VOUT, STATUS_INPUT */ +#define PB_VOLTAGE_VIN_OFF BIT(3) #define PB_VOLTAGE_UV_FAULT BIT(4) #define PB_VOLTAGE_UV_WARNING BIT(5) #define PB_VOLTAGE_OV_WARNING BIT(6) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 8eed7968a50e..b2618b1d529e 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -1373,7 +1373,7 @@ static const struct pmbus_limit_attr vin_limit_attrs[] = { .reg = PMBUS_VIN_UV_FAULT_LIMIT, .attr = "lcrit", .alarm = "lcrit_alarm", - .sbit = PB_VOLTAGE_UV_FAULT, + .sbit = PB_VOLTAGE_UV_FAULT | PB_VOLTAGE_VIN_OFF, }, { .reg = PMBUS_VIN_OV_WARN_LIMIT, .attr = "max", From 6ba463edccb978e3c0248c3a193b759436b51ac8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 18 Mar 2022 19:34:08 +0100 Subject: [PATCH 86/86] hwmon: (dell-smm) Add Inspiron 3505 to fan type blacklist Sadly, while firmware 1.5 fixed temperature labels on my Inspiron 3505, it also caused fan type calls to take ca. 4 seconds with the fan being at full speed. Fix the resulting delays by adding the model to the blacklist. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220318183408.13286-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index c5939e68586d..84cb1ede7bc0 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1157,6 +1157,13 @@ static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "), }, }, + { + .ident = "Dell Inspiron 3505", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 3505"), + }, + }, { } };