From bce776f10069c806290eaac712ba73432ae8ecd7 Mon Sep 17 00:00:00 2001 From: Zhang Qilong Date: Wed, 2 Dec 2020 22:53:20 +0800 Subject: [PATCH 01/41] hwmon: (ina3221) Fix PM usage counter unbalance in ina3221_write_enable pm_runtime_get_sync will increment pm usage counter even it failed. Forgetting to putting operation will result in reference leak here. We fix it by replacing it with pm_runtime_resume_and_get to keep usage counter balanced. It depends on the mainline commit[PM: runtime: Add pm_runtime_resume_and_get to deal with usagecounter]. Fixes: 323aeb0eb5d9a ("hwmon: (ina3221) Add PM runtime support") Signed-off-by: Zhang Qilong Link: https://lore.kernel.org/r/20201202145320.1135614-1-zhangqilong3@huawei.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina3221.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index 41fb17e0d641..ad11cbddc3a7 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -489,7 +489,7 @@ static int ina3221_write_enable(struct device *dev, int channel, bool enable) /* For enabling routine, increase refcount and resume() at first */ if (enable) { - ret = pm_runtime_get_sync(ina->pm_dev); + ret = pm_runtime_resume_and_get(ina->pm_dev); if (ret < 0) { dev_err(dev, "Failed to get PM runtime\n"); return ret; From 96eca8c97fccd3c02f61a87b0341a079b4096730 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 7 Oct 2020 10:51:48 +0300 Subject: [PATCH 02/41] hwmon: (acpi_power_meter) clean up freeing code This code works okay but Smatch flagged it as a double free. I've changed three things to make it more clear. 1) Remove the call to free_capabilities() in acpi_power_meter_add(). This call is a no-op because the capabilities have not been allocated yet. 2) Set "*str" to NULL in free_capabilities() so that way the function can be called twice in a row without leading to a double free. 3) Call free_capabilities() in read_capabilities() instead of open coding the free. Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20201007075148.GB2529578@mwanda Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index a270b975e90b..848718ab7312 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -725,8 +725,10 @@ static void free_capabilities(struct acpi_power_meter_resource *resource) int i; str = &resource->model_number; - for (i = 0; i < 3; i++, str++) + for (i = 0; i < 3; i++, str++) { kfree(*str); + *str = NULL; + } } static int read_capabilities(struct acpi_power_meter_resource *resource) @@ -801,9 +803,7 @@ static int read_capabilities(struct acpi_power_meter_resource *resource) dev_info(&resource->acpi_dev->dev, "Found ACPI power meter.\n"); goto end; error: - str = &resource->model_number; - for (i = 0; i < 3; i++, str++) - kfree(*str); + free_capabilities(resource); end: kfree(buffer.pointer); return res; @@ -874,7 +874,6 @@ static int acpi_power_meter_add(struct acpi_device *device) strcpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); device->driver_data = resource; - free_capabilities(resource); res = read_capabilities(resource); if (res) goto exit_free; From 92bc2e1f82afa2e456d7416598ffdf3dbf267835 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 22 Oct 2020 10:06:59 +0300 Subject: [PATCH 03/41] hwmon: (pmbus/max20730) delete some dead code The debugfs_create_dir() function never returns NULL. Normal users are not supposed to check the return value so the correct fix is just to delete this check. In the case where the debugfs_create_dir() fails, the function returns NULL. The other debugfs function check for NULL directory and handle it. Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20201022070659.GA2817762@mwanda Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max20730.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/hwmon/pmbus/max20730.c b/drivers/hwmon/pmbus/max20730.c index be83b98411c7..00fea16acab4 100644 --- a/drivers/hwmon/pmbus/max20730.c +++ b/drivers/hwmon/pmbus/max20730.c @@ -328,8 +328,6 @@ static int max20730_init_debugfs(struct i2c_client *client, return -ENOENT; max20730_dir = debugfs_create_dir(client->name, debugfs); - if (!max20730_dir) - return -ENOENT; for (i = 0; i < MAX20730_DEBUGFS_NUM_ENTRIES; ++i) psu->debugfs_entries[i] = i; From ad00a02e34b481396938c5fa62ee642bff7fbb08 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 20 Oct 2020 11:34:22 +1300 Subject: [PATCH 04/41] hwmon: (adt7470) Create functions for updating readings and limits Split the body of adt7470_update_device() into two helper functions adt7470_update_sensors() and adt7470_update_limits(). Although neither of the new helpers returns an error yet lay the groundwork for propagating failures through to the sysfs readers. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20201019223423.31488-2-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 154 ++++++++++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 36 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 740f39a54ab0..2e8feacccf84 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -270,37 +270,11 @@ static int adt7470_update_thread(void *p) return 0; } -static struct adt7470_data *adt7470_update_device(struct device *dev) +static int adt7470_update_sensors(struct adt7470_data *data) { - struct adt7470_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; - unsigned long local_jiffies = jiffies; u8 cfg; int i; - int need_sensors = 1; - int need_limits = 1; - - /* - * Figure out if we need to update the shadow registers. - * Lockless means that we may occasionally report out of - * date data. - */ - if (time_before(local_jiffies, data->sensors_last_updated + - SENSOR_REFRESH_INTERVAL) && - data->sensors_valid) - need_sensors = 0; - - if (time_before(local_jiffies, data->limits_last_updated + - LIMIT_REFRESH_INTERVAL) && - data->limits_valid) - need_limits = 0; - - if (!need_sensors && !need_limits) - return data; - - mutex_lock(&data->lock); - if (!need_sensors) - goto no_sensor_update; if (!data->temperatures_probed) adt7470_read_temperatures(client, data); @@ -352,12 +326,13 @@ static struct adt7470_data *adt7470_update_device(struct device *dev) data->alarms_mask = adt7470_read_word_data(client, ADT7470_REG_ALARM1_MASK); - data->sensors_last_updated = local_jiffies; - data->sensors_valid = 1; + return 0; +} -no_sensor_update: - if (!need_limits) - goto out; +static int adt7470_update_limits(struct adt7470_data *data) +{ + struct i2c_client *client = data->client; + int i; for (i = 0; i < ADT7470_TEMP_COUNT; i++) { data->temp_min[i] = i2c_smbus_read_byte_data(client, @@ -382,12 +357,55 @@ no_sensor_update: ADT7470_REG_PWM_TMIN(i)); } - data->limits_last_updated = local_jiffies; - data->limits_valid = 1; + return 0; +} +static struct adt7470_data *adt7470_update_device(struct device *dev) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + unsigned long local_jiffies = jiffies; + int need_sensors = 1; + int need_limits = 1; + int err; + + /* + * Figure out if we need to update the shadow registers. + * Lockless means that we may occasionally report out of + * date data. + */ + if (time_before(local_jiffies, data->sensors_last_updated + + SENSOR_REFRESH_INTERVAL) && + data->sensors_valid) + need_sensors = 0; + + if (time_before(local_jiffies, data->limits_last_updated + + LIMIT_REFRESH_INTERVAL) && + data->limits_valid) + need_limits = 0; + + if (!need_sensors && !need_limits) + return data; + + mutex_lock(&data->lock); + if (need_sensors) { + err = adt7470_update_sensors(data); + if (err < 0) + goto out; + data->sensors_last_updated = local_jiffies; + data->sensors_valid = 1; + } + + if (need_limits) { + err = adt7470_update_limits(data); + if (err < 0) + goto out; + data->limits_last_updated = local_jiffies; + data->limits_valid = 1; + } out: mutex_unlock(&data->lock); - return data; + + return err < 0 ? ERR_PTR(err) : data; } static ssize_t auto_update_interval_show(struct device *dev, @@ -395,6 +413,10 @@ static ssize_t auto_update_interval_show(struct device *dev, char *buf) { struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->auto_update_interval); } @@ -422,6 +444,10 @@ static ssize_t num_temp_sensors_show(struct device *dev, char *buf) { struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->num_temp_sensors); } @@ -451,6 +477,10 @@ static ssize_t temp_min_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", 1000 * data->temp_min[attr->index]); } @@ -483,6 +513,10 @@ static ssize_t temp_max_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", 1000 * data->temp_max[attr->index]); } @@ -515,6 +549,10 @@ static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", 1000 * data->temp[attr->index]); } @@ -524,6 +562,9 @@ static ssize_t alarm_mask_show(struct device *dev, { struct adt7470_data *data = adt7470_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%x\n", data->alarms_mask); } @@ -554,6 +595,9 @@ static ssize_t fan_max_show(struct device *dev, struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + if (FAN_DATA_VALID(data->fan_max[attr->index])) return sprintf(buf, "%d\n", FAN_PERIOD_TO_RPM(data->fan_max[attr->index])); @@ -590,6 +634,9 @@ static ssize_t fan_min_show(struct device *dev, struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + if (FAN_DATA_VALID(data->fan_min[attr->index])) return sprintf(buf, "%d\n", FAN_PERIOD_TO_RPM(data->fan_min[attr->index])); @@ -626,6 +673,9 @@ static ssize_t fan_show(struct device *dev, struct device_attribute *devattr, struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + if (FAN_DATA_VALID(data->fan[attr->index])) return sprintf(buf, "%d\n", FAN_PERIOD_TO_RPM(data->fan[attr->index])); @@ -637,6 +687,10 @@ static ssize_t force_pwm_max_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->force_pwm_max); } @@ -670,6 +724,10 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *devattr, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->pwm[attr->index]); } @@ -763,6 +821,10 @@ static ssize_t pwm_max_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->pwm_max[attr->index]); } @@ -794,6 +856,10 @@ static ssize_t pwm_min_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->pwm_min[attr->index]); } @@ -825,6 +891,10 @@ static ssize_t pwm_tmax_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + /* the datasheet says that tmax = tmin + 20C */ return sprintf(buf, "%d\n", 1000 * (20 + data->pwm_tmin[attr->index])); } @@ -834,6 +904,10 @@ static ssize_t pwm_tmin_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", 1000 * data->pwm_tmin[attr->index]); } @@ -866,6 +940,10 @@ static ssize_t pwm_auto_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", 1 + data->pwm_automatic[attr->index]); } @@ -911,8 +989,12 @@ static ssize_t pwm_auto_temp_show(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = adt7470_update_device(dev); - u8 ctrl = data->pwm_auto_temp[attr->index]; + u8 ctrl; + if (IS_ERR(data)) + return PTR_ERR(data); + + ctrl = data->pwm_auto_temp[attr->index]; if (ctrl) return sprintf(buf, "%d\n", 1 << (ctrl - 1)); else From 3bce071a301f44ffd08bada9b2e8238b625f8f8c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 26 Oct 2020 11:53:52 +0100 Subject: [PATCH 05/41] hwmon: (pmbus) shrink code and remove pmbus_do_remove() The only action currently performed in pmbus_do_remove() is removing the debugfs hierarchy. We can schedule a devm action at probe time and remove pmbus_do_remove() entirely from all pmbus drivers. Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20201026105352.20359-1-brgl@bgdev.pl [groeck: Removed references to pmbus_do_remove from documentation] Signed-off-by: Guenter Roeck --- Documentation/hwmon/pmbus-core.rst | 6 ------ Documentation/hwmon/pmbus.rst | 6 ------ drivers/hwmon/pmbus/adm1266.c | 1 - drivers/hwmon/pmbus/adm1275.c | 1 - drivers/hwmon/pmbus/bel-pfe.c | 1 - drivers/hwmon/pmbus/ibm-cffps.c | 1 - drivers/hwmon/pmbus/inspur-ipsps.c | 1 - drivers/hwmon/pmbus/ir35221.c | 1 - drivers/hwmon/pmbus/ir38064.c | 1 - drivers/hwmon/pmbus/irps5401.c | 1 - drivers/hwmon/pmbus/isl68137.c | 1 - drivers/hwmon/pmbus/lm25066.c | 1 - drivers/hwmon/pmbus/ltc2978.c | 1 - drivers/hwmon/pmbus/ltc3815.c | 1 - drivers/hwmon/pmbus/max16064.c | 1 - drivers/hwmon/pmbus/max16601.c | 1 - drivers/hwmon/pmbus/max20730.c | 1 - drivers/hwmon/pmbus/max20751.c | 1 - drivers/hwmon/pmbus/max31785.c | 1 - drivers/hwmon/pmbus/max34440.c | 1 - drivers/hwmon/pmbus/max8688.c | 1 - drivers/hwmon/pmbus/mp2975.c | 1 - drivers/hwmon/pmbus/pmbus.c | 1 - drivers/hwmon/pmbus/pmbus.h | 1 - drivers/hwmon/pmbus/pmbus_core.c | 20 +++++++++----------- drivers/hwmon/pmbus/pxe1610.c | 1 - drivers/hwmon/pmbus/tps40422.c | 1 - drivers/hwmon/pmbus/tps53679.c | 1 - drivers/hwmon/pmbus/ucd9000.c | 1 - drivers/hwmon/pmbus/ucd9200.c | 1 - drivers/hwmon/pmbus/xdpe12284.c | 1 - drivers/hwmon/pmbus/zl6100.c | 1 - 32 files changed, 9 insertions(+), 52 deletions(-) diff --git a/Documentation/hwmon/pmbus-core.rst b/Documentation/hwmon/pmbus-core.rst index e22c4f6808bc..73e23ab42cc3 100644 --- a/Documentation/hwmon/pmbus-core.rst +++ b/Documentation/hwmon/pmbus-core.rst @@ -277,12 +277,6 @@ with the pointer to struct pmbus_driver_info as additional argument. Calls identify function if supported. Must only be called from device probe function. -:: - - void pmbus_do_remove(struct i2c_client *client); - -Execute driver remove function. Similar to standard driver remove function. - :: const struct pmbus_driver_info diff --git a/Documentation/hwmon/pmbus.rst b/Documentation/hwmon/pmbus.rst index fb3ad67dedc1..c44f14115413 100644 --- a/Documentation/hwmon/pmbus.rst +++ b/Documentation/hwmon/pmbus.rst @@ -148,11 +148,6 @@ Emerson DS1200 power modules might look as follows:: return pmbus_do_probe(client, &ds1200_info); } - static int ds1200_remove(struct i2c_client *client) - { - return pmbus_do_remove(client); - } - static const struct i2c_device_id ds1200_id[] = { {"ds1200", 0}, {} @@ -166,7 +161,6 @@ Emerson DS1200 power modules might look as follows:: .name = "ds1200", }, .probe_new = ds1200_probe, - .remove = ds1200_remove, .id_table = ds1200_id, }; diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index c7b373ba92f2..4d2e4ddcfbfd 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -502,7 +502,6 @@ static struct i2c_driver adm1266_driver = { .of_match_table = adm1266_of_match, }, .probe_new = adm1266_probe, - .remove = pmbus_do_remove, .id_table = adm1266_id, }; diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index e7997f37b266..38a6515b0763 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -797,7 +797,6 @@ static struct i2c_driver adm1275_driver = { .name = "adm1275", }, .probe_new = adm1275_probe, - .remove = pmbus_do_remove, .id_table = adm1275_id, }; diff --git a/drivers/hwmon/pmbus/bel-pfe.c b/drivers/hwmon/pmbus/bel-pfe.c index 2c5b853d6c7f..aed7542d7ce5 100644 --- a/drivers/hwmon/pmbus/bel-pfe.c +++ b/drivers/hwmon/pmbus/bel-pfe.c @@ -121,7 +121,6 @@ static struct i2c_driver pfe_pmbus_driver = { .name = "bel-pfe", }, .probe_new = pfe_pmbus_probe, - .remove = pmbus_do_remove, .id_table = pfe_device_id, }; diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 2fb7540ee952..d6bbbb223871 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -617,7 +617,6 @@ static struct i2c_driver ibm_cffps_driver = { .of_match_table = ibm_cffps_of_match, }, .probe_new = ibm_cffps_probe, - .remove = pmbus_do_remove, .id_table = ibm_cffps_id, }; diff --git a/drivers/hwmon/pmbus/inspur-ipsps.c b/drivers/hwmon/pmbus/inspur-ipsps.c index be493182174d..88c5865c4d6f 100644 --- a/drivers/hwmon/pmbus/inspur-ipsps.c +++ b/drivers/hwmon/pmbus/inspur-ipsps.c @@ -216,7 +216,6 @@ static struct i2c_driver ipsps_driver = { .of_match_table = of_match_ptr(ipsps_of_match), }, .probe_new = ipsps_probe, - .remove = pmbus_do_remove, .id_table = ipsps_id, }; diff --git a/drivers/hwmon/pmbus/ir35221.c b/drivers/hwmon/pmbus/ir35221.c index 5fadb1def49f..3aebeb1443fd 100644 --- a/drivers/hwmon/pmbus/ir35221.c +++ b/drivers/hwmon/pmbus/ir35221.c @@ -137,7 +137,6 @@ static struct i2c_driver ir35221_driver = { .name = "ir35221", }, .probe_new = ir35221_probe, - .remove = pmbus_do_remove, .id_table = ir35221_id, }; diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 9ac563ce7dd8..46f17c4b4873 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -53,7 +53,6 @@ static struct i2c_driver ir38064_driver = { .name = "ir38064", }, .probe_new = ir38064_probe, - .remove = pmbus_do_remove, .id_table = ir38064_id, }; diff --git a/drivers/hwmon/pmbus/irps5401.c b/drivers/hwmon/pmbus/irps5401.c index 44aeafcbd56c..93ef6d64a33a 100644 --- a/drivers/hwmon/pmbus/irps5401.c +++ b/drivers/hwmon/pmbus/irps5401.c @@ -55,7 +55,6 @@ static struct i2c_driver irps5401_driver = { .name = "irps5401", }, .probe_new = irps5401_probe, - .remove = pmbus_do_remove, .id_table = irps5401_id, }; diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index 7cad76e07f70..2bee930d3900 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -324,7 +324,6 @@ static struct i2c_driver isl68137_driver = { .name = "isl68137", }, .probe_new = isl68137_probe, - .remove = pmbus_do_remove, .id_table = raa_dmpvr_id, }; diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 429172a42902..c75a6bf39641 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -508,7 +508,6 @@ static struct i2c_driver lm25066_driver = { .name = "lm25066", }, .probe_new = lm25066_probe, - .remove = pmbus_do_remove, .id_table = lm25066_id, }; diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 9a024cf70145..7e53fa95b92d 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -875,7 +875,6 @@ static struct i2c_driver ltc2978_driver = { .of_match_table = of_match_ptr(ltc2978_of_match), }, .probe_new = ltc2978_probe, - .remove = pmbus_do_remove, .id_table = ltc2978_id, }; diff --git a/drivers/hwmon/pmbus/ltc3815.c b/drivers/hwmon/pmbus/ltc3815.c index 8328fb367ad6..e45e14d26c9a 100644 --- a/drivers/hwmon/pmbus/ltc3815.c +++ b/drivers/hwmon/pmbus/ltc3815.c @@ -200,7 +200,6 @@ static struct i2c_driver ltc3815_driver = { .name = "ltc3815", }, .probe_new = ltc3815_probe, - .remove = pmbus_do_remove, .id_table = ltc3815_id, }; diff --git a/drivers/hwmon/pmbus/max16064.c b/drivers/hwmon/pmbus/max16064.c index 26e7f5ef9d7f..d79add99083e 100644 --- a/drivers/hwmon/pmbus/max16064.c +++ b/drivers/hwmon/pmbus/max16064.c @@ -103,7 +103,6 @@ static struct i2c_driver max16064_driver = { .name = "max16064", }, .probe_new = max16064_probe, - .remove = pmbus_do_remove, .id_table = max16064_id, }; diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index 71bb74e27a5c..a960b86e72d2 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -302,7 +302,6 @@ static struct i2c_driver max16601_driver = { .name = "max16601", }, .probe_new = max16601_probe, - .remove = pmbus_do_remove, .id_table = max16601_id, }; diff --git a/drivers/hwmon/pmbus/max20730.c b/drivers/hwmon/pmbus/max20730.c index 00fea16acab4..9dd3dd79bc18 100644 --- a/drivers/hwmon/pmbus/max20730.c +++ b/drivers/hwmon/pmbus/max20730.c @@ -777,7 +777,6 @@ static struct i2c_driver max20730_driver = { .of_match_table = max20730_of_match, }, .probe_new = max20730_probe, - .remove = pmbus_do_remove, .id_table = max20730_id, }; diff --git a/drivers/hwmon/pmbus/max20751.c b/drivers/hwmon/pmbus/max20751.c index 921e92d82aec..9d42f82fdd99 100644 --- a/drivers/hwmon/pmbus/max20751.c +++ b/drivers/hwmon/pmbus/max20751.c @@ -43,7 +43,6 @@ static struct i2c_driver max20751_driver = { .name = "max20751", }, .probe_new = max20751_probe, - .remove = pmbus_do_remove, .id_table = max20751_id, }; diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 839b957bc03e..e5a9f4019cd5 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -390,7 +390,6 @@ static struct i2c_driver max31785_driver = { .of_match_table = max31785_of_match, }, .probe_new = max31785_probe, - .remove = pmbus_do_remove, .id_table = max31785_id, }; diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index f4cb196aaaf3..dad66b3c0116 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -521,7 +521,6 @@ static struct i2c_driver max34440_driver = { .name = "max34440", }, .probe_new = max34440_probe, - .remove = pmbus_do_remove, .id_table = max34440_id, }; diff --git a/drivers/hwmon/pmbus/max8688.c b/drivers/hwmon/pmbus/max8688.c index 4b2239a6afd3..329dc851fc59 100644 --- a/drivers/hwmon/pmbus/max8688.c +++ b/drivers/hwmon/pmbus/max8688.c @@ -183,7 +183,6 @@ static struct i2c_driver max8688_driver = { .name = "max8688", }, .probe_new = max8688_probe, - .remove = pmbus_do_remove, .id_table = max8688_id, }; diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c index 1c3e2a9453b1..60fbdb371332 100644 --- a/drivers/hwmon/pmbus/mp2975.c +++ b/drivers/hwmon/pmbus/mp2975.c @@ -758,7 +758,6 @@ static struct i2c_driver mp2975_driver = { .of_match_table = of_match_ptr(mp2975_of_match), }, .probe_new = mp2975_probe, - .remove = pmbus_do_remove, .id_table = mp2975_id, }; diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c index 20f1af9165c2..a1b4260e75b2 100644 --- a/drivers/hwmon/pmbus/pmbus.c +++ b/drivers/hwmon/pmbus/pmbus.c @@ -238,7 +238,6 @@ static struct i2c_driver pmbus_driver = { .name = "pmbus", }, .probe_new = pmbus_probe, - .remove = pmbus_do_remove, .id_table = pmbus_id, }; diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 88a5df2633fb..4c30ec89f5bf 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -490,7 +490,6 @@ void pmbus_clear_faults(struct i2c_client *client); bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); -int pmbus_do_remove(struct i2c_client *client); const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client); int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index b0e2820a2d57..192442b3b7a2 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -2395,6 +2395,13 @@ static int pmbus_debugfs_set_pec(void *data, u64 val) DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_pec, pmbus_debugfs_get_pec, pmbus_debugfs_set_pec, "%llu\n"); +static void pmbus_remove_debugfs(void *data) +{ + struct dentry *entry = data; + + debugfs_remove_recursive(entry); +} + static int pmbus_init_debugfs(struct i2c_client *client, struct pmbus_data *data) { @@ -2530,7 +2537,8 @@ static int pmbus_init_debugfs(struct i2c_client *client, } } - return 0; + return devm_add_action_or_reset(data->dev, + pmbus_remove_debugfs, data->debugfs); } #else static int pmbus_init_debugfs(struct i2c_client *client, @@ -2617,16 +2625,6 @@ int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info) } EXPORT_SYMBOL_GPL(pmbus_do_probe); -int pmbus_do_remove(struct i2c_client *client) -{ - struct pmbus_data *data = i2c_get_clientdata(client); - - debugfs_remove_recursive(data->debugfs); - - return 0; -} -EXPORT_SYMBOL_GPL(pmbus_do_remove); - struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); diff --git a/drivers/hwmon/pmbus/pxe1610.c b/drivers/hwmon/pmbus/pxe1610.c index fa5c5dd29b7a..da27ce34ee3f 100644 --- a/drivers/hwmon/pmbus/pxe1610.c +++ b/drivers/hwmon/pmbus/pxe1610.c @@ -131,7 +131,6 @@ static struct i2c_driver pxe1610_driver = { .name = "pxe1610", }, .probe_new = pxe1610_probe, - .remove = pmbus_do_remove, .id_table = pxe1610_id, }; diff --git a/drivers/hwmon/pmbus/tps40422.c b/drivers/hwmon/pmbus/tps40422.c index edbdfa809d51..f7f00ab6f46c 100644 --- a/drivers/hwmon/pmbus/tps40422.c +++ b/drivers/hwmon/pmbus/tps40422.c @@ -43,7 +43,6 @@ static struct i2c_driver tps40422_driver = { .name = "tps40422", }, .probe_new = tps40422_probe, - .remove = pmbus_do_remove, .id_table = tps40422_id, }; diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index db2bdf2a1f02..ba838fa311c3 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -251,7 +251,6 @@ static struct i2c_driver tps53679_driver = { .of_match_table = of_match_ptr(tps53679_of_match), }, .probe_new = tps53679_probe, - .remove = pmbus_do_remove, .id_table = tps53679_id, }; diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c index f8017993e2b4..a15e6fe3e425 100644 --- a/drivers/hwmon/pmbus/ucd9000.c +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -621,7 +621,6 @@ static struct i2c_driver ucd9000_driver = { .of_match_table = of_match_ptr(ucd9000_of_match), }, .probe_new = ucd9000_probe, - .remove = pmbus_do_remove, .id_table = ucd9000_id, }; diff --git a/drivers/hwmon/pmbus/ucd9200.c b/drivers/hwmon/pmbus/ucd9200.c index e111e25e1619..47cc7ca9d329 100644 --- a/drivers/hwmon/pmbus/ucd9200.c +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -201,7 +201,6 @@ static struct i2c_driver ucd9200_driver = { .of_match_table = of_match_ptr(ucd9200_of_match), }, .probe_new = ucd9200_probe, - .remove = pmbus_do_remove, .id_table = ucd9200_id, }; diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c index c95ac934fde4..f8bc0f41cd5f 100644 --- a/drivers/hwmon/pmbus/xdpe12284.c +++ b/drivers/hwmon/pmbus/xdpe12284.c @@ -160,7 +160,6 @@ static struct i2c_driver xdpe122_driver = { .of_match_table = of_match_ptr(xdpe122_of_match), }, .probe_new = xdpe122_probe, - .remove = pmbus_do_remove, .id_table = xdpe122_id, }; diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c index e8bda340482b..69120ca7aaa8 100644 --- a/drivers/hwmon/pmbus/zl6100.c +++ b/drivers/hwmon/pmbus/zl6100.c @@ -396,7 +396,6 @@ static struct i2c_driver zl6100_driver = { .name = "zl6100", }, .probe_new = zl6100_probe, - .remove = pmbus_do_remove, .id_table = zl6100_id, }; From d115b51e0e567199c821fc39e13b6af7e78f247d Mon Sep 17 00:00:00 2001 From: Wilken Gottwalt Date: Tue, 27 Oct 2020 14:17:10 +0100 Subject: [PATCH 06/41] hwmon: add Corsair PSU HID controller driver The Corsair digital power supplies of the series RMi, HXi and AXi include a small micro-controller with a lot of sensors attached. The sensors can be accessed by an USB connector from the outside. This micro-controller provides the data by a simple proprietary USB HID protocol. The data consist of temperatures, current and voltage levels, power usage, uptimes, fan speed and some more. It is also possible to configure the PSU (fan mode, mono/multi-rail, over current protection). This driver provides access to the sensors/statistics of the RMi and HXi series power supplies. It does not support configuring these devices, because there would be many ways to misconfigure or even damage the PSU. This patch adds: - hwmon driver corsair-psu - hwmon documentation - updates MAINTAINERS Signed-off-by: Wilken Gottwalt Link: https://lore.kernel.org/r/20201027131710.GA253280@monster.powergraphx.local Signed-off-by: Guenter Roeck --- Documentation/hwmon/corsair-psu.rst | 82 ++++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 7 + drivers/hwmon/Kconfig | 13 + drivers/hwmon/Makefile | 1 + drivers/hwmon/corsair-psu.c | 605 ++++++++++++++++++++++++++++ 6 files changed, 709 insertions(+) create mode 100644 Documentation/hwmon/corsair-psu.rst create mode 100644 drivers/hwmon/corsair-psu.c diff --git a/Documentation/hwmon/corsair-psu.rst b/Documentation/hwmon/corsair-psu.rst new file mode 100644 index 000000000000..396b95c9a76a --- /dev/null +++ b/Documentation/hwmon/corsair-psu.rst @@ -0,0 +1,82 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver corsair-psu +========================= + +Supported devices: + +* Corsair Power Supplies + + Corsair HX550i + + Corsair HX650i + + Corsair HX750i + + Corsair HX850i + + Corsair HX1000i + + Corsair HX1200i + + Corsair RM550i + + Corsair RM650i + + Corsair RM750i + + Corsair RM850i + + Corsair RM1000i + +Author: Wilken Gottwalt + +Description +----------- + +This driver implements the sysfs interface for the Corsair PSUs with a HID protocol +interface of the HXi and RMi series. +These power supplies provide access to a micro-controller with 2 attached +temperature sensors, 1 fan rpm sensor, 4 sensors for volt levels, 4 sensors for +power usage and 4 sensors for current levels and addtional non-sensor information +like uptimes. + +Sysfs entries +------------- + +======================= ======================================================== +curr1_input Total current usage +curr2_input Current on the 12v psu rail +curr3_input Current on the 5v psu rail +curr4_input Current on the 3.3v psu rail +fan1_input RPM of psu fan +in0_input Voltage of the psu ac input +in1_input Voltage of the 12v psu rail +in2_input Voltage of the 5v psu rail +in3_input Voltage of the 3.3 psu rail +power1_input Total power usage +power2_input Power usage of the 12v psu rail +power3_input Power usage of the 5v psu rail +power4_input Power usage of the 3.3v psu rail +temp1_input Temperature of the psu vrm component +temp2_input Temperature of the psu case +======================= ======================================================== + +Usage Notes +----------- + +It is an USB HID device, so it is auto-detected and supports hot-swapping. + +Flickering values in the rail voltage levels can be an indicator for a failing +PSU. The driver also provides some additional useful values via debugfs, which +do not fit into the hwmon class. + +Debugfs entries +--------------- + +======================= ======================================================== +uptime Current uptime of the psu +uptime_total Total uptime of the psu +vendor Vendor name of the psu +product Product name of the psu +======================= ======================================================== diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b797db738225..00844f0a8c55 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -49,6 +49,7 @@ Hardware Monitoring Kernel Drivers bt1-pvt coretemp corsair-cpro + corsair-psu da9052 da9055 dell-smm-hwmon diff --git a/MAINTAINERS b/MAINTAINERS index 2daa6ee673f7..9ccea4c36b95 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4485,6 +4485,13 @@ L: linux-hwmon@vger.kernel.org S: Maintained F: drivers/hwmon/corsair-cpro.c +CORSAIR-PSU HARDWARE MONITOR DRIVER +M: Wilken Gottwalt +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/corsair-psu.rst +F: drivers/hwmon/corsair-psu.c + COSA/SRP SYNC SERIAL DRIVER M: Jan "Yenya" Kasprzak S: Maintained diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a850e4f0e0bd..9d600e0c5584 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -449,6 +449,19 @@ config SENSORS_CORSAIR_CPRO This driver can also be built as a module. If so, the module will be called corsair-cpro. +config SENSORS_CORSAIR_PSU + tristate "Corsair PSU HID controller" + depends on HID + help + If you say yes here you get support for Corsair PSUs with a HID + interface. + Currently this driver supports the (RM/HX)550i, (RM/HX)650i, + (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies + by Corsair. + + This driver can also be built as a module. If so, the module + will be called corsair-psu. + config SENSORS_DRIVETEMP tristate "Hard disk drives with temperature sensors" depends on SCSI && ATA diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9db2903b61e5..1083bbfac779 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o +obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c new file mode 100644 index 000000000000..e92d0376e7ac --- /dev/null +++ b/drivers/hwmon/corsair-psu.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * corsair-psu.c - Linux driver for Corsair power supplies with HID sensors interface + * Copyright (C) 2020 Wilken Gottwalt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Corsair protocol for PSUs + * + * message size = 64 bytes (request and response, little endian) + * request: + * [length][command][param0][param1][paramX]... + * reply: + * [echo of length][echo of command][data0][data1][dataX]... + * + * - commands are byte sized opcodes + * - length is the sum of all bytes of the commands/params + * - the micro-controller of most of these PSUs support concatenation in the request and reply, + * but it is better to not rely on this (it is also hard to parse) + * - the driver uses raw events to be accessible from userspace (though this is not really + * supported, it is just there for convenience, may be removed in the future) + * - a reply always start with the length and command in the same order the request used it + * - length of the reply data is specific to the command used + * - some of the commands work on a rail and can be switched to a specific rail (0 = 12v, + * 1 = 5v, 2 = 3.3v) + * - the format of the init command 0xFE is swapped length/command bytes + * - parameter bytes amount and values are specific to the command (rail setting is the only + * for now that uses non-zero values) + * - there are much more commands, especially for configuring the device, but they are not + * supported because a wrong command/length can lockup the micro-controller + * - the driver supports debugfs for values not fitting into the hwmon class + * - not every device class (HXi, RMi or AXi) supports all commands + * - it is a pure sensors reading driver (will not support configuring) + */ + +#define DRIVER_NAME "corsair-psu" + +#define REPLY_SIZE 16 /* max length of a reply to a single command */ +#define CMD_BUFFER_SIZE 64 +#define CMD_TIMEOUT_MS 250 +#define SECONDS_PER_HOUR (60 * 60) +#define SECONDS_PER_DAY (SECONDS_PER_HOUR * 24) + +#define PSU_CMD_SELECT_RAIL 0x00 /* expects length 2 */ +#define PSU_CMD_IN_VOLTS 0x88 /* the rest of the commands expect length 3 */ +#define PSU_CMD_IN_AMPS 0x89 +#define PSU_CMD_RAIL_OUT_VOLTS 0x8B +#define PSU_CMD_RAIL_AMPS 0x8C +#define PSU_CMD_TEMP0 0x8D +#define PSU_CMD_TEMP1 0x8E +#define PSU_CMD_FAN 0x90 +#define PSU_CMD_RAIL_WATTS 0x96 +#define PSU_CMD_VEND_STR 0x99 +#define PSU_CMD_PROD_STR 0x9A +#define PSU_CMD_TOTAL_WATTS 0xEE +#define PSU_CMD_TOTAL_UPTIME 0xD1 +#define PSU_CMD_UPTIME 0xD2 +#define PSU_CMD_INIT 0xFE + +#define L_IN_VOLTS "v_in" +#define L_OUT_VOLTS_12V "v_out +12v" +#define L_OUT_VOLTS_5V "v_out +5v" +#define L_OUT_VOLTS_3_3V "v_out +3.3v" +#define L_IN_AMPS "curr in" +#define L_AMPS_12V "curr +12v" +#define L_AMPS_5V "curr +5v" +#define L_AMPS_3_3V "curr +3.3v" +#define L_FAN "psu fan" +#define L_TEMP0 "vrm temp" +#define L_TEMP1 "case temp" +#define L_WATTS "power total" +#define L_WATTS_12V "power +12v" +#define L_WATTS_5V "power +5v" +#define L_WATTS_3_3V "power +3.3v" + +static const char *const label_watts[] = { + L_WATTS, + L_WATTS_12V, + L_WATTS_5V, + L_WATTS_3_3V +}; + +static const char *const label_volts[] = { + L_IN_VOLTS, + L_OUT_VOLTS_12V, + L_OUT_VOLTS_5V, + L_OUT_VOLTS_3_3V +}; + +static const char *const label_amps[] = { + L_IN_AMPS, + L_AMPS_12V, + L_AMPS_5V, + L_AMPS_3_3V +}; + +struct corsairpsu_data { + struct hid_device *hdev; + struct device *hwmon_dev; + struct dentry *debugfs; + struct completion wait_completion; + struct mutex lock; /* for locking access to cmd_buffer */ + u8 *cmd_buffer; + char vendor[REPLY_SIZE]; + char product[REPLY_SIZE]; +}; + +/* some values are SMBus LINEAR11 data which need a conversion */ +static int corsairpsu_linear11_to_int(const int val) +{ + int exp = (val & 0xFFFF) >> 0x0B; + int mant = val & 0x7FF; + int i; + + if (exp > 0x0F) + exp -= 0x20; + if (mant > 0x3FF) + mant -= 0x800; + if ((mant & 0x01) == 1) + ++mant; + if (exp < 0) { + for (i = 0; i < -exp; ++i) + mant /= 2; + } else { + for (i = 0; i < exp; ++i) + mant *= 2; + } + + return mant; +} + +static int corsairpsu_usb_cmd(struct corsairpsu_data *priv, u8 p0, u8 p1, u8 p2, void *data) +{ + unsigned long time; + int ret; + + memset(priv->cmd_buffer, 0, CMD_BUFFER_SIZE); + priv->cmd_buffer[0] = p0; + priv->cmd_buffer[1] = p1; + priv->cmd_buffer[2] = p2; + + reinit_completion(&priv->wait_completion); + + ret = hid_hw_output_report(priv->hdev, priv->cmd_buffer, CMD_BUFFER_SIZE); + if (ret < 0) + return ret; + + time = wait_for_completion_timeout(&priv->wait_completion, + msecs_to_jiffies(CMD_TIMEOUT_MS)); + if (!time) + return -ETIMEDOUT; + + /* + * at the start of the reply is an echo of the send command/length in the same order it + * was send, not every command is supported on every device class, if a command is not + * supported, the length value in the reply is okay, but the command value is set to 0 + */ + if (p0 != priv->cmd_buffer[0] || p1 != priv->cmd_buffer[1]) + return -EOPNOTSUPP; + + if (data) + memcpy(data, priv->cmd_buffer + 2, REPLY_SIZE); + + return 0; +} + +static int corsairpsu_init(struct corsairpsu_data *priv) +{ + /* + * PSU_CMD_INIT uses swapped length/command and expects 2 parameter bytes, this command + * actually generates a reply, but we don't need it + */ + return corsairpsu_usb_cmd(priv, PSU_CMD_INIT, 3, 0, NULL); +} + +static int corsairpsu_fwinfo(struct corsairpsu_data *priv) +{ + int ret; + + ret = corsairpsu_usb_cmd(priv, 3, PSU_CMD_VEND_STR, 0, priv->vendor); + if (ret < 0) + return ret; + + ret = corsairpsu_usb_cmd(priv, 3, PSU_CMD_PROD_STR, 0, priv->product); + if (ret < 0) + return ret; + + return 0; +} + +static int corsairpsu_request(struct corsairpsu_data *priv, u8 cmd, u8 rail, void *data) +{ + int ret; + + mutex_lock(&priv->lock); + switch (cmd) { + case PSU_CMD_RAIL_OUT_VOLTS: + case PSU_CMD_RAIL_AMPS: + case PSU_CMD_RAIL_WATTS: + ret = corsairpsu_usb_cmd(priv, 2, PSU_CMD_SELECT_RAIL, rail, NULL); + if (ret < 0) + goto cmd_fail; + break; + default: + break; + } + + ret = corsairpsu_usb_cmd(priv, 3, cmd, 0, data); + +cmd_fail: + mutex_unlock(&priv->lock); + return ret; +} + +static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, long *val) +{ + u8 data[REPLY_SIZE]; + long tmp; + int ret; + + ret = corsairpsu_request(priv, cmd, rail, data); + if (ret < 0) + return ret; + + /* + * the biggest value here comes from the uptime command and to exceed MAXINT total uptime + * needs to be about 68 years, the rest are u16 values and the biggest value coming out of + * the LINEAR11 conversion are the watts values which are about 1200 for the strongest psu + * supported (HX1200i) + */ + tmp = (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0]; + switch (cmd) { + case PSU_CMD_IN_VOLTS: + case PSU_CMD_IN_AMPS: + case PSU_CMD_RAIL_OUT_VOLTS: + case PSU_CMD_RAIL_AMPS: + case PSU_CMD_TEMP0: + case PSU_CMD_TEMP1: + *val = corsairpsu_linear11_to_int(tmp & 0xFFFF) * 1000; + break; + case PSU_CMD_FAN: + /* + * this value is best guess, so the calculated value could be wrong, it is hard + * to ge the fan to spin in these semi-passive power supplies, which need a + * quite high load to do so + */ + *val = ((tmp & 0xFF) << 8) + ((tmp >> 8) & 0xFF); + break; + case PSU_CMD_RAIL_WATTS: + case PSU_CMD_TOTAL_WATTS: + *val = corsairpsu_linear11_to_int(tmp & 0xFFFF) * 1000000; + break; + case PSU_CMD_TOTAL_UPTIME: + case PSU_CMD_UPTIME: + *val = tmp; + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} + +static umode_t corsairpsu_hwmon_ops_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_temp && (attr == hwmon_temp_input || attr == hwmon_temp_label)) + return 0444; + else if (type == hwmon_fan && (attr == hwmon_fan_input || attr == hwmon_fan_label)) + return 0444; + else if (type == hwmon_power && (attr == hwmon_power_input || attr == hwmon_power_label)) + return 0444; + else if (type == hwmon_in && (attr == hwmon_in_input || attr == hwmon_in_label)) + return 0444; + else if (type == hwmon_curr && (attr == hwmon_curr_input || attr == hwmon_curr_label)) + return 0444; + + return 0; +} + +static int corsairpsu_hwmon_ops_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct corsairpsu_data *priv = dev_get_drvdata(dev); + int ret; + + if (type == hwmon_temp && attr == hwmon_temp_input && channel < 2) { + ret = corsairpsu_get_value(priv, channel ? PSU_CMD_TEMP1 : PSU_CMD_TEMP0, channel, + val); + } else if (type == hwmon_fan && attr == hwmon_fan_input) { + ret = corsairpsu_get_value(priv, PSU_CMD_FAN, 0, val); + } else if (type == hwmon_power && attr == hwmon_power_input) { + switch (channel) { + case 0: + ret = corsairpsu_get_value(priv, PSU_CMD_TOTAL_WATTS, 0, val); + break; + case 1 ... 3: + ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_WATTS, channel - 1, val); + break; + default: + return -EOPNOTSUPP; + } + } else if (type == hwmon_in && attr == hwmon_in_input) { + switch (channel) { + case 0: + ret = corsairpsu_get_value(priv, PSU_CMD_IN_VOLTS, 0, val); + break; + case 1 ... 3: + ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_OUT_VOLTS, channel - 1, val); + break; + default: + return -EOPNOTSUPP; + } + } else if (type == hwmon_curr && attr == hwmon_curr_input) { + switch (channel) { + case 0: + ret = corsairpsu_get_value(priv, PSU_CMD_IN_AMPS, 0, val); + break; + case 1 ... 3: + ret = corsairpsu_get_value(priv, PSU_CMD_RAIL_AMPS, channel - 1, val); + break; + default: + return -EOPNOTSUPP; + } + } else { + return -EOPNOTSUPP; + } + + if (ret < 0) + return ret; + + return 0; +} + +static int corsairpsu_hwmon_ops_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + if (type == hwmon_temp && attr == hwmon_temp_label) { + *str = channel ? L_TEMP1 : L_TEMP0; + return 0; + } else if (type == hwmon_fan && attr == hwmon_fan_label) { + *str = L_FAN; + return 0; + } else if (type == hwmon_power && attr == hwmon_power_label && channel < 4) { + *str = label_watts[channel]; + return 0; + } else if (type == hwmon_in && attr == hwmon_in_label && channel < 4) { + *str = label_volts[channel]; + return 0; + } else if (type == hwmon_curr && attr == hwmon_curr_label && channel < 4) { + *str = label_amps[channel]; + return 0; + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops corsairpsu_hwmon_ops = { + .is_visible = corsairpsu_hwmon_ops_is_visible, + .read = corsairpsu_hwmon_ops_read, + .read_string = corsairpsu_hwmon_ops_read_string, +}; + +static const struct hwmon_channel_info *corsairpsu_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + 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_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL), + NULL +}; + +static const struct hwmon_chip_info corsairpsu_chip_info = { + .ops = &corsairpsu_hwmon_ops, + .info = corsairpsu_info, +}; + +#ifdef CONFIG_DEBUG_FS + +static void print_uptime(struct seq_file *seqf, u8 cmd) +{ + struct corsairpsu_data *priv = seqf->private; + long val; + int ret; + + ret = corsairpsu_get_value(priv, cmd, 0, &val); + if (ret < 0) { + seq_puts(seqf, "N/A\n"); + return; + } + + if (val > SECONDS_PER_DAY) { + seq_printf(seqf, "%ld day(s), %02ld:%02ld:%02ld\n", val / SECONDS_PER_DAY, + val % SECONDS_PER_DAY / SECONDS_PER_HOUR, val % SECONDS_PER_HOUR / 60, + val % 60); + return; + } + + seq_printf(seqf, "%02ld:%02ld:%02ld\n", val % SECONDS_PER_DAY / SECONDS_PER_HOUR, + val % SECONDS_PER_HOUR / 60, val % 60); +} + +static int uptime_show(struct seq_file *seqf, void *unused) +{ + print_uptime(seqf, PSU_CMD_UPTIME); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(uptime); + +static int uptime_total_show(struct seq_file *seqf, void *unused) +{ + print_uptime(seqf, PSU_CMD_TOTAL_UPTIME); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(uptime_total); + +static int vendor_show(struct seq_file *seqf, void *unused) +{ + struct corsairpsu_data *priv = seqf->private; + + seq_printf(seqf, "%s\n", priv->vendor); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(vendor); + +static int product_show(struct seq_file *seqf, void *unused) +{ + struct corsairpsu_data *priv = seqf->private; + + seq_printf(seqf, "%s\n", priv->product); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(product); + +static void corsairpsu_debugfs_init(struct corsairpsu_data *priv) +{ + char name[32]; + + scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev)); + + priv->debugfs = debugfs_create_dir(name, NULL); + debugfs_create_file("uptime", 0444, priv->debugfs, priv, &uptime_fops); + debugfs_create_file("uptime_total", 0444, priv->debugfs, priv, &uptime_total_fops); + debugfs_create_file("vendor", 0444, priv->debugfs, priv, &vendor_fops); + debugfs_create_file("product", 0444, priv->debugfs, priv, &product_fops); +} + +#else + +static void corsairpsu_debugfs_init(struct corsairpsu_data *priv) +{ +} + +#endif + +static int corsairpsu_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct corsairpsu_data *priv; + int ret; + + priv = devm_kzalloc(&hdev->dev, sizeof(struct corsairpsu_data), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->cmd_buffer = devm_kmalloc(&hdev->dev, CMD_BUFFER_SIZE, GFP_KERNEL); + if (!priv->cmd_buffer) + return -ENOMEM; + + ret = hid_parse(hdev); + if (ret) + return ret; + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) + return ret; + + ret = hid_hw_open(hdev); + if (ret) + goto fail_and_stop; + + priv->hdev = hdev; + hid_set_drvdata(hdev, priv); + mutex_init(&priv->lock); + init_completion(&priv->wait_completion); + + hid_device_io_start(hdev); + + ret = corsairpsu_init(priv); + if (ret < 0) { + dev_err(&hdev->dev, "unable to initialize device (%d)\n", ret); + goto fail_and_stop; + } + + ret = corsairpsu_fwinfo(priv); + if (ret < 0) { + dev_err(&hdev->dev, "unable to query firmware (%d)\n", ret); + goto fail_and_stop; + } + + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsairpsu", priv, + &corsairpsu_chip_info, 0); + + if (IS_ERR(priv->hwmon_dev)) { + ret = PTR_ERR(priv->hwmon_dev); + goto fail_and_close; + } + + corsairpsu_debugfs_init(priv); + + return 0; + +fail_and_close: + hid_hw_close(hdev); +fail_and_stop: + hid_hw_stop(hdev); + return ret; +} + +static void corsairpsu_remove(struct hid_device *hdev) +{ + struct corsairpsu_data *priv = hid_get_drvdata(hdev); + + debugfs_remove_recursive(priv->debugfs); + hwmon_device_unregister(priv->hwmon_dev); + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static int corsairpsu_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) +{ + struct corsairpsu_data *priv = hid_get_drvdata(hdev); + + if (completion_done(&priv->wait_completion)) + return 0; + + memcpy(priv->cmd_buffer, data, min(CMD_BUFFER_SIZE, size)); + complete(&priv->wait_completion); + + return 0; +} + +static const struct hid_device_id corsairpsu_idtable[] = { + { HID_USB_DEVICE(0x1b1c, 0x1c03) }, /* Corsair HX550i */ + { HID_USB_DEVICE(0x1b1c, 0x1c04) }, /* Corsair HX650i */ + { HID_USB_DEVICE(0x1b1c, 0x1c05) }, /* Corsair HX750i */ + { HID_USB_DEVICE(0x1b1c, 0x1c06) }, /* Corsair HX850i */ + { HID_USB_DEVICE(0x1b1c, 0x1c07) }, /* Corsair HX1000i */ + { HID_USB_DEVICE(0x1b1c, 0x1c08) }, /* Corsair HX1200i */ + { HID_USB_DEVICE(0x1b1c, 0x1c09) }, /* Corsair RM550i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0a) }, /* Corsair RM650i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0b) }, /* Corsair RM750i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */ + { }, +}; +MODULE_DEVICE_TABLE(hid, corsairpsu_idtable); + +static struct hid_driver corsairpsu_driver = { + .name = DRIVER_NAME, + .id_table = corsairpsu_idtable, + .probe = corsairpsu_probe, + .remove = corsairpsu_remove, + .raw_event = corsairpsu_raw_event, +}; +module_hid_driver(corsairpsu_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wilken Gottwalt "); +MODULE_DESCRIPTION("Linux driver for Corsair power supplies with HID sensors interface"); From 90673f713fceaa50eef1bff0bcc8ee4e6fbc8953 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 5 Nov 2020 11:50:19 +0000 Subject: [PATCH 07/41] hwmon: (corsair-psu) fix unintentional sign extension issue The shifting of the u8 integer data[3] by 24 bits to the left will be promoted to a 32 bit signed int and then sign-extended to a long. In the event that the top bit of data[3] is set then all then all the upper 32 bits of a 64 bit long end up as also being set because of the sign-extension. Fix this by casting data[3] to a long before the shift. Addresses-Coverity: ("Unintended sign extension") Fixes: ce15cd2cee8b ("hwmon: add Corsair PSU HID controller driver") Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20201105115019.41735-1-colin.king@canonical.com Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-psu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c index e92d0376e7ac..5d19a888231a 100644 --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -241,7 +241,7 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, l * the LINEAR11 conversion are the watts values which are about 1200 for the strongest psu * supported (HX1200i) */ - tmp = (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0]; + tmp = ((long)data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0]; switch (cmd) { case PSU_CMD_IN_VOLTS: case PSU_CMD_IN_AMPS: From 3478c2699346deef69474d9892cd25f07b7b5442 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 12 Nov 2020 09:57:13 +0000 Subject: [PATCH 08/41] hwmon: (adm1177) Fix kerneldoc attribute formatting Kerneldoc expects attributes/parameters to be in '@*.: ' format. Also fix repeated word "the the". Fixes the following W=1 kernel build warning(s): drivers/hwmon/adm1177.c:40: warning: Function parameter or member 'client' not described in 'adm1177_state' drivers/hwmon/adm1177.c:40: warning: Function parameter or member 'reg' not described in 'adm1177_state' drivers/hwmon/adm1177.c:40: warning: Function parameter or member 'r_sense_uohm' not described in 'adm1177_state' drivers/hwmon/adm1177.c:40: warning: Function parameter or member 'alert_threshold_ua' not described in 'adm1177_state' drivers/hwmon/adm1177.c:40: warning: Function parameter or member 'vrange_high' not described in 'adm1177_state' Cc: Beniamin Bia Cc: Michael Hennerich Signed-off-by: Lee Jones Link: https://lore.kernel.org/r/20201112095715.1993117-2-lee.jones@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/adm1177.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/adm1177.c b/drivers/hwmon/adm1177.c index 6e8bb661894b..0c5dbc5e33b4 100644 --- a/drivers/hwmon/adm1177.c +++ b/drivers/hwmon/adm1177.c @@ -25,11 +25,11 @@ /** * struct adm1177_state - driver instance specific data - * @client pointer to i2c client - * @reg regulator info for the the power supply of the device - * @r_sense_uohm current sense resistor value - * @alert_threshold_ua current limit for shutdown - * @vrange_high internal voltage divider + * @client: pointer to i2c client + * @reg: regulator info for the power supply of the device + * @r_sense_uohm: current sense resistor value + * @alert_threshold_ua: current limit for shutdown + * @vrange_high: internal voltage divider */ struct adm1177_state { struct i2c_client *client; From f9279cdaa69f96dd04ad644e2713abdee606d05d Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 12 Nov 2020 09:57:14 +0000 Subject: [PATCH 09/41] hwmon: (ina3221) Demote seemingly unintentional kerneldoc header This is the only use of kerneldoc in the sourcefile and no descriptions are provided. Fixes the following W=1 kernel build warning(s): drivers/hwmon/ina3221.c:152: warning: Function parameter or member 'ina' not described in 'ina3221_summation_shunt_resistor' Cc: "Andrew F. Davis" Signed-off-by: Lee Jones Link: https://lore.kernel.org/r/20201112095715.1993117-3-lee.jones@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ina3221.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index ad11cbddc3a7..d80bd3efcd6d 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -139,7 +139,7 @@ static inline bool ina3221_is_enabled(struct ina3221_data *ina, int channel) (ina->reg_config & INA3221_CONFIG_CHx_EN(channel)); } -/** +/* * Helper function to return the resistor value for current summation. * * There is a condition to calculate current summation -- all the shunt From 66d5b034ea5cc617a402c5d81f56c22aa85a801f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 12 Nov 2020 09:57:15 +0000 Subject: [PATCH 10/41] hwmon: (ibmpowernv) Silence strncpy() warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the following W=1 kernel build warning(s): from drivers/hwmon/ibmpowernv.c:11: In function ‘strncpy’, inlined from ‘get_sensor_index_attr’ at drivers/hwmon/ibmpowernv.c:243:2, inlined from ‘create_device_attrs’ at drivers/hwmon/ibmpowernv.c:280:8: include/linux/string.h:297:30: warning: ‘__builtin_strncpy’ specified bound 32 equals destination size [-Wstringop-truncation] 297 | #define __underlying_strncpy __builtin_strncpy | ^ include/linux/string.h:307:9: note: in expansion of macro ‘__underlying_strncpy’ 307 | return __underlying_strncpy(p, q, size); | ^~~~~~~~~~~~~~~~~~~~ Cc: Jean Delvare Cc: Guenter Roeck Cc: Michael Ellerman Cc: Benjamin Herrenschmidt Cc: Paul Mackerras Cc: Neelesh Gupta Cc: linux-hwmon@vger.kernel.org Cc: linuxppc-dev@lists.ozlabs.org Signed-off-by: Lee Jones Link: https://lore.kernel.org/r/20201112095715.1993117-4-lee.jones@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ibmpowernv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ibmpowernv.c b/drivers/hwmon/ibmpowernv.c index a750647e66a4..8e3724728cce 100644 --- a/drivers/hwmon/ibmpowernv.c +++ b/drivers/hwmon/ibmpowernv.c @@ -240,7 +240,7 @@ static int get_sensor_index_attr(const char *name, u32 *index, char *attr) if (err) return err; - strncpy(attr, dash_pos + 1, MAX_ATTR_LEN); + strscpy(attr, dash_pos + 1, MAX_ATTR_LEN); return 0; } From 72969073dedb678d5ec5abfeb840a0863f79c5a9 Mon Sep 17 00:00:00 2001 From: Wilken Gottwalt Date: Fri, 13 Nov 2020 13:19:54 +0100 Subject: [PATCH 11/41] hwmon: (corsair-psu) Fix fan rpm calculation The correct fan rpm value is also a LINEAR11 value but without a factor. Verified by using the fan test button on the psu to let the fan spin up to maximum for some seconds. Fixes: 933222c98445 ("hwmon: (corsair-psu) fix unintentional sign extension issue") Signed-off-by: Wilken Gottwalt Link: https://lore.kernel.org/r/20201113121954.GA8488@monster.powergraphx.local Signed-off-by: Guenter Roeck --- drivers/hwmon/corsair-psu.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c index 5d19a888231a..99494056f4bd 100644 --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -252,12 +252,7 @@ static int corsairpsu_get_value(struct corsairpsu_data *priv, u8 cmd, u8 rail, l *val = corsairpsu_linear11_to_int(tmp & 0xFFFF) * 1000; break; case PSU_CMD_FAN: - /* - * this value is best guess, so the calculated value could be wrong, it is hard - * to ge the fan to spin in these semi-passive power supplies, which need a - * quite high load to do so - */ - *val = ((tmp & 0xFF) << 8) + ((tmp >> 8) & 0xFF); + *val = corsairpsu_linear11_to_int(tmp & 0xFFFF); break; case PSU_CMD_RAIL_WATTS: case PSU_CMD_TOTAL_WATTS: From 00e1fc8247167f3cc298923a134a37f61e8ed495 Mon Sep 17 00:00:00 2001 From: Naveen Krishna Chatradhi Date: Fri, 20 Nov 2020 00:12:45 +0530 Subject: [PATCH 12/41] hwmon: (amd_energy) Add AMD family 19h model 01h x86 match Add X86 CPU match for AMD family 19h model 01h. This is necessary to enable support for energy reporting via the amd_energy module. Signed-off-by: Naveen Krishna Chatradhi Link: https://lore.kernel.org/r/20201119184246.228322-1-NaveenKrishna.Chatradhi@amd.com Signed-off-by: Guenter Roeck --- drivers/hwmon/amd_energy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/amd_energy.c b/drivers/hwmon/amd_energy.c index 3197cda7bcd9..9b306448b7a0 100644 --- a/drivers/hwmon/amd_energy.c +++ b/drivers/hwmon/amd_energy.c @@ -331,6 +331,7 @@ static struct platform_device *amd_energy_platdev; static const struct x86_cpu_id cpu_ids[] __initconst = { X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL), + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL), {} }; MODULE_DEVICE_TABLE(x86cpu, cpu_ids); From 0c8bed26044445cda6afb6fe4875752164cb03cd Mon Sep 17 00:00:00 2001 From: Naveen Krishna Chatradhi Date: Fri, 20 Nov 2020 00:12:46 +0530 Subject: [PATCH 13/41] docs: hwmon: (amd_energy) update documentation Update the supported processors and mention vulnerability fix. Signed-off-by: Naveen Krishna Chatradhi Link: https://lore.kernel.org/r/20201119184246.228322-2-NaveenKrishna.Chatradhi@amd.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/amd_energy.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/amd_energy.rst b/Documentation/hwmon/amd_energy.rst index 86e4ebc5cbc2..9d58cd5ee3da 100644 --- a/Documentation/hwmon/amd_energy.rst +++ b/Documentation/hwmon/amd_energy.rst @@ -5,7 +5,9 @@ Kernel driver amd_energy Supported chips: -* AMD Family 17h Processors +* AMD Family 17h Processors: Model 30h + +* AMD Family 19h Processors: Model 01h Prefix: 'amd_energy' @@ -112,3 +114,6 @@ energy[N]_input EcoreX Core Energy X = [0] to [nr_cpus - 1] energy[N]_input EsocketX Socket Energy X = [0] to [nr_socks -1] Measured input socket energy =============== ======== ====================================== + +Note: To address CVE-2020-12912, the visibility of the energy[N]_input +attributes is restricted to owner and groups only. From bde58ca86df9b4974dddd651a54f0793007d4fac Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Sun, 15 Nov 2020 19:09:10 +0000 Subject: [PATCH 14/41] hwmon: drivetemp: fix typo temperatire => temperature This patch fix a trivial typo temperatire => temperature. Signed-off-by: Corentin Labbe Link: https://lore.kernel.org/r/20201115190910.12539-1-clabbe@baylibre.com Signed-off-by: Guenter Roeck --- drivers/hwmon/drivetemp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/drivetemp.c b/drivers/hwmon/drivetemp.c index 72c760373957..1eb37106a220 100644 --- a/drivers/hwmon/drivetemp.c +++ b/drivers/hwmon/drivetemp.c @@ -10,7 +10,7 @@ * hwmon: Driver for SCSI/ATA temperature sensors * by Constantin Baranov , submitted September 2009 * - * This drive supports reporting the temperatire of SATA drives. It can be + * This drive supports reporting the temperature of SATA drives. It can be * easily extended to report the temperature of SCSI drives. * * The primary means to read drive temperatures and temperature limits From 09c7188be3a6b8c141bdf24d31fba9fb078325c3 Mon Sep 17 00:00:00 2001 From: Paul Barker Date: Sun, 20 Sep 2020 19:09:40 +0100 Subject: [PATCH 15/41] dt-bindings: hwmon: pwm-fan: Support multiple fan tachometer inputs Document and give an example of how to define multiple fan tachometer inputs for the pwm-fan driver. Signed-off-by: Paul Barker Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20200920180943.352526-2-pbarker@konsulko.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/pwm-fan.txt | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/pwm-fan.txt b/Documentation/devicetree/bindings/hwmon/pwm-fan.txt index 41b76762953a..4509e688623a 100644 --- a/Documentation/devicetree/bindings/hwmon/pwm-fan.txt +++ b/Documentation/devicetree/bindings/hwmon/pwm-fan.txt @@ -8,15 +8,16 @@ Required properties: Optional properties: - fan-supply : phandle to the regulator that provides power to the fan -- interrupts : This contains a single interrupt specifier which - describes the tachometer output of the fan as an - interrupt source. The output signal must generate a - defined number of interrupts per fan revolution, which - require that it must be self resetting edge interrupts. - See interrupt-controller/interrupts.txt for the format. -- pulses-per-revolution : define the tachometer pulses per fan revolution as - an integer (default is 2 interrupts per revolution). - The value must be greater than zero. +- interrupts : This contains an interrupt specifier for each fan + tachometer output connected to an interrupt source. + The output signal must generate a defined number of + interrupts per fan revolution, which require that + it must be self resetting edge interrupts. See + interrupt-controller/interrupts.txt for the format. +- pulses-per-revolution : define the number of pulses per fan revolution for + each tachometer input as an integer (default is 2 + interrupts per revolution). The value must be + greater than zero. Example: fan0: pwm-fan { @@ -55,3 +56,12 @@ Example 2: interrupts = <1 IRQ_TYPE_EDGE_FALLING>; pulses-per-revolution = <2>; }; + +Example 3: + fan0: pwm-fan { + compatible = "pwm-fan"; + pwms = <&pwm1 0 25000 0>; + interrupts-extended = <&gpio1 1 IRQ_TYPE_EDGE_FALLING>, + <&gpio2 5 IRQ_TYPE_EDGE_FALLING>; + pulses-per-revolution = <2>, <1>; + }; From 8efd10147cd282c99fa5109b5497017a503ecbcd Mon Sep 17 00:00:00 2001 From: Tao Ren Date: Mon, 23 Nov 2020 10:56:57 -0800 Subject: [PATCH 16/41] hwmon: (max127) Add Maxim MAX127 hardware monitoring driver Add hardware monitoring driver for the Maxim MAX127 chip. MAX127 min/max range handling code is inspired by the max197 driver. Signed-off-by: Tao Ren Link: https://lore.kernel.org/r/20201123185658.7632-2-rentao.bupt@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 9 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/max127.c | 352 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 drivers/hwmon/max127.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9d600e0c5584..716df51edc87 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -950,6 +950,15 @@ config SENSORS_MAX1111 This driver can also be built as a module. If so, the module will be called max1111. +config SENSORS_MAX127 + tristate "Maxim MAX127 12-bit 8-channel Data Acquisition System" + depends on I2C + help + Say y here to support Maxim's MAX127 DAS chips. + + This driver can also be built as a module. If so, the module + will be called max127. + config SENSORS_MAX16065 tristate "Maxim MAX16065 System Manager and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 1083bbfac779..01ca5d3fbad4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -127,6 +127,7 @@ obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o +obj-$(CONFIG_SENSORS_MAX127) += max127.o obj-$(CONFIG_SENSORS_MAX16065) += max16065.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX1668) += max1668.o diff --git a/drivers/hwmon/max127.c b/drivers/hwmon/max127.c new file mode 100644 index 000000000000..402ffdc2f425 --- /dev/null +++ b/drivers/hwmon/max127.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for MAX127. + * + * Copyright (c) 2020 Facebook Inc. + */ + +#include +#include +#include +#include +#include + +/* + * MAX127 Control Byte. Refer to MAX127 datasheet, Table 1 "Control-Byte + * Format" for details. + */ +#define MAX127_CTRL_START BIT(7) +#define MAX127_CTRL_SEL_SHIFT 4 +#define MAX127_CTRL_RNG BIT(3) +#define MAX127_CTRL_BIP BIT(2) +#define MAX127_CTRL_PD1 BIT(1) +#define MAX127_CTRL_PD0 BIT(0) + +#define MAX127_NUM_CHANNELS 8 +#define MAX127_SET_CHANNEL(ch) (((ch) & 7) << MAX127_CTRL_SEL_SHIFT) + +/* + * MAX127 channel input ranges. Refer to MAX127 datasheet, Table 3 "Range + * and Polarity Selection" for details. + */ +#define MAX127_FULL_RANGE 10000 /* 10V */ +#define MAX127_HALF_RANGE 5000 /* 5V */ + +/* + * MAX127 returns 2 bytes at read: + * - the first byte contains data[11:4]. + * - the second byte contains data[3:0] (MSB) and 4 dummy 0s (LSB). + * Refer to MAX127 datasheet, "Read a Conversion (Read Cycle)" section + * for details. + */ +#define MAX127_DATA_LEN 2 +#define MAX127_DATA_SHIFT 4 + +#define MAX127_SIGN_BIT BIT(11) + +struct max127_data { + struct mutex lock; + struct i2c_client *client; + u8 ctrl_byte[MAX127_NUM_CHANNELS]; +}; + +static int max127_select_channel(struct i2c_client *client, u8 ctrl_byte) +{ + int status; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = sizeof(ctrl_byte), + .buf = &ctrl_byte, + }; + + status = i2c_transfer(client->adapter, &msg, 1); + if (status < 0) + return status; + if (status != 1) + return -EIO; + + return 0; +} + +static int max127_read_channel(struct i2c_client *client, long *val) +{ + int status; + u8 i2c_data[MAX127_DATA_LEN]; + struct i2c_msg msg = { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(i2c_data), + .buf = i2c_data, + }; + + status = i2c_transfer(client->adapter, &msg, 1); + if (status < 0) + return status; + if (status != 1) + return -EIO; + + *val = (i2c_data[1] >> MAX127_DATA_SHIFT) | + ((u16)i2c_data[0] << MAX127_DATA_SHIFT); + return 0; +} + +static long max127_process_raw(u8 ctrl_byte, long raw) +{ + long scale, weight; + + /* + * MAX127's data coding is binary in unipolar mode with 1 LSB = + * (Full-Scale/4096) and two’s complement binary in bipolar mode + * with 1 LSB = [(2 x |FS|)/4096]. + * Refer to MAX127 datasheet, "Transfer Function" section for + * details. + */ + scale = (ctrl_byte & MAX127_CTRL_RNG) ? MAX127_FULL_RANGE : + MAX127_HALF_RANGE; + if (ctrl_byte & MAX127_CTRL_BIP) { + weight = (raw & MAX127_SIGN_BIT); + raw &= ~MAX127_SIGN_BIT; + raw -= weight; + raw *= 2; + } + + return raw * scale / 4096; +} + +static int max127_read_input(struct max127_data *data, int channel, long *val) +{ + long raw; + int status; + struct i2c_client *client = data->client; + u8 ctrl_byte = data->ctrl_byte[channel]; + + mutex_lock(&data->lock); + + status = max127_select_channel(client, ctrl_byte); + if (status) + goto exit; + + status = max127_read_channel(client, &raw); + if (status) + goto exit; + + *val = max127_process_raw(ctrl_byte, raw); + +exit: + mutex_unlock(&data->lock); + return status; +} + +static int max127_read_min(struct max127_data *data, int channel, long *val) +{ + u8 rng_bip = (data->ctrl_byte[channel] >> 2) & 3; + static const int min_input_map[4] = { + 0, /* RNG=0, BIP=0 */ + -MAX127_HALF_RANGE, /* RNG=0, BIP=1 */ + 0, /* RNG=1, BIP=0 */ + -MAX127_FULL_RANGE, /* RNG=1, BIP=1 */ + }; + + *val = min_input_map[rng_bip]; + return 0; +} + +static int max127_read_max(struct max127_data *data, int channel, long *val) +{ + u8 rng_bip = (data->ctrl_byte[channel] >> 2) & 3; + static const int max_input_map[4] = { + MAX127_HALF_RANGE, /* RNG=0, BIP=0 */ + MAX127_HALF_RANGE, /* RNG=0, BIP=1 */ + MAX127_FULL_RANGE, /* RNG=1, BIP=0 */ + MAX127_FULL_RANGE, /* RNG=1, BIP=1 */ + }; + + *val = max_input_map[rng_bip]; + return 0; +} + +static int max127_write_min(struct max127_data *data, int channel, long val) +{ + u8 ctrl; + + mutex_lock(&data->lock); + + ctrl = data->ctrl_byte[channel]; + if (val <= -MAX127_FULL_RANGE) { + ctrl |= (MAX127_CTRL_RNG | MAX127_CTRL_BIP); + } else if (val < 0) { + ctrl |= MAX127_CTRL_BIP; + ctrl &= ~MAX127_CTRL_RNG; + } else { + ctrl &= ~MAX127_CTRL_BIP; + } + data->ctrl_byte[channel] = ctrl; + + mutex_unlock(&data->lock); + + return 0; +} + +static int max127_write_max(struct max127_data *data, int channel, long val) +{ + mutex_lock(&data->lock); + + if (val >= MAX127_FULL_RANGE) + data->ctrl_byte[channel] |= MAX127_CTRL_RNG; + else + data->ctrl_byte[channel] &= ~MAX127_CTRL_RNG; + + mutex_unlock(&data->lock); + + return 0; +} + +static umode_t max127_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_in) { + switch (attr) { + case hwmon_in_input: + return 0444; + + case hwmon_in_min: + case hwmon_in_max: + return 0644; + + default: + break; + } + } + + return 0; +} + +static int max127_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int status; + struct max127_data *data = dev_get_drvdata(dev); + + if (type != hwmon_in) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_in_input: + status = max127_read_input(data, channel, val); + break; + + case hwmon_in_min: + status = max127_read_min(data, channel, val); + break; + + case hwmon_in_max: + status = max127_read_max(data, channel, val); + break; + + default: + status = -EOPNOTSUPP; + break; + } + + return status; +} + +static int max127_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int status; + struct max127_data *data = dev_get_drvdata(dev); + + if (type != hwmon_in) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_in_min: + status = max127_write_min(data, channel, val); + break; + + case hwmon_in_max: + status = max127_write_max(data, channel, val); + break; + + default: + status = -EOPNOTSUPP; + break; + } + + return status; +} + +static const struct hwmon_ops max127_hwmon_ops = { + .is_visible = max127_is_visible, + .read = max127_read, + .write = max127_write, +}; + +static const struct hwmon_channel_info *max127_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX, + HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX), + NULL, +}; + +static const struct hwmon_chip_info max127_chip_info = { + .ops = &max127_hwmon_ops, + .info = max127_info, +}; + +static int max127_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i; + struct device *hwmon_dev; + struct max127_data *data; + struct device *dev = &client->dev; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + for (i = 0; i < ARRAY_SIZE(data->ctrl_byte); i++) + data->ctrl_byte[i] = (MAX127_CTRL_START | + MAX127_SET_CHANNEL(i)); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &max127_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id max127_id[] = { + { "max127", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max127_id); + +static struct i2c_driver max127_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max127", + }, + .probe = max127_probe, + .id_table = max127_id, +}; + +module_i2c_driver(max127_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mike Choi "); +MODULE_AUTHOR("Tao Ren "); +MODULE_DESCRIPTION("MAX127 Hardware Monitoring driver"); From 12d36c8362d090ce108ce4283f9e655373e2b472 Mon Sep 17 00:00:00 2001 From: Tao Ren Date: Mon, 23 Nov 2020 10:56:58 -0800 Subject: [PATCH 17/41] docs: hwmon: Document max127 driver Add documentation for the max127 hardware monitoring driver. Signed-off-by: Tao Ren Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20201123185658.7632-3-rentao.bupt@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/max127.rst | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 Documentation/hwmon/max127.rst diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 00844f0a8c55..fd6fae46b99c 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -111,6 +111,7 @@ Hardware Monitoring Kernel Drivers ltc4245 ltc4260 ltc4261 + max127 max16064 max16065 max1619 diff --git a/Documentation/hwmon/max127.rst b/Documentation/hwmon/max127.rst new file mode 100644 index 000000000000..dc192dd9c37c --- /dev/null +++ b/Documentation/hwmon/max127.rst @@ -0,0 +1,45 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver max127 +==================== + +Author: + + * Tao Ren + +Supported chips: + + * Maxim MAX127 + + Prefix: 'max127' + + Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX127-MAX128.pdf + +Description +----------- + +The MAX127 is a multirange, 12-bit data acquisition system (DAS) providing +8 analog input channels that are independently software programmable for +a variety of ranges. The available ranges are {0,5V}, {0,10V}, {-5,5V} +and {-10,10V}. + +The MAX127 features a 2-wire, I2C-compatible serial interface that allows +communication among multiple devices using SDA and SCL lines. + +Sysfs interface +--------------- + + ============== ============================================================== + in[0-7]_input The input voltage (in mV) of the corresponding channel. + RO + + in[0-7]_min The lower input limit (in mV) for the corresponding channel. + ADC range and LSB will be updated when the limit is changed. + For the MAX127, it will be adjusted to -10000, -5000, or 0. + RW + + in[0-7]_max The higher input limit (in mV) for the corresponding channel. + ADC range and LSB will be updated when the limit is changed. + For the MAX127, it will be adjusted to 0, 5000, or 10000. + RW + ============== ============================================================== From 02c155cb321643df9400a9af082902fcff222ad9 Mon Sep 17 00:00:00 2001 From: Tian Tao Date: Thu, 26 Nov 2020 10:21:20 +0800 Subject: [PATCH 18/41] hwmon: (abx500) Switch to using the new API kobj_to_dev() fixed the following coccicheck: drivers/hwmon/abx500.c:266:60-61: WARNING opportunity for kobj_to_dev(). Signed-off-by: Tian Tao Link: https://lore.kernel.org/r/1606357280-51921-1-git-send-email-tiantao6@hisilicon.com Signed-off-by: Guenter Roeck --- drivers/hwmon/abx500.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c index 50e67cdd8e5e..4b9648819836 100644 --- a/drivers/hwmon/abx500.c +++ b/drivers/hwmon/abx500.c @@ -263,7 +263,7 @@ static ssize_t max_alarm_show(struct device *dev, static umode_t abx500_attrs_visible(struct kobject *kobj, struct attribute *attr, int n) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct abx500_temp *data = dev_get_drvdata(dev); if (data->ops.is_visible) From b5fcb8a4018dabf9cbcb9a83757bd74576c66174 Mon Sep 17 00:00:00 2001 From: Paul Barker Date: Thu, 26 Nov 2020 17:44:07 +0000 Subject: [PATCH 19/41] hwmon: pwm-fan: Refactor pwm_fan_probe Use platform_irq_count to determine the number of fan tachometer inputs configured in the device tree. At this stage we support either 0 or 1 inputs. Once we have this information we only need to read the pulses-per-revolution value if a fan tachometer is actually configured via an IRQ value. Also add a debug print of the IRQ number and the pulses-per-revolution value to aid in investigating issues. Signed-off-by: Paul Barker Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20201126174408.755-2-pbarker@konsulko.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 50 +++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 1f63807c0399..efe2764f42d3 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -286,7 +286,7 @@ static int pwm_fan_probe(struct platform_device *pdev) struct device *hwmon; int ret; struct pwm_state state = { }; - u32 ppr = 2; + int tach_count; ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -300,10 +300,6 @@ static int pwm_fan_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ctx); - ctx->irq = platform_get_irq_optional(pdev, 0); - if (ctx->irq == -EPROBE_DEFER) - return ctx->irq; - ctx->reg_en = devm_regulator_get_optional(dev, "fan"); if (IS_ERR(ctx->reg_en)) { if (PTR_ERR(ctx->reg_en) != -ENODEV) @@ -339,20 +335,40 @@ static int pwm_fan_probe(struct platform_device *pdev) if (ret) return ret; - of_property_read_u32(dev->of_node, "pulses-per-revolution", &ppr); - ctx->pulses_per_revolution = ppr; - if (!ctx->pulses_per_revolution) { - dev_err(dev, "pulses-per-revolution can't be zero.\n"); - return -EINVAL; - } + tach_count = platform_irq_count(pdev); + if (tach_count < 0) + return dev_err_probe(dev, tach_count, + "Could not get number of fan tachometer inputs\n"); - if (ctx->irq > 0) { - ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0, - pdev->name, ctx); - if (ret) { - dev_err(dev, "Failed to request interrupt: %d\n", ret); - return ret; + if (tach_count > 0) { + u32 ppr = 2; + + ctx->irq = platform_get_irq(pdev, 0); + if (ctx->irq == -EPROBE_DEFER) + return ctx->irq; + if (ctx->irq > 0) { + ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0, + pdev->name, ctx); + if (ret) { + dev_err(dev, + "Failed to request interrupt: %d\n", + ret); + return ret; + } } + + of_property_read_u32(dev->of_node, + "pulses-per-revolution", + &ppr); + ctx->pulses_per_revolution = ppr; + if (!ctx->pulses_per_revolution) { + dev_err(dev, "pulses-per-revolution can't be zero.\n"); + return -EINVAL; + } + + dev_dbg(dev, "tach: irq=%d, pulses_per_revolution=%d\n", + ctx->irq, ctx->pulses_per_revolution); + ctx->sample_start = ktime_get(); mod_timer(&ctx->rpm_timer, jiffies + HZ); } From 1aa0365574ab63838ac851ca340475bf4b58b9af Mon Sep 17 00:00:00 2001 From: Paul Barker Date: Sat, 28 Nov 2020 17:49:09 +0000 Subject: [PATCH 20/41] hwmon: (pwm-fan) Convert to hwmon_device_register_with_info API The pwm-fan driver is updated to use the recommended API. Signed-off-by: Paul Barker [groeck: Dropped unused variable] Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 122 ++++++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 48 deletions(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index efe2764f42d3..777439f48c14 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -8,7 +8,6 @@ */ #include -#include #include #include #include @@ -39,6 +38,28 @@ struct pwm_fan_ctx { unsigned int pwm_fan_max_state; unsigned int *pwm_fan_cooling_levels; struct thermal_cooling_device *cdev; + + struct hwmon_chip_info info; +}; + +static const u32 pwm_fan_channel_config_pwm[] = { + HWMON_PWM_INPUT, + 0 +}; + +static const struct hwmon_channel_info pwm_fan_channel_pwm = { + .type = hwmon_pwm, + .config = pwm_fan_channel_config_pwm, +}; + +static const u32 pwm_fan_channel_config_fan[] = { + HWMON_F_INPUT, + 0 +}; + +static const struct hwmon_channel_info pwm_fan_channel_fan = { + .type = hwmon_fan, + .config = pwm_fan_channel_config_fan, }; /* This handler assumes self resetting edge triggered interrupt. */ @@ -103,70 +124,62 @@ static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm) ctx->pwm_fan_state = i; } -static ssize_t pwm_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static int pwm_fan_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) { struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); - unsigned long pwm; int ret; - if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM) + if (val < 0 || val > MAX_PWM) return -EINVAL; - ret = __set_pwm(ctx, pwm); + ret = __set_pwm(ctx, val); if (ret) return ret; - pwm_fan_update_state(ctx, pwm); - return count; + pwm_fan_update_state(ctx, val); + return 0; } -static ssize_t pwm_show(struct device *dev, struct device_attribute *attr, - char *buf) +static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) { struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); - return sprintf(buf, "%u\n", ctx->pwm_value); -} - -static ssize_t rpm_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); - - return sprintf(buf, "%u\n", ctx->rpm); -} - -static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0); -static SENSOR_DEVICE_ATTR_RO(fan1_input, rpm, 0); - -static struct attribute *pwm_fan_attrs[] = { - &sensor_dev_attr_pwm1.dev_attr.attr, - &sensor_dev_attr_fan1_input.dev_attr.attr, - NULL, -}; - -static umode_t pwm_fan_attrs_visible(struct kobject *kobj, struct attribute *a, - int n) -{ - struct device *dev = container_of(kobj, struct device, kobj); - struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); - - /* Hide fan_input in case no interrupt is available */ - if (n == 1 && ctx->irq <= 0) + switch (type) { + case hwmon_pwm: + *val = ctx->pwm_value; return 0; - return a->mode; + case hwmon_fan: + *val = ctx->rpm; + return 0; + + default: + return -ENOTSUPP; + } } -static const struct attribute_group pwm_fan_group = { - .attrs = pwm_fan_attrs, - .is_visible = pwm_fan_attrs_visible, -}; +static umode_t pwm_fan_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_pwm: + return 0644; -static const struct attribute_group *pwm_fan_groups[] = { - &pwm_fan_group, - NULL, + case hwmon_fan: + return 0444; + + default: + return 0; + } +} + +static const struct hwmon_ops pwm_fan_hwmon_ops = { + .is_visible = pwm_fan_is_visible, + .read = pwm_fan_read, + .write = pwm_fan_write, }; /* thermal cooling device callbacks */ @@ -287,6 +300,7 @@ static int pwm_fan_probe(struct platform_device *pdev) int ret; struct pwm_state state = { }; int tach_count; + const struct hwmon_channel_info **channels; ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) @@ -340,6 +354,13 @@ static int pwm_fan_probe(struct platform_device *pdev) return dev_err_probe(dev, tach_count, "Could not get number of fan tachometer inputs\n"); + channels = devm_kcalloc(dev, tach_count + 2, + sizeof(struct hwmon_channel_info *), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + channels[0] = &pwm_fan_channel_pwm; + if (tach_count > 0) { u32 ppr = 2; @@ -371,10 +392,15 @@ static int pwm_fan_probe(struct platform_device *pdev) ctx->sample_start = ktime_get(); mod_timer(&ctx->rpm_timer, jiffies + HZ); + + channels[1] = &pwm_fan_channel_fan; } - hwmon = devm_hwmon_device_register_with_groups(dev, "pwmfan", - ctx, pwm_fan_groups); + ctx->info.ops = &pwm_fan_hwmon_ops; + ctx->info.info = channels; + + hwmon = devm_hwmon_device_register_with_info(dev, "pwmfan", + ctx, &ctx->info, NULL); if (IS_ERR(hwmon)) { dev_err(dev, "Failed to register hwmon device\n"); return PTR_ERR(hwmon); From 6cbf7964b4af7952add97f440a6ceb6564451897 Mon Sep 17 00:00:00 2001 From: Joe Jamison Date: Sat, 28 Nov 2020 10:54:03 -0600 Subject: [PATCH 21/41] hwmon: (applesmc) Add DMI product matches for Intel-based Xserves (non-RackMac*) This patch adds the DMI Product ID for Intel-based Xserve machines. They use the same SMC accessible from the same data ports. The 'Xserve' product ID only resolves to SMC-containing Intel-based Xserves, as the PowerPC machines are identified by the 'RackMac' identifier. Tested on: Xserve3,1 Tested-by: Joe Jamison # Xserve3,1 Signed-off-by: Joe Jamison Signed-off-by: Guenter Roeck --- drivers/hwmon/applesmc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c index 79b498f816fe..89207af81c48 100644 --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -1299,6 +1299,10 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), DMI_MATCH(DMI_PRODUCT_NAME, "iMac") }, }, + { applesmc_dmi_match, "Apple Xserve", { + DMI_MATCH(DMI_BOARD_VENDOR, "Apple"), + DMI_MATCH(DMI_PRODUCT_NAME, "Xserve") }, + }, { .ident = NULL } }; From daf4fedde6177941b55ba3c3293a8585d5280b94 Mon Sep 17 00:00:00 2001 From: David Bartley Date: Tue, 1 Dec 2020 18:50:57 -0800 Subject: [PATCH 22/41] hwmon: (nct6683) Support NCT6687D. This is found on many MSI motherboards. Signed-off-by: David Bartley Link: https://lore.kernel.org/r/20201202025057.5492-1-andareed@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6683.rst | 3 ++- drivers/hwmon/nct6683.c | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Documentation/hwmon/nct6683.rst b/Documentation/hwmon/nct6683.rst index efbf7e9703ec..8646ad519fcd 100644 --- a/Documentation/hwmon/nct6683.rst +++ b/Documentation/hwmon/nct6683.rst @@ -3,7 +3,7 @@ Kernel driver nct6683 Supported chips: - * Nuvoton NCT6683D + * Nuvoton NCT6683D/NCT6687D Prefix: 'nct6683' @@ -61,4 +61,5 @@ Board Firmware version Intel DH87RL NCT6683D EC firmware version 1.0 build 04/03/13 Intel DH87MC NCT6683D EC firmware version 1.0 build 04/03/13 Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13 +MSI B550 NCT6687D EC firmware version 1.0 build 05/07/20 =============== =============================================== diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c index 2d299149f4d2..7f7e30f0de7b 100644 --- a/drivers/hwmon/nct6683.c +++ b/drivers/hwmon/nct6683.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * nct6683 - Driver for the hardware monitoring functionality of - * Nuvoton NCT6683D eSIO + * Nuvoton NCT6683D/NCT6687D eSIO * * Copyright (C) 2013 Guenter Roeck * @@ -12,6 +12,7 @@ * * Chip #vin #fan #pwm #temp chip ID * nct6683d 21(1) 16 8 32(1) 0xc730 + * nct6687d 21(1) 16 8 32(1) 0xd590 * * Notes: * (1) Total number of vin and temp inputs is 32. @@ -32,7 +33,7 @@ #include #include -enum kinds { nct6683 }; +enum kinds { nct6683, nct6687 }; static bool force; module_param(force, bool, 0); @@ -40,10 +41,12 @@ MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors"); static const char * const nct6683_device_names[] = { "nct6683", + "nct6687", }; static const char * const nct6683_chip_names[] = { "NCT6683D", + "NCT6687D", }; #define DRVNAME "nct6683" @@ -63,6 +66,7 @@ static const char * const nct6683_chip_names[] = { #define SIO_NCT6681_ID 0xb270 /* for later */ #define SIO_NCT6683_ID 0xc730 +#define SIO_NCT6687_ID 0xd590 #define SIO_ID_MASK 0xFFF0 static inline void @@ -164,6 +168,7 @@ superio_exit(int ioreg) #define NCT6683_REG_CUSTOMER_ID 0x602 #define NCT6683_CUSTOMER_ID_INTEL 0x805 #define NCT6683_CUSTOMER_ID_MITAC 0xa0e +#define NCT6683_CUSTOMER_ID_MSI 0x201 #define NCT6683_REG_BUILD_YEAR 0x604 #define NCT6683_REG_BUILD_MONTH 0x605 @@ -1218,6 +1223,8 @@ static int nct6683_probe(struct platform_device *pdev) break; case NCT6683_CUSTOMER_ID_MITAC: break; + case NCT6683_CUSTOMER_ID_MSI: + break; default: if (!force) return -ENODEV; @@ -1352,6 +1359,9 @@ static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data) case SIO_NCT6683_ID: sio_data->kind = nct6683; break; + case SIO_NCT6687_ID: + sio_data->kind = nct6687; + break; default: if (val != 0xffff) pr_debug("unsupported chip ID: 0x%04x\n", val); From 7537862a90b8b9106d42ad2f53de2b96fd1673c5 Mon Sep 17 00:00:00 2001 From: Charles Date: Wed, 2 Dec 2020 14:11:04 +0800 Subject: [PATCH 23/41] hwmon: Add driver for STMicroelectronics PM6764 Voltage Regulator Add the pmbus driver for the STMicroelectronics pm6764 voltage regulator. the output voltage use the MFR_READ_VOUT 0xD4 vout value returned is linear11 Signed-off-by: Charles Hsu [groeck: Fixed various compile errors; marked pm6764tr_of_match __maybe_unused] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/pm6764tr.rst | 32 ++++++++++++++ MAINTAINERS | 7 +++ drivers/hwmon/pmbus/Kconfig | 9 ++++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/pm6764tr.c | 75 ++++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+) create mode 100644 Documentation/hwmon/pm6764tr.rst create mode 100644 drivers/hwmon/pmbus/pm6764tr.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index fd6fae46b99c..9a699e4f0234 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -146,6 +146,7 @@ Hardware Monitoring Kernel Drivers pc87360 pc87427 pcf8591 + pm6764tr pmbus powr1220 pxe1610 diff --git a/Documentation/hwmon/pm6764tr.rst b/Documentation/hwmon/pm6764tr.rst new file mode 100644 index 000000000000..a1fb8fea2326 --- /dev/null +++ b/Documentation/hwmon/pm6764tr.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver pm6764tr +====================== + +Supported chips: + + * ST PM6764TR + + Prefix: 'pm6764tr' + + Addresses scanned: - + + Datasheet: http://www.st.com/resource/en/data_brief/pm6764.pdf + +Authors: + + +Description: +------------ + +This driver supports the STMicroelectronics PM6764TR chip. The PM6764TR is a high +performance digital controller designed to power Intel’s VR12.5 processors and memories. + +The device utilizes digital technology to implement all control and power management +functions to provide maximum flexibility and performance. The NVM is embedded to store +custom configurations. The PM6764TR device features up to 4-phase programmable operation. + +The PM6764TR supports power state transitions featuring VFDE, and programmable DPM +maintaining the best efficiency over all loading conditions without compromising transient +response. The device assures fast and independent protection against load overcurrent, +under/overvoltage and feedback disconnections. diff --git a/MAINTAINERS b/MAINTAINERS index 9ccea4c36b95..65245db06452 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13900,6 +13900,13 @@ M: Logan Gunthorpe S: Maintained F: drivers/dma/plx_dma.c +PM6764TR DRIVER +M: Charles Hsu +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/pm6764tr.rst +F: drivers/hwmon/pmbus/pm6764tr.c + PM-GRAPH UTILITY M: "Todd E Brandt" L: linux-pm@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index a25faf69fce3..9c846facce9f 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -220,6 +220,15 @@ config SENSORS_MP2975 This driver can also be built as a module. If so, the module will be called mp2975. +config SENSORS_PM6764TR + tristate "ST PM6764TR" + help + If you say yes here you get hardware monitoring support for ST + PM6764TR. + + This driver can also be built as a module. If so, the module will + be called pm6764tr. + config SENSORS_PXE1610 tristate "Infineon PXE1610" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 4c97ad0bd791..31ebdef5d4a6 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o +obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o diff --git a/drivers/hwmon/pmbus/pm6764tr.c b/drivers/hwmon/pmbus/pm6764tr.c new file mode 100644 index 000000000000..d97cb6d6c87f --- /dev/null +++ b/drivers/hwmon/pmbus/pm6764tr.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for STMicroelectronics digital controller PM6764TR + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define PM6764TR_PMBUS_READ_VOUT 0xD4 + +static int pm6764tr_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, page, phase, PM6764TR_PMBUS_READ_VOUT); + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info pm6764tr_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | + PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | PMBUS_HAVE_VMON | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = pm6764tr_read_word_data, +}; + +static int pm6764tr_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &pm6764tr_info); +} + +static const struct i2c_device_id pm6764tr_id[] = { + {"pm6764tr", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, pm6764tr_id); + +static const struct of_device_id __maybe_unused pm6764tr_of_match[] = { + {.compatible = "st,pm6764tr"}, + {} +}; + +/* This is the driver that will be inserted */ +static struct i2c_driver pm6764tr_driver = { + .driver = { + .name = "pm6764tr", + .of_match_table = of_match_ptr(pm6764tr_of_match), + }, + .probe_new = pm6764tr_probe, + .id_table = pm6764tr_id, +}; + +module_i2c_driver(pm6764tr_driver); + +MODULE_AUTHOR("Charles Hsu"); +MODULE_DESCRIPTION("PMBus driver for ST PM6764TR"); +MODULE_LICENSE("GPL"); From d014538aa38561cd24c5eb228223585f26c5ec71 Mon Sep 17 00:00:00 2001 From: "xiao.ma" Date: Tue, 1 Dec 2020 16:59:00 -1000 Subject: [PATCH 24/41] hwmon: (pmbus) Driver for Delta power supplies Q54SJ108A2 The driver supports Q54SJ108A2 series modules of Delta. Standard attributes are in sysfs, and other attributes are in debugfs. Signed-off-by: xiao.ma Link: https://lore.kernel.org/r/20201202025900.1842-1-max701@126.com [groeck: Replaced spaces with tabs, dropped excessive spaces, fixed module prefix in documentation] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/q54sj108a2.rst | 54 ++++ drivers/hwmon/pmbus/Kconfig | 9 + drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/q54sj108a2.c | 422 +++++++++++++++++++++++++++++ 5 files changed, 487 insertions(+) create mode 100644 Documentation/hwmon/q54sj108a2.rst create mode 100644 drivers/hwmon/pmbus/q54sj108a2.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 9a699e4f0234..f1205c64423d 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -151,6 +151,7 @@ Hardware Monitoring Kernel Drivers powr1220 pxe1610 pwm-fan + q54sj108a2 raspberrypi-hwmon sch5627 sch5636 diff --git a/Documentation/hwmon/q54sj108a2.rst b/Documentation/hwmon/q54sj108a2.rst new file mode 100644 index 000000000000..da925e343e79 --- /dev/null +++ b/Documentation/hwmon/q54sj108a2.rst @@ -0,0 +1,54 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver q54sj108a2 +===================== + +Supported chips: + + * DELTA Q54SJ108A2NCAH, Q54SJ108A2NCDH, Q54SJ108A2NCPG, Q54SJ108A2NCPH + + Prefix: 'q54sj108a2' + + Addresses scanned: - + + Datasheet: https://filecenter.delta-china.com.cn/products/download/01/0102/datasheet/DS_Q54SJ108A2.pdf + +Authors: + Xiao.ma + + +Description +----------- + +This driver implements support for DELTA Q54SJ108A2NCAH, Q54SJ108A2NCDH, +Q54SJ108A2NCPG, and Q54SJ108A2NCPH 1/4 Brick DC/DC Regulated Power Module +with PMBus support. + +The driver is a client driver to the core PMBus driver. +Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers. + + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate the +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for +details. + + +Sysfs entries +------------- + +===================== ===== ================================================== +curr1_alarm RO Output current alarm +curr1_input RO Output current +curr1_label RO 'iout1' +in1_alarm RO Input voltage alarm +in1_input RO Input voltage +in1_label RO 'vin' +in2_alarm RO Output voltage alarm +in2_input RO Output voltage +in2_label RO 'vout1' +temp1_alarm RO Temperature alarm +temp1_input RO Chip temperature +===================== ===== ================================================== diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 9c846facce9f..03606d4298a4 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -238,6 +238,15 @@ config SENSORS_PXE1610 This driver can also be built as a module. If so, the module will be called pxe1610. +config SENSORS_Q54SJ108A2 + tristate "Delta Power Supplies Q54SJ108A2" + help + If you say yes here you get hardware monitoring support for Delta + Q54SJ108A2 series Power Supplies. + + This driver can also be built as a module. If so, the module will + be called q54sj108a2. + config SENSORS_TPS40422 tristate "TI TPS40422" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 31ebdef5d4a6..6a4ba0fdc1db 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o +obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o diff --git a/drivers/hwmon/pmbus/q54sj108a2.c b/drivers/hwmon/pmbus/q54sj108a2.c new file mode 100644 index 000000000000..aec512766c31 --- /dev/null +++ b/drivers/hwmon/pmbus/q54sj108a2.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Delta modules, Q54SJ108A2 series 1/4 Brick DC/DC + * Regulated Power Module + * + * Copyright 2020 Delta LLC. + */ + +#include +#include +#include +#include +#include "pmbus.h" + +#define STORE_DEFAULT_ALL 0x11 +#define ERASE_BLACKBOX_DATA 0xD1 +#define READ_HISTORY_EVENT_NUMBER 0xD2 +#define READ_HISTORY_EVENTS 0xE0 +#define SET_HISTORY_EVENT_OFFSET 0xE1 +#define PMBUS_FLASH_KEY_WRITE 0xEC + +enum chips { + q54sj108a2 +}; + +enum { + Q54SJ108A2_DEBUGFS_OPERATION = 0, + Q54SJ108A2_DEBUGFS_CLEARFAULT, + Q54SJ108A2_DEBUGFS_WRITEPROTECT, + Q54SJ108A2_DEBUGFS_STOREDEFAULT, + Q54SJ108A2_DEBUGFS_VOOV_RESPONSE, + Q54SJ108A2_DEBUGFS_IOOC_RESPONSE, + Q54SJ108A2_DEBUGFS_PMBUS_VERSION, + Q54SJ108A2_DEBUGFS_MFR_ID, + Q54SJ108A2_DEBUGFS_MFR_MODEL, + Q54SJ108A2_DEBUGFS_MFR_REVISION, + Q54SJ108A2_DEBUGFS_MFR_LOCATION, + Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE, + Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET, + Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET, + Q54SJ108A2_DEBUGFS_BLACKBOX_READ, + Q54SJ108A2_DEBUGFS_FLASH_KEY, + Q54SJ108A2_DEBUGFS_NUM_ENTRIES +}; + +struct q54sj108a2_data { + enum chips chip; + struct i2c_client *client; + + int debugfs_entries[Q54SJ108A2_DEBUGFS_NUM_ENTRIES]; +}; + +#define to_psu(x, y) container_of((x), struct q54sj108a2_data, debugfs_entries[(y)]) + +static struct pmbus_driver_info q54sj108a2_info[] = { + [q54sj108a2] = { + .pages = 1, + + /* Source : Delta Q54SJ108A2 */ + .format[PSC_TEMPERATURE] = linear, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT, + }, +}; + +static ssize_t q54sj108a2_debugfs_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int rc; + int *idxp = file->private_data; + int idx = *idxp; + struct q54sj108a2_data *psu = to_psu(idxp, idx); + char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + char data_char[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; + char *res; + + switch (idx) { + case Q54SJ108A2_DEBUGFS_OPERATION: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_OPERATION); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_WRITEPROTECT: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_WRITE_PROTECT); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_VOOV_RESPONSE: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_VOUT_OV_FAULT_RESPONSE); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_IOOC_RESPONSE: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_IOUT_OC_FAULT_RESPONSE); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_PMBUS_VERSION: + rc = i2c_smbus_read_byte_data(psu->client, PMBUS_REVISION); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_MFR_ID: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_ID, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_MFR_MODEL: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_MODEL, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_MFR_REVISION: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_REVISION, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_MFR_LOCATION: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_LOCATION, data); + if (rc < 0) + return rc; + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET: + rc = i2c_smbus_read_byte_data(psu->client, READ_HISTORY_EVENT_NUMBER); + if (rc < 0) + return rc; + + rc = snprintf(data, 3, "%02x", rc); + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_READ: + rc = i2c_smbus_read_block_data(psu->client, READ_HISTORY_EVENTS, data); + if (rc < 0) + return rc; + + res = bin2hex(data, data_char, 32); + rc = res - data; + + break; + case Q54SJ108A2_DEBUGFS_FLASH_KEY: + rc = i2c_smbus_read_block_data(psu->client, PMBUS_FLASH_KEY_WRITE, data); + if (rc < 0) + return rc; + + res = bin2hex(data, data_char, 4); + rc = res - data; + + break; + default: + return -EINVAL; + } + + data[rc] = '\n'; + rc += 2; + + return simple_read_from_buffer(buf, count, ppos, data, rc); +} + +static ssize_t q54sj108a2_debugfs_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + u8 flash_key[4]; + u8 dst_data; + ssize_t rc; + int *idxp = file->private_data; + int idx = *idxp; + struct q54sj108a2_data *psu = to_psu(idxp, idx); + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_WRITE_PROTECT, 0); + if (rc) + return rc; + + switch (idx) { + case Q54SJ108A2_DEBUGFS_OPERATION: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_OPERATION, dst_data); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_CLEARFAULT: + rc = i2c_smbus_write_byte(psu->client, PMBUS_CLEAR_FAULTS); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_STOREDEFAULT: + flash_key[0] = 0x7E; + flash_key[1] = 0x15; + flash_key[2] = 0xDC; + flash_key[3] = 0x42; + rc = i2c_smbus_write_block_data(psu->client, PMBUS_FLASH_KEY_WRITE, 4, flash_key); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte(psu->client, STORE_DEFAULT_ALL); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_VOOV_RESPONSE: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_VOUT_OV_FAULT_RESPONSE, dst_data); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_IOOC_RESPONSE: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, PMBUS_IOUT_OC_FAULT_RESPONSE, dst_data); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE: + rc = i2c_smbus_write_byte(psu->client, ERASE_BLACKBOX_DATA); + if (rc < 0) + return rc; + + break; + case Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET: + rc = kstrtou8_from_user(buf, count, 0, &dst_data); + if (rc < 0) + return rc; + + rc = i2c_smbus_write_byte_data(psu->client, SET_HISTORY_EVENT_OFFSET, dst_data); + if (rc < 0) + return rc; + + break; + default: + return -EINVAL; + } + + return count; +} + +static const struct file_operations q54sj108a2_fops = { + .llseek = noop_llseek, + .read = q54sj108a2_debugfs_read, + .write = q54sj108a2_debugfs_write, + .open = simple_open, +}; + +static const struct i2c_device_id q54sj108a2_id[] = { + { "q54sj108a2", q54sj108a2 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, q54sj108a2_id); + +static int q54sj108a2_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; + enum chips chip_id; + int ret, i; + struct dentry *debugfs; + struct dentry *q54sj108a2_dir; + struct q54sj108a2_data *psu; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) + return -ENODEV; + + if (client->dev.of_node) + chip_id = (enum chips)(unsigned long)of_device_get_match_data(dev); + else + chip_id = i2c_match_id(q54sj108a2_id, client)->driver_data; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if (ret != 5 || strncmp(buf, "DELTA", 5)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer ID '%s'\n", buf); + return -ENODEV; + } + + /* + * The chips support reading PMBUS_MFR_MODEL. + */ + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) { + dev_err(dev, "Failed to read Manufacturer Model\n"); + return ret; + } + if (ret != 14 || strncmp(buf, "Q54SJ108A2", 10)) { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_REVISION, buf); + if (ret < 0) { + dev_err(dev, "Failed to read Manufacturer Revision\n"); + return ret; + } + if (ret != 4 || buf[0] != 'S') { + buf[ret] = '\0'; + dev_err(dev, "Unsupported Manufacturer Revision '%s'\n", buf); + return -ENODEV; + } + + ret = pmbus_do_probe(client, &q54sj108a2_info[chip_id]); + if (ret) + return ret; + + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return 0; + + psu->client = client; + + debugfs = pmbus_get_debugfs_dir(client); + + q54sj108a2_dir = debugfs_create_dir(client->name, debugfs); + + for (i = 0; i < Q54SJ108A2_DEBUGFS_NUM_ENTRIES; ++i) + psu->debugfs_entries[i] = i; + + debugfs_create_file("operation", 0644, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_OPERATION], + &q54sj108a2_fops); + debugfs_create_file("clear_fault", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_CLEARFAULT], + &q54sj108a2_fops); + debugfs_create_file("write_protect", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_WRITEPROTECT], + &q54sj108a2_fops); + debugfs_create_file("store_default", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_STOREDEFAULT], + &q54sj108a2_fops); + debugfs_create_file("vo_ov_response", 0644, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_VOOV_RESPONSE], + &q54sj108a2_fops); + debugfs_create_file("io_oc_response", 0644, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_IOOC_RESPONSE], + &q54sj108a2_fops); + debugfs_create_file("pmbus_revision", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_PMBUS_VERSION], + &q54sj108a2_fops); + debugfs_create_file("mfr_id", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_ID], + &q54sj108a2_fops); + debugfs_create_file("mfr_model", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_MODEL], + &q54sj108a2_fops); + debugfs_create_file("mfr_revision", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_REVISION], + &q54sj108a2_fops); + debugfs_create_file("mfr_location", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_MFR_LOCATION], + &q54sj108a2_fops); + debugfs_create_file("blackbox_erase", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_ERASE], + &q54sj108a2_fops); + debugfs_create_file("blackbox_read_offset", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_READ_OFFSET], + &q54sj108a2_fops); + debugfs_create_file("blackbox_set_offset", 0200, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_SET_OFFSET], + &q54sj108a2_fops); + debugfs_create_file("blackbox_read", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_BLACKBOX_READ], + &q54sj108a2_fops); + debugfs_create_file("flash_key", 0444, q54sj108a2_dir, + &psu->debugfs_entries[Q54SJ108A2_DEBUGFS_FLASH_KEY], + &q54sj108a2_fops); + + return 0; +} + +static const struct of_device_id q54sj108a2_of_match[] = { + { .compatible = "delta,q54sj108a2", .data = (void *)q54sj108a2 }, + { }, +}; + +MODULE_DEVICE_TABLE(of, q54sj108a2_of_match); + +static struct i2c_driver q54sj108a2_driver = { + .driver = { + .name = "q54sj108a2", + .of_match_table = q54sj108a2_of_match, + }, + .probe_new = q54sj108a2_probe, + .id_table = q54sj108a2_id, +}; + +module_i2c_driver(q54sj108a2_driver); + +MODULE_AUTHOR("Xiao.Ma "); +MODULE_DESCRIPTION("PMBus driver for Delta Q54SJ108A2 series modules"); +MODULE_LICENSE("GPL"); From b0bd407e94b036d597c6060d64c22094ff85b13c Mon Sep 17 00:00:00 2001 From: Alexandru Tachici Date: Thu, 3 Dec 2020 09:11:53 +0200 Subject: [PATCH 25/41] hwmon: (ltc2992) Add support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LTC2992 is a rail-to-rail system monitor that measures current, voltage, and power of two supplies. Two ADCs simultaneously measure each supply’s current. A third ADC monitors the input voltages and four auxiliary external voltages. Signed-off-by: Alexandru Tachici Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/ltc2992.rst | 56 +++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ltc2992.c | 813 ++++++++++++++++++++++++++++++++ 5 files changed, 882 insertions(+) create mode 100644 Documentation/hwmon/ltc2992.rst create mode 100644 drivers/hwmon/ltc2992.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index f1205c64423d..7e5104c8f477 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -101,6 +101,7 @@ Hardware Monitoring Kernel Drivers lm95234 lm95245 lochnagar + ltc2992 ltc2945 ltc2947 ltc2978 diff --git a/Documentation/hwmon/ltc2992.rst b/Documentation/hwmon/ltc2992.rst new file mode 100644 index 000000000000..46aa1aa84a1a --- /dev/null +++ b/Documentation/hwmon/ltc2992.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver ltc2992 +===================== + +Supported chips: + * Linear Technology LTC2992 + Prefix: 'ltc2992' + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf + +Author: Alexandru Tachici + + +Description +----------- + +This driver supports hardware monitoring for Linear Technology LTC2992 power monitor. + +LTC2992 is a rail-to-rail system monitor that measures current, +voltage, and power of two supplies. + +Two ADCs simultaneously measure each supply’s current. A third ADC monitors +the input voltages and four auxiliary external voltages. + + +Sysfs entries +------------- + +The following attributes are supported. Limits are read-write, +all other attributes are read-only. + +in_reset_history Reset all highest/lowest values. + +inX_input Measured voltage. +inX_lowest Minimum measured voltage. +inX_highest Maximum measured voltage. +inX_min Minimum voltage allowed. +inX_max Maximum voltage allowed. +inX_min_alarm An undervoltage occurred. Cleared on read. +inX_max_alarm An overvoltage occurred. Cleared on read. + +currX_input Measured current. +currX_lowest Minimum measured current. +currX_highest Maximum measured current. +currX_min Minimum current allowed. +currX_max Maximum current allowed. +currX_min_alarm An undercurrent occurred. Cleared on read. +currX_max_alarm An overcurrent occurred. Cleared on read. + +powerX_input Measured power. +powerX_input_lowest Minimum measured voltage. +powerX_input_highest Maximum measured voltage. +powerX_min Minimum power. +powerX_max Maximum power. +powerX_min_alarm An underpower occurred. Cleared on read. +powerX_max_alarm An overpower occurred. Cleared on read. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 716df51edc87..d3e4dc00f442 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -871,6 +871,17 @@ config SENSORS_LTC2990 This driver can also be built as a module. If so, the module will be called ltc2990. +config SENSORS_LTC2992 + tristate "Linear Technology LTC2992" + depends on I2C + help + If you say yes here you get support for Linear Technology LTC2992 + I2C System Monitor. The LTC2992 measures current, voltage, and + power of two supplies. + + This driver can also be built as a module. If so, the module will + be called ltc2992. + config SENSORS_LTC4151 tristate "Linear Technology LTC4151" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 01ca5d3fbad4..32cff6b0593e 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_SENSORS_LTC2947) += ltc2947-core.o obj-$(CONFIG_SENSORS_LTC2947_I2C) += ltc2947-i2c.o obj-$(CONFIG_SENSORS_LTC2947_SPI) += ltc2947-spi.o obj-$(CONFIG_SENSORS_LTC2990) += ltc2990.o +obj-$(CONFIG_SENSORS_LTC2992) += ltc2992.o obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c new file mode 100644 index 000000000000..c11d585a9600 --- /dev/null +++ b/drivers/hwmon/ltc2992.c @@ -0,0 +1,813 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* + * LTC2992 - Dual Wide Range Power Monitor + * + * Copyright 2020 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC2992_CTRLB 0x01 +#define LTC2992_FAULT1 0x03 +#define LTC2992_POWER1 0x05 +#define LTC2992_POWER1_MAX 0x08 +#define LTC2992_POWER1_MIN 0x0B +#define LTC2992_POWER1_MAX_THRESH 0x0E +#define LTC2992_POWER1_MIN_THRESH 0x11 +#define LTC2992_DSENSE1 0x14 +#define LTC2992_DSENSE1_MAX 0x16 +#define LTC2992_DSENSE1_MIN 0x18 +#define LTC2992_DSENSE1_MAX_THRESH 0x1A +#define LTC2992_DSENSE1_MIN_THRESH 0x1C +#define LTC2992_SENSE1 0x1E +#define LTC2992_SENSE1_MAX 0x20 +#define LTC2992_SENSE1_MIN 0x22 +#define LTC2992_SENSE1_MAX_THRESH 0x24 +#define LTC2992_SENSE1_MIN_THRESH 0x26 +#define LTC2992_G1 0x28 +#define LTC2992_G1_MAX 0x2A +#define LTC2992_G1_MIN 0x2C +#define LTC2992_G1_MAX_THRESH 0x2E +#define LTC2992_G1_MIN_THRESH 0x30 +#define LTC2992_FAULT2 0x35 +#define LTC2992_G2 0x5A +#define LTC2992_G2_MAX 0x5C +#define LTC2992_G2_MIN 0x5E +#define LTC2992_G2_MAX_THRESH 0x60 +#define LTC2992_G2_MIN_THRESH 0x62 +#define LTC2992_G3 0x64 +#define LTC2992_G3_MAX 0x66 +#define LTC2992_G3_MIN 0x68 +#define LTC2992_G3_MAX_THRESH 0x6A +#define LTC2992_G3_MIN_THRESH 0x6C +#define LTC2992_G4 0x6E +#define LTC2992_G4_MAX 0x70 +#define LTC2992_G4_MIN 0x72 +#define LTC2992_G4_MAX_THRESH 0x74 +#define LTC2992_G4_MIN_THRESH 0x76 +#define LTC2992_FAULT3 0x92 + +#define LTC2992_POWER(x) (LTC2992_POWER1 + ((x) * 0x32)) +#define LTC2992_POWER_MAX(x) (LTC2992_POWER1_MAX + ((x) * 0x32)) +#define LTC2992_POWER_MIN(x) (LTC2992_POWER1_MIN + ((x) * 0x32)) +#define LTC2992_POWER_MAX_THRESH(x) (LTC2992_POWER1_MAX_THRESH + ((x) * 0x32)) +#define LTC2992_POWER_MIN_THRESH(x) (LTC2992_POWER1_MIN_THRESH + ((x) * 0x32)) +#define LTC2992_DSENSE(x) (LTC2992_DSENSE1 + ((x) * 0x32)) +#define LTC2992_DSENSE_MAX(x) (LTC2992_DSENSE1_MAX + ((x) * 0x32)) +#define LTC2992_DSENSE_MIN(x) (LTC2992_DSENSE1_MIN + ((x) * 0x32)) +#define LTC2992_DSENSE_MAX_THRESH(x) (LTC2992_DSENSE1_MAX_THRESH + ((x) * 0x32)) +#define LTC2992_DSENSE_MIN_THRESH(x) (LTC2992_DSENSE1_MIN_THRESH + ((x) * 0x32)) +#define LTC2992_SENSE(x) (LTC2992_SENSE1 + ((x) * 0x32)) +#define LTC2992_SENSE_MAX(x) (LTC2992_SENSE1_MAX + ((x) * 0x32)) +#define LTC2992_SENSE_MIN(x) (LTC2992_SENSE1_MIN + ((x) * 0x32)) +#define LTC2992_SENSE_MAX_THRESH(x) (LTC2992_SENSE1_MAX_THRESH + ((x) * 0x32)) +#define LTC2992_SENSE_MIN_THRESH(x) (LTC2992_SENSE1_MIN_THRESH + ((x) * 0x32)) +#define LTC2992_POWER_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32)) +#define LTC2992_SENSE_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32)) +#define LTC2992_DSENSE_FAULT(x) (LTC2992_FAULT1 + ((x) * 0x32)) + +/* CTRLB register bitfields */ +#define LTC2992_RESET_HISTORY BIT(3) + +/* FAULT1 FAULT2 registers common bitfields */ +#define LTC2992_POWER_FAULT_MSK(x) (BIT(6) << (x)) +#define LTC2992_DSENSE_FAULT_MSK(x) (BIT(4) << (x)) +#define LTC2992_SENSE_FAULT_MSK(x) (BIT(2) << (x)) + +/* FAULT1 bitfields */ +#define LTC2992_GPIO1_FAULT_MSK(x) (BIT(0) << (x)) + +/* FAULT2 bitfields */ +#define LTC2992_GPIO2_FAULT_MSK(x) (BIT(0) << (x)) + +/* FAULT3 bitfields */ +#define LTC2992_GPIO3_FAULT_MSK(x) (BIT(6) << (x)) +#define LTC2992_GPIO4_FAULT_MSK(x) (BIT(4) << (x)) + +#define LTC2992_IADC_NANOV_LSB 12500 +#define LTC2992_VADC_UV_LSB 25000 +#define LTC2992_VADC_GPIO_UV_LSB 500 + +struct ltc2992_state { + struct i2c_client *client; + struct regmap *regmap; + u32 r_sense_uohm[2]; +}; + +struct ltc2992_gpio_regs { + u8 data; + u8 max; + u8 min; + u8 max_thresh; + u8 min_thresh; + u8 alarm; + u8 min_alarm_msk; + u8 max_alarm_msk; +}; + +static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { + { + .data = LTC2992_G1, + .max = LTC2992_G1_MAX, + .min = LTC2992_G1_MIN, + .max_thresh = LTC2992_G1_MAX_THRESH, + .min_thresh = LTC2992_G1_MIN_THRESH, + .alarm = LTC2992_FAULT1, + .min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1), + }, + { + .data = LTC2992_G2, + .max = LTC2992_G2_MAX, + .min = LTC2992_G2_MIN, + .max_thresh = LTC2992_G2_MAX_THRESH, + .min_thresh = LTC2992_G2_MIN_THRESH, + .alarm = LTC2992_FAULT2, + .min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1), + }, + { + .data = LTC2992_G3, + .max = LTC2992_G3_MAX, + .min = LTC2992_G3_MIN, + .max_thresh = LTC2992_G3_MAX_THRESH, + .min_thresh = LTC2992_G3_MIN_THRESH, + .alarm = LTC2992_FAULT3, + .min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1), + }, + { + .data = LTC2992_G4, + .max = LTC2992_G4_MAX, + .min = LTC2992_G4_MIN, + .max_thresh = LTC2992_G4_MAX_THRESH, + .min_thresh = LTC2992_G4_MIN_THRESH, + .alarm = LTC2992_FAULT3, + .min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0), + .max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1), + }, +}; + +static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len) +{ + u8 regvals[4]; + int ret; + int val; + int i; + + ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len); + if (ret < 0) + return ret; + + val = 0; + for (i = 0; i < reg_len; i++) + val |= regvals[reg_len - i - 1] << (i * 8); + + return val; +} + +static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len, u32 val) +{ + u8 regvals[4]; + int i; + + for (i = 0; i < reg_len; i++) + regvals[reg_len - i - 1] = (val >> (i * 8)) & 0xFF; + + return regmap_bulk_write(st->regmap, addr, regvals, reg_len); +} + +static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct ltc2992_state *st = data; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_in_reset_history: + return 0200; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_lowest: + case hwmon_in_highest: + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return 0444; + case hwmon_in_min: + case hwmon_in_max: + return 0644; + } + break; + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_lowest: + case hwmon_curr_highest: + case hwmon_curr_min_alarm: + case hwmon_curr_max_alarm: + if (st->r_sense_uohm[channel]) + return 0444; + break; + case hwmon_curr_min: + case hwmon_curr_max: + if (st->r_sense_uohm[channel]) + return 0644; + break; + } + break; + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_input_lowest: + case hwmon_power_input_highest: + case hwmon_power_min_alarm: + case hwmon_power_max_alarm: + if (st->r_sense_uohm[channel]) + return 0444; + break; + case hwmon_power_min: + case hwmon_power_max: + if (st->r_sense_uohm[channel]) + return 0644; + break; + } + break; + default: + break; + } + + return 0; +} + +static int ltc2992_get_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long *val) +{ + int reg_val; + + reg_val = ltc2992_read_reg(st, reg, 2); + if (reg_val < 0) + return reg_val; + + reg_val = reg_val >> 4; + *val = DIV_ROUND_CLOSEST(reg_val * scale, 1000); + + return 0; +} + +static int ltc2992_set_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long val) +{ + val = DIV_ROUND_CLOSEST(val * 1000, scale); + val = val << 4; + + return ltc2992_write_reg(st, reg, 2, val); +} + +static int ltc2992_read_gpio_alarm(struct ltc2992_state *st, int nr_gpio, u32 attr, long *val) +{ + int reg_val; + u32 mask; + + if (attr == hwmon_in_max_alarm) + mask = ltc2992_gpio_addr_map[nr_gpio].max_alarm_msk; + else + mask = ltc2992_gpio_addr_map[nr_gpio].min_alarm_msk; + + reg_val = ltc2992_read_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + reg_val &= ~mask; + + return ltc2992_write_reg(st, ltc2992_gpio_addr_map[nr_gpio].alarm, 1, reg_val); +} + +static int ltc2992_read_gpios_in(struct device *dev, u32 attr, int nr_gpio, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_in_input: + reg = ltc2992_gpio_addr_map[nr_gpio].data; + break; + case hwmon_in_lowest: + reg = ltc2992_gpio_addr_map[nr_gpio].min; + break; + case hwmon_in_highest: + reg = ltc2992_gpio_addr_map[nr_gpio].max; + break; + case hwmon_in_min: + reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh; + break; + case hwmon_in_max: + reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh; + break; + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return ltc2992_read_gpio_alarm(st, nr_gpio, attr, val); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val); +} + +static int ltc2992_read_in_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) +{ + u32 reg_val; + u32 mask; + + if (attr == hwmon_in_max_alarm) + mask = LTC2992_SENSE_FAULT_MSK(1); + else + mask = LTC2992_SENSE_FAULT_MSK(0); + + reg_val = ltc2992_read_reg(st, LTC2992_SENSE_FAULT(channel), 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + reg_val &= ~mask; + + return ltc2992_write_reg(st, LTC2992_SENSE_FAULT(channel), 1, reg_val); +} + +static int ltc2992_read_in(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + if (channel > 1) + return ltc2992_read_gpios_in(dev, attr, channel - 2, val); + + switch (attr) { + case hwmon_in_input: + reg = LTC2992_SENSE(channel); + break; + case hwmon_in_lowest: + reg = LTC2992_SENSE_MIN(channel); + break; + case hwmon_in_highest: + reg = LTC2992_SENSE_MAX(channel); + break; + case hwmon_in_min: + reg = LTC2992_SENSE_MIN_THRESH(channel); + break; + case hwmon_in_max: + reg = LTC2992_SENSE_MAX_THRESH(channel); + break; + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return ltc2992_read_in_alarm(st, channel, val, attr); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_voltage(st, reg, LTC2992_VADC_UV_LSB, val); +} + +static int ltc2992_get_current(struct ltc2992_state *st, u32 reg, u32 channel, long *val) +{ + u32 reg_val; + + reg_val = ltc2992_read_reg(st, reg, 2); + if (reg_val < 0) + return reg_val; + + reg_val = reg_val >> 4; + *val = DIV_ROUND_CLOSEST(reg_val * LTC2992_IADC_NANOV_LSB, st->r_sense_uohm[channel]); + + return 0; +} + +static int ltc2992_set_current(struct ltc2992_state *st, u32 reg, u32 channel, long val) +{ + u32 reg_val; + + reg_val = DIV_ROUND_CLOSEST(val * st->r_sense_uohm[channel], LTC2992_IADC_NANOV_LSB); + reg_val = reg_val << 4; + + return ltc2992_write_reg(st, reg, 2, reg_val); +} + +static int ltc2992_read_curr_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) +{ + u32 reg_val; + u32 mask; + + if (attr == hwmon_curr_max_alarm) + mask = LTC2992_DSENSE_FAULT_MSK(1); + else + mask = LTC2992_DSENSE_FAULT_MSK(0); + + reg_val = ltc2992_read_reg(st, LTC2992_DSENSE_FAULT(channel), 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + + reg_val &= ~mask; + return ltc2992_write_reg(st, LTC2992_DSENSE_FAULT(channel), 1, reg_val); +} + +static int ltc2992_read_curr(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_curr_input: + reg = LTC2992_DSENSE(channel); + break; + case hwmon_curr_lowest: + reg = LTC2992_DSENSE_MIN(channel); + break; + case hwmon_curr_highest: + reg = LTC2992_DSENSE_MAX(channel); + break; + case hwmon_curr_min: + reg = LTC2992_DSENSE_MIN_THRESH(channel); + break; + case hwmon_curr_max: + reg = LTC2992_DSENSE_MAX_THRESH(channel); + break; + case hwmon_curr_min_alarm: + case hwmon_curr_max_alarm: + return ltc2992_read_curr_alarm(st, channel, val, attr); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_current(st, reg, channel, val); +} + +static int ltc2992_get_power(struct ltc2992_state *st, u32 reg, u32 channel, long *val) +{ + u32 reg_val; + + reg_val = ltc2992_read_reg(st, reg, 3); + if (reg_val < 0) + return reg_val; + + *val = mul_u64_u32_div(reg_val, LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB, + st->r_sense_uohm[channel] * 1000); + + return 0; +} + +static int ltc2992_set_power(struct ltc2992_state *st, u32 reg, u32 channel, long val) +{ + u32 reg_val; + + reg_val = mul_u64_u32_div(val, st->r_sense_uohm[channel] * 1000, + LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB); + + return ltc2992_write_reg(st, reg, 3, reg_val); +} + +static int ltc2992_read_power_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) +{ + u32 reg_val; + u32 mask; + + if (attr == hwmon_power_max_alarm) + mask = LTC2992_POWER_FAULT_MSK(1); + else + mask = LTC2992_POWER_FAULT_MSK(0); + + reg_val = ltc2992_read_reg(st, LTC2992_POWER_FAULT(channel), 1); + if (reg_val < 0) + return reg_val; + + *val = !!(reg_val & mask); + reg_val &= ~mask; + + return ltc2992_write_reg(st, LTC2992_POWER_FAULT(channel), 1, reg_val); +} + +static int ltc2992_read_power(struct device *dev, u32 attr, int channel, long *val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_power_input: + reg = LTC2992_POWER(channel); + break; + case hwmon_power_input_lowest: + reg = LTC2992_POWER_MIN(channel); + break; + case hwmon_power_input_highest: + reg = LTC2992_POWER_MAX(channel); + break; + case hwmon_power_min: + reg = LTC2992_POWER_MIN_THRESH(channel); + break; + case hwmon_power_max: + reg = LTC2992_POWER_MAX_THRESH(channel); + break; + case hwmon_power_min_alarm: + case hwmon_power_max_alarm: + return ltc2992_read_power_alarm(st, channel, val, attr); + default: + return -EOPNOTSUPP; + } + + return ltc2992_get_power(st, reg, channel, val); +} + +static int ltc2992_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) +{ + switch (type) { + case hwmon_in: + return ltc2992_read_in(dev, attr, channel, val); + case hwmon_curr: + return ltc2992_read_curr(dev, attr, channel, val); + case hwmon_power: + return ltc2992_read_power(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc2992_write_curr(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_curr_min: + reg = LTC2992_DSENSE_MIN_THRESH(channel); + break; + case hwmon_curr_max: + reg = LTC2992_DSENSE_MAX_THRESH(channel); + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_current(st, reg, channel, val); +} + +static int ltc2992_write_gpios_in(struct device *dev, u32 attr, int nr_gpio, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_in_min: + reg = ltc2992_gpio_addr_map[nr_gpio].min_thresh; + break; + case hwmon_in_max: + reg = ltc2992_gpio_addr_map[nr_gpio].max_thresh; + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_voltage(st, reg, LTC2992_VADC_GPIO_UV_LSB, val); +} + +static int ltc2992_write_in(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + if (channel > 1) + return ltc2992_write_gpios_in(dev, attr, channel - 2, val); + + switch (attr) { + case hwmon_in_min: + reg = LTC2992_SENSE_MIN_THRESH(channel); + break; + case hwmon_in_max: + reg = LTC2992_SENSE_MAX_THRESH(channel); + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_voltage(st, reg, LTC2992_VADC_UV_LSB, val); +} + +static int ltc2992_write_power(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + u32 reg; + + switch (attr) { + case hwmon_power_min: + reg = LTC2992_POWER_MIN_THRESH(channel); + break; + case hwmon_power_max: + reg = LTC2992_POWER_MAX_THRESH(channel); + break; + default: + return -EOPNOTSUPP; + } + + return ltc2992_set_power(st, reg, channel, val); +} + +static int ltc2992_write_chip(struct device *dev, u32 attr, int channel, long val) +{ + struct ltc2992_state *st = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_in_reset_history: + return regmap_update_bits(st->regmap, LTC2992_CTRLB, LTC2992_RESET_HISTORY, + LTC2992_RESET_HISTORY); + default: + return -EOPNOTSUPP; + } +} + +static int ltc2992_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + switch (type) { + case hwmon_chip: + return ltc2992_write_chip(dev, attr, channel, val); + case hwmon_in: + return ltc2992_write_in(dev, attr, channel, val); + case hwmon_curr: + return ltc2992_write_curr(dev, attr, channel, val); + case hwmon_power: + return ltc2992_write_power(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops ltc2992_hwmon_ops = { + .is_visible = ltc2992_is_visible, + .read = ltc2992_read, + .write = ltc2992_write, +}; + +static const u32 ltc2992_chip_config[] = { + HWMON_C_IN_RESET_HISTORY, + 0 +}; + +static const struct hwmon_channel_info ltc2992_chip = { + .type = hwmon_chip, + .config = ltc2992_chip_config, +}; + +static const u32 ltc2992_in_config[] = { + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info ltc2992_in = { + .type = hwmon_in, + .config = ltc2992_in_config, +}; + +static const u32 ltc2992_curr_config[] = { + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | + HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | + HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info ltc2992_curr = { + .type = hwmon_curr, + .config = ltc2992_curr_config, +}; + +static const u32 ltc2992_power_config[] = { + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | + HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | + HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info ltc2992_power = { + .type = hwmon_power, + .config = ltc2992_power_config, +}; + +static const struct hwmon_channel_info *ltc2992_info[] = { + <c2992_chip, + <c2992_in, + <c2992_curr, + <c2992_power, + NULL +}; + +static const struct hwmon_chip_info ltc2992_chip_info = { + .ops = <c2992_hwmon_ops, + .info = ltc2992_info, +}; + +static const struct regmap_config ltc2992_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xE8, +}; + +static int ltc2992_parse_dt(struct ltc2992_state *st) +{ + struct fwnode_handle *fwnode; + struct fwnode_handle *child; + u32 addr; + u32 val; + int ret; + + fwnode = dev_fwnode(&st->client->dev); + + fwnode_for_each_available_child_node(fwnode, child) { + ret = fwnode_property_read_u32(child, "reg", &addr); + if (ret < 0) + return ret; + + if (addr > 1) + return -EINVAL; + + ret = fwnode_property_read_u32(child, "shunt-resistor-micro-ohms", &val); + if (!ret) + st->r_sense_uohm[addr] = val; + } + + return 0; +} + +static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *hwmon_dev; + struct ltc2992_state *st; + int ret; + + st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->client = client; + st->regmap = devm_regmap_init_i2c(client, <c2992_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ltc2992_parse_dt(st); + if (ret < 0) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st, + <c2992_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id ltc2992_of_match[] = { + { .compatible = "adi,ltc2992" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2992_of_match); + +static const struct i2c_device_id ltc2992_i2c_id[] = { + {"ltc2992", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ltc2992_i2c_id); + +static struct i2c_driver ltc2992_i2c_driver = { + .driver = { + .name = "ltc2992", + .of_match_table = ltc2992_of_match, + }, + .probe = ltc2992_i2c_probe, + .id_table = ltc2992_i2c_id, +}; + +module_i2c_driver(ltc2992_i2c_driver); + +MODULE_AUTHOR("Alexandru Tachici "); +MODULE_DESCRIPTION("Hwmon driver for Linear Technology 2992"); +MODULE_LICENSE("Dual BSD/GPL"); From 9ca26df1ba25c362113d9bb7b08ca93e260ce3e8 Mon Sep 17 00:00:00 2001 From: Alexandru Tachici Date: Thu, 3 Dec 2020 09:11:54 +0200 Subject: [PATCH 26/41] hwmon: (ltc2992) Add support for GPIOs. LTC2992 has 4 open-drain GPIOS. This patch exports to user space the 4 GPIOs using the GPIO driver Linux API. Signed-off-by: Alexandru Tachici Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/ltc2992.c | 160 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d3e4dc00f442..49920a1af3d0 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -874,6 +874,7 @@ config SENSORS_LTC2990 config SENSORS_LTC2992 tristate "Linear Technology LTC2992" depends on I2C + depends on GPIOLIB help If you say yes here you get support for Linear Technology LTC2992 I2C System Monitor. The LTC2992 measures current, voltage, and diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index c11d585a9600..69dbb5aa5dc2 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,9 @@ #define LTC2992_G4_MAX_THRESH 0x74 #define LTC2992_G4_MIN_THRESH 0x76 #define LTC2992_FAULT3 0x92 +#define LTC2992_GPIO_STATUS 0x95 +#define LTC2992_GPIO_IO_CTRL 0x96 +#define LTC2992_GPIO_CTRL 0x97 #define LTC2992_POWER(x) (LTC2992_POWER1 + ((x) * 0x32)) #define LTC2992_POWER_MAX(x) (LTC2992_POWER1_MAX + ((x) * 0x32)) @@ -96,8 +100,18 @@ #define LTC2992_VADC_UV_LSB 25000 #define LTC2992_VADC_GPIO_UV_LSB 500 +#define LTC2992_GPIO_NR 4 +#define LTC2992_GPIO1_BIT 7 +#define LTC2992_GPIO2_BIT 6 +#define LTC2992_GPIO3_BIT 0 +#define LTC2992_GPIO4_BIT 6 +#define LTC2992_GPIO_BIT(x) (LTC2992_GPIO_NR - (x) - 1) + struct ltc2992_state { struct i2c_client *client; + struct gpio_chip gc; + struct mutex gpio_mutex; /* lock for gpio access */ + const char *gpio_names[LTC2992_GPIO_NR]; struct regmap *regmap; u32 r_sense_uohm[2]; }; @@ -111,6 +125,8 @@ struct ltc2992_gpio_regs { u8 alarm; u8 min_alarm_msk; u8 max_alarm_msk; + u8 ctrl; + u8 ctrl_bit; }; static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { @@ -123,6 +139,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT1, .min_alarm_msk = LTC2992_GPIO1_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO1_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_IO_CTRL, + .ctrl_bit = LTC2992_GPIO1_BIT, }, { .data = LTC2992_G2, @@ -133,6 +151,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT2, .min_alarm_msk = LTC2992_GPIO2_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO2_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_IO_CTRL, + .ctrl_bit = LTC2992_GPIO2_BIT, }, { .data = LTC2992_G3, @@ -143,6 +163,8 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT3, .min_alarm_msk = LTC2992_GPIO3_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO3_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_IO_CTRL, + .ctrl_bit = LTC2992_GPIO3_BIT, }, { .data = LTC2992_G4, @@ -153,14 +175,20 @@ static const struct ltc2992_gpio_regs ltc2992_gpio_addr_map[] = { .alarm = LTC2992_FAULT3, .min_alarm_msk = LTC2992_GPIO4_FAULT_MSK(0), .max_alarm_msk = LTC2992_GPIO4_FAULT_MSK(1), + .ctrl = LTC2992_GPIO_CTRL, + .ctrl_bit = LTC2992_GPIO4_BIT, }, }; +static const char *ltc2992_gpio_names[LTC2992_GPIO_NR] = { + "GPIO1", "GPIO2", "GPIO3", "GPIO4", +}; + static int ltc2992_read_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len) { u8 regvals[4]; - int ret; int val; + int ret; int i; ret = regmap_bulk_read(st->regmap, addr, regvals, reg_len); @@ -185,6 +213,132 @@ static int ltc2992_write_reg(struct ltc2992_state *st, u8 addr, const u8 reg_len return regmap_bulk_write(st->regmap, addr, regvals, reg_len); } +static int ltc2992_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_status; + int reg; + + mutex_lock(&st->gpio_mutex); + reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1); + mutex_unlock(&st->gpio_mutex); + + if (reg < 0) + return reg; + + gpio_status = reg; + + return !test_bit(LTC2992_GPIO_BIT(offset), &gpio_status); +} + +static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_status; + unsigned int gpio_nr; + int reg; + + mutex_lock(&st->gpio_mutex); + reg = ltc2992_read_reg(st, LTC2992_GPIO_STATUS, 1); + mutex_unlock(&st->gpio_mutex); + + if (reg < 0) + return reg; + + gpio_status = reg; + + gpio_nr = 0; + for_each_set_bit_from(gpio_nr, mask, LTC2992_GPIO_NR) { + if (test_bit(LTC2992_GPIO_BIT(gpio_nr), &gpio_status)) + set_bit(gpio_nr, bits); + } + + return 0; +} + +static void ltc2992_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_ctrl; + int reg; + + mutex_lock(&st->gpio_mutex); + reg = ltc2992_read_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1); + if (reg < 0) { + mutex_unlock(&st->gpio_mutex); + return; + } + + gpio_ctrl = reg; + assign_bit(ltc2992_gpio_addr_map[offset].ctrl_bit, &gpio_ctrl, value); + + ltc2992_write_reg(st, ltc2992_gpio_addr_map[offset].ctrl, 1, gpio_ctrl); + mutex_unlock(&st->gpio_mutex); +} + +static void ltc2992_gpio_set_multiple(struct gpio_chip *chip, unsigned long *mask, + unsigned long *bits) +{ + struct ltc2992_state *st = gpiochip_get_data(chip); + unsigned long gpio_ctrl_io = 0; + unsigned long gpio_ctrl = 0; + unsigned int gpio_nr; + + for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) { + if (gpio_nr < 3) + assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl_io, true); + + if (gpio_nr == 3) + assign_bit(ltc2992_gpio_addr_map[gpio_nr].ctrl_bit, &gpio_ctrl, true); + } + + mutex_lock(&st->gpio_mutex); + ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, gpio_ctrl_io); + ltc2992_write_reg(st, LTC2992_GPIO_CTRL, 1, gpio_ctrl); + mutex_unlock(&st->gpio_mutex); +} + +static int ltc2992_config_gpio(struct ltc2992_state *st) +{ + const char *name = dev_name(&st->client->dev); + char *gpio_name; + int ret; + int i; + + ret = ltc2992_write_reg(st, LTC2992_GPIO_IO_CTRL, 1, 0); + if (ret < 0) + return ret; + + mutex_init(&st->gpio_mutex); + + for (i = 0; i < ARRAY_SIZE(st->gpio_names); i++) { + gpio_name = devm_kasprintf(&st->client->dev, GFP_KERNEL, "ltc2992-%x-%s", + st->client->addr, ltc2992_gpio_names[i]); + if (!gpio_name) + return -ENOMEM; + + st->gpio_names[i] = gpio_name; + } + + st->gc.label = name; + st->gc.parent = &st->client->dev; + st->gc.owner = THIS_MODULE; + st->gc.base = -1; + st->gc.names = st->gpio_names; + st->gc.ngpio = ARRAY_SIZE(st->gpio_names); + st->gc.get = ltc2992_gpio_get; + st->gc.get_multiple = ltc2992_gpio_get_multiple; + st->gc.set = ltc2992_gpio_set; + st->gc.set_multiple = ltc2992_gpio_set_multiple; + + ret = devm_gpiochip_add_data(&st->client->dev, &st->gc, st); + if (ret) + dev_err(&st->client->dev, "GPIO registering failed (%d)\n", ret); + + return ret; +} + static umode_t ltc2992_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { @@ -779,6 +933,10 @@ static int ltc2992_i2c_probe(struct i2c_client *client, const struct i2c_device_ if (ret < 0) return ret; + ret = ltc2992_config_gpio(st); + if (ret < 0) + return ret; + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, client->name, st, <c2992_chip_info, NULL); From fe8c573c89ebfd8ff1fd099704ad4ced1a9dfba9 Mon Sep 17 00:00:00 2001 From: Alexandru Tachici Date: Thu, 3 Dec 2020 09:11:55 +0200 Subject: [PATCH 27/41] dt-bindings: hwmon: Add documentation for ltc2992 Add documentation for ltc2992. Signed-off-by: Alexandru Tachici Reviewed-by: Rob Herring Signed-off-by: Guenter Roeck --- .../bindings/hwmon/adi,ltc2992.yaml | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml new file mode 100644 index 000000000000..64a8fcb7bc46 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2992.yaml @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,ltc2992.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Linear Technology 2992 Power Monitor + +maintainers: + - Alexandru Tachici + +description: | + Linear Technology 2992 Dual Wide Range Power Monitor + https://www.analog.com/media/en/technical-documentation/data-sheets/ltc2992.pdf + +properties: + compatible: + enum: + - adi,ltc2992 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + avcc-supply: true + +patternProperties: + "^channel@([0-1])$": + type: object + description: | + Represents the two supplies to be monitored. + + properties: + reg: + description: | + The channel number. LTC2992 can monitor two supplies. + items: + minimum: 0 + maximum: 1 + + shunt-resistor-micro-ohms: + description: + The value of curent sense resistor in microohms. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c1 { + #address-cells = <1>; + #size-cells = <0>; + + ltc2992@6F { + #address-cells = <1>; + #size-cells = <0>; + + compatible = "adi,ltc2992"; + reg = <0x6F>; + + channel@0 { + reg = <0x0>; + shunt-resistor-micro-ohms = <10000>; + }; + + channel@1 { + reg = <0x1>; + shunt-resistor-micro-ohms = <10000>; + }; + }; + }; +... From 966ead9153c25ee08d14a494585df4439b210474 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Mon, 7 Dec 2020 16:26:58 +0100 Subject: [PATCH 28/41] hwmon: (pmbus/q54sj108a2) Correct title underline length Commit b722d7b9e4da ("hwmon: (pmbus) Driver for Delta power supplies Q54SJ108A2") provides new documentation for DELTA Q54SJ108A2NC* drivers, but the title underline was too short. make htmldocs warns: Documentation/hwmon/q54sj108a2.rst:4: WARNING: Title underline too short. Adjust the title underline to the correct length. Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20201207152658.32444-1-lukas.bulwahn@gmail.com [groeck: Adjust subject] Signed-off-by: Guenter Roeck --- Documentation/hwmon/q54sj108a2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/q54sj108a2.rst b/Documentation/hwmon/q54sj108a2.rst index da925e343e79..f95d81382a9f 100644 --- a/Documentation/hwmon/q54sj108a2.rst +++ b/Documentation/hwmon/q54sj108a2.rst @@ -1,7 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0-or-later Kernel driver q54sj108a2 -===================== +======================== Supported chips: From e1d15969635ca61aa12091a53dc5d148e5621782 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 7 Dec 2020 14:24:10 +0000 Subject: [PATCH 29/41] hwmon: (ltc2992) Fix less than zero comparisons with an unsigned integer There are several occurrances of a less than zero error check on a u32 unsigned integer. These will never be true. Fix this by making reg_value a plain int. Addresses-Coverity: ("Unsigned comparison against zero") Fixes: e126370240e0 ("hwmon: (ltc2992) Add support") Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20201207142410.168987-1-colin.king@canonical.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc2992.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index 69dbb5aa5dc2..4382105bf142 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -480,7 +480,7 @@ static int ltc2992_read_gpios_in(struct device *dev, u32 attr, int nr_gpio, long static int ltc2992_read_in_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) { - u32 reg_val; + int reg_val; u32 mask; if (attr == hwmon_in_max_alarm) @@ -534,7 +534,7 @@ static int ltc2992_read_in(struct device *dev, u32 attr, int channel, long *val) static int ltc2992_get_current(struct ltc2992_state *st, u32 reg, u32 channel, long *val) { - u32 reg_val; + int reg_val; reg_val = ltc2992_read_reg(st, reg, 2); if (reg_val < 0) @@ -558,7 +558,7 @@ static int ltc2992_set_current(struct ltc2992_state *st, u32 reg, u32 channel, l static int ltc2992_read_curr_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) { - u32 reg_val; + int reg_val; u32 mask; if (attr == hwmon_curr_max_alarm) @@ -609,7 +609,7 @@ static int ltc2992_read_curr(struct device *dev, u32 attr, int channel, long *va static int ltc2992_get_power(struct ltc2992_state *st, u32 reg, u32 channel, long *val) { - u32 reg_val; + int reg_val; reg_val = ltc2992_read_reg(st, reg, 3); if (reg_val < 0) @@ -633,7 +633,7 @@ static int ltc2992_set_power(struct ltc2992_state *st, u32 reg, u32 channel, lon static int ltc2992_read_power_alarm(struct ltc2992_state *st, int channel, long *val, u32 attr) { - u32 reg_val; + int reg_val; u32 mask; if (attr == hwmon_power_max_alarm) From 94f1ab9445652551b3414be6b27b17fd90cb077d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 17 Nov 2020 23:08:04 +0100 Subject: [PATCH 30/41] dt-bindings: hwmon: convert TI INA2xx bindings to dt-schema Convert the TI INA2xx bindings to dt-schema. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20201117220807.208747-1-krzk@kernel.org Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ina2xx.txt | 24 -------- .../devicetree/bindings/hwmon/ti,ina2xx.yaml | 55 +++++++++++++++++++ MAINTAINERS | 2 +- 3 files changed, 56 insertions(+), 25 deletions(-) delete mode 100644 Documentation/devicetree/bindings/hwmon/ina2xx.txt create mode 100644 Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml diff --git a/Documentation/devicetree/bindings/hwmon/ina2xx.txt b/Documentation/devicetree/bindings/hwmon/ina2xx.txt deleted file mode 100644 index 02af0d94e921..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ina2xx.txt +++ /dev/null @@ -1,24 +0,0 @@ -ina2xx properties - -Required properties: -- compatible: Must be one of the following: - - "ti,ina209" for ina209 - - "ti,ina219" for ina219 - - "ti,ina220" for ina220 - - "ti,ina226" for ina226 - - "ti,ina230" for ina230 - - "ti,ina231" for ina231 -- reg: I2C address - -Optional properties: - -- shunt-resistor - Shunt resistor value in micro-Ohm - -Example: - -ina220@44 { - compatible = "ti,ina220"; - reg = <0x44>; - shunt-resistor = <1000>; -}; diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml new file mode 100644 index 000000000000..6f0443322a36 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/ti,ina2xx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments INA209 family of power/voltage monitors + +maintainers: + - Krzysztof Kozlowski + +description: | + The INA209 is a high-side current shunt and power monitor with + an I2C interface. + + Datasheets: + https://www.ti.com/product/INA209 + +properties: + compatible: + enum: + - ti,ina209 + - ti,ina219 + - ti,ina220 + - ti,ina226 + - ti,ina230 + - ti,ina231 + + reg: + maxItems: 1 + + shunt-resistor: + description: + Shunt resistor value in micro-Ohm. + $ref: /schemas/types.yaml#/definitions/uint32 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + power-sensor@44 { + compatible = "ti,ina220"; + reg = <0x44>; + shunt-resistor = <1000>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 65245db06452..7e6d48559b26 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8623,7 +8623,7 @@ INA209 HARDWARE MONITOR DRIVER M: Guenter Roeck L: linux-hwmon@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/hwmon/ina2xx.txt +F: Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml F: Documentation/hwmon/ina209.rst F: drivers/hwmon/ina209.c From 27b4bc2b877ea085d6a3935ab1ca1b2e07c9123d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 17 Nov 2020 23:08:05 +0100 Subject: [PATCH 31/41] dt-bindings: hwmon: convert AD AD741x bindings to dt-schema Convert the Analog Devices AD741x bindings to dt-schema. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20201117220807.208747-2-krzk@kernel.org Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ad741x.txt | 15 ------- .../devicetree/bindings/hwmon/adi,ad741x.yaml | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 15 deletions(-) delete mode 100644 Documentation/devicetree/bindings/hwmon/ad741x.txt create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ad741x.yaml diff --git a/Documentation/devicetree/bindings/hwmon/ad741x.txt b/Documentation/devicetree/bindings/hwmon/ad741x.txt deleted file mode 100644 index 9102152c8410..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ad741x.txt +++ /dev/null @@ -1,15 +0,0 @@ -* AD7416/AD7417/AD7418 Temperature Sensor Device Tree Bindings - -Required properties: -- compatible: one of - "adi,ad7416" - "adi,ad7417" - "adi,ad7418" -- reg: I2C address - -Example: - -hwmon@28 { - compatible = "adi,ad7418"; - reg = <0x28>; -}; diff --git a/Documentation/devicetree/bindings/hwmon/adi,ad741x.yaml b/Documentation/devicetree/bindings/hwmon/adi,ad741x.yaml new file mode 100644 index 000000000000..ce7f8ce9da0a --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,ad741x.yaml @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/adi,ad741x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD7416/AD7417/AD7418 temperature sensors + +maintainers: + - Krzysztof Kozlowski + +properties: + compatible: + enum: + - adi,ad7416 + - adi,ad7417 + - adi,ad7418 + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@28 { + compatible = "adi,ad7418"; + reg = <0x28>; + }; + }; From 2f070176a0a47495c1dc49473e4b591779c073d4 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 17 Nov 2020 23:08:06 +0100 Subject: [PATCH 32/41] dt-bindings: hwmon: convert TI ADS7828 bindings to dt-schema Convert the TI ADS7828 bindings to dt-schema. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20201117220807.208747-3-krzk@kernel.org Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ads7828.txt | 25 -------- .../devicetree/bindings/hwmon/ti,ads7828.yaml | 57 +++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 4 -- 3 files changed, 57 insertions(+), 29 deletions(-) delete mode 100644 Documentation/devicetree/bindings/hwmon/ads7828.txt create mode 100644 Documentation/devicetree/bindings/hwmon/ti,ads7828.yaml diff --git a/Documentation/devicetree/bindings/hwmon/ads7828.txt b/Documentation/devicetree/bindings/hwmon/ads7828.txt deleted file mode 100644 index fe0cc4ad7ea9..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ads7828.txt +++ /dev/null @@ -1,25 +0,0 @@ -ads7828 properties - -Required properties: -- compatible: Should be one of - ti,ads7828 - ti,ads7830 -- reg: I2C address - -Optional properties: - -- ti,differential-input - Set to use the device in differential mode. -- vref-supply - The external reference on the device is set to this regulators output. If it - does not exists the internal reference will be used and output by the ads78xx - on the "external vref" pin. - - Example ADS7828 node: - - ads7828: ads@48 { - comatible = "ti,ads7828"; - reg = <0x48>; - vref-supply = <&vref>; - ti,differential-input; - }; diff --git a/Documentation/devicetree/bindings/hwmon/ti,ads7828.yaml b/Documentation/devicetree/bindings/hwmon/ti,ads7828.yaml new file mode 100644 index 000000000000..33ee575bb09d --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/ti,ads7828.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/ti,ads7828.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS7828/ADS7830 Analog to Digital Converter (ADC) + +maintainers: + - Krzysztof Kozlowski + +description: | + The ADS7828 is 12-Bit, 8-Channel Sampling Analog to Digital Converter (ADC) + with an I2C interface. + + Datasheets: + https://www.ti.com/product/ADS7828 + +properties: + compatible: + enum: + - ti,ads7828 + - ti,ads7830 + + reg: + maxItems: 1 + + ti,differential-input: + description: + Set to use the device in differential mode. + type: boolean + + vref-supply: + description: + The regulator to use as an external reference. If it does not exists the + internal reference will be used. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + adc@48 { + comatible = "ti,ads7828"; + reg = <0x48>; + vref-supply = <&vref>; + ti,differential-input; + }; + }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index ab623ba930d5..1b1fa0424539 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -350,10 +350,6 @@ properties: - st,24c256 # Ambient Light Sensor with SMBUS/Two Wire Serial Interface - taos,tsl2550 - # 8-Channels, 12-bit ADC - - ti,ads7828 - # 8-Channels, 8-bit ADC - - ti,ads7830 # Temperature Monitoring and Fan Control - ti,amc6821 # Temperature sensor with 2-wire interface From eedc65cb083bab7faf0501bcf528fc236384f302 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Fri, 20 Nov 2020 11:33:13 +1030 Subject: [PATCH 33/41] dt-bindings: fsi: Add P10 OCC device documentation Add the P10 compatible string. Signed-off-by: Eddie James Reviewed-by: Joel Stanley Signed-off-by: Joel Stanley Acked-by: Rob Herring Link: https://lore.kernel.org/r/20201120010315.190737-2-joel@jms.id.au Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt index 99ca9862a586..e73358075a90 100644 --- a/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt +++ b/Documentation/devicetree/bindings/fsi/ibm,p9-occ.txt @@ -1,13 +1,13 @@ -Device-tree bindings for FSI-attached POWER9 On-Chip Controller (OCC) ---------------------------------------------------------------------- +Device-tree bindings for FSI-attached POWER9/POWER10 On-Chip Controller (OCC) +----------------------------------------------------------------------------- -This is the binding for the P9 On-Chip Controller accessed over FSI from a -service processor. See fsi.txt for details on bindings for FSI slave and CFAM +This is the binding for the P9 or P10 On-Chip Controller accessed over FSI from +a service processor. See fsi.txt for details on bindings for FSI slave and CFAM nodes. The OCC is not an FSI slave device itself, rather it is accessed -through the SBE fifo. +through the SBE FIFO. Required properties: - - compatible = "ibm,p9-occ" + - compatible = "ibm,p9-occ" or "ibm,p10-occ" Examples: From 5ec96d74cf28c939e01b829f556832949b4c272a Mon Sep 17 00:00:00 2001 From: Eddie James Date: Fri, 20 Nov 2020 11:33:14 +1030 Subject: [PATCH 34/41] fsi: occ: Add support for P10 The P10 OCC has a different SRAM address for the command and response buffers. In addition, the SBE commands to access the SRAM have changed format. Add versioning to the driver to handle these differences. Signed-off-by: Eddie James Reviewed-by: Joel Stanley Signed-off-by: Joel Stanley Link: https://lore.kernel.org/r/20201120010315.190737-3-joel@jms.id.au Signed-off-by: Guenter Roeck --- drivers/fsi/fsi-occ.c | 127 ++++++++++++++++++++++++++++++------------ 1 file changed, 92 insertions(+), 35 deletions(-) diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c index 9eeb856c8905..10ca2e290655 100644 --- a/drivers/fsi/fsi-occ.c +++ b/drivers/fsi/fsi-occ.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -24,8 +25,13 @@ #define OCC_CMD_DATA_BYTES 4090 #define OCC_RESP_DATA_BYTES 4089 -#define OCC_SRAM_CMD_ADDR 0xFFFBE000 -#define OCC_SRAM_RSP_ADDR 0xFFFBF000 +#define OCC_P9_SRAM_CMD_ADDR 0xFFFBE000 +#define OCC_P9_SRAM_RSP_ADDR 0xFFFBF000 + +#define OCC_P10_SRAM_CMD_ADDR 0xFFFFD000 +#define OCC_P10_SRAM_RSP_ADDR 0xFFFFE000 + +#define OCC_P10_SRAM_MODE 0x58 /* Normal mode, OCB channel 2 */ /* * Assume we don't have much FFDC, if we do we'll overflow and @@ -37,11 +43,14 @@ #define OCC_TIMEOUT_MS 1000 #define OCC_CMD_IN_PRG_WAIT_MS 50 +enum versions { occ_p9, occ_p10 }; + struct occ { struct device *dev; struct device *sbefifo; char name[32]; int idx; + enum versions version; struct miscdevice mdev; struct mutex occ_lock; }; @@ -235,29 +244,43 @@ static int occ_verify_checksum(struct occ_response *resp, u16 data_length) return 0; } -static int occ_getsram(struct occ *occ, u32 address, void *data, ssize_t len) +static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len) { u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */ - size_t resp_len, resp_data_len; - __be32 *resp, cmd[5]; - int rc; + size_t cmd_len, resp_len, resp_data_len; + __be32 *resp, cmd[6]; + int idx = 0, rc; /* * Magic sequence to do SBE getsram command. SBE will fetch data from * specified SRAM address. */ - cmd[0] = cpu_to_be32(0x5); + switch (occ->version) { + default: + case occ_p9: + cmd_len = 5; + cmd[2] = cpu_to_be32(1); /* Normal mode */ + cmd[3] = cpu_to_be32(OCC_P9_SRAM_RSP_ADDR + offset); + break; + case occ_p10: + idx = 1; + cmd_len = 6; + cmd[2] = cpu_to_be32(OCC_P10_SRAM_MODE); + cmd[3] = 0; + cmd[4] = cpu_to_be32(OCC_P10_SRAM_RSP_ADDR + offset); + break; + } + + cmd[0] = cpu_to_be32(cmd_len); cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM); - cmd[2] = cpu_to_be32(1); - cmd[3] = cpu_to_be32(address); - cmd[4] = cpu_to_be32(data_len); + cmd[4 + idx] = cpu_to_be32(data_len); resp_len = (data_len >> 2) + OCC_SBE_STATUS_WORDS; resp = kzalloc(resp_len << 2, GFP_KERNEL); if (!resp) return -ENOMEM; - rc = sbefifo_submit(occ->sbefifo, cmd, 5, resp, &resp_len); + rc = sbefifo_submit(occ->sbefifo, cmd, cmd_len, resp, &resp_len); if (rc) goto free; @@ -287,20 +310,21 @@ free: return rc; } -static int occ_putsram(struct occ *occ, u32 address, const void *data, - ssize_t len) +static int occ_putsram(struct occ *occ, const void *data, ssize_t len) { size_t cmd_len, buf_len, resp_len, resp_data_len; u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */ __be32 *buf; - int rc; + int idx = 0, rc; + + cmd_len = (occ->version == occ_p10) ? 6 : 5; /* * We use the same buffer for command and response, make * sure it's big enough */ resp_len = OCC_SBE_STATUS_WORDS; - cmd_len = (data_len >> 2) + 5; + cmd_len += data_len >> 2; buf_len = max(cmd_len, resp_len); buf = kzalloc(buf_len << 2, GFP_KERNEL); if (!buf) @@ -312,11 +336,23 @@ static int occ_putsram(struct occ *occ, u32 address, const void *data, */ buf[0] = cpu_to_be32(cmd_len); buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM); - buf[2] = cpu_to_be32(1); - buf[3] = cpu_to_be32(address); - buf[4] = cpu_to_be32(data_len); - memcpy(&buf[5], data, len); + switch (occ->version) { + default: + case occ_p9: + buf[2] = cpu_to_be32(1); /* Normal mode */ + buf[3] = cpu_to_be32(OCC_P9_SRAM_CMD_ADDR); + break; + case occ_p10: + idx = 1; + buf[2] = cpu_to_be32(OCC_P10_SRAM_MODE); + buf[3] = 0; + buf[4] = cpu_to_be32(OCC_P10_SRAM_CMD_ADDR); + break; + } + + buf[4 + idx] = cpu_to_be32(data_len); + memcpy(&buf[5 + idx], data, len); rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len); if (rc) @@ -356,21 +392,35 @@ free: static int occ_trigger_attn(struct occ *occ) { __be32 buf[OCC_SBE_STATUS_WORDS]; - size_t resp_len, resp_data_len; - int rc; + size_t cmd_len, resp_len, resp_data_len; + int idx = 0, rc; - BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 7); + BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 8); resp_len = OCC_SBE_STATUS_WORDS; - buf[0] = cpu_to_be32(0x5 + 0x2); /* Chip-op length in words */ - buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM); - buf[2] = cpu_to_be32(0x3); /* Mode: Circular */ - buf[3] = cpu_to_be32(0x0); /* Address: ignore in mode 3 */ - buf[4] = cpu_to_be32(0x8); /* Data length in bytes */ - buf[5] = cpu_to_be32(0x20010000); /* Trigger OCC attention */ - buf[6] = 0; + switch (occ->version) { + default: + case occ_p9: + cmd_len = 7; + buf[2] = cpu_to_be32(3); /* Circular mode */ + buf[3] = 0; + break; + case occ_p10: + idx = 1; + cmd_len = 8; + buf[2] = cpu_to_be32(0xd0); /* Circular mode, OCB Channel 1 */ + buf[3] = 0; + buf[4] = 0; + break; + } - rc = sbefifo_submit(occ->sbefifo, buf, 7, buf, &resp_len); + buf[0] = cpu_to_be32(cmd_len); /* Chip-op length in words */ + buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM); + buf[4 + idx] = cpu_to_be32(8); /* Data length in bytes */ + buf[5 + idx] = cpu_to_be32(0x20010000); /* Trigger OCC attention */ + buf[6 + idx] = 0; + + rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len); if (rc) goto error; @@ -429,7 +479,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, /* Extract the seq_no from the command (first byte) */ seq_no = *(const u8 *)request; - rc = occ_putsram(occ, OCC_SRAM_CMD_ADDR, request, req_len); + rc = occ_putsram(occ, request, req_len); if (rc) goto done; @@ -440,7 +490,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, /* Read occ response header */ start = jiffies; do { - rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR, resp, 8); + rc = occ_getsram(occ, 0, resp, 8); if (rc) goto done; @@ -476,8 +526,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, /* Grab the rest */ if (resp_data_length > 1) { /* already got 3 bytes resp, also need 2 bytes checksum */ - rc = occ_getsram(occ, OCC_SRAM_RSP_ADDR + 8, - &resp->data[3], resp_data_length - 1); + rc = occ_getsram(occ, 8, &resp->data[3], resp_data_length - 1); if (rc) goto done; } @@ -517,6 +566,7 @@ static int occ_probe(struct platform_device *pdev) if (!occ) return -ENOMEM; + occ->version = (uintptr_t)of_device_get_match_data(dev); occ->dev = dev; occ->sbefifo = dev->parent; mutex_init(&occ->occ_lock); @@ -575,7 +625,14 @@ static int occ_remove(struct platform_device *pdev) } static const struct of_device_id occ_match[] = { - { .compatible = "ibm,p9-occ" }, + { + .compatible = "ibm,p9-occ", + .data = (void *)occ_p9 + }, + { + .compatible = "ibm,p10-occ", + .data = (void *)occ_p10 + }, { }, }; From db4919ec86ff405273a767e1a9b51e2760e73ce5 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Fri, 20 Nov 2020 11:33:15 +1030 Subject: [PATCH 35/41] hwmon: (occ) Add new temperature sensor type The latest version of the On-Chip Controller (OCC) has a different format for the temperature sensor data. Add a new temperature sensor version to handle this data. Signed-off-by: Eddie James Reviewed-by: Joel Stanley Signed-off-by: Joel Stanley Link: https://lore.kernel.org/r/20201120010315.190737-4-joel@jms.id.au Signed-off-by: Guenter Roeck --- drivers/hwmon/occ/common.c | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index a71777990d49..7a5e539b567b 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -41,6 +41,14 @@ struct temp_sensor_2 { u8 value; } __packed; +struct temp_sensor_10 { + u32 sensor_id; + u8 fru_type; + u8 value; + u8 throttle; + u8 reserved; +} __packed; + struct freq_sensor_1 { u16 sensor_id; u16 value; @@ -307,6 +315,60 @@ static ssize_t occ_show_temp_2(struct device *dev, return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); } +static ssize_t occ_show_temp_10(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct temp_sensor_10 *temp; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + temp = ((struct temp_sensor_10 *)sensors->temp.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be32(&temp->sensor_id); + break; + case 1: + val = temp->value; + if (val == OCC_TEMP_SENSOR_FAULT) + return -EREMOTEIO; + + /* + * VRM doesn't return temperature, only alarm bit. This + * attribute maps to tempX_alarm instead of tempX_input for + * VRM + */ + if (temp->fru_type != OCC_FRU_TYPE_VRM) { + /* sensor not ready */ + if (val == 0) + return -EAGAIN; + + val *= 1000; + } + break; + case 2: + val = temp->fru_type; + break; + case 3: + val = temp->value == OCC_TEMP_SENSOR_FAULT; + break; + case 4: + val = temp->throttle * 1000; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + static ssize_t occ_show_freq_1(struct device *dev, struct device_attribute *attr, char *buf) { @@ -745,6 +807,10 @@ static int occ_setup_sensor_attrs(struct occ *occ) num_attrs += (sensors->temp.num_sensors * 4); show_temp = occ_show_temp_2; break; + case 0x10: + num_attrs += (sensors->temp.num_sensors * 5); + show_temp = occ_show_temp_10; + break; default: sensors->temp.num_sensors = 0; } @@ -844,6 +910,15 @@ static int occ_setup_sensor_attrs(struct occ *occ) attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL, 3, i); attr++; + + if (sensors->temp.version == 0x10) { + snprintf(attr->name, sizeof(attr->name), + "temp%d_max", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_temp, NULL, + 4, i); + attr++; + } } } From 15d4a6f736c446231907e9f04ce6fc4b57c32aa2 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 17 Nov 2020 23:08:07 +0100 Subject: [PATCH 36/41] dt-bindings: hwmon: convert AD ADM1275 bindings to dt-schema Convert the Analog Devices ADM1275 bindings to dt-schema. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20201117220807.208747-4-krzk@kernel.org [groeck: Removed unnecessary type ($ref) declaration] Signed-off-by: Guenter Roeck --- .../bindings/hwmon/adi,adm1275.yaml | 57 +++++++++++++++++++ .../devicetree/bindings/hwmon/adm1275.txt | 25 -------- Documentation/hwmon/adm1275.rst | 2 +- 3 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml delete mode 100644 Documentation/devicetree/bindings/hwmon/adm1275.txt diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml new file mode 100644 index 000000000000..223393d7cafd --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/adi,adm1275.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADM1075/ADM127x/ADM129x digital power monitors + +maintainers: + - Krzysztof Kozlowski + +description: | + The ADM1293 and ADM1294 are high accuracy integrated digital power monitors + that offer digital current, voltage, and power monitoring using an on-chip, + 12-bit analog-to-digital converter (ADC), communicated through a PMBus + compliant I2C interface. + + Datasheets: + https://www.analog.com/en/products/adm1294.html + +properties: + compatible: + enum: + - adi,adm1075 + - adi,adm1272 + - adi,adm1275 + - adi,adm1276 + - adi,adm1278 + - adi,adm1293 + - adi,adm1294 + + reg: + maxItems: 1 + + shunt-resistor-micro-ohms: + description: + Shunt resistor value in micro-Ohm. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + power-sensor@10 { + compatible = "adi,adm1272"; + reg = <0x10>; + shunt-resistor-micro-ohms = <500>; + }; + }; diff --git a/Documentation/devicetree/bindings/hwmon/adm1275.txt b/Documentation/devicetree/bindings/hwmon/adm1275.txt deleted file mode 100644 index 1ecd03f3da4d..000000000000 --- a/Documentation/devicetree/bindings/hwmon/adm1275.txt +++ /dev/null @@ -1,25 +0,0 @@ -adm1275 properties - -Required properties: -- compatible: Must be one of the supported compatible strings: - - "adi,adm1075" for adm1075 - - "adi,adm1272" for adm1272 - - "adi,adm1275" for adm1275 - - "adi,adm1276" for adm1276 - - "adi,adm1278" for adm1278 - - "adi,adm1293" for adm1293 - - "adi,adm1294" for adm1294 -- reg: I2C address - -Optional properties: - -- shunt-resistor-micro-ohms - Shunt resistor value in micro-Ohm - -Example: - -adm1272@10 { - compatible = "adi,adm1272"; - reg = <0x10>; - shunt-resistor-micro-ohms = <500>; -}; diff --git a/Documentation/hwmon/adm1275.rst b/Documentation/hwmon/adm1275.rst index ce6528f90e4a..804590eeabdc 100644 --- a/Documentation/hwmon/adm1275.rst +++ b/Documentation/hwmon/adm1275.rst @@ -83,7 +83,7 @@ or current scaling. Reported voltages, currents, and power are raw measurements, and will typically have to be scaled. The shunt value in micro-ohms can be set via device tree at compile-time. Please -refer to the Documentation/devicetree/bindings/hwmon/adm1275.txt for bindings +refer to the Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml for bindings if the device tree is used. Platform data support From fb42dce4d7b56aac31bd7ef47228515eae9ac968 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 11 Dec 2020 14:35:31 +0100 Subject: [PATCH 37/41] hwmon: (xgene) Drop bogus __refdata annotation As the X-Gene hardware monitoring driver does not have any code or data located in initmem, there is no need to annotate the xgene_hwmon_driver structure with __refdata. Drop the annotation, to avoid suppressing future section warnings. Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20201211133531.2970027-1-geert+renesas@glider.be Signed-off-by: Guenter Roeck --- drivers/hwmon/xgene-hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index f2a5af239c95..1489e83cb0c4 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -784,7 +784,7 @@ static const struct of_device_id xgene_hwmon_of_match[] = { }; MODULE_DEVICE_TABLE(of, xgene_hwmon_of_match); -static struct platform_driver xgene_hwmon_driver __refdata = { +static struct platform_driver xgene_hwmon_driver = { .probe = xgene_hwmon_probe, .remove = xgene_hwmon_remove, .driver = { From 561e3121841a8541402a331c19e9b78e3d2786d3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 11 Dec 2020 14:35:12 +0100 Subject: [PATCH 38/41] hwmon: (iio_hwmon) Drop bogus __refdata annotation As the IIO hardware monitoring driver does not have any code or data located in initmem, there is no need to annotate the iio_hwmon_driver structure with __refdata. Drop the annotation, to avoid suppressing future section warnings. Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20201211133512.2969952-1-geert+renesas@glider.be Signed-off-by: Guenter Roeck --- drivers/hwmon/iio_hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c index b85a125dd86f..580a7d125b88 100644 --- a/drivers/hwmon/iio_hwmon.c +++ b/drivers/hwmon/iio_hwmon.c @@ -169,7 +169,7 @@ static const struct of_device_id iio_hwmon_of_match[] = { }; MODULE_DEVICE_TABLE(of, iio_hwmon_of_match); -static struct platform_driver __refdata iio_hwmon_driver = { +static struct platform_driver iio_hwmon_driver = { .driver = { .name = "iio_hwmon", .of_match_table = iio_hwmon_of_match, From e7bb1a2ab8c4b1569f5b271fd87ca203b1e673a5 Mon Sep 17 00:00:00 2001 From: Kun Yi Date: Fri, 11 Dec 2020 13:54:25 -0800 Subject: [PATCH 39/41] hwmon: (sbtsi) Add basic support for SB-TSI sensors SB Temperature Sensor Interface (SB-TSI) is an SMBus compatible interface that reports AMD SoC's Ttcl (normalized temperature), and resembles a typical 8-pin remote temperature sensor's I2C interface to BMC. This commit adds basic support using this interface to read CPU temperature, and read/write high/low CPU temp thresholds. To instantiate this driver on an AMD CPU with SB-TSI support, the i2c bus number would be the bus connected from the board management controller (BMC) to the CPU. The i2c address is specified in Section 6.3.1 of the spec [1]: The SB-TSI address is normally 98h for socket 0 and 90h for socket 1, but it could vary based on hardware address select pins. [1]: https://www.amd.com/system/files/TechDocs/56255_OSRR.pdf Test status: tested reading temp1_input, and reading/writing temp1_max/min. Signed-off-by: Kun Yi Link: https://lore.kernel.org/r/20201211215427.3281681-2-kunyi@google.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/sbtsi_temp.c | 250 +++++++++++++++++++++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 drivers/hwmon/sbtsi_temp.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 49920a1af3d0..1ecf697d8d99 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1533,6 +1533,16 @@ config SENSORS_SL28CPLD This driver can also be built as a module. If so, the module will be called sl28cpld-hwmon. +config SENSORS_SBTSI + tristate "Emulated SB-TSI temperature sensor" + depends on I2C + help + If you say yes here you get support for emulated temperature + sensors on AMD SoCs with SB-TSI interface connected to a BMC device. + + This driver can also be built as a module. If so, the module will + be called sbtsi_temp. + config SENSORS_SHT15 tristate "Sensiron humidity and temperature sensors. SHT15 and compat." depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 32cff6b0593e..09a86c5e1d29 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -161,6 +161,7 @@ obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o +obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c new file mode 100644 index 000000000000..e35357c48b8e --- /dev/null +++ b/drivers/hwmon/sbtsi_temp.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * sbtsi_temp.c - hwmon driver for a SBI Temperature Sensor Interface (SB-TSI) + * compliant AMD SoC temperature device. + * + * Copyright (c) 2020, Google Inc. + * Copyright (c) 2020, Kun Yi + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * SB-TSI registers only support SMBus byte data access. "_INT" registers are + * the integer part of a temperature value or limit, and "_DEC" registers are + * corresponding decimal parts. + */ +#define SBTSI_REG_TEMP_INT 0x01 /* RO */ +#define SBTSI_REG_STATUS 0x02 /* RO */ +#define SBTSI_REG_CONFIG 0x03 /* RO */ +#define SBTSI_REG_TEMP_HIGH_INT 0x07 /* RW */ +#define SBTSI_REG_TEMP_LOW_INT 0x08 /* RW */ +#define SBTSI_REG_TEMP_DEC 0x10 /* RW */ +#define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */ +#define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */ + +#define SBTSI_CONFIG_READ_ORDER_SHIFT 5 + +#define SBTSI_TEMP_MIN 0 +#define SBTSI_TEMP_MAX 255875 + +/* Each client has this additional data */ +struct sbtsi_data { + struct i2c_client *client; + struct mutex lock; +}; + +/* + * From SB-TSI spec: CPU temperature readings and limit registers encode the + * temperature in increments of 0.125 from 0 to 255.875. The "high byte" + * register encodes the base-2 of the integer portion, and the upper 3 bits of + * the "low byte" encode in base-2 the decimal portion. + * + * e.g. INT=0x19, DEC=0x20 represents 25.125 degrees Celsius + * + * Therefore temperature in millidegree Celsius = + * (INT + DEC / 256) * 1000 = (INT * 8 + DEC / 32) * 125 + */ +static inline int sbtsi_reg_to_mc(s32 integer, s32 decimal) +{ + return ((integer << 3) + (decimal >> 5)) * 125; +} + +/* + * Inversely, given temperature in millidegree Celsius + * INT = (TEMP / 125) / 8 + * DEC = ((TEMP / 125) % 8) * 32 + * Caller have to make sure temp doesn't exceed 255875, the max valid value. + */ +static inline void sbtsi_mc_to_reg(s32 temp, u8 *integer, u8 *decimal) +{ + temp /= 125; + *integer = temp >> 3; + *decimal = (temp & 0x7) << 5; +} + +static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct sbtsi_data *data = dev_get_drvdata(dev); + s32 temp_int, temp_dec; + int err; + + switch (attr) { + case hwmon_temp_input: + /* + * ReadOrder bit specifies the reading order of integer and + * decimal part of CPU temp for atomic reads. If bit == 0, + * reading integer part triggers latching of the decimal part, + * so integer part should be read first. If bit == 1, read + * order should be reversed. + */ + err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG); + if (err < 0) + return err; + + mutex_lock(&data->lock); + if (err & BIT(SBTSI_CONFIG_READ_ORDER_SHIFT)) { + temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC); + temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT); + } else { + temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT); + temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC); + } + mutex_unlock(&data->lock); + break; + case hwmon_temp_max: + mutex_lock(&data->lock); + temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_INT); + temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_DEC); + mutex_unlock(&data->lock); + break; + case hwmon_temp_min: + mutex_lock(&data->lock); + temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_INT); + temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_DEC); + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + + if (temp_int < 0) + return temp_int; + if (temp_dec < 0) + return temp_dec; + + *val = sbtsi_reg_to_mc(temp_int, temp_dec); + + return 0; +} + +static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct sbtsi_data *data = dev_get_drvdata(dev); + int reg_int, reg_dec, err; + u8 temp_int, temp_dec; + + switch (attr) { + case hwmon_temp_max: + reg_int = SBTSI_REG_TEMP_HIGH_INT; + reg_dec = SBTSI_REG_TEMP_HIGH_DEC; + break; + case hwmon_temp_min: + reg_int = SBTSI_REG_TEMP_LOW_INT; + reg_dec = SBTSI_REG_TEMP_LOW_DEC; + break; + default: + return -EINVAL; + } + + val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX); + sbtsi_mc_to_reg(val, &temp_int, &temp_dec); + + mutex_lock(&data->lock); + err = i2c_smbus_write_byte_data(data->client, reg_int, temp_int); + if (err) + goto exit; + + err = i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec); +exit: + mutex_unlock(&data->lock); + return err; +} + +static umode_t sbtsi_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return 0444; + case hwmon_temp_min: + return 0644; + case hwmon_temp_max: + return 0644; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_channel_info *sbtsi_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX), + NULL +}; + +static const struct hwmon_ops sbtsi_hwmon_ops = { + .is_visible = sbtsi_is_visible, + .read = sbtsi_read, + .write = sbtsi_write, +}; + +static const struct hwmon_chip_info sbtsi_chip_info = { + .ops = &sbtsi_hwmon_ops, + .info = sbtsi_info, +}; + +static int sbtsi_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct sbtsi_data *data; + + data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &sbtsi_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id sbtsi_id[] = { + {"sbtsi", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, sbtsi_id); + +static const struct of_device_id __maybe_unused sbtsi_of_match[] = { + { + .compatible = "amd,sbtsi", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, sbtsi_of_match); + +static struct i2c_driver sbtsi_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "sbtsi", + .of_match_table = of_match_ptr(sbtsi_of_match), + }, + .probe = sbtsi_probe, + .id_table = sbtsi_id, +}; + +module_i2c_driver(sbtsi_driver); + +MODULE_AUTHOR("Kun Yi "); +MODULE_DESCRIPTION("Hwmon driver for AMD SB-TSI emulated sensor"); +MODULE_LICENSE("GPL"); From 6ec3fcf556fe44542934b778aea1b725b0793418 Mon Sep 17 00:00:00 2001 From: Kun Yi Date: Fri, 11 Dec 2020 13:54:26 -0800 Subject: [PATCH 40/41] hwmon: (sbtsi) Add documentation Document the SB-TSI sensor interface driver. Signed-off-by: Kun Yi Link: https://lore.kernel.org/r/20201211215427.3281681-3-kunyi@google.com [groeck: Added SPDX license identifier, same as source] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/sbtsi_temp.rst | 42 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 Documentation/hwmon/sbtsi_temp.rst diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 7e5104c8f477..fcb870ce6286 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -154,6 +154,7 @@ Hardware Monitoring Kernel Drivers pwm-fan q54sj108a2 raspberrypi-hwmon + sbtsi_temp sch5627 sch5636 scpi-hwmon diff --git a/Documentation/hwmon/sbtsi_temp.rst b/Documentation/hwmon/sbtsi_temp.rst new file mode 100644 index 000000000000..922b3c8db666 --- /dev/null +++ b/Documentation/hwmon/sbtsi_temp.rst @@ -0,0 +1,42 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver sbtsi_temp +================== + +Supported hardware: + + * Sideband interface (SBI) Temperature Sensor Interface (SB-TSI) + compliant AMD SoC temperature device. + + Prefix: 'sbtsi_temp' + + Addresses scanned: This driver doesn't support address scanning. + + To instantiate this driver on an AMD CPU with SB-TSI + support, the i2c bus number would be the bus connected from the board + management controller (BMC) to the CPU. The i2c address is specified in + Section 6.3.1 of the SoC register reference: The SB-TSI address is normally + 98h for socket 0 and 90h for socket 1, but it could vary based on hardware + address select pins. + + Datasheet: The SB-TSI interface and protocol is available as part of + the open source SoC register reference at: + + https://www.amd.com/system/files/TechDocs/56255_OSRR.pdf + + The Advanced Platform Management Link (APML) Specification is + available at: + + http://developer.amd.com/wordpress/media/2012/10/41918.pdf + +Author: Kun Yi + +Description +----------- + +The SBI temperature sensor interface (SB-TSI) is an emulation of the software +and physical interface of a typical 8-pin remote temperature sensor (RTS) on +AMD SoCs. It implements one temperature sensor with readings and limit +registers encode the temperature in increments of 0.125 from 0 to 255.875. +Limits can be set through the writable thresholds, and if reached will trigger +corresponding alert signals. From 1a033769a4fe9a86ee791fd553b6a996dd76e026 Mon Sep 17 00:00:00 2001 From: Kun Yi Date: Fri, 11 Dec 2020 13:54:27 -0800 Subject: [PATCH 41/41] dt-bindings: (hwmon/sbtsi_temp) Add SB-TSI hwmon driver bindings Document device tree bindings for AMD SB-TSI emulated temperature sensor. Signed-off-by: Kun Yi Link: https://lore.kernel.org/r/20201211215427.3281681-4-kunyi@google.com Reviewed-by: Rob Herring [groeck: Fixed subject] Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/amd,sbtsi.yaml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/amd,sbtsi.yaml diff --git a/Documentation/devicetree/bindings/hwmon/amd,sbtsi.yaml b/Documentation/devicetree/bindings/hwmon/amd,sbtsi.yaml new file mode 100644 index 000000000000..446b09f1ce94 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/amd,sbtsi.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/amd,sbtsi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: > + Sideband interface Temperature Sensor Interface (SB-TSI) compliant + AMD SoC temperature device + +maintainers: + - Kun Yi + - Supreeth Venkatesh + +description: | + SB Temperature Sensor Interface (SB-TSI) is an SMBus compatible + interface that reports AMD SoC's Ttcl (normalized temperature), + and resembles a typical 8-pin remote temperature sensor's I2C interface + to BMC. The emulated thermal sensor can report temperatures in increments + of 0.125 degrees, ranging from 0 to 255.875. + +properties: + compatible: + enum: + - amd,sbtsi + + reg: + maxItems: 1 + description: | + I2C bus address of the device as specified in Section 6.3.1 of the + SoC register reference. The SB-TSI address is normally 98h for socket + 0 and 90h for socket 1, but it could vary based on hardware address + select pins. + \[open source SoC register reference\] + https://www.amd.com/system/files/TechDocs/56255_OSRR.pdf + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + sbtsi@4c { + compatible = "amd,sbtsi"; + reg = <0x4c>; + }; + }; +...