From 276281b8e89863f78cd21922a333fc319c0cc28d Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 27 Jul 2021 16:20:54 -0700 Subject: [PATCH 01/41] hwmon: sht4x: update Documentation for Malformed table Make top and bottom border lines match. Documentation/hwmon/sht4x.rst:42: WARNING: Malformed table. Text in column margin in table line 4. Fixes: 505c2549373f ("hwmon: Add sht4x Temperature and Humidity Sensor Driver") Signed-off-by: Randy Dunlap Cc: Navin Sankar Velliangiri Cc: Guenter Roeck Cc: Jean Delvare Cc: linux-hwmon@vger.kernel.org Link: https://lore.kernel.org/r/20210727232054.7426-1-rdunlap@infradead.org Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht4x.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/sht4x.rst b/Documentation/hwmon/sht4x.rst index 3b37abcd4a464f..c318e5582ead7e 100644 --- a/Documentation/hwmon/sht4x.rst +++ b/Documentation/hwmon/sht4x.rst @@ -42,4 +42,4 @@ humidity1_input Measured humidity in %H update_interval The minimum interval for polling the sensor, in milliseconds. Writable. Must be at least 2000. -============== ============================================= +=============== ============================================ From 02c9dce4df8dd23e67dac1074be732c65c0e67d8 Mon Sep 17 00:00:00 2001 From: David Bartley Date: Sun, 16 May 2021 23:41:31 -0700 Subject: [PATCH 02/41] hwmon: (k10temp) support Zen3 APUs Add support for Zen3 Ryzen APU. Signed-off-by: David Bartley Link: https://lore.kernel.org/r/20210517064131.4369-1-andareed@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 5ff3669c2b6084..fe3d92152e3596 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -450,6 +450,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) switch (boot_cpu_data.x86_model) { case 0x0 ... 0x1: /* Zen3 SP3/TR */ case 0x21: /* Zen3 Ryzen Desktop */ + case 0x50: /* Zen3 Ryzen APU */ k10temp_get_ccd_support(pdev, data, 8); break; } @@ -491,6 +492,7 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, {} }; From bd56c1e9603a4c645fcafdef8df9b2b04125d406 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 5 Jul 2021 00:20:14 +0200 Subject: [PATCH 03/41] hwmon: (ntc_thermistor) Use library interpolation The kernel has a helper function for linear interpolation so use it. It incidentally makes the code easier to read as well. Tested on the ST-Ericsson HREFv60plus hardware reference design with two thermistors forming a thermal zone. Cc: Peter Rosin Cc: Chris Lesiak Cc: linux-iio@vger.kernel.org Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20210704222014.12058-1-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/ntc_thermistor.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index 18fd6f12ca1624..cf26c44f2b880f 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -549,15 +550,16 @@ static int get_temp_mc(struct ntc_data *data, unsigned int ohm) int temp; lookup_comp(data, ohm, &low, &high); - if (low == high) { - /* Unable to use linear approximation */ - temp = data->comp[low].temp_c * 1000; - } else { - temp = data->comp[low].temp_c * 1000 + - ((data->comp[high].temp_c - data->comp[low].temp_c) * - 1000 * ((int)ohm - (int)data->comp[low].ohm)) / - ((int)data->comp[high].ohm - (int)data->comp[low].ohm); - } + /* + * First multiplying the table temperatures with 1000 to get to + * millicentigrades (which is what we want) and then interpolating + * will give the best precision. + */ + temp = fixp_linear_interpolate(data->comp[low].ohm, + data->comp[low].temp_c * 1000, + data->comp[high].ohm, + data->comp[high].temp_c * 1000, + ohm); return temp; } From 129cdce3756117adcb0fced93cb926874355dbba Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Fri, 9 Jul 2021 10:06:18 +1200 Subject: [PATCH 04/41] hwmon: (pmbus/bpa-rs600) Support BPD-RS600 The BPD-RS600 is the DC version of the BPA-RS600. The PMBUS interface is the same between the two models. Keep the same compatible string but accept either BPA-RS600 or BPD-RS600 in the PMBUS_MFR_MODEL. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20210708220618.23576-1-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/bpa-rs600.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/pmbus/bpa-rs600.c b/drivers/hwmon/pmbus/bpa-rs600.c index 2be69fedfa361a..d205b41540cede 100644 --- a/drivers/hwmon/pmbus/bpa-rs600.c +++ b/drivers/hwmon/pmbus/bpa-rs600.c @@ -21,6 +21,8 @@ #define BPARS600_MFR_IOUT_MAX 0xa6 #define BPARS600_MFR_POUT_MAX 0xa7 +enum chips { bpa_rs600, bpd_rs600 }; + static int bpa_rs600_read_byte_data(struct i2c_client *client, int page, int reg) { int ret; @@ -146,11 +148,19 @@ static struct pmbus_driver_info bpa_rs600_info = { .read_word_data = bpa_rs600_read_word_data, }; +static const struct i2c_device_id bpa_rs600_id[] = { + { "bpa-rs600", bpa_rs600 }, + { "bpd-rs600", bpd_rs600 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bpa_rs600_id); + static int bpa_rs600_probe(struct i2c_client *client) { struct device *dev = &client->dev; u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; int ret; + const struct i2c_device_id *mid; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA @@ -164,7 +174,11 @@ static int bpa_rs600_probe(struct i2c_client *client) return ret; } - if (strncmp(buf, "BPA-RS600", 8)) { + for (mid = bpa_rs600_id; mid->name[0]; mid++) { + if (!strncasecmp(buf, mid->name, strlen(mid->name))) + break; + } + if (!mid->name[0]) { buf[ret] = '\0'; dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf); return -ENODEV; @@ -173,12 +187,6 @@ static int bpa_rs600_probe(struct i2c_client *client) return pmbus_do_probe(client, &bpa_rs600_info); } -static const struct i2c_device_id bpa_rs600_id[] = { - { "bpars600", 0 }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, bpa_rs600_id); - static const struct of_device_id __maybe_unused bpa_rs600_of_match[] = { { .compatible = "blutek,bpa-rs600" }, {}, From 964c1c91ed60dbae30ea30ebe488a6195aa5b613 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 9 Jul 2021 20:44:59 +0200 Subject: [PATCH 05/41] hwmon: (w83627ehf) Use platform_create_bundle Using platform_create_bundle() simplifies the module init code and allows w83627ehf_probe() to be marked as __init, lowering the runtime memory footprint. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20210709184501.6546-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/w83627ehf.c | 57 +++++++-------------------------------- 1 file changed, 10 insertions(+), 47 deletions(-) diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 8618aaf32350e2..16aed90ca2ec65 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -1694,7 +1694,7 @@ static const struct hwmon_chip_info w83627ehf_chip_info = { .info = w83627ehf_info, }; -static int w83627ehf_probe(struct platform_device *pdev) +static int __init w83627ehf_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev); @@ -2057,7 +2057,6 @@ static struct platform_driver w83627ehf_driver = { .name = DRVNAME, .pm = W83627EHF_DEV_PM_OPS, }, - .probe = w83627ehf_probe, .remove = w83627ehf_remove, }; @@ -2150,8 +2149,7 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr, /* * when Super-I/O functions move to a separate file, the Super-I/O * bus will manage the lifetime of the device and this module will only keep - * track of the w83627ehf driver. But since we platform_device_alloc(), we - * must keep track of the device + * track of the w83627ehf driver. */ static struct platform_device *pdev; @@ -2159,7 +2157,10 @@ static int __init sensors_w83627ehf_init(void) { int err; unsigned short address; - struct resource res; + struct resource res = { + .name = DRVNAME, + .flags = IORESOURCE_IO, + }; struct w83627ehf_sio_data sio_data; /* @@ -2173,55 +2174,17 @@ static int __init sensors_w83627ehf_init(void) w83627ehf_find(0x4e, &address, &sio_data)) return -ENODEV; - err = platform_driver_register(&w83627ehf_driver); - if (err) - goto exit; - - pdev = platform_device_alloc(DRVNAME, address); - if (!pdev) { - err = -ENOMEM; - pr_err("Device allocation failed\n"); - goto exit_unregister; - } - - err = platform_device_add_data(pdev, &sio_data, - sizeof(struct w83627ehf_sio_data)); - if (err) { - pr_err("Platform data allocation failed\n"); - goto exit_device_put; - } - - memset(&res, 0, sizeof(res)); - res.name = DRVNAME; res.start = address + IOREGION_OFFSET; res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; - res.flags = IORESOURCE_IO; err = acpi_check_resource_conflict(&res); if (err) - goto exit_device_put; + return err; - err = platform_device_add_resources(pdev, &res, 1); - if (err) { - pr_err("Device resource addition failed (%d)\n", err); - goto exit_device_put; - } + pdev = platform_create_bundle(&w83627ehf_driver, w83627ehf_probe, &res, 1, &sio_data, + sizeof(struct w83627ehf_sio_data)); - /* platform_device_add calls probe() */ - err = platform_device_add(pdev); - if (err) { - pr_err("Device addition failed (%d)\n", err); - goto exit_device_put; - } - - return 0; - -exit_device_put: - platform_device_put(pdev); -exit_unregister: - platform_driver_unregister(&w83627ehf_driver); -exit: - return err; + return PTR_ERR_OR_ZERO(pdev); } static void __exit sensors_w83627ehf_exit(void) From 228f2aed8777a6d52cb7fa7f454c1d47511b677f Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 9 Jul 2021 20:45:00 +0200 Subject: [PATCH 06/41] hwmon: (w83627ehf) Remove w83627ehf_remove() Using devm_request_region() allows us to omit w83627ehf_remove() and also simplifies error handling during probe. Also fixed a checkpatch issue. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20210709184501.6546-3-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/w83627ehf.c | 42 +++++++-------------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 16aed90ca2ec65..19af8457432412 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -1705,20 +1705,12 @@ static int __init w83627ehf_probe(struct platform_device *pdev) struct device *hwmon_dev; res = platform_get_resource(pdev, IORESOURCE_IO, 0); - if (!request_region(res->start, IOREGION_LENGTH, DRVNAME)) { - err = -EBUSY; - dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", - (unsigned long)res->start, - (unsigned long)res->start + IOREGION_LENGTH - 1); - goto exit; - } + if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME)) + return -EBUSY; - data = devm_kzalloc(&pdev->dev, sizeof(struct w83627ehf_data), - GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto exit_release; - } + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; data->addr = res->start; mutex_init(&data->lock); @@ -1882,7 +1874,7 @@ static int __init w83627ehf_probe(struct platform_device *pdev) err = superio_enter(sio_data->sioreg); if (err) - goto exit_release; + return err; /* Read VID value */ if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { @@ -1951,26 +1943,7 @@ static int __init w83627ehf_probe(struct platform_device *pdev) data, &w83627ehf_chip_info, w83627ehf_groups); - if (IS_ERR(hwmon_dev)) { - err = PTR_ERR(hwmon_dev); - goto exit_release; - } - - return 0; - -exit_release: - release_region(res->start, IOREGION_LENGTH); -exit: - return err; -} - -static int w83627ehf_remove(struct platform_device *pdev) -{ - struct w83627ehf_data *data = platform_get_drvdata(pdev); - - release_region(data->addr, IOREGION_LENGTH); - - return 0; + return PTR_ERR_OR_ZERO(hwmon_dev); } #ifdef CONFIG_PM @@ -2057,7 +2030,6 @@ static struct platform_driver w83627ehf_driver = { .name = DRVNAME, .pm = W83627EHF_DEV_PM_OPS, }, - .remove = w83627ehf_remove, }; /* w83627ehf_find() looks for a '627 in the Super-I/O config space */ From 1ccdc184056760a620374a7b23fcb8e3aae8ef3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Hundeb=C3=B8ll?= Date: Fri, 16 Jul 2021 15:54:41 +0200 Subject: [PATCH 07/41] hwmon: intel-m10-bmc-hwmon: add n5010 sensors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the list of sensors supported by the Silicom n5010 PAC, and enable the drivers as a subtype of the intel-m10-bmc multi-function driver. Signed-off-by: Martin Hundebøll Reviewed-by: Guenter Roeck Reviewed-by: Moritz Fischer Reviewed-by: Xu Yilun Link: https://lore.kernel.org/r/20210716135441.3235863-4-martin@geanix.com Signed-off-by: Guenter Roeck --- drivers/hwmon/intel-m10-bmc-hwmon.c | 116 ++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/drivers/hwmon/intel-m10-bmc-hwmon.c b/drivers/hwmon/intel-m10-bmc-hwmon.c index bd7ed2ed3a1e63..7a08e4c44a4b4b 100644 --- a/drivers/hwmon/intel-m10-bmc-hwmon.c +++ b/drivers/hwmon/intel-m10-bmc-hwmon.c @@ -228,6 +228,118 @@ static const struct m10bmc_hwmon_board_data d5005bmc_hwmon_bdata = { .hinfo = d5005bmc_hinfo, }; +static const struct m10bmc_sdata n5010bmc_temp_tbl[] = { + { 0x100, 0x0, 0x104, 0x0, 0x0, 1000, "Board Local Temperature" }, + { 0x108, 0x0, 0x10c, 0x0, 0x0, 1000, "FPGA 1 Temperature" }, + { 0x110, 0x0, 0x114, 0x0, 0x0, 1000, "FPGA 2 Temperature" }, + { 0x118, 0x0, 0x0, 0x0, 0x0, 1000, "Card Top Temperature" }, + { 0x11c, 0x0, 0x0, 0x0, 0x0, 1000, "Card Bottom Temperature" }, + { 0x128, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 1.2V Temperature" }, + { 0x134, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 5V Temperature" }, + { 0x140, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 0.9V Temperature" }, + { 0x14c, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 0.85V Temperature" }, + { 0x158, 0x0, 0x0, 0x0, 0x0, 1000, "AUX 12V Temperature" }, + { 0x164, 0x0, 0x0, 0x0, 0x0, 1000, "Backplane 12V Temperature" }, + { 0x1a8, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-1 Temperature" }, + { 0x1ac, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-2 Temperature" }, + { 0x1b0, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-3 Temperature" }, + { 0x1b4, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-4 Temperature" }, + { 0x1b8, 0x0, 0x0, 0x0, 0x0, 1000, "CVL1 Internal Temperature" }, + { 0x1bc, 0x0, 0x0, 0x0, 0x0, 1000, "CVL2 Internal Temperature" }, +}; + +static const struct m10bmc_sdata n5010bmc_in_tbl[] = { + { 0x120, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.2V Voltage" }, + { 0x12c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 5V Voltage" }, + { 0x138, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.9V Voltage" }, + { 0x144, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.85V Voltage" }, + { 0x150, 0x0, 0x0, 0x0, 0x0, 1, "AUX 12V Voltage" }, + { 0x15c, 0x0, 0x0, 0x0, 0x0, 1, "Backplane 12V Voltage" }, + { 0x16c, 0x0, 0x0, 0x0, 0x0, 1, "DDR4 1.2V Voltage" }, + { 0x17c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.8V Voltage" }, + { 0x184, 0x0, 0x0, 0x0, 0x0, 1, "QDR 1.3V Voltage" }, + { 0x18c, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 0.8V Voltage" }, + { 0x194, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 1.05V Voltage" }, + { 0x19c, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 1.05V Voltage" }, + { 0x1a4, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 0.8V Voltage" }, +}; + +static const struct m10bmc_sdata n5010bmc_curr_tbl[] = { + { 0x124, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.2V Current" }, + { 0x130, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 5V Current" }, + { 0x13c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.9V Current" }, + { 0x148, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.85V Current" }, + { 0x154, 0x0, 0x0, 0x0, 0x0, 1, "AUX 12V Current" }, + { 0x160, 0x0, 0x0, 0x0, 0x0, 1, "Backplane 12V Current" }, + { 0x168, 0x0, 0x0, 0x0, 0x0, 1, "DDR4 1.2V Current" }, + { 0x178, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.8V Current" }, + { 0x180, 0x0, 0x0, 0x0, 0x0, 1, "QDR 1.3V Current" }, + { 0x188, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 0.8V Current" }, + { 0x190, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 1.05V Current" }, + { 0x198, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 1.05V Current" }, + { 0x1a0, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 0.8V Current" }, +}; + +static const struct hwmon_channel_info *n5010bmc_hinfo[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(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_I_INPUT | HWMON_I_LABEL, + 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_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, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + 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 m10bmc_hwmon_board_data n5010bmc_hwmon_bdata = { + .tables = { + [hwmon_temp] = n5010bmc_temp_tbl, + [hwmon_in] = n5010bmc_in_tbl, + [hwmon_curr] = n5010bmc_curr_tbl, + }, + + .hinfo = n5010bmc_hinfo, +}; + static umode_t m10bmc_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) @@ -438,6 +550,10 @@ static const struct platform_device_id intel_m10bmc_hwmon_ids[] = { .name = "d5005bmc-hwmon", .driver_data = (unsigned long)&d5005bmc_hwmon_bdata, }, + { + .name = "n5010bmc-hwmon", + .driver_data = (unsigned long)&n5010bmc_hwmon_bdata, + }, { } }; From 04fecf0c6155f73ed446b5b67726ff882b8078a9 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 22 Jul 2021 11:59:59 +0200 Subject: [PATCH 08/41] dt-bindings: firmware: update arm,scpi.yaml reference Changeset 1496be719468 ("dt-bindings: firmware: arm,scpi: Convert to json schema") renamed: Documentation/devicetree/bindings/arm/arm,scpi.txt to: Documentation/devicetree/bindings/firmware/arm,scpi.yaml. Update its cross-reference accordingly. Fixes: 1496be719468 ("dt-bindings: firmware: arm,scpi: Convert to json schema") Signed-off-by: Mauro Carvalho Chehab Acked-by: Sudeep Holla Link: https://lore.kernel.org/r/b5a2b0cb83e7f8193b4be4cef9250dd4c42877ab.1626947923.git.mchehab+huawei@kernel.org Signed-off-by: Guenter Roeck --- Documentation/hwmon/scpi-hwmon.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/scpi-hwmon.rst b/Documentation/hwmon/scpi-hwmon.rst index eee7022b44db66..1e3f83ec065807 100644 --- a/Documentation/hwmon/scpi-hwmon.rst +++ b/Documentation/hwmon/scpi-hwmon.rst @@ -32,5 +32,5 @@ Usage Notes The driver relies on device tree node to indicate the presence of SCPI support in the kernel. See -Documentation/devicetree/bindings/arm/arm,scpi.txt for details of the +Documentation/devicetree/bindings/firmware/arm,scpi.yaml for details of the devicetree node. From ef9e78c0d1ff423be08f34e6a8c226aa7c23ce74 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 21 Jul 2021 21:25:19 +0200 Subject: [PATCH 09/41] hwmon: (w83627ehf) Switch to SIMPLE_DEV_PM_OPS Use SIMPLE_DEV_PM_OPS() to also assign poweroff and thaw callbacks. Remove the now obsolete checking of CONFIG_PM too. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20210721192519.28784-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/w83627ehf.c | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 19af8457432412..3cea66c58c257f 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -372,12 +372,10 @@ struct w83627ehf_data { u8 temp3_val_only:1; u8 have_vid:1; -#ifdef CONFIG_PM /* Remember extra register values over suspend/resume */ u8 vbat; u8 fandiv1; u8 fandiv2; -#endif }; struct w83627ehf_sio_data { @@ -1946,8 +1944,7 @@ static int __init w83627ehf_probe(struct platform_device *pdev) return PTR_ERR_OR_ZERO(hwmon_dev); } -#ifdef CONFIG_PM -static int w83627ehf_suspend(struct device *dev) +static int __maybe_unused w83627ehf_suspend(struct device *dev) { struct w83627ehf_data *data = w83627ehf_update_device(dev); @@ -1958,7 +1955,7 @@ static int w83627ehf_suspend(struct device *dev) return 0; } -static int w83627ehf_resume(struct device *dev) +static int __maybe_unused w83627ehf_resume(struct device *dev) { struct w83627ehf_data *data = dev_get_drvdata(dev); int i; @@ -2013,22 +2010,12 @@ static int w83627ehf_resume(struct device *dev) return 0; } -static const struct dev_pm_ops w83627ehf_dev_pm_ops = { - .suspend = w83627ehf_suspend, - .resume = w83627ehf_resume, - .freeze = w83627ehf_suspend, - .restore = w83627ehf_resume, -}; - -#define W83627EHF_DEV_PM_OPS (&w83627ehf_dev_pm_ops) -#else -#define W83627EHF_DEV_PM_OPS NULL -#endif /* CONFIG_PM */ +static SIMPLE_DEV_PM_OPS(w83627ehf_dev_pm_ops, w83627ehf_suspend, w83627ehf_resume); static struct platform_driver w83627ehf_driver = { .driver = { .name = DRVNAME, - .pm = W83627EHF_DEV_PM_OPS, + .pm = &w83627ehf_dev_pm_ops, }, }; From 6f447ce0f7c16ee501d92091446362aa3359d89a Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 24 Jul 2021 17:48:17 +0200 Subject: [PATCH 10/41] hwmon: (w83627ehf) Make DEVICE_ATTR_RO static Make DEVICE_ATTR_RO static to fix sparse warning: warning: symbol 'dev_attr_cpu0_vid' was not declared. Should it be static? Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20210724154817.18796-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/w83627ehf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 3cea66c58c257f..705a59663d42c5 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -1081,7 +1081,7 @@ cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf) struct w83627ehf_data *data = dev_get_drvdata(dev); return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); } -DEVICE_ATTR_RO(cpu0_vid); +static DEVICE_ATTR_RO(cpu0_vid); /* Case open detection */ From 5a0f50d110b36aaf624fc2017d5e08357e2c8143 Mon Sep 17 00:00:00 2001 From: Akshay Gupta Date: Mon, 26 Jul 2021 19:06:13 +0530 Subject: [PATCH 11/41] hwmon: Add support for SB-RMI power module On AMD platforms the Out-of-band access is provided by Advanced Platform Management Link (APML), APML is a SMBus v2.0 compatible 2-wire processor client interface. APML is also referred as the sideband interface (SBI). APML is used to communicate with the Side-Band Remote Management Interface (SB-RMI) which provides Soft Mailbox messages to manage power consumption and power limits of the CPU socket. - This module add support to read power consumption, power limit & max power limit and write power limit. - To instantiate this driver on a Board Management Controller (BMC) connected to an AMD CPU with SB-RMI support, the i2c bus number would be the bus connected from the BMC to the CPU. Reviewed-by: Guenter Roeck Signed-off-by: Akshay Gupta Signed-off-by: Naveen Krishna Chatradhi Link: https://lore.kernel.org/r/20210726133615.9709-1-nchatrad@amd.com [groeck: Fix uninitialized variable problem when reporting max power] Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/sbrmi.c | 359 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 370 insertions(+) create mode 100644 drivers/hwmon/sbrmi.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e3675377bc5d83..64533141ea4abe 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1551,6 +1551,16 @@ config SENSORS_SBTSI This driver can also be built as a module. If so, the module will be called sbtsi_temp. +config SENSORS_SBRMI + tristate "Emulated SB-RMI sensor" + depends on I2C + help + If you say yes here you get support for emulated RMI + sensors on AMD SoCs with APML interface connected to a BMC device. + + This driver can also be built as a module. If so, the module will + be called sbrmi. + 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 d712c61c1f5e90..53a8f4b500b826 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -164,6 +164,7 @@ 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_SBRMI) += sbrmi.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/sbrmi.c b/drivers/hwmon/sbrmi.c new file mode 100644 index 00000000000000..7bf0c3fba75fef --- /dev/null +++ b/drivers/hwmon/sbrmi.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * sbrmi.c - hwmon driver for a SB-RMI mailbox + * compliant AMD SoC device. + * + * Copyright (C) 2020-2021 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Do not allow setting negative power limit */ +#define SBRMI_PWR_MIN 0 +/* Mask for Status Register bit[1] */ +#define SW_ALERT_MASK 0x2 + +/* Software Interrupt for triggering */ +#define START_CMD 0x80 +#define TRIGGER_MAILBOX 0x01 + +/* + * SB-RMI supports soft mailbox service request to MP1 (power management + * firmware) through SBRMI inbound/outbound message registers. + * SB-RMI message IDs + */ +enum sbrmi_msg_id { + SBRMI_READ_PKG_PWR_CONSUMPTION = 0x1, + SBRMI_WRITE_PKG_PWR_LIMIT, + SBRMI_READ_PKG_PWR_LIMIT, + SBRMI_READ_PKG_MAX_PWR_LIMIT, +}; + +/* SB-RMI registers */ +enum sbrmi_reg { + SBRMI_CTRL = 0x01, + SBRMI_STATUS, + SBRMI_OUTBNDMSG0 = 0x30, + SBRMI_OUTBNDMSG1, + SBRMI_OUTBNDMSG2, + SBRMI_OUTBNDMSG3, + SBRMI_OUTBNDMSG4, + SBRMI_OUTBNDMSG5, + SBRMI_OUTBNDMSG6, + SBRMI_OUTBNDMSG7, + SBRMI_INBNDMSG0, + SBRMI_INBNDMSG1, + SBRMI_INBNDMSG2, + SBRMI_INBNDMSG3, + SBRMI_INBNDMSG4, + SBRMI_INBNDMSG5, + SBRMI_INBNDMSG6, + SBRMI_INBNDMSG7, + SBRMI_SW_INTERRUPT, +}; + +/* Each client has this additional data */ +struct sbrmi_data { + struct i2c_client *client; + struct mutex lock; + u32 pwr_limit_max; +}; + +struct sbrmi_mailbox_msg { + u8 cmd; + bool read; + u32 data_in; + u32 data_out; +}; + +static int sbrmi_enable_alert(struct i2c_client *client) +{ + int ctrl; + + /* + * Enable the SB-RMI Software alert status + * by writing 0 to bit 4 of Control register(0x1) + */ + ctrl = i2c_smbus_read_byte_data(client, SBRMI_CTRL); + if (ctrl < 0) + return ctrl; + + if (ctrl & 0x10) { + ctrl &= ~0x10; + return i2c_smbus_write_byte_data(client, + SBRMI_CTRL, ctrl); + } + + return 0; +} + +static int rmi_mailbox_xfer(struct sbrmi_data *data, + struct sbrmi_mailbox_msg *msg) +{ + int i, ret, retry = 10; + int sw_status; + u8 byte; + + mutex_lock(&data->lock); + + /* Indicate firmware a command is to be serviced */ + ret = i2c_smbus_write_byte_data(data->client, + SBRMI_INBNDMSG7, START_CMD); + if (ret < 0) + goto exit_unlock; + + /* Write the command to SBRMI::InBndMsg_inst0 */ + ret = i2c_smbus_write_byte_data(data->client, + SBRMI_INBNDMSG0, msg->cmd); + if (ret < 0) + goto exit_unlock; + + /* + * For both read and write the initiator (BMC) writes + * Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1] + * SBRMI_x3C(MSB):SBRMI_x39(LSB) + */ + for (i = 0; i < 4; i++) { + byte = (msg->data_in >> i * 8) & 0xff; + ret = i2c_smbus_write_byte_data(data->client, + SBRMI_INBNDMSG1 + i, byte); + if (ret < 0) + goto exit_unlock; + } + + /* + * Write 0x01 to SBRMI::SoftwareInterrupt to notify firmware to + * perform the requested read or write command + */ + ret = i2c_smbus_write_byte_data(data->client, + SBRMI_SW_INTERRUPT, TRIGGER_MAILBOX); + if (ret < 0) + goto exit_unlock; + + /* + * Firmware will write SBRMI::Status[SwAlertSts]=1 to generate + * an ALERT (if enabled) to initiator (BMC) to indicate completion + * of the requested command + */ + do { + sw_status = i2c_smbus_read_byte_data(data->client, + SBRMI_STATUS); + if (sw_status < 0) { + ret = sw_status; + goto exit_unlock; + } + if (sw_status & SW_ALERT_MASK) + break; + usleep_range(50, 100); + } while (retry--); + + if (retry < 0) { + dev_err(&data->client->dev, + "Firmware fail to indicate command completion\n"); + ret = -EIO; + goto exit_unlock; + } + + /* + * For a read operation, the initiator (BMC) reads the firmware + * response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1] + * {SBRMI_x34(MSB):SBRMI_x31(LSB)}. + */ + if (msg->read) { + for (i = 0; i < 4; i++) { + ret = i2c_smbus_read_byte_data(data->client, + SBRMI_OUTBNDMSG1 + i); + if (ret < 0) + goto exit_unlock; + msg->data_out |= ret << i * 8; + } + } + + /* + * BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the + * ALERT to initiator + */ + ret = i2c_smbus_write_byte_data(data->client, SBRMI_STATUS, + sw_status | SW_ALERT_MASK); + +exit_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct sbrmi_data *data = dev_get_drvdata(dev); + struct sbrmi_mailbox_msg msg = { 0 }; + int ret; + + if (type != hwmon_power) + return -EINVAL; + + msg.read = true; + switch (attr) { + case hwmon_power_input: + msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION; + ret = rmi_mailbox_xfer(data, &msg); + break; + case hwmon_power_cap: + msg.cmd = SBRMI_READ_PKG_PWR_LIMIT; + ret = rmi_mailbox_xfer(data, &msg); + break; + case hwmon_power_cap_max: + msg.data_out = data->pwr_limit_max; + ret = 0; + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + /* hwmon power attributes are in microWatt */ + *val = (long)msg.data_out * 1000; + return ret; +} + +static int sbrmi_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct sbrmi_data *data = dev_get_drvdata(dev); + struct sbrmi_mailbox_msg msg = { 0 }; + + if (type != hwmon_power && attr != hwmon_power_cap) + return -EINVAL; + /* + * hwmon power attributes are in microWatt + * mailbox read/write is in mWatt + */ + val /= 1000; + + val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max); + + msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT; + msg.data_in = val; + msg.read = false; + + return rmi_mailbox_xfer(data, &msg); +} + +static umode_t sbrmi_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_cap_max: + return 0444; + case hwmon_power_cap: + return 0644; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_channel_info *sbrmi_info[] = { + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX), + NULL +}; + +static const struct hwmon_ops sbrmi_hwmon_ops = { + .is_visible = sbrmi_is_visible, + .read = sbrmi_read, + .write = sbrmi_write, +}; + +static const struct hwmon_chip_info sbrmi_chip_info = { + .ops = &sbrmi_hwmon_ops, + .info = sbrmi_info, +}; + +static int sbrmi_get_max_pwr_limit(struct sbrmi_data *data) +{ + struct sbrmi_mailbox_msg msg = { 0 }; + int ret; + + msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT; + msg.read = true; + ret = rmi_mailbox_xfer(data, &msg); + if (ret < 0) + return ret; + data->pwr_limit_max = msg.data_out; + + return ret; +} + +static int sbrmi_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct sbrmi_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(struct sbrmi_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + + /* Enable alert for SB-RMI sequence */ + ret = sbrmi_enable_alert(client); + if (ret < 0) + return ret; + + /* Cache maximum power limit */ + ret = sbrmi_get_max_pwr_limit(data); + if (ret < 0) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &sbrmi_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id sbrmi_id[] = { + {"sbrmi", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, sbrmi_id); + +static const struct of_device_id __maybe_unused sbrmi_of_match[] = { + { + .compatible = "amd,sbrmi", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, sbrmi_of_match); + +static struct i2c_driver sbrmi_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "sbrmi", + .of_match_table = of_match_ptr(sbrmi_of_match), + }, + .probe = sbrmi_probe, + .id_table = sbrmi_id, +}; + +module_i2c_driver(sbrmi_driver); + +MODULE_AUTHOR("Akshay Gupta "); +MODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor"); +MODULE_LICENSE("GPL"); From 04165fb73f9b8ea9e5cc2f3c21ca2a777516fc7b Mon Sep 17 00:00:00 2001 From: Akshay Gupta Date: Mon, 26 Jul 2021 19:06:14 +0530 Subject: [PATCH 12/41] hwmon: (sbrmi) Add Documentation - Add documentation for SB-RMI module Reviewed-by: Guenter Roeck Signed-off-by: Akshay Gupta Signed-off-by: Naveen Krishna Chatradhi Link: https://lore.kernel.org/r/20210726133615.9709-2-nchatrad@amd.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/sbrmi.rst | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 Documentation/hwmon/sbrmi.rst diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index bc01601ea81aa8..470f2c50ecc2c7 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -160,6 +160,7 @@ Hardware Monitoring Kernel Drivers pwm-fan q54sj108a2 raspberrypi-hwmon + sbrmi sbtsi_temp sch5627 sch5636 diff --git a/Documentation/hwmon/sbrmi.rst b/Documentation/hwmon/sbrmi.rst new file mode 100644 index 00000000000000..296049e13ac990 --- /dev/null +++ b/Documentation/hwmon/sbrmi.rst @@ -0,0 +1,79 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver sbrmi +=================== + +Supported hardware: + + * Sideband Remote Management Interface (SB-RMI) compliant AMD SoC + device connected to the BMC via the APML. + + Prefix: 'sbrmi' + + Addresses scanned: This driver doesn't support address scanning. + + To instantiate this driver on an AMD CPU with SB-RMI + support, the i2c bus number would be the bus connected from the board + management controller (BMC) to the CPU. + The SMBus address is really 7 bits. Some vendors and the SMBus + specification show the address as 8 bits, left justified with the R/W + bit as a write (0) making bit 0. Some vendors use only the 7 bits + to describe the address. + As mentioned in AMD's APML specification, The SB-RMI address is + normally 78h(0111 100W) or 3Ch(011 1100) for socket 0 and 70h(0111 000W) + or 38h(011 1000) for socket 1, but it could vary based on hardware + address select pins. + + Datasheet: The SB-RMI interface and protocol along with the Advanced + Platform Management Link (APML) Specification is available + as part of the open source SoC register reference at: + + https://www.amd.com/en/support/tech-docs?keyword=55898 + +Author: Akshay Gupta + +Description +----------- + +The APML provides a way to communicate with the SB Remote Management interface +(SB-RMI) module from the external SMBus master that can be used to report socket +power on AMD platforms using mailbox command and resembles a typical 8-pin remote +power sensor's I2C interface to BMC. + +This driver implements current power with power cap and power cap max. + +sysfs-Interface +--------------- +Power sensors can be queried and set via the standard ``hwmon`` interface +on ``sysfs``, under the directory ``/sys/class/hwmon/hwmonX`` for some value +of ``X`` (search for the ``X`` such that ``/sys/class/hwmon/hwmonX/name`` has +content ``sbrmi``) + +================ ===== ======================================================== +Name Perm Description +================ ===== ======================================================== +power1_input RO Current Power consumed +power1_cap RW Power limit can be set between 0 and power1_cap_max +power1_cap_max RO Maximum powerlimit calculated and reported by the SMU FW +================ ===== ======================================================== + +The following example show how the 'Power' attribute from the i2c-addresses +can be monitored using the userspace utilities like ``sensors`` binary:: + + # sensors + sbrmi-i2c-1-38 + Adapter: bcm2835 I2C adapter + power1: 61.00 W (cap = 225.00 W) + + sbrmi-i2c-1-3c + Adapter: bcm2835 I2C adapter + power1: 28.39 W (cap = 224.77 W) + # + +Also, Below shows how get and set the values from sysfs entries individually:: + # cat /sys/class/hwmon/hwmon1/power1_cap_max + 225000000 + + # echo 180000000 > /sys/class/hwmon/hwmon1/power1_cap + # cat /sys/class/hwmon/hwmon1/power1_cap + 180000000 From 60b76c3a117ce076f60f58de17bae1122849746a Mon Sep 17 00:00:00 2001 From: Akshay Gupta Date: Mon, 26 Jul 2021 19:06:15 +0530 Subject: [PATCH 13/41] dt-bindings: sbrmi: Add SB-RMI hwmon driver bindings - Document device tree bindings for AMD SB-RMI emulated service. Signed-off-by: Akshay Gupta Signed-off-by: Naveen Krishna Chatradhi Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20210726133615.9709-3-nchatrad@amd.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/amd,sbrmi.yaml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/amd,sbrmi.yaml diff --git a/Documentation/devicetree/bindings/hwmon/amd,sbrmi.yaml b/Documentation/devicetree/bindings/hwmon/amd,sbrmi.yaml new file mode 100644 index 00000000000000..7598b083979c73 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/amd,sbrmi.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/amd,sbrmi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: > + Sideband Remote Management Interface (SB-RMI) compliant + AMD SoC power device. + +maintainers: + - Akshay Gupta + +description: | + SB Remote Management Interface (SB-RMI) is an SMBus compatible + interface that reports AMD SoC's Power (normalized Power) using, + Mailbox Service Request and resembles a typical 8-pin remote power + sensor's I2C interface to BMC. The power attributes in hwmon + reports power in microwatts. + +properties: + compatible: + enum: + - amd,sbrmi + + reg: + maxItems: 1 + description: | + I2C bus address of the device as specified in Section SBI SMBus Address + of the SoC register reference. The SB-RMI address is normally 78h for + socket 0 and 70h for socket 1, but it could vary based on hardware + address select pins. + \[open source SoC register reference\] + https://www.amd.com/en/support/tech-docs?keyword=55898 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + sbrmi@3c { + compatible = "amd,sbrmi"; + reg = <0x3c>; + }; + }; +... From 1492fa21c0ba531e3ae4fc1b5cdaaf96603bf800 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 29 Jul 2021 00:15:52 +0200 Subject: [PATCH 14/41] hwmon: (dell-smm-hwmon) Use platform device Register a platform device for usage with devm_hwmon_device_register_with_groups since the platform device is necessary for future changes. Also fix some checkpatch warnings. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20210728221557.8891-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 113 ++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index f2221ca0aa7be0..f06873413aea3f 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -14,7 +14,9 @@ #include #include +#include #include +#include #include #include #include @@ -896,7 +898,7 @@ static const struct attribute_group i8k_group = { }; __ATTRIBUTE_GROUPS(i8k); -static int __init i8k_init_hwmon(void) +static int __init dell_smm_init_hwmon(struct device *dev) { int err; @@ -956,15 +958,9 @@ static int __init i8k_init_hwmon(void) if (err >= 0) i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; - i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm", - NULL, i8k_groups); - if (IS_ERR(i8k_hwmon_dev)) { - err = PTR_ERR(i8k_hwmon_dev); - i8k_hwmon_dev = NULL; - pr_err("hwmon registration failed (%d)\n", err); - return err; - } - return 0; + i8k_hwmon_dev = devm_hwmon_device_register_with_groups(dev, "dell_smm", NULL, i8k_groups); + + return PTR_ERR_OR_ZERO(i8k_hwmon_dev); } struct i8k_config_data { @@ -1221,28 +1217,11 @@ static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = { { } }; -/* - * Probe for the presence of a supported laptop. - */ -static int __init i8k_probe(void) +static int __init dell_smm_probe(struct platform_device *pdev) { const struct dmi_system_id *id, *fan_control; int fan, ret; - /* - * Get DMI information - */ - if (!dmi_check_system(i8k_dmi_table)) { - if (!ignore_dmi && !force) - return -ENODEV; - - pr_info("not running on a supported Dell system.\n"); - pr_info("vendor=%s, model=%s, version=%s\n", - i8k_get_dmi_data(DMI_SYS_VENDOR), - i8k_get_dmi_data(DMI_PRODUCT_NAME), - i8k_get_dmi_data(DMI_BIOS_VERSION)); - } - if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { pr_warn("broken Dell BIOS detected, disallow fan support\n"); if (!force) @@ -1255,21 +1234,11 @@ static int __init i8k_probe(void) disallow_fan_type_call = true; } - strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), + strscpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), sizeof(bios_version)); - strlcpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), + strscpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), sizeof(bios_machineid)); - /* - * Get SMM Dell signature - */ - if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && - i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { - pr_err("unable to get SMM Dell signature\n"); - if (!force) - return -ENODEV; - } - /* * Set fan multiplier and maximal fan speed from dmi config * Values specified in module parameters override values from dmi @@ -1277,6 +1246,7 @@ static int __init i8k_probe(void) id = dmi_first_match(i8k_dmi_table); if (id && id->driver_data) { const struct i8k_config_data *conf = id->driver_data; + if (!fan_mult && conf->fan_mult) fan_mult = conf->fan_mult; if (!fan_max && conf->fan_max) @@ -1313,29 +1283,70 @@ static int __init i8k_probe(void) i8k_fan_mult = fan_mult; } + ret = dell_smm_init_hwmon(&pdev->dev); + if (ret) + return ret; + + i8k_init_procfs(); + return 0; } +static int dell_smm_remove(struct platform_device *pdev) +{ + i8k_exit_procfs(); + + return 0; +} + +static struct platform_driver dell_smm_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .remove = dell_smm_remove, +}; + +static struct platform_device *dell_smm_device; + +/* + * Probe for the presence of a supported laptop. + */ static int __init i8k_init(void) { - int err; + /* + * Get DMI information + */ + if (!dmi_check_system(i8k_dmi_table)) { + if (!ignore_dmi && !force) + return -ENODEV; - /* Are we running on an supported laptop? */ - if (i8k_probe()) - return -ENODEV; + pr_info("not running on a supported Dell system.\n"); + pr_info("vendor=%s, model=%s, version=%s\n", + i8k_get_dmi_data(DMI_SYS_VENDOR), + i8k_get_dmi_data(DMI_PRODUCT_NAME), + i8k_get_dmi_data(DMI_BIOS_VERSION)); + } - err = i8k_init_hwmon(); - if (err) - return err; + /* + * Get SMM Dell signature + */ + if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && + i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { + pr_err("unable to get SMM Dell signature\n"); + if (!force) + return -ENODEV; + } - i8k_init_procfs(); - return 0; + dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL, + 0); + + return PTR_ERR_OR_ZERO(dell_smm_device); } static void __exit i8k_exit(void) { - hwmon_device_unregister(i8k_hwmon_dev); - i8k_exit_procfs(); + platform_device_unregister(dell_smm_device); + platform_driver_unregister(&dell_smm_driver); } module_init(i8k_init); From c9363cdf3aab9f48c3c8bd8bfa77dcfd3a3a92d2 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 29 Jul 2021 00:15:53 +0200 Subject: [PATCH 15/41] hwmon: (dell-smm-hwmon) Mark functions as __init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i8k_get_dmi_data() and i8k_get_dell_signature() are only called during module init and probe, which both are marked as __init. Also mark these function as __init to lower the runtime memory footprint. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Tested-by: Pali Rohár Link: https://lore.kernel.org/r/20210728221557.8891-3-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index f06873413aea3f..c898d6bd6a189b 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -128,7 +128,7 @@ struct smm_regs { unsigned int edi __packed; }; -static inline const char *i8k_get_dmi_data(int field) +static inline const char __init *i8k_get_dmi_data(int field) { const char *dmi_data = dmi_get_system_info(field); @@ -384,7 +384,7 @@ static int i8k_get_temp(int sensor) return temp; } -static int i8k_get_dell_signature(int req_fn) +static int __init i8k_get_dell_signature(int req_fn) { struct smm_regs regs = { .eax = req_fn, }; int rc; From a2cb66b476e282b4fc1ce8b69f1fae0f9991d0b6 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 29 Jul 2021 00:15:54 +0200 Subject: [PATCH 16/41] hwmon: (dell-smm-hwmon) Use devm_add_action_or_reset() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use devm_add_action_or_reset() for calling i8k_exit_procfs() so the remove() function in dell_smm_driver can be omitted. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Tested-by: Pali Rohár Link: https://lore.kernel.org/r/20210728221557.8891-4-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index c898d6bd6a189b..da7040f2442ee5 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -605,24 +605,22 @@ static const struct proc_ops i8k_proc_ops = { .proc_ioctl = i8k_ioctl, }; -static void __init i8k_init_procfs(void) +static void i8k_exit_procfs(void *param) { - /* Register the proc entry */ - proc_create("i8k", 0, NULL, &i8k_proc_ops); + remove_proc_entry("i8k", NULL); } -static void __exit i8k_exit_procfs(void) +static void __init i8k_init_procfs(struct device *dev) { - remove_proc_entry("i8k", NULL); + /* Register the proc entry */ + proc_create("i8k", 0, NULL, &i8k_proc_ops); + + devm_add_action_or_reset(dev, i8k_exit_procfs, NULL); } #else -static inline void __init i8k_init_procfs(void) -{ -} - -static inline void __exit i8k_exit_procfs(void) +static void __init i8k_init_procfs(struct device *dev) { } @@ -1287,14 +1285,7 @@ static int __init dell_smm_probe(struct platform_device *pdev) if (ret) return ret; - i8k_init_procfs(); - - return 0; -} - -static int dell_smm_remove(struct platform_device *pdev) -{ - i8k_exit_procfs(); + i8k_init_procfs(&pdev->dev); return 0; } @@ -1303,7 +1294,6 @@ static struct platform_driver dell_smm_driver = { .driver = { .name = KBUILD_MODNAME, }, - .remove = dell_smm_remove, }; static struct platform_device *dell_smm_device; From ba04d73c26edf1b577b39b708418a931b0b73c40 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 29 Jul 2021 00:15:55 +0200 Subject: [PATCH 17/41] hwmon: (dell-smm-hwmon) Move variables into a driver private data structure Move Variables into a driver private data structure. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20210728221557.8891-5-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 277 ++++++++++++++++++--------------- 1 file changed, 152 insertions(+), 125 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index da7040f2442ee5..8841fc1e587268 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -60,18 +60,20 @@ #define I8K_POWER_AC 0x05 #define I8K_POWER_BATTERY 0x01 -static DEFINE_MUTEX(i8k_mutex); -static char bios_version[4]; -static char bios_machineid[16]; -static struct device *i8k_hwmon_dev; -static u32 i8k_hwmon_flags; -static uint i8k_fan_mult = I8K_FAN_MULT; -static uint i8k_pwm_mult; -static uint i8k_fan_max = I8K_FAN_HIGH; -static bool disallow_fan_type_call; -static bool disallow_fan_support; -static unsigned int manual_fan; -static unsigned int auto_fan; +struct dell_smm_data { + struct mutex i8k_mutex; /* lock for sensors writes */ + char bios_version[4]; + char bios_machineid[16]; + u32 i8k_hwmon_flags; + uint i8k_fan_mult; + uint i8k_pwm_mult; + uint i8k_fan_max; + bool disallow_fan_type_call; + bool disallow_fan_support; + unsigned int manual_fan; + unsigned int auto_fan; + int types[3]; +}; #define I8K_HWMON_HAVE_TEMP1 (1 << 0) #define I8K_HWMON_HAVE_TEMP2 (1 << 1) @@ -240,11 +242,11 @@ static int i8k_smm(struct smm_regs *regs) /* * Read the fan status. */ -static int i8k_get_fan_status(int fan) +static int i8k_get_fan_status(const struct dell_smm_data *data, int fan) { struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; - if (disallow_fan_support) + if (data->disallow_fan_support) return -EINVAL; regs.ebx = fan & 0xff; @@ -254,84 +256,82 @@ static int i8k_get_fan_status(int fan) /* * Read the fan speed in RPM. */ -static int i8k_get_fan_speed(int fan) +static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan) { struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; - if (disallow_fan_support) + if (data->disallow_fan_support) return -EINVAL; regs.ebx = fan & 0xff; - return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; + return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; } /* * Read the fan type. */ -static int _i8k_get_fan_type(int fan) +static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan) { struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; - if (disallow_fan_support || disallow_fan_type_call) + if (data->disallow_fan_support || data->disallow_fan_type_call) return -EINVAL; regs.ebx = fan & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } -static int i8k_get_fan_type(int fan) +static int i8k_get_fan_type(struct dell_smm_data *data, int fan) { /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ - static int types[3] = { INT_MIN, INT_MIN, INT_MIN }; + if (data->types[fan] == INT_MIN) + data->types[fan] = _i8k_get_fan_type(data, fan); - if (types[fan] == INT_MIN) - types[fan] = _i8k_get_fan_type(fan); - - return types[fan]; + return data->types[fan]; } /* * Read the fan nominal rpm for specific fan speed. */ -static int i8k_get_fan_nominal_speed(int fan, int speed) +static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; - if (disallow_fan_support) + if (data->disallow_fan_support) return -EINVAL; regs.ebx = (fan & 0xff) | (speed << 8); - return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; + return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; } /* * Enable or disable automatic BIOS fan control support */ -static int i8k_enable_fan_auto_mode(bool enable) +static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enable) { struct smm_regs regs = { }; - if (disallow_fan_support) + if (data->disallow_fan_support) return -EINVAL; - regs.eax = enable ? auto_fan : manual_fan; + regs.eax = enable ? data->auto_fan : data->manual_fan; return i8k_smm(®s); } /* * Set the fan speed (off, low, high). Returns the new fan status. */ -static int i8k_set_fan(int fan, int speed) +static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; - if (disallow_fan_support) + if (data->disallow_fan_support) return -EINVAL; - speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed); + speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed); regs.ebx = (fan & 0xff) | (speed << 8); - return i8k_smm(®s) ? : i8k_get_fan_status(fan); + return i8k_smm(®s) ? : i8k_get_fan_status(data, fan); } static int i8k_get_temp_type(int sensor) @@ -442,7 +442,7 @@ static int i8k_get_power_status(void) */ static int -i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) +i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg) { int val = 0; int speed; @@ -454,12 +454,12 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) switch (cmd) { case I8K_BIOS_VERSION: - if (!isdigit(bios_version[0]) || !isdigit(bios_version[1]) || - !isdigit(bios_version[2])) + if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) || + !isdigit(data->bios_version[2])) return -EINVAL; - val = (bios_version[0] << 16) | - (bios_version[1] << 8) | bios_version[2]; + val = (data->bios_version[0] << 16) | + (data->bios_version[1] << 8) | data->bios_version[2]; break; case I8K_MACHINE_ID: @@ -467,7 +467,7 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) return -EPERM; memset(buff, 0, sizeof(buff)); - strlcpy(buff, bios_machineid, sizeof(buff)); + strscpy(buff, data->bios_machineid, sizeof(buff)); break; case I8K_FN_STATUS: @@ -486,14 +486,14 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; - val = i8k_get_fan_speed(val); + val = i8k_get_fan_speed(data, val); break; case I8K_GET_FAN: if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; - val = i8k_get_fan_status(val); + val = i8k_get_fan_status(data, val); break; case I8K_SET_FAN: @@ -506,7 +506,7 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) if (copy_from_user(&speed, argp + 1, sizeof(int))) return -EFAULT; - val = i8k_set_fan(val, speed); + val = i8k_set_fan(data, val, speed); break; default: @@ -539,11 +539,12 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { + struct dell_smm_data *data = PDE_DATA(file_inode(fp)); long ret; - mutex_lock(&i8k_mutex); - ret = i8k_ioctl_unlocked(fp, cmd, arg); - mutex_unlock(&i8k_mutex); + mutex_lock(&data->i8k_mutex); + ret = i8k_ioctl_unlocked(fp, data, cmd, arg); + mutex_unlock(&data->i8k_mutex); return ret; } @@ -553,17 +554,18 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) */ static int i8k_proc_show(struct seq_file *seq, void *offset) { + struct dell_smm_data *data = seq->private; int fn_key, cpu_temp, ac_power; int left_fan, right_fan, left_speed, right_speed; - cpu_temp = i8k_get_temp(0); /* 11100 µs */ - left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */ - right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */ - left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */ - right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */ - fn_key = i8k_get_fn_status(); /* 750 µs */ + cpu_temp = i8k_get_temp(0); /* 11100 µs */ + left_fan = i8k_get_fan_status(data, I8K_FAN_LEFT); /* 580 µs */ + right_fan = i8k_get_fan_status(data, I8K_FAN_RIGHT); /* 580 µs */ + left_speed = i8k_get_fan_speed(data, I8K_FAN_LEFT); /* 580 µs */ + right_speed = i8k_get_fan_speed(data, I8K_FAN_RIGHT); /* 580 µs */ + fn_key = i8k_get_fn_status(); /* 750 µs */ if (power_status) - ac_power = i8k_get_power_status(); /* 14700 µs */ + ac_power = i8k_get_power_status(); /* 14700 µs */ else ac_power = -1; @@ -583,8 +585,8 @@ static int i8k_proc_show(struct seq_file *seq, void *offset) */ seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", I8K_PROC_FMT, - bios_version, - (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : bios_machineid, + data->bios_version, + (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : data->bios_machineid, cpu_temp, left_fan, right_fan, left_speed, right_speed, ac_power, fn_key); @@ -594,7 +596,7 @@ static int i8k_proc_show(struct seq_file *seq, void *offset) static int i8k_open_fs(struct inode *inode, struct file *file) { - return single_open(file, i8k_proc_show, NULL); + return single_open(file, i8k_proc_show, PDE_DATA(inode)); } static const struct proc_ops i8k_proc_ops = { @@ -612,8 +614,10 @@ static void i8k_exit_procfs(void *param) static void __init i8k_init_procfs(struct device *dev) { + struct dell_smm_data *data = dev_get_drvdata(dev); + /* Register the proc entry */ - proc_create("i8k", 0, NULL, &i8k_proc_ops); + proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data); devm_add_action_or_reset(dev, i8k_exit_procfs, NULL); } @@ -670,6 +674,7 @@ static ssize_t i8k_hwmon_fan_label_show(struct device *dev, struct device_attribute *devattr, char *buf) { + struct dell_smm_data *data = dev_get_drvdata(dev); static const char * const labels[] = { "Processor Fan", "Motherboard Fan", @@ -682,7 +687,7 @@ static ssize_t i8k_hwmon_fan_label_show(struct device *dev, bool dock = false; int type; - type = i8k_get_fan_type(index); + type = i8k_get_fan_type(data, index); if (type < 0) return type; @@ -700,10 +705,11 @@ static ssize_t i8k_hwmon_fan_label_show(struct device *dev, static ssize_t i8k_hwmon_fan_show(struct device *dev, struct device_attribute *devattr, char *buf) { + struct dell_smm_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(devattr)->index; int fan_speed; - fan_speed = i8k_get_fan_speed(index); + fan_speed = i8k_get_fan_speed(data, index); if (fan_speed < 0) return fan_speed; return sprintf(buf, "%d\n", fan_speed); @@ -712,19 +718,21 @@ static ssize_t i8k_hwmon_fan_show(struct device *dev, static ssize_t i8k_hwmon_pwm_show(struct device *dev, struct device_attribute *devattr, char *buf) { + struct dell_smm_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(devattr)->index; int status; - status = i8k_get_fan_status(index); + status = i8k_get_fan_status(data, index); if (status < 0) return -EIO; - return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255)); + return sprintf(buf, "%d\n", clamp_val(status * data->i8k_pwm_mult, 0, 255)); } static ssize_t i8k_hwmon_pwm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct dell_smm_data *data = dev_get_drvdata(dev); int index = to_sensor_dev_attr(attr)->index; unsigned long val; int err; @@ -732,11 +740,11 @@ static ssize_t i8k_hwmon_pwm_store(struct device *dev, err = kstrtoul(buf, 10, &val); if (err) return err; - val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max); + val = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0, data->i8k_fan_max); - mutex_lock(&i8k_mutex); - err = i8k_set_fan(index, val); - mutex_unlock(&i8k_mutex); + mutex_lock(&data->i8k_mutex); + err = i8k_set_fan(data, index, val); + mutex_unlock(&data->i8k_mutex); return err < 0 ? -EIO : count; } @@ -745,11 +753,12 @@ static ssize_t i8k_hwmon_pwm_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct dell_smm_data *data = dev_get_drvdata(dev); int err; bool enable; unsigned long val; - if (!auto_fan) + if (!data->auto_fan) return -ENODEV; err = kstrtoul(buf, 10, &val); @@ -763,9 +772,9 @@ static ssize_t i8k_hwmon_pwm_enable_store(struct device *dev, else return -EINVAL; - mutex_lock(&i8k_mutex); - err = i8k_enable_fan_auto_mode(enable); - mutex_unlock(&i8k_mutex); + mutex_lock(&data->i8k_mutex); + err = i8k_enable_fan_auto_mode(data, enable); + mutex_unlock(&data->i8k_mutex); return err ? err : count; } @@ -838,53 +847,56 @@ static struct attribute *i8k_attrs[] = { static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - if (disallow_fan_support && index >= 20) + struct device *dev = kobj_to_dev(kobj); + struct dell_smm_data *data = dev_get_drvdata(dev); + + if (data->disallow_fan_support && index >= 20) return 0; - if (disallow_fan_type_call && + if (data->disallow_fan_type_call && (index == 21 || index == 25 || index == 28)) return 0; if (index >= 0 && index <= 1 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) return 0; if (index >= 2 && index <= 3 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) return 0; if (index >= 4 && index <= 5 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) return 0; if (index >= 6 && index <= 7 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) return 0; if (index >= 8 && index <= 9 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP5)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP5)) return 0; if (index >= 10 && index <= 11 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP6)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP6)) return 0; if (index >= 12 && index <= 13 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP7)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP7)) return 0; if (index >= 14 && index <= 15 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP8)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP8)) return 0; if (index >= 16 && index <= 17 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP9)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP9)) return 0; if (index >= 18 && index <= 19 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10)) return 0; if (index >= 20 && index <= 23 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) return 0; if (index >= 24 && index <= 26 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) return 0; if (index >= 27 && index <= 29 && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3)) + !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3)) return 0; - if (index == 23 && !auto_fan) + if (index == 23 && !data->auto_fan) return 0; return attr->mode; @@ -898,65 +910,65 @@ __ATTRIBUTE_GROUPS(i8k); static int __init dell_smm_init_hwmon(struct device *dev) { + struct dell_smm_data *data = dev_get_drvdata(dev); + struct device *i8k_hwmon_dev; int err; - i8k_hwmon_flags = 0; - /* CPU temperature attributes, if temperature type is OK */ err = i8k_get_temp_type(0); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1; /* check for additional temperature sensors */ err = i8k_get_temp_type(1); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2; err = i8k_get_temp_type(2); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3; err = i8k_get_temp_type(3); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4; err = i8k_get_temp_type(4); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP5; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP5; err = i8k_get_temp_type(5); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP6; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP6; err = i8k_get_temp_type(6); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP7; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP7; err = i8k_get_temp_type(7); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP8; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP8; err = i8k_get_temp_type(8); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP9; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP9; err = i8k_get_temp_type(9); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP10; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP10; /* First fan attributes, if fan status or type is OK */ - err = i8k_get_fan_status(0); + err = i8k_get_fan_status(data, 0); if (err < 0) - err = i8k_get_fan_type(0); + err = i8k_get_fan_type(data, 0); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1; /* Second fan attributes, if fan status or type is OK */ - err = i8k_get_fan_status(1); + err = i8k_get_fan_status(data, 1); if (err < 0) - err = i8k_get_fan_type(1); + err = i8k_get_fan_type(data, 1); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; /* Third fan attributes, if fan status or type is OK */ - err = i8k_get_fan_status(2); + err = i8k_get_fan_status(data, 2); if (err < 0) - err = i8k_get_fan_type(2); + err = i8k_get_fan_type(data, 2); if (err >= 0) - i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; + data->i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; - i8k_hwmon_dev = devm_hwmon_device_register_with_groups(dev, "dell_smm", NULL, i8k_groups); + i8k_hwmon_dev = devm_hwmon_device_register_with_groups(dev, "dell_smm", data, i8k_groups); return PTR_ERR_OR_ZERO(i8k_hwmon_dev); } @@ -1217,25 +1229,38 @@ static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = { static int __init dell_smm_probe(struct platform_device *pdev) { + struct dell_smm_data *data; const struct dmi_system_id *id, *fan_control; int fan, ret; + data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->i8k_mutex); + data->i8k_fan_mult = I8K_FAN_MULT; + data->i8k_fan_max = I8K_FAN_HIGH; + data->types[0] = INT_MIN; + data->types[1] = INT_MIN; + data->types[2] = INT_MIN; + platform_set_drvdata(pdev, data); + if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { - pr_warn("broken Dell BIOS detected, disallow fan support\n"); + dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n"); if (!force) - disallow_fan_support = true; + data->disallow_fan_support = true; } if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) { - pr_warn("broken Dell BIOS detected, disallow fan type call\n"); + dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n"); if (!force) - disallow_fan_type_call = true; + data->disallow_fan_type_call = true; } - strscpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), - sizeof(bios_version)); - strscpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), - sizeof(bios_machineid)); + strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), + sizeof(data->bios_version)); + strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), + sizeof(data->bios_machineid)); /* * Set fan multiplier and maximal fan speed from dmi config @@ -1247,20 +1272,21 @@ static int __init dell_smm_probe(struct platform_device *pdev) if (!fan_mult && conf->fan_mult) fan_mult = conf->fan_mult; + if (!fan_max && conf->fan_max) fan_max = conf->fan_max; } - i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ - i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max); + data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ + data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max); fan_control = dmi_first_match(i8k_whitelist_fan_control); if (fan_control && fan_control->driver_data) { - const struct i8k_fan_control_data *data = fan_control->driver_data; + const struct i8k_fan_control_data *control = fan_control->driver_data; - manual_fan = data->manual_fan; - auto_fan = data->auto_fan; - pr_info("enabling support for setting automatic/manual fan control\n"); + data->manual_fan = control->manual_fan; + data->auto_fan = control->auto_fan; + dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n"); } if (!fan_mult) { @@ -1269,16 +1295,17 @@ static int __init dell_smm_probe(struct platform_device *pdev) * If fan reports rpm value too high then set multiplier to 1 */ for (fan = 0; fan < 2; ++fan) { - ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max); + ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max); if (ret < 0) continue; + if (ret > I8K_FAN_MAX_RPM) - i8k_fan_mult = 1; + data->i8k_fan_mult = 1; break; } } else { /* Fan multiplier was specified in module param or in dmi */ - i8k_fan_mult = fan_mult; + data->i8k_fan_mult = fan_mult; } ret = dell_smm_init_hwmon(&pdev->dev); From deeba244b0feab6180b77fe98f2c19eb89163428 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 29 Jul 2021 00:15:56 +0200 Subject: [PATCH 18/41] hwmon: (dell-smm-hwmon) Convert to devm_hwmon_device_register_with_info() Convert to new registration API to get rid of attribute magic numbers and reduce module size. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20210728221557.8891-6-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 589 ++++++++++++++++----------------- 1 file changed, 279 insertions(+), 310 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 8841fc1e587268..aaf864dbe53a51 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -60,11 +59,13 @@ #define I8K_POWER_AC 0x05 #define I8K_POWER_BATTERY 0x01 +#define DELL_SMM_NO_TEMP 10 +#define DELL_SMM_NO_FANS 3 + struct dell_smm_data { struct mutex i8k_mutex; /* lock for sensors writes */ char bios_version[4]; char bios_machineid[16]; - u32 i8k_hwmon_flags; uint i8k_fan_mult; uint i8k_pwm_mult; uint i8k_fan_max; @@ -72,23 +73,11 @@ struct dell_smm_data { bool disallow_fan_support; unsigned int manual_fan; unsigned int auto_fan; - int types[3]; + int temp_type[DELL_SMM_NO_TEMP]; + bool fan[DELL_SMM_NO_FANS]; + int fan_type[DELL_SMM_NO_FANS]; }; -#define I8K_HWMON_HAVE_TEMP1 (1 << 0) -#define I8K_HWMON_HAVE_TEMP2 (1 << 1) -#define I8K_HWMON_HAVE_TEMP3 (1 << 2) -#define I8K_HWMON_HAVE_TEMP4 (1 << 3) -#define I8K_HWMON_HAVE_TEMP5 (1 << 4) -#define I8K_HWMON_HAVE_TEMP6 (1 << 5) -#define I8K_HWMON_HAVE_TEMP7 (1 << 6) -#define I8K_HWMON_HAVE_TEMP8 (1 << 7) -#define I8K_HWMON_HAVE_TEMP9 (1 << 8) -#define I8K_HWMON_HAVE_TEMP10 (1 << 9) -#define I8K_HWMON_HAVE_FAN1 (1 << 10) -#define I8K_HWMON_HAVE_FAN2 (1 << 11) -#define I8K_HWMON_HAVE_FAN3 (1 << 12) - MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); MODULE_AUTHOR("Pali Rohár "); MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver"); @@ -130,6 +119,33 @@ struct smm_regs { unsigned int edi __packed; }; +static const char * const temp_labels[] = { + "CPU", + "GPU", + "SODIMM", + "Other", + "Ambient", + "Other", +}; + +static const char * const fan_labels[] = { + "Processor Fan", + "Motherboard Fan", + "Video Fan", + "Power Supply Fan", + "Chipset Fan", + "Other Fan", +}; + +static const char * const docking_labels[] = { + "Docking Processor Fan", + "Docking Motherboard Fan", + "Docking Video Fan", + "Docking Power Supply Fan", + "Docking Chipset Fan", + "Docking Other Fan", +}; + static inline const char __init *i8k_get_dmi_data(int field) { const char *dmi_data = dmi_get_system_info(field); @@ -284,10 +300,10 @@ static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan) static int i8k_get_fan_type(struct dell_smm_data *data, int fan) { /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ - if (data->types[fan] == INT_MIN) - data->types[fan] = _i8k_get_fan_type(data, fan); + if (data->fan_type[fan] == INT_MIN) + data->fan_type[fan] = _i8k_get_fan_type(data, fan); - return data->types[fan]; + return data->fan_type[fan]; } /* @@ -334,7 +350,7 @@ static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed) return i8k_smm(®s) ? : i8k_get_fan_status(data, fan); } -static int i8k_get_temp_type(int sensor) +static int __init i8k_get_temp_type(int sensor) { struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; @@ -634,343 +650,299 @@ static void __init i8k_init_procfs(struct device *dev) * Hwmon interface */ -static ssize_t i8k_hwmon_temp_label_show(struct device *dev, - struct device_attribute *devattr, - char *buf) +static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, + int channel) { - static const char * const labels[] = { - "CPU", - "GPU", - "SODIMM", - "Other", - "Ambient", - "Other", - }; - int index = to_sensor_dev_attr(devattr)->index; - int type; + const struct dell_smm_data *data = drvdata; - type = i8k_get_temp_type(index); - if (type < 0) - return type; - if (type >= ARRAY_SIZE(labels)) - type = ARRAY_SIZE(labels) - 1; - return sprintf(buf, "%s\n", labels[type]); + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + if (data->temp_type[channel] >= 0) + return 0444; + + break; + default: + break; + } + break; + case hwmon_fan: + if (data->disallow_fan_support) + break; + + switch (attr) { + case hwmon_fan_input: + if (data->fan[channel]) + return 0444; + + break; + case hwmon_fan_label: + if (data->fan[channel] && !data->disallow_fan_type_call) + return 0444; + + break; + default: + break; + } + break; + case hwmon_pwm: + if (data->disallow_fan_support) + break; + + switch (attr) { + case hwmon_pwm_input: + if (data->fan[channel]) + return 0644; + + break; + case hwmon_pwm_enable: + if (data->auto_fan) + /* + * There is no command for retrieve the current status + * from BIOS, and userspace/firmware itself can change + * it. + * Thus we can only provide write-only access for now. + */ + return 0200; + + break; + default: + break; + } + break; + default: + break; + } + + return 0; } -static ssize_t i8k_hwmon_temp_show(struct device *dev, - struct device_attribute *devattr, - char *buf) +static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) { - int index = to_sensor_dev_attr(devattr)->index; - int temp; + struct dell_smm_data *data = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + ret = i8k_get_temp(channel); + if (ret < 0) + return ret; + + *val = ret * 1000; + + return 0; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + ret = i8k_get_fan_speed(data, channel); + if (ret < 0) + return ret; - temp = i8k_get_temp(index); - if (temp < 0) - return temp; - return sprintf(buf, "%d\n", temp * 1000); + *val = ret; + + return 0; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + ret = i8k_get_fan_status(data, channel); + if (ret < 0) + return ret; + + *val = clamp_val(ret * data->i8k_pwm_mult, 0, 255); + + return 0; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; } -static ssize_t i8k_hwmon_fan_label_show(struct device *dev, - struct device_attribute *devattr, - char *buf) +static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel) { - struct dell_smm_data *data = dev_get_drvdata(dev); - static const char * const labels[] = { - "Processor Fan", - "Motherboard Fan", - "Video Fan", - "Power Supply Fan", - "Chipset Fan", - "Other Fan", - }; - int index = to_sensor_dev_attr(devattr)->index; bool dock = false; - int type; + int type = i8k_get_fan_type(data, channel); - type = i8k_get_fan_type(data, index); if (type < 0) - return type; + return ERR_PTR(type); if (type & 0x10) { dock = true; type &= 0x0F; } - if (type >= ARRAY_SIZE(labels)) - type = (ARRAY_SIZE(labels) - 1); + if (type >= ARRAY_SIZE(fan_labels)) + type = ARRAY_SIZE(fan_labels) - 1; - return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]); + return dock ? docking_labels[type] : fan_labels[type]; } -static ssize_t i8k_hwmon_fan_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) { struct dell_smm_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(devattr)->index; - int fan_speed; - - fan_speed = i8k_get_fan_speed(data, index); - if (fan_speed < 0) - return fan_speed; - return sprintf(buf, "%d\n", fan_speed); -} -static ssize_t i8k_hwmon_pwm_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct dell_smm_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(devattr)->index; - int status; + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str = temp_labels[data->temp_type[channel]]; + return 0; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_label: + *str = dell_smm_fan_label(data, channel); + return PTR_ERR_OR_ZERO(*str); + default: + break; + } + break; + default: + break; + } - status = i8k_get_fan_status(data, index); - if (status < 0) - return -EIO; - return sprintf(buf, "%d\n", clamp_val(status * data->i8k_pwm_mult, 0, 255)); + return -EOPNOTSUPP; } -static ssize_t i8k_hwmon_pwm_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) { struct dell_smm_data *data = dev_get_drvdata(dev); - int index = to_sensor_dev_attr(attr)->index; - unsigned long val; + unsigned long pwm; + bool enable; int err; - err = kstrtoul(buf, 10, &val); - if (err) - return err; - val = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0, data->i8k_fan_max); + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0, + data->i8k_fan_max); - mutex_lock(&data->i8k_mutex); - err = i8k_set_fan(data, index, val); - mutex_unlock(&data->i8k_mutex); + mutex_lock(&data->i8k_mutex); + err = i8k_set_fan(data, channel, pwm); + mutex_unlock(&data->i8k_mutex); - return err < 0 ? -EIO : count; -} + if (err < 0) + return err; -static ssize_t i8k_hwmon_pwm_enable_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct dell_smm_data *data = dev_get_drvdata(dev); - int err; - bool enable; - unsigned long val; + return 0; + case hwmon_pwm_enable: + if (!val) + return -EINVAL; - if (!data->auto_fan) - return -ENODEV; + if (val == 1) + enable = false; + else + enable = true; - err = kstrtoul(buf, 10, &val); - if (err) - return err; + mutex_lock(&data->i8k_mutex); + err = i8k_enable_fan_auto_mode(data, enable); + mutex_unlock(&data->i8k_mutex); - if (val == 1) - enable = false; - else if (val == 2) - enable = true; - else - return -EINVAL; + if (err < 0) + return err; - mutex_lock(&data->i8k_mutex); - err = i8k_enable_fan_auto_mode(data, enable); - mutex_unlock(&data->i8k_mutex); + return 0; + default: + break; + } + break; + default: + break; + } - return err ? err : count; + return -EOPNOTSUPP; } -static SENSOR_DEVICE_ATTR_RO(temp1_input, i8k_hwmon_temp, 0); -static SENSOR_DEVICE_ATTR_RO(temp1_label, i8k_hwmon_temp_label, 0); -static SENSOR_DEVICE_ATTR_RO(temp2_input, i8k_hwmon_temp, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_label, i8k_hwmon_temp_label, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_input, i8k_hwmon_temp, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_label, i8k_hwmon_temp_label, 2); -static SENSOR_DEVICE_ATTR_RO(temp4_input, i8k_hwmon_temp, 3); -static SENSOR_DEVICE_ATTR_RO(temp4_label, i8k_hwmon_temp_label, 3); -static SENSOR_DEVICE_ATTR_RO(temp5_input, i8k_hwmon_temp, 4); -static SENSOR_DEVICE_ATTR_RO(temp5_label, i8k_hwmon_temp_label, 4); -static SENSOR_DEVICE_ATTR_RO(temp6_input, i8k_hwmon_temp, 5); -static SENSOR_DEVICE_ATTR_RO(temp6_label, i8k_hwmon_temp_label, 5); -static SENSOR_DEVICE_ATTR_RO(temp7_input, i8k_hwmon_temp, 6); -static SENSOR_DEVICE_ATTR_RO(temp7_label, i8k_hwmon_temp_label, 6); -static SENSOR_DEVICE_ATTR_RO(temp8_input, i8k_hwmon_temp, 7); -static SENSOR_DEVICE_ATTR_RO(temp8_label, i8k_hwmon_temp_label, 7); -static SENSOR_DEVICE_ATTR_RO(temp9_input, i8k_hwmon_temp, 8); -static SENSOR_DEVICE_ATTR_RO(temp9_label, i8k_hwmon_temp_label, 8); -static SENSOR_DEVICE_ATTR_RO(temp10_input, i8k_hwmon_temp, 9); -static SENSOR_DEVICE_ATTR_RO(temp10_label, i8k_hwmon_temp_label, 9); -static SENSOR_DEVICE_ATTR_RO(fan1_input, i8k_hwmon_fan, 0); -static SENSOR_DEVICE_ATTR_RO(fan1_label, i8k_hwmon_fan_label, 0); -static SENSOR_DEVICE_ATTR_RW(pwm1, i8k_hwmon_pwm, 0); -static SENSOR_DEVICE_ATTR_WO(pwm1_enable, i8k_hwmon_pwm_enable, 0); -static SENSOR_DEVICE_ATTR_RO(fan2_input, i8k_hwmon_fan, 1); -static SENSOR_DEVICE_ATTR_RO(fan2_label, i8k_hwmon_fan_label, 1); -static SENSOR_DEVICE_ATTR_RW(pwm2, i8k_hwmon_pwm, 1); -static SENSOR_DEVICE_ATTR_RO(fan3_input, i8k_hwmon_fan, 2); -static SENSOR_DEVICE_ATTR_RO(fan3_label, i8k_hwmon_fan_label, 2); -static SENSOR_DEVICE_ATTR_RW(pwm3, i8k_hwmon_pwm, 2); - -static struct attribute *i8k_attrs[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */ - &sensor_dev_attr_temp1_label.dev_attr.attr, /* 1 */ - &sensor_dev_attr_temp2_input.dev_attr.attr, /* 2 */ - &sensor_dev_attr_temp2_label.dev_attr.attr, /* 3 */ - &sensor_dev_attr_temp3_input.dev_attr.attr, /* 4 */ - &sensor_dev_attr_temp3_label.dev_attr.attr, /* 5 */ - &sensor_dev_attr_temp4_input.dev_attr.attr, /* 6 */ - &sensor_dev_attr_temp4_label.dev_attr.attr, /* 7 */ - &sensor_dev_attr_temp5_input.dev_attr.attr, /* 8 */ - &sensor_dev_attr_temp5_label.dev_attr.attr, /* 9 */ - &sensor_dev_attr_temp6_input.dev_attr.attr, /* 10 */ - &sensor_dev_attr_temp6_label.dev_attr.attr, /* 11 */ - &sensor_dev_attr_temp7_input.dev_attr.attr, /* 12 */ - &sensor_dev_attr_temp7_label.dev_attr.attr, /* 13 */ - &sensor_dev_attr_temp8_input.dev_attr.attr, /* 14 */ - &sensor_dev_attr_temp8_label.dev_attr.attr, /* 15 */ - &sensor_dev_attr_temp9_input.dev_attr.attr, /* 16 */ - &sensor_dev_attr_temp9_label.dev_attr.attr, /* 17 */ - &sensor_dev_attr_temp10_input.dev_attr.attr, /* 18 */ - &sensor_dev_attr_temp10_label.dev_attr.attr, /* 19 */ - &sensor_dev_attr_fan1_input.dev_attr.attr, /* 20 */ - &sensor_dev_attr_fan1_label.dev_attr.attr, /* 21 */ - &sensor_dev_attr_pwm1.dev_attr.attr, /* 22 */ - &sensor_dev_attr_pwm1_enable.dev_attr.attr, /* 23 */ - &sensor_dev_attr_fan2_input.dev_attr.attr, /* 24 */ - &sensor_dev_attr_fan2_label.dev_attr.attr, /* 25 */ - &sensor_dev_attr_pwm2.dev_attr.attr, /* 26 */ - &sensor_dev_attr_fan3_input.dev_attr.attr, /* 27 */ - &sensor_dev_attr_fan3_label.dev_attr.attr, /* 28 */ - &sensor_dev_attr_pwm3.dev_attr.attr, /* 29 */ +static const struct hwmon_ops dell_smm_ops = { + .is_visible = dell_smm_is_visible, + .read = dell_smm_read, + .read_string = dell_smm_read_string, + .write = dell_smm_write, +}; + +static const struct hwmon_channel_info *dell_smm_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_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL + ), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL + ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT + ), NULL }; -static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, - int index) +static const struct hwmon_chip_info dell_smm_chip_info = { + .ops = &dell_smm_ops, + .info = dell_smm_info, +}; + +static int __init dell_smm_init_hwmon(struct device *dev) { - struct device *dev = kobj_to_dev(kobj); struct dell_smm_data *data = dev_get_drvdata(dev); + struct device *dell_smm_hwmon_dev; + int i, err; - if (data->disallow_fan_support && index >= 20) - return 0; - if (data->disallow_fan_type_call && - (index == 21 || index == 25 || index == 28)) - return 0; - if (index >= 0 && index <= 1 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) - return 0; - if (index >= 2 && index <= 3 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) - return 0; - if (index >= 4 && index <= 5 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) - return 0; - if (index >= 6 && index <= 7 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) - return 0; - if (index >= 8 && index <= 9 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP5)) - return 0; - if (index >= 10 && index <= 11 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP6)) - return 0; - if (index >= 12 && index <= 13 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP7)) - return 0; - if (index >= 14 && index <= 15 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP8)) - return 0; - if (index >= 16 && index <= 17 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP9)) - return 0; - if (index >= 18 && index <= 19 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10)) - return 0; - - if (index >= 20 && index <= 23 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) - return 0; - if (index >= 24 && index <= 26 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) - return 0; - if (index >= 27 && index <= 29 && - !(data->i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3)) - return 0; - - if (index == 23 && !data->auto_fan) - return 0; + for (i = 0; i < DELL_SMM_NO_TEMP; i++) { + data->temp_type[i] = i8k_get_temp_type(i); + if (data->temp_type[i] < 0) + continue; - return attr->mode; -} + if (data->temp_type[i] >= ARRAY_SIZE(temp_labels)) + data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1; + } -static const struct attribute_group i8k_group = { - .attrs = i8k_attrs, - .is_visible = i8k_is_visible, -}; -__ATTRIBUTE_GROUPS(i8k); + for (i = 0; i < DELL_SMM_NO_FANS; i++) { + data->fan_type[i] = INT_MIN; + err = i8k_get_fan_status(data, i); + if (err < 0) + err = i8k_get_fan_type(data, i); + if (err >= 0) + data->fan[i] = true; + } -static int __init dell_smm_init_hwmon(struct device *dev) -{ - struct dell_smm_data *data = dev_get_drvdata(dev); - struct device *i8k_hwmon_dev; - int err; + dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data, + &dell_smm_chip_info, NULL); - /* CPU temperature attributes, if temperature type is OK */ - err = i8k_get_temp_type(0); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1; - /* check for additional temperature sensors */ - err = i8k_get_temp_type(1); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2; - err = i8k_get_temp_type(2); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3; - err = i8k_get_temp_type(3); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4; - err = i8k_get_temp_type(4); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP5; - err = i8k_get_temp_type(5); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP6; - err = i8k_get_temp_type(6); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP7; - err = i8k_get_temp_type(7); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP8; - err = i8k_get_temp_type(8); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP9; - err = i8k_get_temp_type(9); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP10; - - /* First fan attributes, if fan status or type is OK */ - err = i8k_get_fan_status(data, 0); - if (err < 0) - err = i8k_get_fan_type(data, 0); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1; - - /* Second fan attributes, if fan status or type is OK */ - err = i8k_get_fan_status(data, 1); - if (err < 0) - err = i8k_get_fan_type(data, 1); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; - - /* Third fan attributes, if fan status or type is OK */ - err = i8k_get_fan_status(data, 2); - if (err < 0) - err = i8k_get_fan_type(data, 2); - if (err >= 0) - data->i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; - - i8k_hwmon_dev = devm_hwmon_device_register_with_groups(dev, "dell_smm", data, i8k_groups); - - return PTR_ERR_OR_ZERO(i8k_hwmon_dev); + return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev); } struct i8k_config_data { @@ -1240,9 +1212,6 @@ static int __init dell_smm_probe(struct platform_device *pdev) mutex_init(&data->i8k_mutex); data->i8k_fan_mult = I8K_FAN_MULT; data->i8k_fan_max = I8K_FAN_HIGH; - data->types[0] = INT_MIN; - data->types[1] = INT_MIN; - data->types[2] = INT_MIN; platform_set_drvdata(pdev, data); if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { From 2757269a7defe96e871be795b680b602efb877ce Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Thu, 29 Jul 2021 00:15:57 +0200 Subject: [PATCH 19/41] hwmon: (dell-smm-hwmon) Fix fan mutliplier detection for 3rd fan There are up to three fans, but the detection omits the 3rd one. Fix that by using DELL_SMM_NO_FANS. Signed-off-by: Armin Wolf Fixes: 747bc8b063ae (hwmon: (dell-smm) Detect fan with index=2) Link: https://lore.kernel.org/r/20210728221557.8891-7-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index aaf864dbe53a51..206418405440c3 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1263,7 +1263,7 @@ static int __init dell_smm_probe(struct platform_device *pdev) * Autodetect fan multiplier based on nominal rpm * If fan reports rpm value too high then set multiplier to 1 */ - for (fan = 0; fan < 2; ++fan) { + for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) { ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max); if (ret < 0) continue; From 95d88d054ad9dfe43c9634865764ac20fc373583 Mon Sep 17 00:00:00 2001 From: Carlos Alberto Lopez Perez Date: Mon, 2 Aug 2021 14:15:38 +0100 Subject: [PATCH 20/41] hwmon: (dell-smm) Add Dell Precision 7510 to fan control whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows manual PWM control without the BIOS fighting back on Dell Precision 7510. Meanwhile at it, also sort alphabetically the entries of the i8k_whitelist_fan_control struct. Signed-off-by: Carlos Alberto Lopez Perez Acked-by: Pali Rohár Link: https://lore.kernel.org/r/20210802131538.8660-1-clopez@igalia.com Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 206418405440c3..2313f8f7e43e76 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1164,14 +1164,6 @@ static const struct i8k_fan_control_data i8k_fan_control_data[] = { }; static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = { - { - .ident = "Dell Precision 5530", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"), - }, - .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], - }, { .ident = "Dell Latitude 5480", .matches = { @@ -1196,6 +1188,22 @@ static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = { }, .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], }, + { + .ident = "Dell Precision 5530", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], + }, + { + .ident = "Dell Precision 7510", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7510"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], + }, { } }; From e104d530f3734c7666edf86bbf1b83b1bfafe6ee Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Tue, 3 Aug 2021 16:15:56 +0200 Subject: [PATCH 21/41] hwmon: Replace deprecated CPU-hotplug functions. The functions get_online_cpus() and put_online_cpus() have been deprecated during the CPU hotplug rework. They map directly to cpus_read_lock() and cpus_read_unlock(). Replace deprecated CPU-hotplug functions with the official version. The behavior remains unchanged. Cc: Jean Delvare Cc: Guenter Roeck Cc: linux-hwmon@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20210803141621.780504-14-bigeasy@linutronix.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 4 ++-- drivers/hwmon/fam15h_power.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 2313f8f7e43e76..43da32ad2dce16 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -248,9 +248,9 @@ static int i8k_smm(struct smm_regs *regs) { int ret; - get_online_cpus(); + cpus_read_lock(); ret = smp_call_on_cpu(0, i8k_smm_func, regs, true); - put_online_cpus(); + cpus_read_unlock(); return ret; } diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c index 29f5fed28c2a78..521534d5c1e5f7 100644 --- a/drivers/hwmon/fam15h_power.c +++ b/drivers/hwmon/fam15h_power.c @@ -166,7 +166,7 @@ static int read_registers(struct fam15h_power_data *data) memset(data->cu_on, 0, sizeof(int) * MAX_CUS); - get_online_cpus(); + cpus_read_lock(); /* * Choose the first online core of each compute unit, and then @@ -190,7 +190,7 @@ static int read_registers(struct fam15h_power_data *data) on_each_cpu_mask(mask, do_read_registers_on_cu, data, true); - put_online_cpus(); + cpus_read_unlock(); free_cpumask_var(mask); return 0; From 542613a25eff333488c331dd92066388a6bf95bb Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 30 Jul 2021 01:05:42 +0200 Subject: [PATCH 22/41] dt-bindings: hwmon: Add bindings for Winbond W83781D This adds a device tree binding for the Winbond W83781D and its sibling HW monitoring ICs. This is used in for example the Freecom FSG-3 router/NAS. Cc: devicetree@vger.kernel.org Signed-off-by: Linus Walleij Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20210729230543.2853485-1-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- .../bindings/hwmon/winbond,w83781d.yaml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/winbond,w83781d.yaml diff --git a/Documentation/devicetree/bindings/hwmon/winbond,w83781d.yaml b/Documentation/devicetree/bindings/hwmon/winbond,w83781d.yaml new file mode 100644 index 00000000000000..31ce77a4b087ef --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/winbond,w83781d.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/hwmon/winbond,w83781d.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Winbond W83781 and compatible hardware monitor IC + +maintainers: + - Linus Walleij + +properties: + compatible: + enum: + - winbond,w83781d + - winbond,w83781g + - winbond,w83782d + - winbond,w83783s + - asus,as99127f + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@28 { + compatible = "winbond,w83781d"; + reg = <0x28>; + }; + }; From 2284ed9ffc062d1755a5299e2cc6896d2cfba000 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Fri, 30 Jul 2021 01:05:43 +0200 Subject: [PATCH 23/41] hwmon: (w83781d) Match on device tree compatibles I2C devices should match on the proper compatible string. This is already used in one device tree in the kernel (MIPS) so let's add the matches. Signed-off-by: Linus Walleij Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20210729230543.2853485-2-linus.walleij@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/w83781d.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/hwmon/w83781d.c b/drivers/hwmon/w83781d.c index e84aa5604e64a4..ce8e2c10e854f1 100644 --- a/drivers/hwmon/w83781d.c +++ b/drivers/hwmon/w83781d.c @@ -1571,10 +1571,21 @@ static const struct i2c_device_id w83781d_ids[] = { }; MODULE_DEVICE_TABLE(i2c, w83781d_ids); +static const struct of_device_id w83781d_of_match[] = { + { .compatible = "winbond,w83781d" }, + { .compatible = "winbond,w83781g" }, + { .compatible = "winbond,w83782d" }, + { .compatible = "winbond,w83783s" }, + { .compatible = "asus,as99127f" }, + { }, +}; +MODULE_DEVICE_TABLE(of, w83781d_of_match); + static struct i2c_driver w83781d_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "w83781d", + .of_match_table = w83781d_of_match, }, .probe_new = w83781d_probe, .remove = w83781d_remove, From 76b72736f574ec38b3e94603ea5f74b1853f26b0 Mon Sep 17 00:00:00 2001 From: Brandon Wyman Date: Fri, 6 Aug 2021 22:51:31 +0000 Subject: [PATCH 24/41] hwmon: (pmbus/ibm-cffps) Fix write bits for LED control When doing a PMBus write for the LED control on the IBM Common Form Factor Power Supplies (ibm-cffps), the DAh command requires that bit 7 be low and bit 6 be high in order to indicate that you are truly attempting to do a write. Signed-off-by: Brandon Wyman Link: https://lore.kernel.org/r/20210806225131.1808759-1-bjwyman@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ibm-cffps.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 5668d8305b78ec..df712ce4b164db 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -50,9 +50,9 @@ #define CFFPS_MFR_VAUX_FAULT BIT(6) #define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) -#define CFFPS_LED_BLINK BIT(0) -#define CFFPS_LED_ON BIT(1) -#define CFFPS_LED_OFF BIT(2) +#define CFFPS_LED_BLINK (BIT(0) | BIT(6)) +#define CFFPS_LED_ON (BIT(1) | BIT(6)) +#define CFFPS_LED_OFF (BIT(2) | BIT(6)) #define CFFPS_BLINK_RATE_MS 250 enum { From a3933625de28e730c7822d2b14f896125577abe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Wed, 11 Aug 2021 13:48:51 +0200 Subject: [PATCH 25/41] hwmon: (axi-fan-control) Make sure the clock is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The core will only work if it's clock is enabled. This patch is a minor enhancement to make sure that's the case. Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20210811114853.159298-2-nuno.sa@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index e3f6b03e6764b9..901d1588234d20 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -351,6 +351,11 @@ static int axi_fan_control_init(struct axi_fan_control_data *ctl, return ret; } +static void axi_fan_control_clk_disable(void *clk) +{ + clk_disable_unprepare(clk); +} + static const struct hwmon_channel_info *axi_fan_control_info[] = { HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_LABEL), @@ -406,6 +411,14 @@ static int axi_fan_control_probe(struct platform_device *pdev) return PTR_ERR(clk); } + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, axi_fan_control_clk_disable, clk); + if (ret) + return ret; + ctl->clk_rate = clk_get_rate(clk); if (!ctl->clk_rate) return -EINVAL; From e66705de8206350bf24cb66c4a0e675be35f3430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Wed, 11 Aug 2021 13:48:52 +0200 Subject: [PATCH 26/41] hwmon: (axi-fan-control) Handle irqs in natural order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The core will now start out of reset at boot as soon as clocking is available. Hence, by the time we unmask the interrupts we already might have some of them set. Thus, it's important to handle them in the natural order the core generates them. Otherwise, we could process 'ADI_IRQ_SRC_PWM_CHANGED' before 'ADI_IRQ_SRC_TEMP_INCREASE' and erroneously set 'update_tacho_params' to true. Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20210811114853.159298-3-nuno.sa@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index 901d1588234d20..c898ad121dc712 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -283,18 +283,9 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data) u32 irq_pending = axi_ioread(ADI_REG_IRQ_PENDING, ctl); u32 clear_mask; - if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) { - if (ctl->update_tacho_params) { - u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl); - - /* get 25% tolerance */ - u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100); - /* set new tacho parameters */ - axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl); - axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl); - ctl->update_tacho_params = false; - } - } + if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE) + /* hardware requested a new pwm */ + ctl->hw_pwm_req = true; if (irq_pending & ADI_IRQ_SRC_PWM_CHANGED) { /* @@ -310,9 +301,18 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data) } } - if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE) - /* hardware requested a new pwm */ - ctl->hw_pwm_req = true; + if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) { + if (ctl->update_tacho_params) { + u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl); + /* get 25% tolerance */ + u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100); + + /* set new tacho parameters */ + axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl); + axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl); + ctl->update_tacho_params = false; + } + } if (irq_pending & ADI_IRQ_SRC_TACH_ERR) ctl->fan_fault = 1; From 2aee7e67bee7a5aa741bad6a0a472f108b29ad40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Wed, 11 Aug 2021 13:48:53 +0200 Subject: [PATCH 27/41] hwmon: (axi-fan-control) Support temperature vs pwm points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HW has some predefined points where it will associate a PWM value. However some users might want to better set these points to their usecases. This patch exposes these points as pwm auto_points: * pwm1_auto_point1_temp_hyst: temperature threshold below which PWM should be 0%; * pwm1_auto_point1_temp: temperature threshold above which PWM should be 25%; * pwm1_auto_point2_temp_hyst: temperature threshold below which PWM should be 25%; * pwm1_auto_point2_temp: temperature threshold above which PWM should be 50%; * pwm1_auto_point3_temp_hyst: temperature threshold below which PWM should be 50%; * pwm1_auto_point3_temp: temperature threshold above which PWM should be 75%; * pwm1_auto_point4_temp_hyst: temperature threshold below which PWM should be 75%; * pwm1_auto_point4_temp: temperature threshold above which PWM should be 100%; Signed-off-by: Nuno Sá Link: https://lore.kernel.org/r/20210811114853.159298-4-nuno.sa@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 74 ++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index c898ad121dc712..d2092c17d99355 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,14 @@ #define ADI_REG_PWM_PERIOD 0x00c0 #define ADI_REG_TACH_MEASUR 0x00c4 #define ADI_REG_TEMPERATURE 0x00c8 +#define ADI_REG_TEMP_00_H 0x0100 +#define ADI_REG_TEMP_25_L 0x0104 +#define ADI_REG_TEMP_25_H 0x0108 +#define ADI_REG_TEMP_50_L 0x010c +#define ADI_REG_TEMP_50_H 0x0110 +#define ADI_REG_TEMP_75_L 0x0114 +#define ADI_REG_TEMP_75_H 0x0118 +#define ADI_REG_TEMP_100_L 0x011c #define ADI_REG_IRQ_MASK 0x0040 #define ADI_REG_IRQ_PENDING 0x0044 @@ -62,6 +71,39 @@ static inline u32 axi_ioread(const u32 reg, return ioread32(ctl->base + reg); } +/* + * The core calculates the temperature as: + * T = /raw * 509.3140064 / 65535) - 280.2308787 + */ +static ssize_t axi_fan_control_show(struct device *dev, struct device_attribute *da, char *buf) +{ + struct axi_fan_control_data *ctl = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + u32 temp = axi_ioread(attr->index, ctl); + + temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230; + + return sprintf(buf, "%u\n", temp); +} + +static ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct axi_fan_control_data *ctl = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + u32 temp; + int ret; + + ret = kstrtou32(buf, 10, &temp); + if (ret) + return ret; + + temp = DIV_ROUND_CLOSEST_ULL((temp + 280230) * 65535ULL, 509314); + axi_iowrite(temp, attr->index, ctl); + + return count; +} + static long axi_fan_control_get_pwm_duty(const struct axi_fan_control_data *ctl) { u32 pwm_width = axi_ioread(ADI_REG_PWM_WIDTH, ctl); @@ -375,6 +417,36 @@ static const struct hwmon_chip_info axi_chip_info = { .info = axi_fan_control_info, }; +/* temperature threshold below which PWM should be 0% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp_hyst, axi_fan_control, ADI_REG_TEMP_00_H); +/* temperature threshold above which PWM should be 25% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, axi_fan_control, ADI_REG_TEMP_25_L); +/* temperature threshold below which PWM should be 25% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp_hyst, axi_fan_control, ADI_REG_TEMP_25_H); +/* temperature threshold above which PWM should be 50% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, axi_fan_control, ADI_REG_TEMP_50_L); +/* temperature threshold below which PWM should be 50% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp_hyst, axi_fan_control, ADI_REG_TEMP_50_H); +/* temperature threshold above which PWM should be 75% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, axi_fan_control, ADI_REG_TEMP_75_L); +/* temperature threshold below which PWM should be 75% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp_hyst, axi_fan_control, ADI_REG_TEMP_75_H); +/* temperature threshold above which PWM should be 100% */ +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, axi_fan_control, ADI_REG_TEMP_100_L); + +static struct attribute *axi_fan_control_attrs[] = { + &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + NULL, +}; +ATTRIBUTE_GROUPS(axi_fan_control); + static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a'); static const struct of_device_id axi_fan_control_of_match[] = { @@ -459,7 +531,7 @@ static int axi_fan_control_probe(struct platform_device *pdev) name, ctl, &axi_chip_info, - NULL); + axi_fan_control_groups); return PTR_ERR_OR_ZERO(ctl->hdev); } From 7a8c68c57fd09541377f6971f25efdeb9a926c37 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Thu, 12 Aug 2021 13:39:59 +1200 Subject: [PATCH 28/41] hwmon: (pmbus/bpa-rs600) Don't use rated limits as warn limits In the initial implementation a number of PMBUS_x_WARN_LIMITs were mapped to MFR fields. This was incorrect as these MFR limits reflect the rated limit as opposed to a limit which will generate warning. Instead return -ENXIO like we were already doing for other WARN_LIMITs. Subsequently these rated limits have been exposed generically as new fields in the sysfs ABI so the values are still available. Fixes: 15b2703e5e02 ("hwmon: (pmbus) Add driver for BluTek BPA-RS600") Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20210812014000.26293-2-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/bpa-rs600.c | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/drivers/hwmon/pmbus/bpa-rs600.c b/drivers/hwmon/pmbus/bpa-rs600.c index d205b41540cede..84dee86399cb1a 100644 --- a/drivers/hwmon/pmbus/bpa-rs600.c +++ b/drivers/hwmon/pmbus/bpa-rs600.c @@ -12,15 +12,6 @@ #include #include "pmbus.h" -#define BPARS600_MFR_VIN_MIN 0xa0 -#define BPARS600_MFR_VIN_MAX 0xa1 -#define BPARS600_MFR_IIN_MAX 0xa2 -#define BPARS600_MFR_PIN_MAX 0xa3 -#define BPARS600_MFR_VOUT_MIN 0xa4 -#define BPARS600_MFR_VOUT_MAX 0xa5 -#define BPARS600_MFR_IOUT_MAX 0xa6 -#define BPARS600_MFR_POUT_MAX 0xa7 - enum chips { bpa_rs600, bpd_rs600 }; static int bpa_rs600_read_byte_data(struct i2c_client *client, int page, int reg) @@ -83,29 +74,13 @@ static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int pha switch (reg) { case PMBUS_VIN_UV_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MIN); - break; case PMBUS_VIN_OV_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MAX); - break; case PMBUS_VOUT_UV_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MIN); - break; case PMBUS_VOUT_OV_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MAX); - break; case PMBUS_IIN_OC_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IIN_MAX); - break; case PMBUS_IOUT_OC_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IOUT_MAX); - break; case PMBUS_PIN_OP_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_PIN_MAX); - break; case PMBUS_POUT_OP_WARN_LIMIT: - ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_POUT_MAX); - break; case PMBUS_VIN_UV_FAULT_LIMIT: case PMBUS_VIN_OV_FAULT_LIMIT: case PMBUS_VOUT_UV_FAULT_LIMIT: From 1125bacbf36cc53121cecba20d1def032881085d Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Thu, 12 Aug 2021 13:40:00 +1200 Subject: [PATCH 29/41] hwmon: (pmbus/bpa-rs600) Add workaround for incorrect Pin max BPD-RS600 modules running firmware v5.70 misreport the MFR_PIN_MAX. The indicate a maximum of 1640W instead of 700W. Detect the invalid reading and return a sensible value instead. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20210812014000.26293-3-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/bpa-rs600.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/hwmon/pmbus/bpa-rs600.c b/drivers/hwmon/pmbus/bpa-rs600.c index 84dee86399cb1a..f2d4e378a77572 100644 --- a/drivers/hwmon/pmbus/bpa-rs600.c +++ b/drivers/hwmon/pmbus/bpa-rs600.c @@ -65,6 +65,26 @@ static int bpa_rs600_read_vin(struct i2c_client *client) return ret; } +/* + * Firmware V5.70 incorrectly reports 1640W for MFR_PIN_MAX. + * Deal with this by returning a sensible value. + */ +static int bpa_rs600_read_pin_max(struct i2c_client *client) +{ + int ret; + + ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_MFR_PIN_MAX); + if (ret < 0) + return ret; + + /* Detect invalid 1640W (linear encoding) */ + if (ret == 0x0b34) + /* Report 700W (linear encoding) */ + return 0x095e; + + return ret; +} + static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int phase, int reg) { int ret; @@ -91,6 +111,9 @@ static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int pha case PMBUS_READ_VIN: ret = bpa_rs600_read_vin(client); break; + case PMBUS_MFR_PIN_MAX: + ret = bpa_rs600_read_pin_max(client); + break; default: if (reg >= PMBUS_VIRT_BASE) ret = -ENXIO; From c510f6accbba66af900d710a05b635581f4129f4 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 14 Aug 2021 16:36:34 +0200 Subject: [PATCH 30/41] hwmon: (dell-smm) Mark tables as __initconst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the config and the DMI tables never change and are only used during module init for setting up the device data struct. Mark all of them as const and __initconst for a smaller runtime memory footprint. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20210814143637.11922-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 43da32ad2dce16..68af95c1d90c9b 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -957,7 +957,7 @@ enum i8k_configs { DELL_XPS, }; -static const struct i8k_config_data i8k_config_data[] = { +static const struct i8k_config_data i8k_config_data[] __initconst = { [DELL_LATITUDE_D520] = { .fan_mult = 1, .fan_max = I8K_FAN_TURBO, @@ -1115,7 +1115,7 @@ static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst * support for affected blacklisted Dell machines stay disabled. * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751 */ -static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = { +static const struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initconst = { { .ident = "Dell Inspiron 7720", .matches = { @@ -1156,14 +1156,14 @@ enum i8k_fan_controls { I8K_FAN_34A3_35A3, }; -static const struct i8k_fan_control_data i8k_fan_control_data[] = { +static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = { [I8K_FAN_34A3_35A3] = { .manual_fan = 0x34a3, .auto_fan = 0x35a3, }, }; -static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = { +static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = { { .ident = "Dell Latitude 5480", .matches = { From 782a99c146ffe9d0e06f139daea0e56d866eabc8 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 14 Aug 2021 16:36:37 +0200 Subject: [PATCH 31/41] hwmon: (dell-smm) Mark i8k_get_fan_nominal_speed as __init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mark function i8k_get_fan_nominal_speed() as __init since it is only used in code also marked as __init. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20210814143637.11922-5-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 68af95c1d90c9b..f93baf1035cc6c 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -309,7 +309,7 @@ static int i8k_get_fan_type(struct dell_smm_data *data, int fan) /* * Read the fan nominal rpm for specific fan speed. */ -static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed) +static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; From 8713b4a49c8a9acc1fe25e6b98a9bbd0e9b3a680 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 14 Aug 2021 21:05:16 +0200 Subject: [PATCH 32/41] hwmon: (dell-smm) Rework SMM function debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop #ifdef DEBUG and use ktime_us_delta() for improved precision. Signed-off-by: Armin Wolf Reviewed-by: Pali Rohár Link: https://lore.kernel.org/r/20210814190516.26718-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index f93baf1035cc6c..774c1b0715d914 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -158,17 +158,12 @@ static inline const char __init *i8k_get_dmi_data(int field) */ static int i8k_smm_func(void *par) { - int rc; + ktime_t calltime = ktime_get(); struct smm_regs *regs = par; int eax = regs->eax; - -#ifdef DEBUG int ebx = regs->ebx; - unsigned long duration; - ktime_t calltime, delta, rettime; - - calltime = ktime_get(); -#endif + long long duration; + int rc; /* SMM requires CPU 0 */ if (smp_processor_id() != 0) @@ -230,13 +225,9 @@ static int i8k_smm_func(void *par) if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax) rc = -EINVAL; -#ifdef DEBUG - rettime = ktime_get(); - delta = ktime_sub(rettime, calltime); - duration = ktime_to_ns(delta) >> 10; - pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lu usecs)\n", eax, ebx, - (rc ? 0xffff : regs->eax & 0xffff), duration); -#endif + duration = ktime_us_delta(ktime_get(), calltime); + pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lld usecs)\n", eax, ebx, + (rc ? 0xffff : regs->eax & 0xffff), duration); return rc; } From b3a7ab2d4376726178909e27b6956c012277ac4e Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Tue, 17 Aug 2021 10:48:11 +0200 Subject: [PATCH 33/41] hwmon: remove amd_energy driver in Makefile Commit 9049572fb145 ("hwmon: Remove amd_energy driver") removes the driver, but misses to adjust the Makefile. Hence, ./scripts/checkkconfigsymbols.py warns: SENSORS_AMD_ENERGY Referencing files: drivers/hwmon/Makefile Remove the missing piece of this driver removal. Fixes: 9049572fb145 ("hwmon: Remove amd_energy driver") Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20210817084811.10673-1-lukas.bulwahn@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 53a8f4b500b826..4b33421746c0a5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -45,7 +45,6 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_AHT10) += aht10.o -obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o From 128066c88770c7b26352fa400cb67297775cc4e8 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 26 Aug 2021 13:40:52 -0500 Subject: [PATCH 34/41] hwmon: (k10temp) Add additional missing Zen2 and Zen3 APUs These follow the rest of the existing codepaths for families 17h and 19h. Signed-off-by: Mario Limonciello Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index fe3d92152e3596..1d3c8d31994137 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -437,6 +437,8 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) k10temp_get_ccd_support(pdev, data, 4); break; case 0x31: /* Zen2 Threadripper */ + case 0x60: /* Renoir */ + case 0x68: /* Lucienne */ case 0x71: /* Zen2 */ k10temp_get_ccd_support(pdev, data, 8); break; @@ -450,7 +452,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) switch (boot_cpu_data.x86_model) { case 0x0 ... 0x1: /* Zen3 SP3/TR */ case 0x21: /* Zen3 Ryzen Desktop */ - case 0x50: /* Zen3 Ryzen APU */ + case 0x50 ... 0x5f: /* Green Sardine */ k10temp_get_ccd_support(pdev, data, 8); break; } From 02a2484cf8d17a2acf3b9b151147bafaa55ad38c Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 26 Aug 2021 13:40:56 -0500 Subject: [PATCH 35/41] hwmon: (k10temp) Don't show Tdie for all Zen/Zen2/Zen3 CPU/APU Tdie is an offset calculation that should only be shown when temp_offset is actually put into a table. This is useless to show for all CPU/APU. Show it only when necessary. Signed-off-by: Mario Limonciello Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 1d3c8d31994137..f6b325b8463e6e 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -426,7 +426,6 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) } else if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) { data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK; data->read_tempreg = read_tempreg_nb_zen; - data->show_temp |= BIT(TDIE_BIT); /* show Tdie */ data->is_zen = true; switch (boot_cpu_data.x86_model) { @@ -446,7 +445,6 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) } else if (boot_cpu_data.x86 == 0x19) { data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK; data->read_tempreg = read_tempreg_nb_zen; - data->show_temp |= BIT(TDIE_BIT); data->is_zen = true; switch (boot_cpu_data.x86_model) { @@ -466,6 +464,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (boot_cpu_data.x86 == entry->model && strstr(boot_cpu_data.x86_model_id, entry->id)) { + data->show_temp |= BIT(TDIE_BIT); /* show Tdie */ data->temp_offset = entry->offset; break; } From 0e3f52bbd9ebaef5fba1b4c70e4132b9782c7c7e Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 27 Aug 2021 15:15:25 -0500 Subject: [PATCH 36/41] hwmon: (k10temp) Rework the temperature offset calculation Some of the existing assumptions made do not scale properly to new silicon in upcoming changes. This commit should cause no functional changes to existing silicon. Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20210827201527.24454-2-mario.limonciello@amd.com Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index f6b325b8463e6e..159dbad73d82ee 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -65,10 +65,11 @@ static DEFINE_MUTEX(nb_smu_ind_mutex); #define F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET 0xd8200c64 #define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET 0xd8200ca4 -/* Common for Zen CPU families (Family 17h and 18h) */ -#define ZEN_REPORTED_TEMP_CTRL_OFFSET 0x00059800 +/* Common for Zen CPU families (Family 17h and 18h and 19h) */ +#define ZEN_REPORTED_TEMP_CTRL_BASE 0x00059800 -#define ZEN_CCD_TEMP(x) (0x00059954 + ((x) * 4)) +#define ZEN_CCD_TEMP(offset, x) (ZEN_REPORTED_TEMP_CTRL_BASE + \ + (offset) + ((x) * 4)) #define ZEN_CCD_TEMP_VALID BIT(11) #define ZEN_CCD_TEMP_MASK GENMASK(10, 0) @@ -103,6 +104,7 @@ struct k10temp_data { u32 temp_adjust_mask; u32 show_temp; bool is_zen; + u32 ccd_offset; }; #define TCTL_BIT 0 @@ -163,7 +165,7 @@ static void read_tempreg_nb_f15(struct pci_dev *pdev, u32 *regval) static void read_tempreg_nb_zen(struct pci_dev *pdev, u32 *regval) { amd_smn_read(amd_pci_dev_to_node_id(pdev), - ZEN_REPORTED_TEMP_CTRL_OFFSET, regval); + ZEN_REPORTED_TEMP_CTRL_BASE, regval); } static long get_raw_temp(struct k10temp_data *data) @@ -226,7 +228,8 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, break; case 2 ... 9: /* Tccd{1-8} */ amd_smn_read(amd_pci_dev_to_node_id(data->pdev), - ZEN_CCD_TEMP(channel - 2), ®val); + ZEN_CCD_TEMP(data->ccd_offset, channel - 2), + ®val); *val = (regval & ZEN_CCD_TEMP_MASK) * 125 - 49000; break; default: @@ -387,7 +390,7 @@ static void k10temp_get_ccd_support(struct pci_dev *pdev, for (i = 0; i < limit; i++) { amd_smn_read(amd_pci_dev_to_node_id(pdev), - ZEN_CCD_TEMP(i), ®val); + ZEN_CCD_TEMP(data->ccd_offset, i), ®val); if (regval & ZEN_CCD_TEMP_VALID) data->show_temp |= BIT(TCCD_BIT(i)); } @@ -433,12 +436,14 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) case 0x8: /* Zen+ */ case 0x11: /* Zen APU */ case 0x18: /* Zen+ APU */ + data->ccd_offset = 0x154; k10temp_get_ccd_support(pdev, data, 4); break; case 0x31: /* Zen2 Threadripper */ case 0x60: /* Renoir */ case 0x68: /* Lucienne */ case 0x71: /* Zen2 */ + data->ccd_offset = 0x154; k10temp_get_ccd_support(pdev, data, 8); break; } @@ -451,6 +456,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) case 0x0 ... 0x1: /* Zen3 SP3/TR */ case 0x21: /* Zen3 Ryzen Desktop */ case 0x50 ... 0x5f: /* Green Sardine */ + data->ccd_offset = 0x154; k10temp_get_ccd_support(pdev, data, 8); break; } From 25572c818d2e40b5d7231a9dc49bd45a6b6c3dfa Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 27 Aug 2021 15:15:26 -0500 Subject: [PATCH 37/41] hwmon: (k10temp) Add support for yellow carp Yellow carp matches same behavior as green sardine and other Zen3 products, but have different CCD offsets. Signed-off-by: Mario Limonciello Acked-by: Borislav Petkov Link: https://lore.kernel.org/r/20210827201527.24454-3-mario.limonciello@amd.com Signed-off-by: Guenter Roeck --- arch/x86/kernel/amd_nb.c | 5 +++++ drivers/hwmon/k10temp.c | 5 +++++ include/linux/pci_ids.h | 1 + 3 files changed, 11 insertions(+) diff --git a/arch/x86/kernel/amd_nb.c b/arch/x86/kernel/amd_nb.c index 23dda362dc0f35..c92c9c774c0e52 100644 --- a/arch/x86/kernel/amd_nb.c +++ b/arch/x86/kernel/amd_nb.c @@ -25,6 +25,8 @@ #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F4 0x144c #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F4 0x1444 #define PCI_DEVICE_ID_AMD_19H_DF_F4 0x1654 +#define PCI_DEVICE_ID_AMD_19H_M40H_ROOT 0x14b5 +#define PCI_DEVICE_ID_AMD_19H_M40H_DF_F4 0x167d #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F4 0x166e /* Protect the PCI config register pairs used for SMN and DF indirect access. */ @@ -37,6 +39,7 @@ static const struct pci_device_id amd_root_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M10H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M30H_ROOT) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_ROOT) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_ROOT) }, {} }; @@ -58,6 +61,7 @@ static const struct pci_device_id amd_nb_misc_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, {} }; @@ -74,6 +78,7 @@ static const struct pci_device_id amd_nb_link_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F4) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F4) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F4) }, {} diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 159dbad73d82ee..38bc35ac8135ee 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -459,6 +459,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->ccd_offset = 0x154; k10temp_get_ccd_support(pdev, data, 8); break; + case 0x40 ... 0x4f: /* Yellow Carp */ + data->ccd_offset = 0x300; + k10temp_get_ccd_support(pdev, data, 8); + break; } } else { data->read_htcreg = read_htcreg_pci; @@ -499,6 +503,7 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, {} diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 4bac1831de802e..3d50d5bbf037fd 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -555,6 +555,7 @@ #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443 #define PCI_DEVICE_ID_AMD_19H_DF_F3 0x1653 +#define PCI_DEVICE_ID_AMD_19H_M40H_DF_F3 0x167c #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F3 0x166d #define PCI_DEVICE_ID_AMD_CNB17H_F3 0x1703 #define PCI_DEVICE_ID_AMD_LANCE 0x2000 From 23bd022aa6182367e1add7b4d05777cdba283756 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Thu, 26 Aug 2021 14:41:18 +1200 Subject: [PATCH 38/41] hwmon: (adt7470) Fix some style issues In preparation for the changes that follow fix up some existing style issues. Specifically: - add blank line between variable declaration and code - use strscpy instead of strlcpy - remove unnecessary braces Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20210826024121.15665-2-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 2e8feacccf84d7..3358ec58b9771e 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -174,6 +174,7 @@ struct adt7470_data { static inline int adt7470_read_word_data(struct i2c_client *client, u8 reg) { u16 foo; + foo = i2c_smbus_read_byte_data(client, reg); foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8); return foo; @@ -1282,7 +1283,7 @@ static int adt7470_detect(struct i2c_client *client, if (revision != ADT7470_REVISION) return -ENODEV; - strlcpy(info->type, "adt7470", I2C_NAME_SIZE); + strscpy(info->type, "adt7470", I2C_NAME_SIZE); return 0; } @@ -1331,9 +1332,8 @@ static int adt7470_probe(struct i2c_client *client) data->auto_update = kthread_run(adt7470_update_thread, client, "%s", dev_name(hwmon_dev)); - if (IS_ERR(data->auto_update)) { + if (IS_ERR(data->auto_update)) return PTR_ERR(data->auto_update); - } return 0; } From ef67959c42539bfece4d7c4335c07656703bb027 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Thu, 26 Aug 2021 14:41:19 +1200 Subject: [PATCH 39/41] hwmon: (adt7470) Convert to use regmap Convert the adt7470 to using regmap which allows better error handling. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20210826024121.15665-3-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 414 +++++++++++++++++++++++----------------- 1 file changed, 241 insertions(+), 173 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 3358ec58b9771e..ad3e46667be8eb 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -35,7 +36,10 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; #define ADT7470_REG_PWM_MAX_BASE_ADDR 0x38 #define ADT7470_REG_PWM_MAX_MAX_ADDR 0x3B #define ADT7470_REG_CFG 0x40 +#define ADT7470_STRT_MASK 0x01 +#define ADT7470_TEST_MASK 0x02 #define ADT7470_FSPD_MASK 0x04 +#define ADT7470_T05_STB_MASK 0x80 #define ADT7470_REG_ALARM1 0x41 #define ADT7470_R1T_ALARM 0x01 #define ADT7470_R2T_ALARM 0x02 @@ -137,7 +141,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; #define ADT7470_FREQ_SHIFT 4 struct adt7470_data { - struct i2c_client *client; + struct regmap *regmap; struct mutex lock; char sensors_valid; char limits_valid; @@ -171,52 +175,76 @@ struct adt7470_data { * 16-bit registers on the ADT7470 are low-byte first. The data sheet says * that the low byte must be read before the high byte. */ -static inline int adt7470_read_word_data(struct i2c_client *client, u8 reg) +static inline int adt7470_read_word_data(struct adt7470_data *data, unsigned int reg, + unsigned int *val) { - u16 foo; + u8 regval[2]; + int err; + + err = regmap_bulk_read(data->regmap, reg, ®val, 2); + if (err < 0) + return err; + + *val = regval[0] | (regval[1] << 8); - foo = i2c_smbus_read_byte_data(client, reg); - foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8); - return foo; + return 0; } -static inline int adt7470_write_word_data(struct i2c_client *client, u8 reg, - u16 value) +static inline int adt7470_write_word_data(struct adt7470_data *data, unsigned int reg, + unsigned int val) { - return i2c_smbus_write_byte_data(client, reg, value & 0xFF) - || i2c_smbus_write_byte_data(client, reg + 1, value >> 8); + u8 regval[2]; + + regval[0] = val & 0xFF; + regval[1] = val >> 8; + + return regmap_bulk_write(data->regmap, reg, ®val, 2); } /* Probe for temperature sensors. Assumes lock is held */ -static int adt7470_read_temperatures(struct i2c_client *client, - struct adt7470_data *data) +static int adt7470_read_temperatures(struct adt7470_data *data) { unsigned long res; + unsigned int pwm_cfg[2]; + int err; int i; - u8 cfg, pwm[4], pwm_cfg[2]; + u8 pwm[ADT7470_FAN_COUNT]; /* save pwm[1-4] config register */ - pwm_cfg[0] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(0)); - pwm_cfg[1] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(2)); + err = regmap_read(data->regmap, ADT7470_REG_PWM_CFG(0), &pwm_cfg[0]); + if (err < 0) + return err; + err = regmap_read(data->regmap, ADT7470_REG_PWM_CFG(2), &pwm_cfg[1]); + if (err < 0) + return err; /* set manual pwm to whatever it is set to now */ - for (i = 0; i < ADT7470_FAN_COUNT; i++) - pwm[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM(i)); + err = regmap_bulk_read(data->regmap, ADT7470_REG_PWM(0), &pwm[0], + ADT7470_PWM_COUNT); + if (err < 0) + return err; /* put pwm in manual mode */ - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0), - pwm_cfg[0] & ~(ADT7470_PWM_AUTO_MASK)); - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2), - pwm_cfg[1] & ~(ADT7470_PWM_AUTO_MASK)); + err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(0), + ADT7470_PWM_AUTO_MASK, 0); + if (err < 0) + return err; + err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(2), + ADT7470_PWM_AUTO_MASK, 0); + if (err < 0) + return err; /* write pwm control to whatever it was */ - for (i = 0; i < ADT7470_FAN_COUNT; i++) - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(i), pwm[i]); + err = regmap_bulk_write(data->regmap, ADT7470_REG_PWM(0), &pwm[0], + ADT7470_PWM_COUNT); + if (err < 0) + return err; /* start reading temperature sensors */ - cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); - cfg |= 0x80; - i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg); + err = regmap_update_bits(data->regmap, ADT7470_REG_CFG, + ADT7470_T05_STB_MASK, ADT7470_T05_STB_MASK); + if (err < 0) + return err; /* Delay is 200ms * number of temp sensors. */ res = msleep_interruptible((data->num_temp_sensors >= 0 ? @@ -224,26 +252,31 @@ static int adt7470_read_temperatures(struct i2c_client *client, TEMP_COLLECTION_TIME)); /* done reading temperature sensors */ - cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); - cfg &= ~0x80; - i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg); + err = regmap_update_bits(data->regmap, ADT7470_REG_CFG, + ADT7470_T05_STB_MASK, 0); + if (err < 0) + return err; /* restore pwm[1-4] config registers */ - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]); - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]); - - if (res) { - pr_err("ha ha, interrupted\n"); + err = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]); + if (err < 0) + return err; + err = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]); + if (err < 0) + return err; + + if (res) return -EAGAIN; - } /* Only count fans if we have to */ if (data->num_temp_sensors >= 0) return 0; + err = regmap_bulk_read(data->regmap, ADT7470_TEMP_REG(0), &data->temp[0], + ADT7470_TEMP_COUNT); + if (err < 0) + return err; for (i = 0; i < ADT7470_TEMP_COUNT; i++) { - data->temp[i] = i2c_smbus_read_byte_data(client, - ADT7470_TEMP_REG(i)); if (data->temp[i]) data->num_temp_sensors = i + 1; } @@ -258,7 +291,7 @@ static int adt7470_update_thread(void *p) while (!kthread_should_stop()) { mutex_lock(&data->lock); - adt7470_read_temperatures(client, data); + adt7470_read_temperatures(data); mutex_unlock(&data->lock); set_current_state(TASK_INTERRUPTIBLE); @@ -273,89 +306,116 @@ static int adt7470_update_thread(void *p) static int adt7470_update_sensors(struct adt7470_data *data) { - struct i2c_client *client = data->client; - u8 cfg; + unsigned int val; + int err; int i; if (!data->temperatures_probed) - adt7470_read_temperatures(client, data); + err = adt7470_read_temperatures(data); else - for (i = 0; i < ADT7470_TEMP_COUNT; i++) - data->temp[i] = i2c_smbus_read_byte_data(client, - ADT7470_TEMP_REG(i)); + err = regmap_bulk_read(data->regmap, ADT7470_TEMP_REG(0), &data->temp[0], + ADT7470_TEMP_COUNT); + if (err < 0) + return err; - for (i = 0; i < ADT7470_FAN_COUNT; i++) - data->fan[i] = adt7470_read_word_data(client, - ADT7470_REG_FAN(i)); + for (i = 0; i < ADT7470_FAN_COUNT; i++) { + err = adt7470_read_word_data(data, ADT7470_REG_FAN(i), &val); + if (err < 0) + return err; + data->fan[i] = val; + } - for (i = 0; i < ADT7470_PWM_COUNT; i++) { - int reg; - int reg_mask; + err = regmap_bulk_read(data->regmap, ADT7470_REG_PWM(0), &data->pwm[0], ADT7470_PWM_COUNT); + if (err < 0) + return err; - data->pwm[i] = i2c_smbus_read_byte_data(client, - ADT7470_REG_PWM(i)); + for (i = 0; i < ADT7470_PWM_COUNT; i++) { + unsigned int mask; if (i % 2) - reg_mask = ADT7470_PWM2_AUTO_MASK; + mask = ADT7470_PWM2_AUTO_MASK; else - reg_mask = ADT7470_PWM1_AUTO_MASK; + mask = ADT7470_PWM1_AUTO_MASK; - reg = ADT7470_REG_PWM_CFG(i); - if (i2c_smbus_read_byte_data(client, reg) & reg_mask) - data->pwm_automatic[i] = 1; - else - data->pwm_automatic[i] = 0; + err = regmap_read(data->regmap, ADT7470_REG_PWM_CFG(i), &val); + if (err < 0) + return err; + data->pwm_automatic[i] = !!(val & mask); - reg = ADT7470_REG_PWM_AUTO_TEMP(i); - cfg = i2c_smbus_read_byte_data(client, reg); + err = regmap_read(data->regmap, ADT7470_REG_PWM_AUTO_TEMP(i), &val); + if (err < 0) + return err; if (!(i % 2)) - data->pwm_auto_temp[i] = cfg >> 4; + data->pwm_auto_temp[i] = val >> 4; else - data->pwm_auto_temp[i] = cfg & 0xF; + data->pwm_auto_temp[i] = val & 0xF; } - if (i2c_smbus_read_byte_data(client, ADT7470_REG_CFG) & - ADT7470_FSPD_MASK) - data->force_pwm_max = 1; - else - data->force_pwm_max = 0; + err = regmap_read(data->regmap, ADT7470_REG_CFG, &val); + if (err < 0) + return err; + data->force_pwm_max = !!(val & ADT7470_FSPD_MASK); + + err = regmap_read(data->regmap, ADT7470_REG_ALARM1, &val); + if (err < 0) + return err; + data->alarm = val; + if (data->alarm & ADT7470_OOL_ALARM) { + err = regmap_read(data->regmap, ADT7470_REG_ALARM2, &val); + if (err < 0) + return err; + data->alarm |= ALARM2(val); + } - data->alarm = i2c_smbus_read_byte_data(client, ADT7470_REG_ALARM1); - if (data->alarm & ADT7470_OOL_ALARM) - data->alarm |= ALARM2(i2c_smbus_read_byte_data(client, - ADT7470_REG_ALARM2)); - data->alarms_mask = adt7470_read_word_data(client, - ADT7470_REG_ALARM1_MASK); + err = adt7470_read_word_data(data, ADT7470_REG_ALARM1_MASK, &val); + if (err < 0) + return err; + data->alarms_mask = val; return 0; } static int adt7470_update_limits(struct adt7470_data *data) { - struct i2c_client *client = data->client; + unsigned int val; + int err; int i; for (i = 0; i < ADT7470_TEMP_COUNT; i++) { - data->temp_min[i] = i2c_smbus_read_byte_data(client, - ADT7470_TEMP_MIN_REG(i)); - data->temp_max[i] = i2c_smbus_read_byte_data(client, - ADT7470_TEMP_MAX_REG(i)); + err = regmap_read(data->regmap, ADT7470_TEMP_MIN_REG(i), &val); + if (err < 0) + return err; + data->temp_min[i] = (s8)val; + err = regmap_read(data->regmap, ADT7470_TEMP_MAX_REG(i), &val); + if (err < 0) + return err; + data->temp_max[i] = (s8)val; } for (i = 0; i < ADT7470_FAN_COUNT; i++) { - data->fan_min[i] = adt7470_read_word_data(client, - ADT7470_REG_FAN_MIN(i)); - data->fan_max[i] = adt7470_read_word_data(client, - ADT7470_REG_FAN_MAX(i)); + err = adt7470_read_word_data(data, ADT7470_REG_FAN_MIN(i), &val); + if (err < 0) + return err; + data->fan_min[i] = val; + err = adt7470_read_word_data(data, ADT7470_REG_FAN_MAX(i), &val); + if (err < 0) + return err; + data->fan_max[i] = val; } for (i = 0; i < ADT7470_PWM_COUNT; i++) { - data->pwm_max[i] = i2c_smbus_read_byte_data(client, - ADT7470_REG_PWM_MAX(i)); - data->pwm_min[i] = i2c_smbus_read_byte_data(client, - ADT7470_REG_PWM_MIN(i)); - data->pwm_tmin[i] = i2c_smbus_read_byte_data(client, - ADT7470_REG_PWM_TMIN(i)); + err = regmap_read(data->regmap, ADT7470_REG_PWM_MAX(i), &val); + if (err < 0) + return err; + data->pwm_max[i] = val; + err = regmap_read(data->regmap, ADT7470_REG_PWM_MIN(i), &val); + if (err < 0) + return err; + data->pwm_min[i] = val; + err = regmap_read(data->regmap, ADT7470_REG_PWM_TMIN(i), &val); + if (err < 0) + return err; + data->pwm_tmin[i] = (s8)val; } return 0; @@ -491,8 +551,8 @@ static ssize_t temp_min_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -502,11 +562,11 @@ static ssize_t temp_min_store(struct device *dev, mutex_lock(&data->lock); data->temp_min[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7470_TEMP_MIN_REG(attr->index), + err = regmap_write(data->regmap, ADT7470_TEMP_MIN_REG(attr->index), temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t temp_max_show(struct device *dev, @@ -527,8 +587,8 @@ static ssize_t temp_max_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -538,11 +598,10 @@ static ssize_t temp_max_store(struct device *dev, mutex_lock(&data->lock); data->temp_max[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7470_TEMP_MAX_REG(attr->index), - temp); + err = regmap_write(data->regmap, ADT7470_TEMP_MAX_REG(attr->index), temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, @@ -575,6 +634,7 @@ static ssize_t alarm_mask_store(struct device *dev, { struct adt7470_data *data = dev_get_drvdata(dev); long mask; + int err; if (kstrtoul(buf, 0, &mask)) return -EINVAL; @@ -584,10 +644,10 @@ static ssize_t alarm_mask_store(struct device *dev, mutex_lock(&data->lock); data->alarms_mask = mask; - adt7470_write_word_data(data->client, ADT7470_REG_ALARM1_MASK, mask); + err = adt7470_write_word_data(data, ADT7470_REG_ALARM1_MASK, mask); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t fan_max_show(struct device *dev, @@ -612,8 +672,8 @@ static ssize_t fan_max_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp) || !temp) return -EINVAL; @@ -623,10 +683,10 @@ static ssize_t fan_max_store(struct device *dev, mutex_lock(&data->lock); data->fan_max[attr->index] = temp; - adt7470_write_word_data(client, ADT7470_REG_FAN_MAX(attr->index), temp); + err = adt7470_write_word_data(data, ADT7470_REG_FAN_MAX(attr->index), temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t fan_min_show(struct device *dev, @@ -651,8 +711,8 @@ static ssize_t fan_min_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp) || !temp) return -EINVAL; @@ -662,10 +722,10 @@ static ssize_t fan_min_store(struct device *dev, mutex_lock(&data->lock); data->fan_min[attr->index] = temp; - adt7470_write_word_data(client, ADT7470_REG_FAN_MIN(attr->index), temp); + err = adt7470_write_word_data(data, ADT7470_REG_FAN_MIN(attr->index), temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t fan_show(struct device *dev, struct device_attribute *devattr, @@ -700,24 +760,20 @@ static ssize_t force_pwm_max_store(struct device *dev, const char *buf, size_t count) { struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; - u8 reg; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; mutex_lock(&data->lock); data->force_pwm_max = temp; - reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); - if (temp) - reg |= ADT7470_FSPD_MASK; - else - reg &= ~ADT7470_FSPD_MASK; - i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg); + err = regmap_update_bits(data->regmap, ADT7470_REG_CFG, + ADT7470_FSPD_MASK, + temp ? ADT7470_FSPD_MASK : 0); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t pwm_show(struct device *dev, struct device_attribute *devattr, @@ -737,8 +793,8 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *devattr, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -747,10 +803,10 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *devattr, mutex_lock(&data->lock); data->pwm[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(attr->index), temp); + err = regmap_write(data->regmap, ADT7470_REG_PWM(attr->index), temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } /* These are the valid PWM frequencies to the nearest Hz */ @@ -762,13 +818,20 @@ static ssize_t pwm1_freq_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct adt7470_data *data = adt7470_update_device(dev); - unsigned char cfg_reg_1; - unsigned char cfg_reg_2; + unsigned int cfg_reg_1, cfg_reg_2; int index; + int err; + + if (IS_ERR(data)) + return PTR_ERR(data); mutex_lock(&data->lock); - cfg_reg_1 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG); - cfg_reg_2 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG_2); + err = regmap_read(data->regmap, ADT7470_REG_CFG, &cfg_reg_1); + if (err < 0) + goto out; + err = regmap_read(data->regmap, ADT7470_REG_CFG_2, &cfg_reg_2); + if (err < 0) + goto out; mutex_unlock(&data->lock); index = (cfg_reg_2 & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT; @@ -778,6 +841,10 @@ static ssize_t pwm1_freq_show(struct device *dev, index = ARRAY_SIZE(adt7470_freq_map) - 1; return scnprintf(buf, PAGE_SIZE, "%d\n", adt7470_freq_map[index]); + +out: + mutex_unlock(&data->lock); + return err; } static ssize_t pwm1_freq_store(struct device *dev, @@ -785,11 +852,10 @@ static ssize_t pwm1_freq_store(struct device *dev, const char *buf, size_t count) { struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long freq; int index; int low_freq = ADT7470_CFG_LF; - unsigned char val; + int err; if (kstrtol(buf, 10, &freq)) return -EINVAL; @@ -805,16 +871,19 @@ static ssize_t pwm1_freq_store(struct device *dev, mutex_lock(&data->lock); /* Configuration Register 1 */ - val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); - i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, - (val & ~ADT7470_CFG_LF) | low_freq); + err = regmap_update_bits(data->regmap, ADT7470_REG_CFG, + ADT7470_CFG_LF, low_freq); + if (err < 0) + goto out; + /* Configuration Register 2 */ - val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG_2); - i2c_smbus_write_byte_data(client, ADT7470_REG_CFG_2, - (val & ~ADT7470_FREQ_MASK) | (index << ADT7470_FREQ_SHIFT)); + err = regmap_update_bits(data->regmap, ADT7470_REG_CFG_2, + ADT7470_FREQ_MASK, + index << ADT7470_FREQ_SHIFT); +out: mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t pwm_max_show(struct device *dev, @@ -835,8 +904,8 @@ static ssize_t pwm_max_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -845,11 +914,11 @@ static ssize_t pwm_max_store(struct device *dev, mutex_lock(&data->lock); data->pwm_max[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MAX(attr->index), - temp); + err = regmap_write(data->regmap, ADT7470_REG_PWM_MAX(attr->index), + temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t pwm_min_show(struct device *dev, @@ -870,8 +939,8 @@ static ssize_t pwm_min_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -880,11 +949,11 @@ static ssize_t pwm_min_store(struct device *dev, mutex_lock(&data->lock); data->pwm_min[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MIN(attr->index), - temp); + err = regmap_write(data->regmap, ADT7470_REG_PWM_MIN(attr->index), + temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t pwm_tmax_show(struct device *dev, @@ -918,8 +987,8 @@ static ssize_t pwm_tmin_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; long temp; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -929,11 +998,11 @@ static ssize_t pwm_tmin_store(struct device *dev, mutex_lock(&data->lock); data->pwm_tmin[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_TMIN(attr->index), - temp); + err = regmap_write(data->regmap, ADT7470_REG_PWM_TMIN(attr->index), + temp); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t pwm_auto_show(struct device *dev, @@ -954,11 +1023,9 @@ static ssize_t pwm_auto_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - int pwm_auto_reg = ADT7470_REG_PWM_CFG(attr->index); int pwm_auto_reg_mask; long temp; - u8 reg; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -974,15 +1041,12 @@ static ssize_t pwm_auto_store(struct device *dev, mutex_lock(&data->lock); data->pwm_automatic[attr->index] = temp; - reg = i2c_smbus_read_byte_data(client, pwm_auto_reg); - if (temp) - reg |= pwm_auto_reg_mask; - else - reg &= ~pwm_auto_reg_mask; - i2c_smbus_write_byte_data(client, pwm_auto_reg, reg); + err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(attr->index), + pwm_auto_reg_mask, + temp ? pwm_auto_reg_mask : 0); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t pwm_auto_temp_show(struct device *dev, @@ -1017,10 +1081,10 @@ static ssize_t pwm_auto_temp_store(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; int pwm_auto_reg = ADT7470_REG_PWM_AUTO_TEMP(attr->index); + unsigned int mask, val; long temp; - u8 reg; + int err; if (kstrtol(buf, 10, &temp)) return -EINVAL; @@ -1031,20 +1095,19 @@ static ssize_t pwm_auto_temp_store(struct device *dev, mutex_lock(&data->lock); data->pwm_automatic[attr->index] = temp; - reg = i2c_smbus_read_byte_data(client, pwm_auto_reg); if (!(attr->index % 2)) { - reg &= 0xF; - reg |= (temp << 4) & 0xF0; + mask = 0xF0; + val = (temp << 4) & 0xF0; } else { - reg &= 0xF0; - reg |= temp & 0xF; + mask = 0x0F; + val = temp & 0x0F; } - i2c_smbus_write_byte_data(client, pwm_auto_reg, reg); + err = regmap_update_bits(data->regmap, pwm_auto_reg, mask, val); mutex_unlock(&data->lock); - return count; + return err < 0 ? err : count; } static ssize_t alarm_show(struct device *dev, @@ -1053,6 +1116,9 @@ static ssize_t alarm_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 (data->alarm & attr->index) return sprintf(buf, "1\n"); else @@ -1288,23 +1354,19 @@ static int adt7470_detect(struct i2c_client *client, return 0; } -static void adt7470_init_client(struct i2c_client *client) -{ - int reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG); - - if (reg < 0) { - dev_err(&client->dev, "cannot read configuration register\n"); - } else { - /* start monitoring (and do a self-test) */ - i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg | 3); - } -} +static const struct regmap_config adt7470_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .use_single_read = true, + .use_single_write = true, +}; static int adt7470_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct adt7470_data *data; struct device *hwmon_dev; + int err; data = devm_kzalloc(dev, sizeof(struct adt7470_data), GFP_KERNEL); if (!data) @@ -1312,15 +1374,21 @@ static int adt7470_probe(struct i2c_client *client) data->num_temp_sensors = -1; data->auto_update_interval = AUTO_UPDATE_INTERVAL; + data->regmap = devm_regmap_init_i2c(client, &adt7470_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); i2c_set_clientdata(client, data); - data->client = client; mutex_init(&data->lock); dev_info(&client->dev, "%s chip found\n", client->name); /* Initialize the ADT7470 chip */ - adt7470_init_client(client); + err = regmap_update_bits(data->regmap, ADT7470_REG_CFG, + ADT7470_STRT_MASK | ADT7470_TEST_MASK, + ADT7470_STRT_MASK | ADT7470_TEST_MASK); + if (err < 0) + return err; /* Register sysfs hooks */ hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, From fc958a61ff6d34a0ec42744d3ff524ee202b2e9f Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Thu, 26 Aug 2021 14:41:20 +1200 Subject: [PATCH 40/41] hwmon: (adt7470) Convert to devm_hwmon_device_register_with_info API Use the devm_hwmon_device_register_with_info API and remove code that deals with the standard sensor attributes. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20210826024121.15665-4-chris.packham@alliedtelesis.co.nz [groeck: Fixed alignment issues] Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 681 +++++++++++++++++----------------------- 1 file changed, 284 insertions(+), 397 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index ad3e46667be8eb..d519aca4a9d645 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -533,92 +533,63 @@ static ssize_t num_temp_sensors_store(struct device *dev, return count; } -static ssize_t temp_min_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int adt7470_temp_read(struct device *dev, u32 attr, int channel, long *val) { - 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]); -} - -static ssize_t temp_min_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7470_data *data = dev_get_drvdata(dev); - long temp; - int err; - - if (kstrtol(buf, 10, &temp)) - return -EINVAL; - - temp = clamp_val(temp, -128000, 127000); - temp = DIV_ROUND_CLOSEST(temp, 1000); - - mutex_lock(&data->lock); - data->temp_min[attr->index] = temp; - err = regmap_write(data->regmap, ADT7470_TEMP_MIN_REG(attr->index), - temp); - mutex_unlock(&data->lock); + switch (attr) { + case hwmon_temp_input: + *val = 1000 * data->temp[channel]; + break; + case hwmon_temp_min: + *val = 1000 * data->temp_min[channel]; + break; + case hwmon_temp_max: + *val = 1000 * data->temp_max[channel]; + break; + case hwmon_temp_alarm: + *val = !!(data->alarm & channel); + break; + default: + return -EOPNOTSUPP; + } - return err < 0 ? err : count; + return 0; } -static ssize_t temp_max_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int adt7470_temp_write(struct device *dev, u32 attr, int channel, long val) { - 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]); -} - -static ssize_t temp_max_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - long temp; int err; - if (kstrtol(buf, 10, &temp)) - return -EINVAL; - - temp = clamp_val(temp, -128000, 127000); - temp = DIV_ROUND_CLOSEST(temp, 1000); - - mutex_lock(&data->lock); - data->temp_max[attr->index] = temp; - err = regmap_write(data->regmap, ADT7470_TEMP_MAX_REG(attr->index), temp); - mutex_unlock(&data->lock); - - return err < 0 ? err : count; -} - -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7470_data *data = adt7470_update_device(dev); + val = clamp_val(val, -128000, 127000); + val = DIV_ROUND_CLOSEST(val, 1000); - if (IS_ERR(data)) - return PTR_ERR(data); + switch (attr) { + case hwmon_temp_min: + mutex_lock(&data->lock); + data->temp_min[channel] = val; + err = regmap_write(data->regmap, ADT7470_TEMP_MIN_REG(channel), val); + mutex_unlock(&data->lock); + break; + case hwmon_temp_max: + mutex_lock(&data->lock); + data->temp_max[channel] = val; + err = regmap_write(data->regmap, ADT7470_TEMP_MAX_REG(channel), val); + mutex_unlock(&data->lock); + break; + default: + return -EOPNOTSUPP; + } - return sprintf(buf, "%d\n", 1000 * data->temp[attr->index]); + return err; } static ssize_t alarm_mask_show(struct device *dev, - struct device_attribute *devattr, - char *buf) + struct device_attribute *devattr, char *buf) { struct adt7470_data *data = adt7470_update_device(dev); @@ -650,98 +621,68 @@ static ssize_t alarm_mask_store(struct device *dev, return err < 0 ? err : count; } -static ssize_t fan_max_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int adt7470_fan_read(struct device *dev, u32 attr, int channel, long *val) { - 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])); - else - return sprintf(buf, "0\n"); -} - -static ssize_t fan_max_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7470_data *data = dev_get_drvdata(dev); - long temp; - int err; - - if (kstrtol(buf, 10, &temp) || !temp) - return -EINVAL; - - temp = FAN_RPM_TO_PERIOD(temp); - temp = clamp_val(temp, 1, 65534); - - mutex_lock(&data->lock); - data->fan_max[attr->index] = temp; - err = adt7470_write_word_data(data, ADT7470_REG_FAN_MAX(attr->index), temp); - mutex_unlock(&data->lock); - - return err < 0 ? err : count; -} - -static ssize_t fan_min_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - 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); + switch (attr) { + case hwmon_fan_input: + if (FAN_DATA_VALID(data->fan[channel])) + *val = FAN_PERIOD_TO_RPM(data->fan[channel]); + else + *val = 0; + break; + case hwmon_fan_min: + if (FAN_DATA_VALID(data->fan_min[channel])) + *val = FAN_PERIOD_TO_RPM(data->fan_min[channel]); + else + *val = 0; + break; + case hwmon_fan_max: + if (FAN_DATA_VALID(data->fan_max[channel])) + *val = FAN_PERIOD_TO_RPM(data->fan_max[channel]); + else + *val = 0; + break; + case hwmon_fan_alarm: + *val = !!(data->alarm & (1 << (12 + channel))); + break; + default: + return -EOPNOTSUPP; + } - if (FAN_DATA_VALID(data->fan_min[attr->index])) - return sprintf(buf, "%d\n", - FAN_PERIOD_TO_RPM(data->fan_min[attr->index])); - else - return sprintf(buf, "0\n"); + return 0; } -static ssize_t fan_min_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) +static int adt7470_fan_write(struct device *dev, u32 attr, int channel, long val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct adt7470_data *data = dev_get_drvdata(dev); - long temp; int err; - if (kstrtol(buf, 10, &temp) || !temp) - return -EINVAL; - - temp = FAN_RPM_TO_PERIOD(temp); - temp = clamp_val(temp, 1, 65534); - - mutex_lock(&data->lock); - data->fan_min[attr->index] = temp; - err = adt7470_write_word_data(data, ADT7470_REG_FAN_MIN(attr->index), temp); - mutex_unlock(&data->lock); - - return err < 0 ? err : count; -} - -static ssize_t fan_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7470_data *data = adt7470_update_device(dev); + val = FAN_RPM_TO_PERIOD(val); + val = clamp_val(val, 1, 65534); - if (IS_ERR(data)) - return PTR_ERR(data); + switch (attr) { + case hwmon_fan_min: + mutex_lock(&data->lock); + data->fan_min[channel] = val; + err = adt7470_write_word_data(data, ADT7470_REG_FAN_MIN(channel), val); + mutex_unlock(&data->lock); + break; + case hwmon_fan_max: + mutex_lock(&data->lock); + data->fan_max[channel] = val; + err = adt7470_write_word_data(data, ADT7470_REG_FAN_MAX(channel), val); + mutex_unlock(&data->lock); + break; + default: + return -EOPNOTSUPP; + } - if (FAN_DATA_VALID(data->fan[attr->index])) - return sprintf(buf, "%d\n", - FAN_PERIOD_TO_RPM(data->fan[attr->index])); - else - return sprintf(buf, "0\n"); + return err; } static ssize_t force_pwm_max_show(struct device *dev, @@ -776,55 +717,18 @@ static ssize_t force_pwm_max_store(struct device *dev, return err < 0 ? err : count; } -static ssize_t pwm_show(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - 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]); -} - -static ssize_t pwm_store(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7470_data *data = dev_get_drvdata(dev); - long temp; - int err; - - if (kstrtol(buf, 10, &temp)) - return -EINVAL; - - temp = clamp_val(temp, 0, 255); - - mutex_lock(&data->lock); - data->pwm[attr->index] = temp; - err = regmap_write(data->regmap, ADT7470_REG_PWM(attr->index), temp); - mutex_unlock(&data->lock); - - return err < 0 ? err : count; -} - /* These are the valid PWM frequencies to the nearest Hz */ static const int adt7470_freq_map[] = { 11, 15, 22, 29, 35, 44, 59, 88, 1400, 22500 }; -static ssize_t pwm1_freq_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int pwm1_freq_get(struct device *dev) { - struct adt7470_data *data = adt7470_update_device(dev); + struct adt7470_data *data = dev_get_drvdata(dev); unsigned int cfg_reg_1, cfg_reg_2; int index; int err; - if (IS_ERR(data)) - return PTR_ERR(data); - mutex_lock(&data->lock); err = regmap_read(data->regmap, ADT7470_REG_CFG, &cfg_reg_1); if (err < 0) @@ -840,26 +744,44 @@ static ssize_t pwm1_freq_show(struct device *dev, if (index >= ARRAY_SIZE(adt7470_freq_map)) index = ARRAY_SIZE(adt7470_freq_map) - 1; - return scnprintf(buf, PAGE_SIZE, "%d\n", adt7470_freq_map[index]); + return adt7470_freq_map[index]; out: mutex_unlock(&data->lock); return err; } -static ssize_t pwm1_freq_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) +static int adt7470_pwm_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct adt7470_data *data = adt7470_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + switch (attr) { + case hwmon_pwm_input: + *val = data->pwm[channel]; + break; + case hwmon_pwm_enable: + *val = 1 + data->pwm_automatic[channel]; + break; + case hwmon_pwm_freq: + *val = pwm1_freq_get(dev); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int pwm1_freq_set(struct device *dev, long freq) { struct adt7470_data *data = dev_get_drvdata(dev); - long freq; + unsigned int low_freq = ADT7470_CFG_LF; int index; - int low_freq = ADT7470_CFG_LF; int err; - if (kstrtol(buf, 10, &freq)) - return -EINVAL; - /* Round the user value given to the closest available frequency */ index = find_closest(freq, adt7470_freq_map, ARRAY_SIZE(adt7470_freq_map)); @@ -883,7 +805,49 @@ static ssize_t pwm1_freq_store(struct device *dev, out: mutex_unlock(&data->lock); - return err < 0 ? err : count; + return err; +} + +static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val) +{ + struct adt7470_data *data = dev_get_drvdata(dev); + unsigned int pwm_auto_reg_mask; + int err; + + switch (attr) { + case hwmon_pwm_input: + val = clamp_val(val, 0, 255); + mutex_lock(&data->lock); + data->pwm[channel] = val; + err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), + data->pwm[channel]); + mutex_unlock(&data->lock); + break; + case hwmon_pwm_enable: + if (channel % 2) + pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; + else + pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; + + if (val != 2 && val != 1) + return -EINVAL; + val--; + + mutex_lock(&data->lock); + data->pwm_automatic[channel] = val; + err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(channel), + pwm_auto_reg_mask, + val ? pwm_auto_reg_mask : 0); + mutex_unlock(&data->lock); + break; + case hwmon_pwm_freq: + err = pwm1_freq_set(dev, val); + break; + default: + return -EOPNOTSUPP; + } + + return err; } static ssize_t pwm_max_show(struct device *dev, @@ -1005,50 +969,6 @@ static ssize_t pwm_tmin_store(struct device *dev, return err < 0 ? err : count; } -static ssize_t pwm_auto_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - 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]); -} - -static ssize_t pwm_auto_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7470_data *data = dev_get_drvdata(dev); - int pwm_auto_reg_mask; - long temp; - int err; - - if (kstrtol(buf, 10, &temp)) - return -EINVAL; - - if (attr->index % 2) - pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; - else - pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; - - if (temp != 2 && temp != 1) - return -EINVAL; - temp--; - - mutex_lock(&data->lock); - data->pwm_automatic[attr->index] = temp; - err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(attr->index), - pwm_auto_reg_mask, - temp ? pwm_auto_reg_mask : 0); - mutex_unlock(&data->lock); - - return err < 0 ? err : count; -} - static ssize_t pwm_auto_temp_show(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -1110,98 +1030,12 @@ static ssize_t pwm_auto_temp_store(struct device *dev, return err < 0 ? err : count; } -static ssize_t alarm_show(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7470_data *data = adt7470_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); - - if (data->alarm & attr->index) - return sprintf(buf, "1\n"); - else - return sprintf(buf, "0\n"); -} - static DEVICE_ATTR_RW(alarm_mask); static DEVICE_ATTR_RW(num_temp_sensors); static DEVICE_ATTR_RW(auto_update_interval); -static SENSOR_DEVICE_ATTR_RW(temp1_max, temp_max, 0); -static SENSOR_DEVICE_ATTR_RW(temp2_max, temp_max, 1); -static SENSOR_DEVICE_ATTR_RW(temp3_max, temp_max, 2); -static SENSOR_DEVICE_ATTR_RW(temp4_max, temp_max, 3); -static SENSOR_DEVICE_ATTR_RW(temp5_max, temp_max, 4); -static SENSOR_DEVICE_ATTR_RW(temp6_max, temp_max, 5); -static SENSOR_DEVICE_ATTR_RW(temp7_max, temp_max, 6); -static SENSOR_DEVICE_ATTR_RW(temp8_max, temp_max, 7); -static SENSOR_DEVICE_ATTR_RW(temp9_max, temp_max, 8); -static SENSOR_DEVICE_ATTR_RW(temp10_max, temp_max, 9); - -static SENSOR_DEVICE_ATTR_RW(temp1_min, temp_min, 0); -static SENSOR_DEVICE_ATTR_RW(temp2_min, temp_min, 1); -static SENSOR_DEVICE_ATTR_RW(temp3_min, temp_min, 2); -static SENSOR_DEVICE_ATTR_RW(temp4_min, temp_min, 3); -static SENSOR_DEVICE_ATTR_RW(temp5_min, temp_min, 4); -static SENSOR_DEVICE_ATTR_RW(temp6_min, temp_min, 5); -static SENSOR_DEVICE_ATTR_RW(temp7_min, temp_min, 6); -static SENSOR_DEVICE_ATTR_RW(temp8_min, temp_min, 7); -static SENSOR_DEVICE_ATTR_RW(temp9_min, temp_min, 8); -static SENSOR_DEVICE_ATTR_RW(temp10_min, temp_min, 9); - -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0); -static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2); -static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3); -static SENSOR_DEVICE_ATTR_RO(temp5_input, temp, 4); -static SENSOR_DEVICE_ATTR_RO(temp6_input, temp, 5); -static SENSOR_DEVICE_ATTR_RO(temp7_input, temp, 6); -static SENSOR_DEVICE_ATTR_RO(temp8_input, temp, 7); -static SENSOR_DEVICE_ATTR_RO(temp9_input, temp, 8); -static SENSOR_DEVICE_ATTR_RO(temp10_input, temp, 9); - -static SENSOR_DEVICE_ATTR_RO(temp1_alarm, alarm, ADT7470_R1T_ALARM); -static SENSOR_DEVICE_ATTR_RO(temp2_alarm, alarm, ADT7470_R2T_ALARM); -static SENSOR_DEVICE_ATTR_RO(temp3_alarm, alarm, ADT7470_R3T_ALARM); -static SENSOR_DEVICE_ATTR_RO(temp4_alarm, alarm, ADT7470_R4T_ALARM); -static SENSOR_DEVICE_ATTR_RO(temp5_alarm, alarm, ADT7470_R5T_ALARM); -static SENSOR_DEVICE_ATTR_RO(temp6_alarm, alarm, ADT7470_R6T_ALARM); -static SENSOR_DEVICE_ATTR_RO(temp7_alarm, alarm, ADT7470_R7T_ALARM); -static SENSOR_DEVICE_ATTR_RO(temp8_alarm, alarm, ALARM2(ADT7470_R8T_ALARM)); -static SENSOR_DEVICE_ATTR_RO(temp9_alarm, alarm, ALARM2(ADT7470_R9T_ALARM)); -static SENSOR_DEVICE_ATTR_RO(temp10_alarm, alarm, ALARM2(ADT7470_R10T_ALARM)); - -static SENSOR_DEVICE_ATTR_RW(fan1_max, fan_max, 0); -static SENSOR_DEVICE_ATTR_RW(fan2_max, fan_max, 1); -static SENSOR_DEVICE_ATTR_RW(fan3_max, fan_max, 2); -static SENSOR_DEVICE_ATTR_RW(fan4_max, fan_max, 3); - -static SENSOR_DEVICE_ATTR_RW(fan1_min, fan_min, 0); -static SENSOR_DEVICE_ATTR_RW(fan2_min, fan_min, 1); -static SENSOR_DEVICE_ATTR_RW(fan3_min, fan_min, 2); -static SENSOR_DEVICE_ATTR_RW(fan4_min, fan_min, 3); - -static SENSOR_DEVICE_ATTR_RO(fan1_input, fan, 0); -static SENSOR_DEVICE_ATTR_RO(fan2_input, fan, 1); -static SENSOR_DEVICE_ATTR_RO(fan3_input, fan, 2); -static SENSOR_DEVICE_ATTR_RO(fan4_input, fan, 3); - -static SENSOR_DEVICE_ATTR_RO(fan1_alarm, alarm, ALARM2(ADT7470_FAN1_ALARM)); -static SENSOR_DEVICE_ATTR_RO(fan2_alarm, alarm, ALARM2(ADT7470_FAN2_ALARM)); -static SENSOR_DEVICE_ATTR_RO(fan3_alarm, alarm, ALARM2(ADT7470_FAN3_ALARM)); -static SENSOR_DEVICE_ATTR_RO(fan4_alarm, alarm, ALARM2(ADT7470_FAN4_ALARM)); - static SENSOR_DEVICE_ATTR_RW(force_pwm_max, force_pwm_max, 0); -static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0); -static SENSOR_DEVICE_ATTR_RW(pwm2, pwm, 1); -static SENSOR_DEVICE_ATTR_RW(pwm3, pwm, 2); -static SENSOR_DEVICE_ATTR_RW(pwm4, pwm, 3); - -static DEVICE_ATTR_RW(pwm1_freq); - static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_min, 0); static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point1_pwm, pwm_min, 1); static SENSOR_DEVICE_ATTR_RW(pwm3_auto_point1_pwm, pwm_min, 2); @@ -1222,11 +1056,6 @@ static SENSOR_DEVICE_ATTR_RO(pwm2_auto_point2_temp, pwm_tmax, 1); static SENSOR_DEVICE_ATTR_RO(pwm3_auto_point2_temp, pwm_tmax, 2); static SENSOR_DEVICE_ATTR_RO(pwm4_auto_point2_temp, pwm_tmax, 3); -static SENSOR_DEVICE_ATTR_RW(pwm1_enable, pwm_auto, 0); -static SENSOR_DEVICE_ATTR_RW(pwm2_enable, pwm_auto, 1); -static SENSOR_DEVICE_ATTR_RW(pwm3_enable, pwm_auto, 2); -static SENSOR_DEVICE_ATTR_RW(pwm4_enable, pwm_auto, 3); - static SENSOR_DEVICE_ATTR_RW(pwm1_auto_channels_temp, pwm_auto_temp, 0); static SENSOR_DEVICE_ATTR_RW(pwm2_auto_channels_temp, pwm_auto_temp, 1); static SENSOR_DEVICE_ATTR_RW(pwm3_auto_channels_temp, pwm_auto_temp, 2); @@ -1236,68 +1065,7 @@ static struct attribute *adt7470_attrs[] = { &dev_attr_alarm_mask.attr, &dev_attr_num_temp_sensors.attr, &dev_attr_auto_update_interval.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp5_max.dev_attr.attr, - &sensor_dev_attr_temp6_max.dev_attr.attr, - &sensor_dev_attr_temp7_max.dev_attr.attr, - &sensor_dev_attr_temp8_max.dev_attr.attr, - &sensor_dev_attr_temp9_max.dev_attr.attr, - &sensor_dev_attr_temp10_max.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, - &sensor_dev_attr_temp4_min.dev_attr.attr, - &sensor_dev_attr_temp5_min.dev_attr.attr, - &sensor_dev_attr_temp6_min.dev_attr.attr, - &sensor_dev_attr_temp7_min.dev_attr.attr, - &sensor_dev_attr_temp8_min.dev_attr.attr, - &sensor_dev_attr_temp9_min.dev_attr.attr, - &sensor_dev_attr_temp10_min.dev_attr.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp5_input.dev_attr.attr, - &sensor_dev_attr_temp6_input.dev_attr.attr, - &sensor_dev_attr_temp7_input.dev_attr.attr, - &sensor_dev_attr_temp8_input.dev_attr.attr, - &sensor_dev_attr_temp9_input.dev_attr.attr, - &sensor_dev_attr_temp10_input.dev_attr.attr, - &sensor_dev_attr_temp1_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_alarm.dev_attr.attr, - &sensor_dev_attr_temp5_alarm.dev_attr.attr, - &sensor_dev_attr_temp6_alarm.dev_attr.attr, - &sensor_dev_attr_temp7_alarm.dev_attr.attr, - &sensor_dev_attr_temp8_alarm.dev_attr.attr, - &sensor_dev_attr_temp9_alarm.dev_attr.attr, - &sensor_dev_attr_temp10_alarm.dev_attr.attr, - &sensor_dev_attr_fan1_max.dev_attr.attr, - &sensor_dev_attr_fan2_max.dev_attr.attr, - &sensor_dev_attr_fan3_max.dev_attr.attr, - &sensor_dev_attr_fan4_max.dev_attr.attr, - &sensor_dev_attr_fan1_min.dev_attr.attr, - &sensor_dev_attr_fan2_min.dev_attr.attr, - &sensor_dev_attr_fan3_min.dev_attr.attr, - &sensor_dev_attr_fan4_min.dev_attr.attr, - &sensor_dev_attr_fan1_input.dev_attr.attr, - &sensor_dev_attr_fan2_input.dev_attr.attr, - &sensor_dev_attr_fan3_input.dev_attr.attr, - &sensor_dev_attr_fan4_input.dev_attr.attr, - &sensor_dev_attr_fan1_alarm.dev_attr.attr, - &sensor_dev_attr_fan2_alarm.dev_attr.attr, - &sensor_dev_attr_fan3_alarm.dev_attr.attr, - &sensor_dev_attr_fan4_alarm.dev_attr.attr, &sensor_dev_attr_force_pwm_max.dev_attr.attr, - &sensor_dev_attr_pwm1.dev_attr.attr, - &dev_attr_pwm1_freq.attr, - &sensor_dev_attr_pwm2.dev_attr.attr, - &sensor_dev_attr_pwm3.dev_attr.attr, - &sensor_dev_attr_pwm4.dev_attr.attr, &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, @@ -1314,10 +1082,6 @@ static struct attribute *adt7470_attrs[] = { &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, &sensor_dev_attr_pwm4_auto_point2_temp.dev_attr.attr, - &sensor_dev_attr_pwm1_enable.dev_attr.attr, - &sensor_dev_attr_pwm2_enable.dev_attr.attr, - &sensor_dev_attr_pwm3_enable.dev_attr.attr, - &sensor_dev_attr_pwm4_enable.dev_attr.attr, &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, @@ -1327,6 +1091,129 @@ static struct attribute *adt7470_attrs[] = { ATTRIBUTE_GROUPS(adt7470); +static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + switch (type) { + case hwmon_temp: + return adt7470_temp_read(dev, attr, channel, val); + case hwmon_fan: + return adt7470_fan_read(dev, attr, channel, val); + case hwmon_pwm: + return adt7470_pwm_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + switch (type) { + case hwmon_temp: + return adt7470_temp_write(dev, attr, channel, val); + case hwmon_fan: + return adt7470_fan_write(dev, attr, channel, val); + case hwmon_pwm: + return adt7470_pwm_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + umode_t mode = 0; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp: + case hwmon_temp_alarm: + mode = 0444; + break; + case hwmon_temp_min: + case hwmon_temp_max: + mode = 0644; + break; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_alarm: + mode = 0444; + break; + case hwmon_fan_min: + case hwmon_fan_max: + mode = 0644; + break; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: + mode = 0644; + break; + case hwmon_pwm_freq: + if (channel == 0) + mode = 0644; + else + mode = 0; + break; + default: + break; + } + break; + default: + break; + } + + return mode; +} + +static const struct hwmon_ops adt7470_hwmon_ops = { + .is_visible = adt7470_is_visible, + .read = adt7470_read, + .write = adt7470_write, +}; + +static const struct hwmon_channel_info *adt7470_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM, + HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM, + HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM, + HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE), + NULL +}; + +static const struct hwmon_chip_info adt7470_chip_info = { + .ops = &adt7470_hwmon_ops, + .info = adt7470_info, +}; + /* Return 0 if detection is successful, -ENODEV otherwise */ static int adt7470_detect(struct i2c_client *client, struct i2c_board_info *info) @@ -1391,9 +1278,9 @@ static int adt7470_probe(struct i2c_client *client) return err; /* Register sysfs hooks */ - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, - adt7470_groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &adt7470_chip_info, + adt7470_groups); if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); From 0e35f63f7f4eebd268ec236fd1bbf4e561ce8de5 Mon Sep 17 00:00:00 2001 From: Aleksa Savic Date: Sat, 28 Aug 2021 07:26:28 +0200 Subject: [PATCH 41/41] hwmon: add driver for Aquacomputer D5 Next This driver exposes hardware sensors of the Aquacomputer D5 Next watercooling pump, which communicates through a proprietary USB HID protocol. Available sensors are pump and fan speed, power, voltage and current, as well as coolant temperature. Also available through debugfs are the serial number, firmware version and power-on count. Attaching a fan is optional and allows it to be controlled using temperature curves directly from the pump. If it's not connected, the fan-related sensors will report zeroes. The pump can be configured either through software or via its physical interface. Configuring the pump through this driver is not implemented, as it seems to require sending it a complete configuration. That includes addressable RGB LEDs, for which there is no standard sysfs interface. Thus, that task is better suited for userspace tools. This driver has been tested on x86_64, both in-kernel and as a module. Signed-off-by: Aleksa Savic Signed-off-by: Guenter Roeck --- Documentation/hwmon/aquacomputer_d5next.rst | 61 ++++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 7 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/aquacomputer_d5next.c | 363 ++++++++++++++++++++ 6 files changed, 443 insertions(+) create mode 100644 Documentation/hwmon/aquacomputer_d5next.rst create mode 100644 drivers/hwmon/aquacomputer_d5next.c diff --git a/Documentation/hwmon/aquacomputer_d5next.rst b/Documentation/hwmon/aquacomputer_d5next.rst new file mode 100644 index 00000000000000..1f4bb4ba2e4bff --- /dev/null +++ b/Documentation/hwmon/aquacomputer_d5next.rst @@ -0,0 +1,61 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver aquacomputer-d5next +================================= + +Supported devices: + +* Aquacomputer D5 Next watercooling pump + +Author: Aleksa Savic + +Description +----------- + +This driver exposes hardware sensors of the Aquacomputer D5 Next watercooling +pump, which communicates through a proprietary USB HID protocol. + +Available sensors are pump and fan speed, power, voltage and current, as +well as coolant temperature. Also available through debugfs are the serial +number, firmware version and power-on count. + +Attaching a fan is optional and allows it to be controlled using temperature +curves directly from the pump. If it's not connected, the fan-related sensors +will report zeroes. + +The pump can be configured either through software or via its physical +interface. Configuring the pump through this driver is not implemented, as it +seems to require sending it a complete configuration. That includes addressable +RGB LEDs, for which there is no standard sysfs interface. Thus, that task is +better suited for userspace tools. + +Usage notes +----------- + +The pump communicates via HID reports. The driver is loaded automatically by +the kernel and supports hotswapping. + +Sysfs entries +------------- + +============ ============================================= +temp1_input Coolant temperature (in millidegrees Celsius) +fan1_input Pump speed (in RPM) +fan2_input Fan speed (in RPM) +power1_input Pump power (in micro Watts) +power2_input Fan power (in micro Watts) +in0_input Pump voltage (in milli Volts) +in1_input Fan voltage (in milli Volts) +in2_input +5V rail voltage (in milli Volts) +curr1_input Pump current (in milli Amperes) +curr2_input Fan current (in milli Amperes) +============ ============================================= + +Debugfs entries +--------------- + +================ =============================================== +serial_number Serial number of the pump +firmware_version Version of installed firmware +power_cycles Count of how many times the pump was powered on +================ =============================================== diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 470f2c50ecc2c7..f790f1260c33af 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -39,6 +39,7 @@ Hardware Monitoring Kernel Drivers adt7475 aht10 amc6821 + aquacomputer_d5next asb100 asc7621 aspeed-pwm-tacho diff --git a/MAINTAINERS b/MAINTAINERS index c9467d2839f5e8..4ce7512e8972df 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1316,6 +1316,13 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/i2c/aptina-pll.* +AQUACOMPUTER D5 NEXT PUMP SENSOR DRIVER +M: Aleksa Savic +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/aquacomputer_d5next.rst +F: drivers/hwmon/aquacomputer_d5next.c + AQUANTIA ETHERNET DRIVER (atlantic) M: Igor Russkikh L: netdev@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 64533141ea4abe..c4578e8f34bb55 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -254,6 +254,16 @@ config SENSORS_AHT10 This driver can also be built as a module. If so, the module will be called aht10. +config SENSORS_AQUACOMPUTER_D5NEXT + tristate "Aquacomputer D5 Next watercooling pump" + depends on USB_HID + help + If you say yes here you get support for the Aquacomputer D5 Next + watercooling pump sensors. + + This driver can also be built as a module. If so, the module + will be called aquacomputer_d5next. + config SENSORS_AS370 tristate "Synaptics AS370 SoC hardware monitoring driver" help diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 4b33421746c0a5..16294027066169 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_AHT10) += aht10.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o +obj-$(CONFIG_SENSORS_AQUACOMPUTER_D5NEXT) += aquacomputer_d5next.o obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c new file mode 100644 index 00000000000000..fb9341a5305111 --- /dev/null +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hwmon driver for Aquacomputer D5 Next watercooling pump + * + * The D5 Next sends HID reports (with ID 0x01) every second to report sensor values + * (coolant temperature, pump and fan speed, voltage, current and power). It responds to + * Get_Report requests, but returns a dummy value of no use. + * + * Copyright 2021 Aleksa Savic + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "aquacomputer-d5next" + +#define D5NEXT_STATUS_REPORT_ID 0x01 +#define D5NEXT_STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */ + +/* Register offsets for the D5 Next pump */ + +#define D5NEXT_SERIAL_FIRST_PART 3 +#define D5NEXT_SERIAL_SECOND_PART 5 +#define D5NEXT_FIRMWARE_VERSION 13 +#define D5NEXT_POWER_CYCLES 24 + +#define D5NEXT_COOLANT_TEMP 87 + +#define D5NEXT_PUMP_SPEED 116 +#define D5NEXT_FAN_SPEED 103 + +#define D5NEXT_PUMP_POWER 114 +#define D5NEXT_FAN_POWER 101 + +#define D5NEXT_PUMP_VOLTAGE 110 +#define D5NEXT_FAN_VOLTAGE 97 +#define D5NEXT_5V_VOLTAGE 57 + +#define D5NEXT_PUMP_CURRENT 112 +#define D5NEXT_FAN_CURRENT 99 + +/* Labels for provided values */ + +#define L_COOLANT_TEMP "Coolant temp" + +#define L_PUMP_SPEED "Pump speed" +#define L_FAN_SPEED "Fan speed" + +#define L_PUMP_POWER "Pump power" +#define L_FAN_POWER "Fan power" + +#define L_PUMP_VOLTAGE "Pump voltage" +#define L_FAN_VOLTAGE "Fan voltage" +#define L_5V_VOLTAGE "+5V voltage" + +#define L_PUMP_CURRENT "Pump current" +#define L_FAN_CURRENT "Fan current" + +static const char *const label_speeds[] = { + L_PUMP_SPEED, + L_FAN_SPEED, +}; + +static const char *const label_power[] = { + L_PUMP_POWER, + L_FAN_POWER, +}; + +static const char *const label_voltages[] = { + L_PUMP_VOLTAGE, + L_FAN_VOLTAGE, + L_5V_VOLTAGE, +}; + +static const char *const label_current[] = { + L_PUMP_CURRENT, + L_FAN_CURRENT, +}; + +struct d5next_data { + struct hid_device *hdev; + struct device *hwmon_dev; + struct dentry *debugfs; + s32 temp_input; + u16 speed_input[2]; + u32 power_input[2]; + u16 voltage_input[3]; + u16 current_input[2]; + u32 serial_number[2]; + u16 firmware_version; + u32 power_cycles; /* How many times the device was powered on */ + unsigned long updated; +}; + +static umode_t d5next_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + return 0444; +} + +static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) +{ + struct d5next_data *priv = dev_get_drvdata(dev); + + if (time_after(jiffies, priv->updated + D5NEXT_STATUS_UPDATE_INTERVAL)) + return -ENODATA; + + switch (type) { + case hwmon_temp: + *val = priv->temp_input; + break; + case hwmon_fan: + *val = priv->speed_input[channel]; + break; + case hwmon_power: + *val = priv->power_input[channel]; + break; + case hwmon_in: + *val = priv->voltage_input[channel]; + break; + case hwmon_curr: + *val = priv->current_input[channel]; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + *str = L_COOLANT_TEMP; + break; + case hwmon_fan: + *str = label_speeds[channel]; + break; + case hwmon_power: + *str = label_power[channel]; + break; + case hwmon_in: + *str = label_voltages[channel]; + break; + case hwmon_curr: + *str = label_current[channel]; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_ops d5next_hwmon_ops = { + .is_visible = d5next_is_visible, + .read = d5next_read, + .read_string = d5next_read_string, +}; + +static const struct hwmon_channel_info *d5next_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL), + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), + NULL +}; + +static const struct hwmon_chip_info d5next_chip_info = { + .ops = &d5next_hwmon_ops, + .info = d5next_info, +}; + +static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct d5next_data *priv; + + if (report->id != D5NEXT_STATUS_REPORT_ID) + return 0; + + priv = hid_get_drvdata(hdev); + + /* Info provided with every report */ + + priv->serial_number[0] = get_unaligned_be16(data + D5NEXT_SERIAL_FIRST_PART); + priv->serial_number[1] = get_unaligned_be16(data + D5NEXT_SERIAL_SECOND_PART); + + priv->firmware_version = get_unaligned_be16(data + D5NEXT_FIRMWARE_VERSION); + priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES); + + /* Sensor readings */ + + priv->temp_input = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10; + + priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED); + priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED); + + priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000; + priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000; + + priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10; + priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10; + priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10; + + priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT); + priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT); + + priv->updated = jiffies; + + return 0; +} + +#ifdef CONFIG_DEBUG_FS + +static int serial_number_show(struct seq_file *seqf, void *unused) +{ + struct d5next_data *priv = seqf->private; + + seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(serial_number); + +static int firmware_version_show(struct seq_file *seqf, void *unused) +{ + struct d5next_data *priv = seqf->private; + + seq_printf(seqf, "%u\n", priv->firmware_version); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(firmware_version); + +static int power_cycles_show(struct seq_file *seqf, void *unused) +{ + struct d5next_data *priv = seqf->private; + + seq_printf(seqf, "%u\n", priv->power_cycles); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(power_cycles); + +static void d5next_debugfs_init(struct d5next_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("serial_number", 0444, priv->debugfs, priv, &serial_number_fops); + debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); + debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); +} + +#else + +static void d5next_debugfs_init(struct d5next_data *priv) +{ +} + +#endif + +static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct d5next_data *priv; + int ret; + + priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->hdev = hdev; + hid_set_drvdata(hdev, priv); + + priv->updated = jiffies - D5NEXT_STATUS_UPDATE_INTERVAL; + + 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->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "d5next", priv, + &d5next_chip_info, NULL); + + if (IS_ERR(priv->hwmon_dev)) { + ret = PTR_ERR(priv->hwmon_dev); + goto fail_and_close; + } + + d5next_debugfs_init(priv); + + return 0; + +fail_and_close: + hid_hw_close(hdev); +fail_and_stop: + hid_hw_stop(hdev); + return ret; +} + +static void d5next_remove(struct hid_device *hdev) +{ + struct d5next_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 const struct hid_device_id d5next_table[] = { + { HID_USB_DEVICE(0x0c70, 0xf00e) }, /* Aquacomputer D5 Next */ + {}, +}; + +MODULE_DEVICE_TABLE(hid, d5next_table); + +static struct hid_driver d5next_driver = { + .name = DRIVER_NAME, + .id_table = d5next_table, + .probe = d5next_probe, + .remove = d5next_remove, + .raw_event = d5next_raw_event, +}; + +static int __init d5next_init(void) +{ + return hid_register_driver(&d5next_driver); +} + +static void __exit d5next_exit(void) +{ + hid_unregister_driver(&d5next_driver); +} + +/* Request to initialize after the HID bus to ensure it's not being loaded before */ + +late_initcall(d5next_init); +module_exit(d5next_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Aleksa Savic "); +MODULE_DESCRIPTION("Hwmon driver for Aquacomputer D5 Next pump");