From b9f55084aa0962af6247fa971f0b1e9c7aa676ef Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Wed, 17 Jan 2024 13:44:05 +0200 Subject: hwmon: put HWMON_CHANNEL_INFO() initializers in rodata HWMON_CHANNEL_INFO() is supposed to be used as initializer for arrays of const struct hwmon_channel_info *. However, without explicit const, HWMON_CHANNEL_INFO() creates mutable compound literals, and the const pointers point at the mutable data. Add const to place the data in rodata. Cc: Jean Delvare Cc: Guenter Roeck Signed-off-by: Jani Nikula Link: https://lore.kernel.org/r/20240117114405.1506775-1-jani.nikula@intel.com Signed-off-by: Guenter Roeck --- include/linux/hwmon.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 8cd6a6b33593..c2c0da18dfa3 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -425,12 +425,12 @@ struct hwmon_channel_info { const u32 *config; }; -#define HWMON_CHANNEL_INFO(stype, ...) \ - (&(struct hwmon_channel_info) { \ - .type = hwmon_##stype, \ - .config = (u32 []) { \ - __VA_ARGS__, 0 \ - } \ +#define HWMON_CHANNEL_INFO(stype, ...) \ + (&(const struct hwmon_channel_info) { \ + .type = hwmon_##stype, \ + .config = (const u32 []) { \ + __VA_ARGS__, 0 \ + } \ }) /** -- cgit v1.2.3 From 7fce84a991cdc1b28e03d14c997ec65187df6da5 Mon Sep 17 00:00:00 2001 From: Forest Crossman Date: Sat, 13 Jan 2024 16:27:55 -0800 Subject: hwmon: (nct6683) Add another customer ID for MSI This value was found on an MSI PRO X670-P WIFI with an NCT6687D chip. Signed-off-by: Forest Crossman Link: https://lore.kernel.org/r/CAO3ALPwot01+bBisj7Roog7SD9UwV+y4NtiakKrBDE0tPvFhbw@mail.gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/nct6683.rst | 1 + drivers/hwmon/nct6683.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Documentation/hwmon/nct6683.rst b/Documentation/hwmon/nct6683.rst index 3e7f6ee779c2..2a7a78eb1b46 100644 --- a/Documentation/hwmon/nct6683.rst +++ b/Documentation/hwmon/nct6683.rst @@ -64,4 +64,5 @@ Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13 ASRock X570 NCT6683D EC firmware version 1.0 build 06/28/19 ASRock X670E NCT6686D EC firmware version 1.0 build 05/19/22 MSI B550 NCT6687D EC firmware version 1.0 build 05/07/20 +MSI X670-P NCT6687D EC firmware version 0.0 build 09/27/22 =============== =============================================== diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c index 3f3f7a88413e..0d016fedb9c2 100644 --- a/drivers/hwmon/nct6683.c +++ b/drivers/hwmon/nct6683.c @@ -174,6 +174,7 @@ superio_exit(int ioreg) #define NCT6683_CUSTOMER_ID_MITAC 0xa0e #define NCT6683_CUSTOMER_ID_MSI 0x201 #define NCT6683_CUSTOMER_ID_MSI2 0x200 +#define NCT6683_CUSTOMER_ID_MSI3 0x207 #define NCT6683_CUSTOMER_ID_ASROCK 0xe2c #define NCT6683_CUSTOMER_ID_ASROCK2 0xe1b #define NCT6683_CUSTOMER_ID_ASROCK3 0x1631 @@ -1224,6 +1225,8 @@ static int nct6683_probe(struct platform_device *pdev) break; case NCT6683_CUSTOMER_ID_MSI2: break; + case NCT6683_CUSTOMER_ID_MSI3: + break; case NCT6683_CUSTOMER_ID_ASROCK: break; case NCT6683_CUSTOMER_ID_ASROCK2: -- cgit v1.2.3 From eea32fafadd3e648537e2653a2e2291431c990ff Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 20 Dec 2023 14:12:13 +0100 Subject: dt-bindings: hwmon: ina2xx: Add label property Add a label property to allow a custom name to be used for identifying a device on the board. This is useful when multiple devices are present on the same board. Similar change was done by commit ffae65fb1ae4 ("dt-bindings: spi: spi-cadence: Add label property"). Signed-off-by: Michal Simek Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/6f3c57d08984c1978569d3918cb38eb295c0c67d.1703077926.git.michal.simek@amd.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index 378d1f6aeeb3..8e5c1935b5f4 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -32,6 +32,9 @@ properties: reg: maxItems: 1 + label: + description: A descriptive name for this device. + shunt-resistor: description: Shunt resistor value in micro-Ohm. @@ -77,6 +80,7 @@ examples: power-sensor@44 { compatible = "ti,ina220"; reg = <0x44>; + label = "vdd_3v0"; shunt-resistor = <1000>; vs-supply = <&vdd_3v0>; }; -- cgit v1.2.3 From f29996d0295ec581d1e7cf5e72894656dc7f9f57 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Wed, 20 Dec 2023 14:12:14 +0100 Subject: dt-bindings: hwmon: ina2xx: Describe #io-channel-cells property There are two drivers in the Linux kernel. One is hwmon based and second IIO. IIO version requires to define #io-channel-cells to operate. Signed-off-by: Michal Simek Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/aa303b9fe3116e7f98d6b72822f7f57694366db3.1703077926.git.michal.simek@amd.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index 8e5c1935b5f4..f324b627bf9c 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -32,6 +32,9 @@ properties: reg: maxItems: 1 + "#io-channel-cells": + const: 1 + label: description: A descriptive name for this device. @@ -80,6 +83,7 @@ examples: power-sensor@44 { compatible = "ti,ina220"; reg = <0x44>; + #io-channel-cells = <1>; label = "vdd_3v0"; shunt-resistor = <1000>; vs-supply = <&vdd_3v0>; -- cgit v1.2.3 From f7ab2d180e6a312b885d1aadb8ca6ea0933a55df Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 8 Jan 2024 15:30:51 +0100 Subject: dt-bindings: hwmon: ina2xx: Describe ina260 chip Describe ina260 chip which is precision digital current and power monitor with precision integrated shunt resistor. Signed-off-by: Michal Simek Acked-by: Conor Dooley Link: https://lore.kernel.org/r/4c82dc4d412e91d1601c1da5bca1cdf1a91cd9b8.1704724242.git.michal.simek@amd.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index f324b627bf9c..a099bb71415e 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -28,6 +28,7 @@ properties: - ti,ina231 - ti,ina237 - ti,ina238 + - ti,ina260 reg: maxItems: 1 -- cgit v1.2.3 From e7b631d798716bba5cf1499caf62c548c7905439 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 27 Jan 2024 16:02:54 +0100 Subject: hwmon: Remove I2C_CLASS_HWMON from drivers w/o detect() and address_list Class-based I2C probing requires detect() and address_list to be set in the I2C client driver, see checks in i2c_detect(). It's misleading to declare I2C_CLASS_HWMON support if this precondition isn't met. Signed-off-by: Heiner Kallweit Link: https://lore.kernel.org/r/75747c6a-d414-4b07-8f66-5a5cdddc3c36@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adm1177.c | 1 - drivers/hwmon/ds1621.c | 1 - drivers/hwmon/ds620.c | 1 - drivers/hwmon/ina209.c | 1 - drivers/hwmon/ina238.c | 1 - drivers/hwmon/max127.c | 1 - drivers/hwmon/max31760.c | 1 - drivers/hwmon/max31790.c | 1 - drivers/hwmon/max31827.c | 1 - drivers/hwmon/max6621.c | 1 - drivers/hwmon/max6697.c | 1 - drivers/hwmon/occ/p8_i2c.c | 1 - drivers/hwmon/pmbus/ir36021.c | 1 - drivers/hwmon/powr1220.c | 1 - drivers/hwmon/sbrmi.c | 1 - drivers/hwmon/sbtsi_temp.c | 1 - drivers/hwmon/w83773g.c | 1 - 17 files changed, 17 deletions(-) diff --git a/drivers/hwmon/adm1177.c b/drivers/hwmon/adm1177.c index 60a893f27159..3390102d2d4a 100644 --- a/drivers/hwmon/adm1177.c +++ b/drivers/hwmon/adm1177.c @@ -250,7 +250,6 @@ static const struct of_device_id adm1177_dt_ids[] = { MODULE_DEVICE_TABLE(of, adm1177_dt_ids); static struct i2c_driver adm1177_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "adm1177", .of_match_table = adm1177_dt_ids, diff --git a/drivers/hwmon/ds1621.c b/drivers/hwmon/ds1621.c index 21b635046521..bffbc8040171 100644 --- a/drivers/hwmon/ds1621.c +++ b/drivers/hwmon/ds1621.c @@ -380,7 +380,6 @@ MODULE_DEVICE_TABLE(i2c, ds1621_id); /* This is the driver that will be inserted */ static struct i2c_driver ds1621_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "ds1621", }, diff --git a/drivers/hwmon/ds620.c b/drivers/hwmon/ds620.c index 2b09536630cb..4fc4df012fac 100644 --- a/drivers/hwmon/ds620.c +++ b/drivers/hwmon/ds620.c @@ -241,7 +241,6 @@ MODULE_DEVICE_TABLE(i2c, ds620_id); /* This is the driver that will be inserted */ static struct i2c_driver ds620_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "ds620", }, diff --git a/drivers/hwmon/ina209.c b/drivers/hwmon/ina209.c index c558143e5285..d9b57a4b3e41 100644 --- a/drivers/hwmon/ina209.c +++ b/drivers/hwmon/ina209.c @@ -589,7 +589,6 @@ MODULE_DEVICE_TABLE(of, ina209_of_match); /* This is the driver that will be inserted */ static struct i2c_driver ina209_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "ina209", .of_match_table = of_match_ptr(ina209_of_match), diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index ca9f5d2c811b..69289293bc38 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -629,7 +629,6 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = { MODULE_DEVICE_TABLE(of, ina238_of_match); static struct i2c_driver ina238_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "ina238", .of_match_table = of_match_ptr(ina238_of_match), diff --git a/drivers/hwmon/max127.c b/drivers/hwmon/max127.c index ee5ead06d612..da2289e3560a 100644 --- a/drivers/hwmon/max127.c +++ b/drivers/hwmon/max127.c @@ -335,7 +335,6 @@ static const struct i2c_device_id max127_id[] = { MODULE_DEVICE_TABLE(i2c, max127_id); static struct i2c_driver max127_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "max127", }, diff --git a/drivers/hwmon/max31760.c b/drivers/hwmon/max31760.c index 79945eb466ae..1b6f71bc61cb 100644 --- a/drivers/hwmon/max31760.c +++ b/drivers/hwmon/max31760.c @@ -578,7 +578,6 @@ static DEFINE_SIMPLE_DEV_PM_OPS(max31760_pm_ops, max31760_suspend, max31760_resume); static struct i2c_driver max31760_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "max31760", .of_match_table = max31760_of_match, diff --git a/drivers/hwmon/max31790.c b/drivers/hwmon/max31790.c index 0cd44c1e998a..3dc95196b229 100644 --- a/drivers/hwmon/max31790.c +++ b/drivers/hwmon/max31790.c @@ -543,7 +543,6 @@ static const struct i2c_device_id max31790_id[] = { MODULE_DEVICE_TABLE(i2c, max31790_id); static struct i2c_driver max31790_driver = { - .class = I2C_CLASS_HWMON, .probe = max31790_probe, .driver = { .name = "max31790", diff --git a/drivers/hwmon/max31827.c b/drivers/hwmon/max31827.c index 4a8c3e37c5d3..f8a13b30f100 100644 --- a/drivers/hwmon/max31827.c +++ b/drivers/hwmon/max31827.c @@ -652,7 +652,6 @@ static const struct of_device_id max31827_of_match[] = { MODULE_DEVICE_TABLE(of, max31827_of_match); static struct i2c_driver max31827_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "max31827", .of_match_table = max31827_of_match, diff --git a/drivers/hwmon/max6621.c b/drivers/hwmon/max6621.c index af7e62685898..05426cde0e36 100644 --- a/drivers/hwmon/max6621.c +++ b/drivers/hwmon/max6621.c @@ -549,7 +549,6 @@ static const struct of_device_id __maybe_unused max6621_of_match[] = { MODULE_DEVICE_TABLE(of, max6621_of_match); static struct i2c_driver max6621_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = MAX6621_DRV_NAME, .of_match_table = of_match_ptr(max6621_of_match), diff --git a/drivers/hwmon/max6697.c b/drivers/hwmon/max6697.c index 7d10dd434f2e..d161ba0e7813 100644 --- a/drivers/hwmon/max6697.c +++ b/drivers/hwmon/max6697.c @@ -780,7 +780,6 @@ static const struct of_device_id __maybe_unused max6697_of_match[] = { MODULE_DEVICE_TABLE(of, max6697_of_match); static struct i2c_driver max6697_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "max6697", .of_match_table = of_match_ptr(max6697_of_match), diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index 06095975f5c8..31159606cec7 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -241,7 +241,6 @@ static const struct of_device_id p8_i2c_occ_of_match[] = { MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match); static struct i2c_driver p8_i2c_occ_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "occ-hwmon", .of_match_table = p8_i2c_occ_of_match, diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c index 382ba6b6031a..a263afeb8ac1 100644 --- a/drivers/hwmon/pmbus/ir36021.c +++ b/drivers/hwmon/pmbus/ir36021.c @@ -63,7 +63,6 @@ static const struct of_device_id __maybe_unused ir36021_of_id[] = { MODULE_DEVICE_TABLE(of, ir36021_of_id); static struct i2c_driver ir36021_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "ir36021", .of_match_table = of_match_ptr(ir36021_of_id), diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c index 4120cadb00ae..2388d0565e7e 100644 --- a/drivers/hwmon/powr1220.c +++ b/drivers/hwmon/powr1220.c @@ -323,7 +323,6 @@ static const struct i2c_device_id powr1220_ids[] = { MODULE_DEVICE_TABLE(i2c, powr1220_ids); static struct i2c_driver powr1220_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "powr1220", }, diff --git a/drivers/hwmon/sbrmi.c b/drivers/hwmon/sbrmi.c index 484703f0ea5f..4318f5121145 100644 --- a/drivers/hwmon/sbrmi.c +++ b/drivers/hwmon/sbrmi.c @@ -342,7 +342,6 @@ static const struct of_device_id __maybe_unused sbrmi_of_match[] = { 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), diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c index dd85cf89f008..a4181acb1aa6 100644 --- a/drivers/hwmon/sbtsi_temp.c +++ b/drivers/hwmon/sbtsi_temp.c @@ -232,7 +232,6 @@ static const struct of_device_id __maybe_unused sbtsi_of_match[] = { MODULE_DEVICE_TABLE(of, sbtsi_of_match); static struct i2c_driver sbtsi_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "sbtsi", .of_match_table = of_match_ptr(sbtsi_of_match), diff --git a/drivers/hwmon/w83773g.c b/drivers/hwmon/w83773g.c index 045eea8378c2..401a28f55f93 100644 --- a/drivers/hwmon/w83773g.c +++ b/drivers/hwmon/w83773g.c @@ -290,7 +290,6 @@ static int w83773_probe(struct i2c_client *client) } static struct i2c_driver w83773_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "w83773g", .of_match_table = of_match_ptr(w83773_of_match), -- cgit v1.2.3 From 3317f2385f093d7dc17cf492a6676e7be5597349 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 27 Jan 2024 07:40:58 -0800 Subject: MAINTAINERS: Drop entries for hwmon devices with unreachable maintainers Drop maintainer entries for MAX31760 and MAX31827 since the e-mail addresses of their maintainers is no longer reachable and there is no known alternative means to contact them. HWMON drivers have a subsystem maintainer, so individual maintainer entries are not mandatory. Reported-by: Heiner Kallweit Cc: Heiner Kallweit Signed-off-by: Guenter Roeck --- MAINTAINERS | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 73d898383e51..ea2debbed7cb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1384,15 +1384,6 @@ F: drivers/iio/amplifiers/hmc425a.c F: drivers/staging/iio/*/ad* X: drivers/iio/*/adjd* -ANALOG DEVICES INC MAX31760 DRIVER -M: Ibrahim Tilki -S: Maintained -W: http://wiki.analog.com/ -W: https://ez.analog.com/linux-software-drivers -F: Documentation/devicetree/bindings/hwmon/adi,max31760.yaml -F: Documentation/hwmon/max31760.rst -F: drivers/hwmon/max31760.c - ANALOGBITS PLL LIBRARIES M: Paul Walmsley S: Supported @@ -13133,15 +13124,6 @@ F: Documentation/userspace-api/media/drivers/max2175.rst F: drivers/media/i2c/max2175* F: include/uapi/linux/max2175.h -MAX31827 TEMPERATURE SWITCH DRIVER -M: Daniel Matyas -L: linux-hwmon@vger.kernel.org -S: Supported -W: https://ez.analog.com/linux-software-drivers -F: Documentation/devicetree/bindings/hwmon/adi,max31827.yaml -F: Documentation/hwmon/max31827.rst -F: drivers/hwmon/max31827.c - MAX31335 RTC DRIVER M: Antoniu Miclaus L: linux-rtc@vger.kernel.org -- cgit v1.2.3 From 7a0564448a861952e9dc032de146abd19ea5a096 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sat, 27 Jan 2024 18:45:00 +0100 Subject: hwmon: Drop non-functional I2C_CLASS_HWMON support for drivers w/o detect() Class-based I2C probing requires detect() and address_list both to be set in the I2C client driver, see checks in i2c_detect(). It's misleading to declare I2C_CLASS_HWMON support if the driver doesn't implement detect(). Class-based probing is a legacy mechanism, in addition apparently nobody ever noticed that class-based probing has been non-functional in both drivers from the very beginning. So drop the fragments of class-based probing support. Signed-off-by: Heiner Kallweit Link: https://lore.kernel.org/r/13ce7c11-a958-4892-ada9-faf5bfdcb89d@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/emc2305.rst | 1 - drivers/hwmon/adt7410.c | 2 -- drivers/hwmon/emc2305.c | 5 ----- 3 files changed, 8 deletions(-) diff --git a/Documentation/hwmon/emc2305.rst b/Documentation/hwmon/emc2305.rst index 2403dbaf2728..d0bfffe46358 100644 --- a/Documentation/hwmon/emc2305.rst +++ b/Documentation/hwmon/emc2305.rst @@ -6,7 +6,6 @@ Kernel driver emc2305 Supported chips: Microchip EMC2305, EMC2303, EMC2302, EMC2301 - Addresses scanned: I2C 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d Prefixes: 'emc2305' Datasheet: Publicly available at the Microchip website : diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c index 952506779336..fd214d9b3a89 100644 --- a/drivers/hwmon/adt7410.c +++ b/drivers/hwmon/adt7410.c @@ -95,14 +95,12 @@ static const struct i2c_device_id adt7410_ids[] = { MODULE_DEVICE_TABLE(i2c, adt7410_ids); static struct i2c_driver adt7410_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "adt7410", .pm = pm_sleep_ptr(&adt7x10_dev_pm_ops), }, .probe = adt7410_i2c_probe, .id_table = adt7410_ids, - .address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b), }; module_i2c_driver(adt7410_driver); diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c index 29f0e4945f19..6ef733c0be16 100644 --- a/drivers/hwmon/emc2305.c +++ b/drivers/hwmon/emc2305.c @@ -12,9 +12,6 @@ #include #include -static const unsigned short -emc2305_normal_i2c[] = { 0x27, 0x2c, 0x2d, 0x2e, 0x2f, 0x4c, 0x4d, I2C_CLIENT_END }; - #define EMC2305_REG_DRIVE_FAIL_STATUS 0x27 #define EMC2305_REG_VENDOR 0xfe #define EMC2305_FAN_MAX 0xff @@ -611,14 +608,12 @@ static void emc2305_remove(struct i2c_client *client) } static struct i2c_driver emc2305_driver = { - .class = I2C_CLASS_HWMON, .driver = { .name = "emc2305", }, .probe = emc2305_probe, .remove = emc2305_remove, .id_table = emc2305_ids, - .address_list = emc2305_normal_i2c, }; module_i2c_driver(emc2305_driver); -- cgit v1.2.3 From 9c6df63a66c1fdf99d6e1ad278d140080c724120 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Tue, 30 Jan 2024 20:59:03 +0530 Subject: hwmon: (pmbus_core) Allow to hook PMBUS_SMBALERT_MASK Use _pmbus_write_word_data to allow intercepting writes to PMBUS_SMBALERT_MASK in the custom chip specific code. This is required for MP2971/MP2973 which doesn't follow the PMBUS specification for PMBUS_SMBALERT_MASK. Signed-off-by: Patrick Rudolph Signed-off-by: Naresh Solanki Link: https://lore.kernel.org/r/20240130152903.3651341-1-naresh.solanki@9elements.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 1363d9f89181..cb4c65a7f288 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -3188,7 +3188,7 @@ static int pmbus_regulator_notify(struct pmbus_data *data, int page, int event) static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, u8 val) { - return pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8)); + return _pmbus_write_word_data(client, page, PMBUS_SMBALERT_MASK, reg | (val << 8)); } static irqreturn_t pmbus_fault_handler(int irq, void *pdata) -- cgit v1.2.3 From d07c41eb963a4f621d1867c36ad3b79b2f5faa20 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Mon, 29 Jan 2024 17:13:23 +0100 Subject: dt-bindings: hwmon: Add LTC4282 bindings Add bindings for the LTC4282 High Current Hot Swap Controller with I2C Compatible Monitoring. Reviewed-by: Conor Dooley Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240129-b4-ltc4282-support-v4-1-fe75798164cc@analog.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/adi,ltc4282.yaml | 159 +++++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 165 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml new file mode 100644 index 000000000000..4854b95a93e3 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml @@ -0,0 +1,159 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,ltc4282.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices LTC4282 I2C High Current Hot Swap Controller over I2C + +maintainers: + - Nuno Sa + +description: | + Analog Devices LTC4282 I2C High Current Hot Swap Controller over I2C. + + https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4282.pdf + +properties: + compatible: + enum: + - adi,ltc4282 + + reg: + maxItems: 1 + + vdd-supply: true + + clocks: + maxItems: 1 + + '#clock-cells': + const: 0 + + adi,rsense-nano-ohms: + description: Value of the sense resistor. + + adi,vin-mode-microvolt: + description: + Selects operating range for the Undervoltage, Overvoltage and Foldback + pins. Also for the ADC. Should be set to the nominal input voltage. + enum: [3300000, 5000000, 12000000, 24000000] + default: 12000000 + + adi,fet-bad-timeout-ms: + description: + From the moment a FET bad conditions is present, this property selects the + wait time/timeout for a FET-bad fault to be signaled. Setting this to 0, + disables FET bad faults to be reported. + default: 255 + maximum: 255 + + adi,overvoltage-dividers: + description: | + Select which dividers to use for VDD Overvoltage detection. Note that + when the internal dividers are used the threshold is referenced to VDD. + The percentages in the datasheet are misleading since the actual values + to look for are in the "Absolute Maximum Ratings" table in the + "Comparator Inputs" section. In there there's a line for each of the 5%, + 10% and 15% settings with the actual min, typical and max tolerances. + $ref: /schemas/types.yaml#/definitions/string + enum: [external, vdd_5_percent, vdd_10_percent, vdd_15_percent] + default: external + + adi,undervoltage-dividers: + description: | + Select which dividers to use for VDD Overvoltage detection. Note that + when the internal dividers are used the threshold is referenced to VDD. + The percentages in the datasheet are misleading since the actual values + to look for are in the "Absolute Maximum Ratings" table in the + "Comparator Inputs" section. In there there's a line for each of the 5%, + 10% and 15% settings with the actual min, typical and max tolerances. + $ref: /schemas/types.yaml#/definitions/string + enum: [external, vdd_5_percent, vdd_10_percent, vdd_15_percent] + default: external + + adi,current-limit-sense-microvolt: + description: + The current limit sense voltage of the chip is adjustable between + 12.5mV and 34.4mV in 3.1mV steps. This effectively limits the current + on the load. + enum: [12500, 15625, 18750, 21875, 25000, 28125, 31250, 34375] + default: 25000 + + adi,overcurrent-retry: + description: + If set, enables the chip to auto-retry 256 timer cycles after an + Overcurrent fault. + type: boolean + + adi,overvoltage-retry-disable: + description: + If set, disables the chip to auto-retry 50ms after an Overvoltage fault. + It's enabled by default. + type: boolean + + adi,undervoltage-retry-disable: + description: + If set, disables the chip to auto-retry 50ms after an Undervoltage fault. + It's enabled by default. + type: boolean + + adi,fault-log-enable: + description: + If set, enables the FAULT_LOG and ADC_ALERT_LOG registers to be written + to the EEPROM when a fault bit transitions high and hence, will be + available after a power cycle (the chip loads the contents of + the EE_FAULT_LOG register - the one in EEPROM - into FAULT_LOG at boot). + type: boolean + + adi,gpio1-mode: + description: Defines the function of the Pin. It can indicate that power is + good (PULL the pin low when power is not good) or that power is bad (Go + into high-z when power is not good). + $ref: /schemas/types.yaml#/definitions/string + enum: [power_bad, power_good] + default: power_good + + adi,gpio2-mode: + description: Defines the function of the Pin. It can be set as the input for + the ADC or indicating that the MOSFET is in stress (dissipating power). + $ref: /schemas/types.yaml#/definitions/string + enum: [adc_input, stress_fet] + default: adc_input + + adi,gpio3-monitor-enable: + description: If set, gpio3 is set as input for the ADC instead of gpio2. + type: boolean + +allOf: + - if: + required: + - adi,gpio3-monitor-enable + then: + properties: + adi,gpio2-mode: + const: stress_fet + +required: + - compatible + - reg + - adi,rsense-nano-ohms + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hwmon@50 { + compatible = "adi,ltc4282"; + reg = <0x50>; + adi,rsense-nano-ohms = <500>; + + adi,gpio1-mode = "power_good"; + adi,gpio2-mode = "adc_input"; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index ea2debbed7cb..3625754d7d1b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12760,6 +12760,12 @@ S: Maintained F: Documentation/hwmon/ltc4261.rst F: drivers/hwmon/ltc4261.c +LTC4282 HARDWARE MONITOR DRIVER +M: Nuno Sa +L: linux-hwmon@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml + LTC4286 HARDWARE MONITOR DRIVER M: Delphine CC Chiu L: linux-i2c@vger.kernel.org -- cgit v1.2.3 From 35c1bfb99fef9c71f9df5c1325db99a79300bc97 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Mon, 29 Jan 2024 17:13:24 +0100 Subject: hwmon: add fault attribute for voltage channels Sometimes a voltage channel might have an hard failure (eg: a shorted MOSFET). Hence, add a fault attribute to report such failures. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240129-b4-ltc4282-support-v4-2-fe75798164cc@analog.com Signed-off-by: Guenter Roeck --- Documentation/ABI/testing/sysfs-class-hwmon | 9 +++++++++ drivers/hwmon/hwmon.c | 1 + include/linux/hwmon.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon index 3dac923c9b0e..6c4e68ad4a83 100644 --- a/Documentation/ABI/testing/sysfs-class-hwmon +++ b/Documentation/ABI/testing/sysfs-class-hwmon @@ -149,6 +149,15 @@ Description: RW +What: /sys/class/hwmon/hwmonX/inY_fault +Description: + Reports a voltage hard failure (eg: shorted component) + + - 1: Failed + - 0: Ok + + RO + What: /sys/class/hwmon/hwmonX/cpuY_vid Description: CPU core reference voltage. diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index c7dd3f5b2bd5..18705049ad61 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -510,6 +510,7 @@ static const char * const hwmon_in_attr_templates[] = { [hwmon_in_rated_min] = "in%d_rated_min", [hwmon_in_rated_max] = "in%d_rated_max", [hwmon_in_beep] = "in%d_beep", + [hwmon_in_fault] = "in%d_fault", }; static const char * const hwmon_curr_attr_templates[] = { diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index c2c0da18dfa3..c7885fdce88f 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -141,6 +141,7 @@ enum hwmon_in_attributes { hwmon_in_rated_min, hwmon_in_rated_max, hwmon_in_beep, + hwmon_in_fault, }; #define HWMON_I_ENABLE BIT(hwmon_in_enable) @@ -162,6 +163,7 @@ enum hwmon_in_attributes { #define HWMON_I_RATED_MIN BIT(hwmon_in_rated_min) #define HWMON_I_RATED_MAX BIT(hwmon_in_rated_max) #define HWMON_I_BEEP BIT(hwmon_in_beep) +#define HWMON_I_FAULT BIT(hwmon_in_fault) enum hwmon_curr_attributes { hwmon_curr_enable, -- cgit v1.2.3 From cbc29538dbf7d7400f1ffc5dd5713e6a551463a0 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Mon, 29 Jan 2024 17:13:25 +0100 Subject: hwmon: Add driver for LTC4282 The LTC4282 hot swap controller allows a board to be safely inserted and removed from a live backplane. Using one or more external N-channel pass transistors, board supply voltage and inrush current are ramped up at an adjustable rate. An I2C interface and onboard ADC allows for monitoring of board current, voltage, power, energy and fault status. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240129-b4-ltc4282-support-v4-3-fe75798164cc@analog.com [groeck: clamp value range in ltc4282_write_voltage_byte_cached()] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/ltc4282.rst | 133 +++ MAINTAINERS | 2 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ltc4282.c | 1782 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 1930 insertions(+) create mode 100644 Documentation/hwmon/ltc4282.rst create mode 100644 drivers/hwmon/ltc4282.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index c7ed1f73ac06..f16c6dfaec7d 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -129,6 +129,7 @@ Hardware Monitoring Kernel Drivers ltc4245 ltc4260 ltc4261 + ltc4282 ltc4286 max127 max15301 diff --git a/Documentation/hwmon/ltc4282.rst b/Documentation/hwmon/ltc4282.rst new file mode 100644 index 000000000000..a87ec3564998 --- /dev/null +++ b/Documentation/hwmon/ltc4282.rst @@ -0,0 +1,133 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel drivers ltc4282 +========================================== + +Supported chips: + + * Analog Devices LTC4282 + + Prefix: 'ltc4282' + + Addresses scanned: - I2C 0x40 - 0x5A (7-bit) + Addresses scanned: - I2C 0x80 - 0xB4 with a step of 2 (8-bit) + + Datasheet: + + https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4282.pdf + +Author: Nuno Sá + +Description +___________ + +The LTC4282 hot swap controller allows a board to be safely inserted and removed +from a live backplane. Using one or more external N-channel pass transistors, +board supply voltage and inrush current are ramped up at an adjustable rate. An +I2C interface and onboard ADC allows for monitoring of board current, voltage, +power, energy and fault status. The device features analog foldback current +limiting and supply monitoring for applications from 2.9V to 33V. Dual 12V gate +drive allows high power applications to either share safe operating area across +parallel MOSFETs or support a 2-stage start-up that first charges the load +capacitance followed by enabling a low on-resistance path to the load. The +LTC4282 is well suited to high power applications because the precise monitoring +capability and accurate current limiting reduce the extremes in which both loads +and power supplies must safely operate. Non-volatile configuration allows for +flexibility in the autonomous generation of alerts and response to faults. + +Sysfs entries +_____________ + +The following attributes are supported. Limits are read-write and all the other +attributes are read-only. Note that in0 and in1 are mutually exclusive. Enabling +one disables the other and disabling one enables the other. + +======================= ========================================== +in0_input Output voltage (mV). +in0_min Undervoltage threshold +in0_max Overvoltage threshold +in0_lowest Lowest measured voltage +in0_highest Highest measured voltage +in0_reset_history Write 1 to reset in0 history. + Also clears fet bad and short fault logs. +in0_min_alarm Undervoltage alarm +in0_max_alarm Overvoltage alarm +in0_enable Enable/Disable VSOURCE monitoring +in0_fault Failure in the MOSFETs. Either bad or shorted FET. +in0_label Channel label (VSOURCE) + +in1_input Input voltage (mV). +in1_min Undervoltage threshold +in1_max Overvoltage threshold +in1_lowest Lowest measured voltage +in1_highest Highest measured voltage +in1_reset_history Write 1 to reset in1 history. + Also clears over/undervoltage fault logs. +in1_min_alarm Undervoltage alarm +in1_max_alarm Overvoltage alarm +in1_lcrit_alarm Critical Undervoltage alarm +in1_crit_alarm Critical Overvoltage alarm +in1_enable Enable/Disable VDD monitoring +in1_label Channel label (VDD) + +in2_input GPIO voltage (mV) +in2_min Undervoltage threshold +in2_max Overvoltage threshold +in2_lowest Lowest measured voltage +in2_highest Highest measured voltage +in2_reset_history Write 1 to reset in2 history +in2_min_alarm Undervoltage alarm +in2_max_alarm Overvoltage alarm +in2_label Channel label (VGPIO) + +curr1_input Sense current (mA) +curr1_min Undercurrent threshold +curr1_max Overcurrent threshold +curr1_lowest Lowest measured current +curr1_highest Highest measured current +curr1_reset_history Write 1 to reset curr1 history. + Also clears overcurrent fault logs. +curr1_min_alarm Undercurrent alarm +curr1_max_alarm Overcurrent alarm +curr1_crit_alarm Critical Overcurrent alarm +curr1_label Channel label (ISENSE) + +power1_input Power (in uW) +power1_min Low power threshold +power1_max High power threshold +power1_input_lowest Historical minimum power use +power1_input_highest Historical maximum power use +power1_reset_history Write 1 to reset power1 history. + Also clears power bad fault logs. +power1_min_alarm Low power alarm +power1_max_alarm High power alarm +power1_label Channel label (Power) + +energy1_input Measured energy over time (in microJoule) +energy1_enable Enable/Disable Energy accumulation +======================= ========================================== + +DebugFs entries +_______________ + +The chip also has a fault log register where failures can be logged. Hence, +as these are logging events, we give access to them in debugfs. Note that +even if some failure is detected in these logs, it does necessarily mean +that the failure is still present. As mentioned in the proper Sysfs entries, +these logs can be cleared by writing in the proper reset_history attribute. + +.. warning:: The debugfs interface is subject to change without notice + and is only available when the kernel is compiled with + ``CONFIG_DEBUG_FS`` defined. + +``/sys/kernel/debug/ltc4282-hwmon[X]/`` +contains the following attributes: + +======================= ========================================== +power1_bad_fault_log Set to 1 by a power1 bad fault occurring. +in0_fet_short_fault_log Set to 1 when the ADC detects a FET-short fault. +in0_fet_bad_fault_log Set to 1 when a FET-BAD fault occurs. +in1_crit_fault_log Set to 1 by a VDD overvoltage fault occurring. +in1_lcrit_fault_log Set to 1 by a VDD undervoltage fault occurring. +curr1_crit_fault_log Set to 1 by an overcurrent fault occurring. +======================= ========================================== diff --git a/MAINTAINERS b/MAINTAINERS index 3625754d7d1b..df8ea2d94a0c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12765,6 +12765,8 @@ M: Nuno Sa L: linux-hwmon@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml +F: Documentation/hwmon/ltc4282.rst +F: drivers/hwmon/ltc4282.c LTC4286 HARDWARE MONITOR DRIVER M: Delphine CC Chiu diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a608264da87d..f6160cc70077 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1038,6 +1038,17 @@ config SENSORS_LTC4261 This driver can also be built as a module. If so, the module will be called ltc4261. +config SENSORS_LTC4282 + tristate "Analog Devices LTC4282" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Analog Devices LTC4282 + High Current Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4282. + config SENSORS_LTQ_CPUTEMP bool "Lantiq cpu temperature sensor driver" depends on SOC_XWAY diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 47be39af5c03..8bfc422a29e5 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -136,6 +136,7 @@ obj-$(CONFIG_SENSORS_LTC4222) += ltc4222.o obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_LTC4260) += ltc4260.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o +obj-$(CONFIG_SENSORS_LTC4282) += ltc4282.o obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX127) += max127.o diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c new file mode 100644 index 000000000000..4f608a3790fb --- /dev/null +++ b/drivers/hwmon/ltc4282.c @@ -0,0 +1,1782 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices LTC4282 I2C High Current Hot Swap Controller over I2C + * + * Copyright 2023 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC4282_CTRL_LSB 0x00 + #define LTC4282_CTRL_OV_RETRY_MASK BIT(0) + #define LTC4282_CTRL_UV_RETRY_MASK BIT(1) + #define LTC4282_CTRL_OC_RETRY_MASK BIT(2) + #define LTC4282_CTRL_ON_ACTIVE_LOW_MASK BIT(5) + #define LTC4282_CTRL_ON_DELAY_MASK BIT(6) +#define LTC4282_CTRL_MSB 0x01 + #define LTC4282_CTRL_VIN_MODE_MASK GENMASK(1, 0) + #define LTC4282_CTRL_OV_MODE_MASK GENMASK(3, 2) + #define LTC4282_CTRL_UV_MODE_MASK GENMASK(5, 4) +#define LTC4282_FAULT_LOG 0x04 + #define LTC4282_OV_FAULT_MASK BIT(0) + #define LTC4282_UV_FAULT_MASK BIT(1) + #define LTC4282_VDD_FAULT_MASK \ + (LTC4282_OV_FAULT_MASK | LTC4282_UV_FAULT_MASK) + #define LTC4282_OC_FAULT_MASK BIT(2) + #define LTC4282_POWER_BAD_FAULT_MASK BIT(3) + #define LTC4282_FET_SHORT_FAULT_MASK BIT(5) + #define LTC4282_FET_BAD_FAULT_MASK BIT(6) + #define LTC4282_FET_FAILURE_FAULT_MASK \ + (LTC4282_FET_SHORT_FAULT_MASK | LTC4282_FET_BAD_FAULT_MASK) +#define LTC4282_ADC_ALERT_LOG 0x05 + #define LTC4282_GPIO_ALARM_L_MASK BIT(0) + #define LTC4282_GPIO_ALARM_H_MASK BIT(1) + #define LTC4282_VSOURCE_ALARM_L_MASK BIT(2) + #define LTC4282_VSOURCE_ALARM_H_MASK BIT(3) + #define LTC4282_VSENSE_ALARM_L_MASK BIT(4) + #define LTC4282_VSENSE_ALARM_H_MASK BIT(5) + #define LTC4282_POWER_ALARM_L_MASK BIT(6) + #define LTC4282_POWER_ALARM_H_MASK BIT(7) +#define LTC4282_FET_BAD_FAULT_TIMEOUT 0x06 + #define LTC4282_FET_BAD_MAX_TIMEOUT 255 +#define LTC4282_GPIO_CONFIG 0x07 + #define LTC4282_GPIO_2_FET_STRESS_MASK BIT(1) + #define LTC4282_GPIO_1_CONFIG_MASK GENMASK(5, 4) +#define LTC4282_VGPIO_MIN 0x08 +#define LTC4282_VGPIO_MAX 0x09 +#define LTC4282_VSOURCE_MIN 0x0a +#define LTC4282_VSOURCE_MAX 0x0b +#define LTC4282_VSENSE_MIN 0x0c +#define LTC4282_VSENSE_MAX 0x0d +#define LTC4282_POWER_MIN 0x0e +#define LTC4282_POWER_MAX 0x0f +#define LTC4282_CLK_DIV 0x10 + #define LTC4282_CLK_DIV_MASK GENMASK(4, 0) + #define LTC4282_CLKOUT_MASK GENMASK(6, 5) +#define LTC4282_ILIM_ADJUST 0x11 + #define LTC4282_GPIO_MODE_MASK BIT(1) + #define LTC4282_VDD_MONITOR_MASK BIT(2) + #define LTC4282_FOLDBACK_MODE_MASK GENMASK(4, 3) + #define LTC4282_ILIM_ADJUST_MASK GENMASK(7, 5) +#define LTC4282_ENERGY 0x12 +#define LTC4282_TIME_COUNTER 0x18 +#define LTC4282_ALERT_CTRL 0x1c + #define LTC4282_ALERT_OUT_MASK BIT(6) +#define LTC4282_ADC_CTRL 0x1d + #define LTC4282_FAULT_LOG_EN_MASK BIT(2) + #define LTC4282_METER_HALT_MASK BIT(5) + #define LTC4282_METER_RESET_MASK BIT(6) + #define LTC4282_RESET_MASK BIT(7) +#define LTC4282_STATUS_LSB 0x1e + #define LTC4282_OV_STATUS_MASK BIT(0) + #define LTC4282_UV_STATUS_MASK BIT(1) + #define LTC4282_VDD_STATUS_MASK \ + (LTC4282_OV_STATUS_MASK | LTC4282_UV_STATUS_MASK) + #define LTC4282_OC_STATUS_MASK BIT(2) + #define LTC4282_POWER_GOOD_MASK BIT(3) + #define LTC4282_FET_FAILURE_MASK GENMASK(6, 5) +#define LTC4282_STATUS_MSB 0x1f +#define LTC4282_RESERVED_1 0x32 +#define LTC4282_RESERVED_2 0x33 +#define LTC4282_VGPIO 0x34 +#define LTC4282_VGPIO_LOWEST 0x36 +#define LTC4282_VGPIO_HIGHEST 0x38 +#define LTC4282_VSOURCE 0x3a +#define LTC4282_VSOURCE_LOWEST 0x3c +#define LTC4282_VSOURCE_HIGHEST 0x3e +#define LTC4282_VSENSE 0x40 +#define LTC4282_VSENSE_LOWEST 0x42 +#define LTC4282_VSENSE_HIGHEST 0x44 +#define LTC4282_POWER 0x46 +#define LTC4282_POWER_LOWEST 0x48 +#define LTC4282_POWER_HIGHEST 0x4a +#define LTC4282_RESERVED_3 0x50 + +#define LTC4282_CLKIN_MIN (250 * KILO) +#define LTC4282_CLKIN_MAX (15500 * KILO) +#define LTC4282_CLKIN_RANGE (LTC4282_CLKIN_MAX - LTC4282_CLKIN_MIN + 1) +#define LTC4282_CLKOUT_SYSTEM (250 * KILO) +#define LTC4282_CLKOUT_CNV 15 + +enum { + LTC4282_CHAN_VSOURCE, + LTC4282_CHAN_VDD, + LTC4282_CHAN_VGPIO, +}; + +struct ltc4282_cache { + u32 in_max_raw; + u32 in_min_raw; + long in_highest; + long in_lowest; + bool en; +}; + +struct ltc4282_state { + struct regmap *map; + /* Protect against multiple accesses to the device registers */ + struct mutex lock; + struct clk_hw clk_hw; + /* + * Used to cache values for VDD/VSOURCE depending which will be used + * when hwmon is not enabled for that channel. Needed because they share + * the same registers. + */ + struct ltc4282_cache in0_1_cache[LTC4282_CHAN_VGPIO]; + u32 vsense_max; + long power_max; + u32 rsense; + u16 vdd; + u16 vfs_out; + bool energy_en; +}; + +enum { + LTC4282_CLKOUT_NONE, + LTC4282_CLKOUT_INT, + LTC4282_CLKOUT_TICK, +}; + +static int ltc4282_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct ltc4282_state *st = container_of(hw, struct ltc4282_state, + clk_hw); + u32 val = LTC4282_CLKOUT_INT; + + if (rate == LTC4282_CLKOUT_CNV) + val = LTC4282_CLKOUT_TICK; + + return regmap_update_bits(st->map, LTC4282_CLK_DIV, LTC4282_CLKOUT_MASK, + FIELD_PREP(LTC4282_CLKOUT_MASK, val)); +} + +/* + * Note the 15HZ conversion rate assumes 12bit ADC which is what we are + * supporting for now. + */ +static const unsigned int ltc4282_out_rates[] = { + LTC4282_CLKOUT_CNV, LTC4282_CLKOUT_SYSTEM +}; + +static long ltc4282_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + int idx = find_closest(rate, ltc4282_out_rates, + ARRAY_SIZE(ltc4282_out_rates)); + + return ltc4282_out_rates[idx]; +} + +static unsigned long ltc4282_recalc_rate(struct clk_hw *hw, + unsigned long parent) +{ + struct ltc4282_state *st = container_of(hw, struct ltc4282_state, + clk_hw); + u32 clkdiv; + int ret; + + ret = regmap_read(st->map, LTC4282_CLK_DIV, &clkdiv); + if (ret) + return 0; + + clkdiv = FIELD_GET(LTC4282_CLKOUT_MASK, clkdiv); + if (!clkdiv) + return 0; + if (clkdiv == LTC4282_CLKOUT_INT) + return LTC4282_CLKOUT_SYSTEM; + + return LTC4282_CLKOUT_CNV; +} + +static void ltc4282_disable(struct clk_hw *clk_hw) +{ + struct ltc4282_state *st = container_of(clk_hw, struct ltc4282_state, + clk_hw); + + regmap_clear_bits(st->map, LTC4282_CLK_DIV, LTC4282_CLKOUT_MASK); +} + +static int ltc4282_read_voltage_word(const struct ltc4282_state *st, u32 reg, + u32 fs, long *val) +{ + __be16 in; + int ret; + + ret = regmap_bulk_read(st->map, reg, &in, sizeof(in)); + if (ret) + return ret; + + /* + * This is also used to calculate current in which case fs comes in + * 10 * uV. Hence the ULL usage. + */ + *val = DIV_ROUND_CLOSEST_ULL(be16_to_cpu(in) * (u64)fs, U16_MAX); + return 0; +} + +static int ltc4282_read_voltage_byte_cached(const struct ltc4282_state *st, + u32 reg, u32 fs, long *val, + u32 *cached_raw) +{ + int ret; + u32 in; + + if (cached_raw) { + in = *cached_raw; + } else { + ret = regmap_read(st->map, reg, &in); + if (ret) + return ret; + } + + *val = DIV_ROUND_CLOSEST(in * fs, U8_MAX); + return 0; +} + +static int ltc4282_read_voltage_byte(const struct ltc4282_state *st, u32 reg, + u32 fs, long *val) +{ + return ltc4282_read_voltage_byte_cached(st, reg, fs, val, NULL); +} + +static int __ltc4282_read_alarm(struct ltc4282_state *st, u32 reg, u32 mask, + long *val) +{ + u32 alarm; + int ret; + + ret = regmap_read(st->map, reg, &alarm); + if (ret) + return ret; + + *val = !!(alarm & mask); + + /* if not status/fault logs, clear the alarm after reading it */ + if (reg != LTC4282_STATUS_LSB && reg != LTC4282_FAULT_LOG) + return regmap_clear_bits(st->map, reg, mask); + + return 0; +} + +static int ltc4282_read_alarm(struct ltc4282_state *st, u32 reg, u32 mask, + long *val) +{ + guard(mutex)(&st->lock); + return __ltc4282_read_alarm(st, reg, mask, val); +} + +static int ltc4282_vdd_source_read_in(struct ltc4282_state *st, u32 channel, + long *val) +{ + guard(mutex)(&st->lock); + if (!st->in0_1_cache[channel].en) + return -ENODATA; + + return ltc4282_read_voltage_word(st, LTC4282_VSOURCE, st->vfs_out, val); +} + +static int ltc4282_vdd_source_read_hist(struct ltc4282_state *st, u32 reg, + u32 channel, long *cached, long *val) +{ + int ret; + + guard(mutex)(&st->lock); + if (!st->in0_1_cache[channel].en) { + *val = *cached; + return 0; + } + + ret = ltc4282_read_voltage_word(st, reg, st->vfs_out, val); + if (ret) + return ret; + + *cached = *val; + return 0; +} + +static int ltc4282_vdd_source_read_lim(struct ltc4282_state *st, u32 reg, + u32 channel, u32 *cached, long *val) +{ + guard(mutex)(&st->lock); + if (!st->in0_1_cache[channel].en) + return ltc4282_read_voltage_byte_cached(st, reg, st->vfs_out, + val, cached); + + return ltc4282_read_voltage_byte(st, reg, st->vfs_out, val); +} + +static int ltc4282_vdd_source_read_alm(struct ltc4282_state *st, u32 mask, + u32 channel, long *val) +{ + guard(mutex)(&st->lock); + if (!st->in0_1_cache[channel].en) { + /* + * Do this otherwise alarms can get confused because we clear + * them after reading them. So, if someone mistakenly reads + * VSOURCE right before VDD (or the other way around), we might + * get no alarm just because it was cleared when reading VSOURCE + * and had no time for a new conversion and thus having the + * alarm again. + */ + *val = 0; + return 0; + } + + return __ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, mask, val); +} + +static int ltc4282_read_in(struct ltc4282_state *st, u32 attr, long *val, + u32 channel) +{ + switch (attr) { + case hwmon_in_input: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_read_voltage_word(st, LTC4282_VGPIO, + 1280, val); + + return ltc4282_vdd_source_read_in(st, channel, val); + case hwmon_in_highest: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_read_voltage_word(st, + LTC4282_VGPIO_HIGHEST, + 1280, val); + + return ltc4282_vdd_source_read_hist(st, LTC4282_VSOURCE_HIGHEST, + channel, + &st->in0_1_cache[channel].in_highest, val); + case hwmon_in_lowest: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_read_voltage_word(st, LTC4282_VGPIO_LOWEST, + 1280, val); + + return ltc4282_vdd_source_read_hist(st, LTC4282_VSOURCE_LOWEST, + channel, + &st->in0_1_cache[channel].in_lowest, val); + case hwmon_in_max_alarm: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, + LTC4282_GPIO_ALARM_H_MASK, + val); + + return ltc4282_vdd_source_read_alm(st, + LTC4282_VSOURCE_ALARM_H_MASK, + channel, val); + case hwmon_in_min_alarm: + if (channel == LTC4282_CHAN_VGPIO) + ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, + LTC4282_GPIO_ALARM_L_MASK, val); + + return ltc4282_vdd_source_read_alm(st, + LTC4282_VSOURCE_ALARM_L_MASK, + channel, val); + case hwmon_in_crit_alarm: + return ltc4282_read_alarm(st, LTC4282_STATUS_LSB, + LTC4282_OV_STATUS_MASK, val); + case hwmon_in_lcrit_alarm: + return ltc4282_read_alarm(st, LTC4282_STATUS_LSB, + LTC4282_UV_STATUS_MASK, val); + case hwmon_in_max: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_read_voltage_byte(st, LTC4282_VGPIO_MAX, + 1280, val); + + return ltc4282_vdd_source_read_lim(st, LTC4282_VSOURCE_MAX, + channel, + &st->in0_1_cache[channel].in_max_raw, val); + case hwmon_in_min: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_read_voltage_byte(st, LTC4282_VGPIO_MIN, + 1280, val); + + return ltc4282_vdd_source_read_lim(st, LTC4282_VSOURCE_MIN, + channel, + &st->in0_1_cache[channel].in_min_raw, val); + case hwmon_in_enable: + scoped_guard(mutex, &st->lock) { + *val = st->in0_1_cache[channel].en; + } + return 0; + case hwmon_in_fault: + /* + * We report failure if we detect either a fer_bad or a + * fet_short in the status register. + */ + return ltc4282_read_alarm(st, LTC4282_STATUS_LSB, + LTC4282_FET_FAILURE_MASK, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc4282_read_current_word(const struct ltc4282_state *st, u32 reg, + long *val) +{ + long in; + int ret; + + /* + * We pass in full scale in 10 * micro (note that 40 is already + * millivolt) so we have better approximations to calculate current. + */ + ret = ltc4282_read_voltage_word(st, reg, DECA * 40 * MILLI, &in); + if (ret) + return ret; + + *val = DIV_ROUND_CLOSEST(in * MILLI, st->rsense); + + return 0; +} + +static int ltc4282_read_current_byte(const struct ltc4282_state *st, u32 reg, + long *val) +{ + long in; + int ret; + + ret = ltc4282_read_voltage_byte(st, reg, DECA * 40 * MILLI, &in); + if (ret) + return ret; + + *val = DIV_ROUND_CLOSEST(in * MILLI, st->rsense); + + return 0; +} + +static int ltc4282_read_curr(struct ltc4282_state *st, const u32 attr, + long *val) +{ + switch (attr) { + case hwmon_curr_input: + return ltc4282_read_current_word(st, LTC4282_VSENSE, val); + case hwmon_curr_highest: + return ltc4282_read_current_word(st, LTC4282_VSENSE_HIGHEST, + val); + case hwmon_curr_lowest: + return ltc4282_read_current_word(st, LTC4282_VSENSE_LOWEST, + val); + case hwmon_curr_max: + return ltc4282_read_current_byte(st, LTC4282_VSENSE_MAX, val); + case hwmon_curr_min: + return ltc4282_read_current_byte(st, LTC4282_VSENSE_MIN, val); + case hwmon_curr_max_alarm: + return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, + LTC4282_VSENSE_ALARM_H_MASK, val); + case hwmon_curr_min_alarm: + return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, + LTC4282_VSENSE_ALARM_L_MASK, val); + case hwmon_curr_crit_alarm: + return ltc4282_read_alarm(st, LTC4282_STATUS_LSB, + LTC4282_OC_STATUS_MASK, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc4282_read_power_word(const struct ltc4282_state *st, u32 reg, + long *val) +{ + u64 temp = DECA * 40ULL * st->vfs_out * BIT(16), temp_2; + __be16 raw; + u16 power; + int ret; + + ret = regmap_bulk_read(st->map, reg, &raw, sizeof(raw)); + if (ret) + return ret; + + power = be16_to_cpu(raw); + /* + * Power is given by: + * P = CODE(16b) * 0.040 * Vfs(out) * 2^16 / ((2^16 - 1)^2 * Rsense) + */ + if (check_mul_overflow(power * temp, MICRO, &temp_2)) { + temp = DIV_ROUND_CLOSEST_ULL(power * temp, U16_MAX); + *val = DIV64_U64_ROUND_CLOSEST(temp * MICRO, + U16_MAX * (u64)st->rsense); + return 0; + } + + *val = DIV64_U64_ROUND_CLOSEST(temp_2, + st->rsense * int_pow(U16_MAX, 2)); + + return 0; +} + +static int ltc4282_read_power_byte(const struct ltc4282_state *st, u32 reg, + long *val) +{ + u32 power; + u64 temp; + int ret; + + ret = regmap_read(st->map, reg, &power); + if (ret) + return ret; + + temp = power * 40 * DECA * st->vfs_out * BIT_ULL(8); + *val = DIV64_U64_ROUND_CLOSEST(temp * MICRO, + int_pow(U8_MAX, 2) * st->rsense); + + return 0; +} + +static int ltc4282_read_energy(const struct ltc4282_state *st, u64 *val) +{ + u64 temp, energy; + __be64 raw; + int ret; + + ret = regmap_bulk_read(st->map, LTC4282_ENERGY, &raw, 6); + if (ret) + return ret; + + energy = be64_to_cpu(raw) >> 16; + /* + * The formula for energy is given by: + * E = CODE(48b) * 0.040 * Vfs(out) * Tconv * 256 / + * ((2^16 - 1)^2 * Rsense) + * + * Since we only support 12bit ADC, Tconv = 0.065535s. Passing Vfs(out) + * and 0.040 to mV and Tconv to us, we can simplify the formula to: + * E = CODE(48b) * 40 * Vfs(out) * 256 / (U16_MAX * Rsense) + * + * As Rsense can have tenths of micro-ohm resolution, we need to + * multiply by DECA to get microujoule. + */ + if (check_mul_overflow(DECA * st->vfs_out * 40 * BIT(8), energy, &temp)) { + temp = DIV_ROUND_CLOSEST(DECA * st->vfs_out * 40 * BIT(8), U16_MAX); + *val = DIV_ROUND_CLOSEST_ULL(temp * energy, st->rsense); + return 0; + } + + *val = DIV64_U64_ROUND_CLOSEST(temp, U16_MAX * (u64)st->rsense); + + return 0; +} + +static int ltc4282_read_power(struct ltc4282_state *st, const u32 attr, + long *val) +{ + switch (attr) { + case hwmon_power_input: + return ltc4282_read_power_word(st, LTC4282_POWER, val); + case hwmon_power_input_highest: + return ltc4282_read_power_word(st, LTC4282_POWER_HIGHEST, val); + case hwmon_power_input_lowest: + return ltc4282_read_power_word(st, LTC4282_POWER_LOWEST, val); + case hwmon_power_max_alarm: + return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, + LTC4282_POWER_ALARM_H_MASK, val); + case hwmon_power_min_alarm: + return ltc4282_read_alarm(st, LTC4282_ADC_ALERT_LOG, + LTC4282_POWER_ALARM_L_MASK, val); + case hwmon_power_max: + return ltc4282_read_power_byte(st, LTC4282_POWER_MAX, val); + case hwmon_power_min: + return ltc4282_read_power_byte(st, LTC4282_POWER_MIN, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc4282_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct ltc4282_state *st = dev_get_drvdata(dev); + + switch (type) { + case hwmon_in: + return ltc4282_read_in(st, attr, val, channel); + case hwmon_curr: + return ltc4282_read_curr(st, attr, val); + case hwmon_power: + return ltc4282_read_power(st, attr, val); + case hwmon_energy: + scoped_guard(mutex, &st->lock) { + *val = st->energy_en; + } + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int ltc4282_write_power_byte(const struct ltc4282_state *st, u32 reg, + long val) +{ + u32 power; + u64 temp; + + if (val > st->power_max) + val = st->power_max; + + temp = val * int_pow(U8_MAX, 2) * st->rsense; + power = DIV64_U64_ROUND_CLOSEST(temp, + MICRO * DECA * 256ULL * st->vfs_out * 40); + + return regmap_write(st->map, reg, power); +} + +static int ltc4282_write_power_word(const struct ltc4282_state *st, u32 reg, + long val) +{ + u64 temp = int_pow(U16_MAX, 2) * st->rsense, temp_2; + __be16 __raw; + u16 code; + + if (check_mul_overflow(temp, val, &temp_2)) { + temp = DIV_ROUND_CLOSEST_ULL(temp, DECA * MICRO); + code = DIV64_U64_ROUND_CLOSEST(temp * val, + 40ULL * BIT(16) * st->vfs_out); + } else { + temp = DECA * MICRO * 40ULL * BIT(16) * st->vfs_out; + code = DIV64_U64_ROUND_CLOSEST(temp_2, temp); + } + + __raw = cpu_to_be16(code); + return regmap_bulk_write(st->map, reg, &__raw, sizeof(__raw)); +} + +static int __ltc4282_in_write_history(const struct ltc4282_state *st, u32 reg, + long lowest, long highest, u32 fs) +{ + __be16 __raw; + u16 tmp; + int ret; + + tmp = DIV_ROUND_CLOSEST(U16_MAX * lowest, fs); + + __raw = cpu_to_be16(tmp); + + ret = regmap_bulk_write(st->map, reg, &__raw, 2); + if (ret) + return ret; + + tmp = DIV_ROUND_CLOSEST(U16_MAX * highest, fs); + + __raw = cpu_to_be16(tmp); + + return regmap_bulk_write(st->map, reg + 2, &__raw, 2); +} + +static int ltc4282_in_write_history(struct ltc4282_state *st, u32 reg, + long lowest, long highest, u32 fs) +{ + guard(mutex)(&st->lock); + return __ltc4282_in_write_history(st, reg, lowest, highest, fs); +} + +static int ltc4282_power_reset_hist(struct ltc4282_state *st) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = ltc4282_write_power_word(st, LTC4282_POWER_LOWEST, + st->power_max); + if (ret) + return ret; + + ret = ltc4282_write_power_word(st, LTC4282_POWER_HIGHEST, 0); + if (ret) + return ret; + + /* now, let's also clear possible power_bad fault logs */ + return regmap_clear_bits(st->map, LTC4282_FAULT_LOG, + LTC4282_POWER_BAD_FAULT_MASK); +} + +static int ltc4282_write_power(struct ltc4282_state *st, u32 attr, + long val) +{ + switch (attr) { + case hwmon_power_max: + return ltc4282_write_power_byte(st, LTC4282_POWER_MAX, val); + case hwmon_power_min: + return ltc4282_write_power_byte(st, LTC4282_POWER_MIN, val); + case hwmon_power_reset_history: + return ltc4282_power_reset_hist(st); + default: + return -EOPNOTSUPP; + } +} + +static int ltc4282_write_voltage_byte_cached(const struct ltc4282_state *st, + u32 reg, u32 fs, long val, + u32 *cache_raw) +{ + u32 in; + + val = clamp_val(val, 0, fs); + in = DIV_ROUND_CLOSEST(val * U8_MAX, fs); + + if (cache_raw) { + *cache_raw = in; + return 0; + } + + return regmap_write(st->map, reg, in); +} + +static int ltc4282_write_voltage_byte(const struct ltc4282_state *st, u32 reg, + u32 fs, long val) +{ + return ltc4282_write_voltage_byte_cached(st, reg, fs, val, NULL); +} + +static int ltc4282_cache_history(struct ltc4282_state *st, u32 channel) +{ + long val; + int ret; + + ret = ltc4282_read_voltage_word(st, LTC4282_VSOURCE_LOWEST, st->vfs_out, + &val); + if (ret) + return ret; + + st->in0_1_cache[channel].in_lowest = val; + + ret = ltc4282_read_voltage_word(st, LTC4282_VSOURCE_HIGHEST, + st->vfs_out, &val); + if (ret) + return ret; + + st->in0_1_cache[channel].in_highest = val; + + ret = regmap_read(st->map, LTC4282_VSOURCE_MIN, + &st->in0_1_cache[channel].in_min_raw); + if (ret) + return ret; + + return regmap_read(st->map, LTC4282_VSOURCE_MAX, + &st->in0_1_cache[channel].in_max_raw); +} + +static int ltc4282_cache_sync(struct ltc4282_state *st, u32 channel) +{ + int ret; + + ret = __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST, + st->in0_1_cache[channel].in_lowest, + st->in0_1_cache[channel].in_highest, + st->vfs_out); + if (ret) + return ret; + + ret = regmap_write(st->map, LTC4282_VSOURCE_MIN, + st->in0_1_cache[channel].in_min_raw); + if (ret) + return ret; + + return regmap_write(st->map, LTC4282_VSOURCE_MAX, + st->in0_1_cache[channel].in_max_raw); +} + +static int ltc4282_vdd_source_write_lim(struct ltc4282_state *st, u32 reg, + int channel, u32 *cache, long val) +{ + int ret; + + guard(mutex)(&st->lock); + if (st->in0_1_cache[channel].en) + ret = ltc4282_write_voltage_byte(st, reg, st->vfs_out, val); + else + ret = ltc4282_write_voltage_byte_cached(st, reg, st->vfs_out, + val, cache); + + return ret; +} + +static int ltc4282_vdd_source_reset_hist(struct ltc4282_state *st, int channel) +{ + long lowest = st->vfs_out; + int ret; + + if (channel == LTC4282_CHAN_VDD) + lowest = st->vdd; + + guard(mutex)(&st->lock); + if (st->in0_1_cache[channel].en) { + ret = __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST, + lowest, 0, st->vfs_out); + if (ret) + return ret; + } + + st->in0_1_cache[channel].in_lowest = lowest; + st->in0_1_cache[channel].in_highest = 0; + + /* + * We are also clearing possible fault logs in reset_history. Clearing + * the logs might be important when the auto retry bits are not enabled + * as the chip only enables the output again after having these logs + * cleared. As some of these logs are related to limits, it makes sense + * to clear them in here. For VDD, we need to clear under/over voltage + * events. For VSOURCE, fet_short and fet_bad... + */ + if (channel == LTC4282_CHAN_VSOURCE) + return regmap_clear_bits(st->map, LTC4282_FAULT_LOG, + LTC4282_FET_FAILURE_FAULT_MASK); + + return regmap_clear_bits(st->map, LTC4282_FAULT_LOG, + LTC4282_VDD_FAULT_MASK); +} + +/* + * We need to mux between VSOURCE and VDD which means they are mutually + * exclusive. Moreover, we can't really disable both VDD and VSOURCE as the ADC + * is continuously running (we cannot independently halt it without also + * stopping VGPIO). Hence, the logic is that disabling or enabling VDD will + * automatically have the reverse effect on VSOURCE and vice-versa. + */ +static int ltc4282_vdd_source_enable(struct ltc4282_state *st, int channel, + long val) +{ + int ret, other_chan = ~channel & 0x1; + u8 __val = val; + + guard(mutex)(&st->lock); + if (st->in0_1_cache[channel].en == !!val) + return 0; + + /* clearing the bit makes the ADC to monitor VDD */ + if (channel == LTC4282_CHAN_VDD) + __val = !__val; + + ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST, + LTC4282_VDD_MONITOR_MASK, + FIELD_PREP(LTC4282_VDD_MONITOR_MASK, !!__val)); + if (ret) + return ret; + + st->in0_1_cache[channel].en = !!val; + st->in0_1_cache[other_chan].en = !val; + + if (st->in0_1_cache[channel].en) { + /* + * Then, we are disabling @other_chan. Let's save it's current + * history. + */ + ret = ltc4282_cache_history(st, other_chan); + if (ret) + return ret; + + return ltc4282_cache_sync(st, channel); + } + /* + * Then, we are enabling @other_chan. We need to do the opposite from + * above. + */ + ret = ltc4282_cache_history(st, channel); + if (ret) + return ret; + + return ltc4282_cache_sync(st, other_chan); +} + +static int ltc4282_write_in(struct ltc4282_state *st, u32 attr, long val, + int channel) +{ + switch (attr) { + case hwmon_in_max: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_write_voltage_byte(st, LTC4282_VGPIO_MAX, + 1280, val); + + return ltc4282_vdd_source_write_lim(st, LTC4282_VSOURCE_MAX, + channel, + &st->in0_1_cache[channel].in_max_raw, val); + case hwmon_in_min: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_write_voltage_byte(st, LTC4282_VGPIO_MIN, + 1280, val); + + return ltc4282_vdd_source_write_lim(st, LTC4282_VSOURCE_MIN, + channel, + &st->in0_1_cache[channel].in_min_raw, val); + case hwmon_in_reset_history: + if (channel == LTC4282_CHAN_VGPIO) + return ltc4282_in_write_history(st, + LTC4282_VGPIO_LOWEST, + 1280, 0, 1280); + + return ltc4282_vdd_source_reset_hist(st, channel); + case hwmon_in_enable: + return ltc4282_vdd_source_enable(st, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int ltc4282_curr_reset_hist(struct ltc4282_state *st) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = __ltc4282_in_write_history(st, LTC4282_VSENSE_LOWEST, + st->vsense_max, 0, 40 * MILLI); + if (ret) + return ret; + + /* now, let's also clear possible overcurrent fault logs */ + return regmap_clear_bits(st->map, LTC4282_FAULT_LOG, + LTC4282_OC_FAULT_MASK); +} + +static int ltc4282_write_curr(struct ltc4282_state *st, u32 attr, + long val) +{ + /* need to pass it in millivolt */ + u32 in = DIV_ROUND_CLOSEST_ULL((u64)val * st->rsense, DECA * MICRO); + + switch (attr) { + case hwmon_curr_max: + return ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MAX, 40, + in); + case hwmon_curr_min: + return ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MIN, 40, + in); + case hwmon_curr_reset_history: + return ltc4282_curr_reset_hist(st); + default: + return -EOPNOTSUPP; + } +} + +static int ltc4282_energy_enable_set(struct ltc4282_state *st, long val) +{ + int ret; + + guard(mutex)(&st->lock); + /* setting the bit halts the meter */ + ret = regmap_update_bits(st->map, LTC4282_ADC_CTRL, + LTC4282_METER_HALT_MASK, + FIELD_PREP(LTC4282_METER_HALT_MASK, !val)); + if (ret) + return ret; + + st->energy_en = !!val; + + return 0; +} + +static int ltc4282_write(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct ltc4282_state *st = dev_get_drvdata(dev); + + switch (type) { + case hwmon_power: + return ltc4282_write_power(st, attr, val); + case hwmon_in: + return ltc4282_write_in(st, attr, val, channel); + case hwmon_curr: + return ltc4282_write_curr(st, attr, val); + case hwmon_energy: + return ltc4282_energy_enable_set(st, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t ltc4282_in_is_visible(const struct ltc4282_state *st, u32 attr) +{ + switch (attr) { + case hwmon_in_input: + case hwmon_in_highest: + case hwmon_in_lowest: + case hwmon_in_max_alarm: + case hwmon_in_min_alarm: + case hwmon_in_label: + case hwmon_in_lcrit_alarm: + case hwmon_in_crit_alarm: + case hwmon_in_fault: + return 0444; + case hwmon_in_max: + case hwmon_in_min: + case hwmon_in_enable: + case hwmon_in_reset_history: + return 0644; + default: + return 0; + } +} + +static umode_t ltc4282_curr_is_visible(u32 attr) +{ + switch (attr) { + case hwmon_curr_input: + case hwmon_curr_highest: + case hwmon_curr_lowest: + case hwmon_curr_max_alarm: + case hwmon_curr_min_alarm: + case hwmon_curr_crit_alarm: + case hwmon_curr_label: + return 0444; + case hwmon_curr_max: + case hwmon_curr_min: + case hwmon_curr_reset_history: + return 0644; + default: + return 0; + } +} + +static umode_t ltc4282_power_is_visible(u32 attr) +{ + switch (attr) { + case hwmon_power_input: + case hwmon_power_input_highest: + case hwmon_power_input_lowest: + case hwmon_power_label: + case hwmon_power_max_alarm: + case hwmon_power_min_alarm: + return 0444; + case hwmon_power_max: + case hwmon_power_min: + case hwmon_power_reset_history: + return 0644; + default: + return 0; + } +} + +static umode_t ltc4282_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + return ltc4282_in_is_visible(data, attr); + case hwmon_curr: + return ltc4282_curr_is_visible(attr); + case hwmon_power: + return ltc4282_power_is_visible(attr); + case hwmon_energy: + /* hwmon_energy_enable */ + return 0644; + default: + return 0; + } +} + +static const char * const ltc4282_in_strs[] = { + "VSOURCE", "VDD", "VGPIO" +}; + +static int ltc4282_read_labels(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_in: + *str = ltc4282_in_strs[channel]; + return 0; + case hwmon_curr: + *str = "ISENSE"; + return 0; + case hwmon_power: + *str = "Power"; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static ssize_t ltc4282_energy_show(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct ltc4282_state *st = dev_get_drvdata(dev); + u64 energy; + int ret; + + guard(mutex)(&st->lock); + if (!st->energy_en) + return -ENODATA; + + ret = ltc4282_read_energy(st, &energy); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%llu\n", energy); +} + +static const struct clk_ops ltc4282_ops = { + .recalc_rate = ltc4282_recalc_rate, + .round_rate = ltc4282_round_rate, + .set_rate = ltc4282_set_rate, + .disable = ltc4282_disable, +}; + +static int ltc428_clk_provider_setup(struct ltc4282_state *st, + struct device *dev) +{ + struct clk_init_data init; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-clk", + fwnode_get_name(dev_fwnode(dev))); + if (!init.name) + return -ENOMEM; + + init.ops = <c4282_ops; + init.flags = CLK_GET_RATE_NOCACHE; + st->clk_hw.init = &init; + + ret = devm_clk_hw_register(dev, &st->clk_hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &st->clk_hw); +} + +static int ltc428_clks_setup(struct ltc4282_state *st, struct device *dev) +{ + unsigned long rate; + struct clk *clkin; + u32 val; + int ret; + + ret = ltc428_clk_provider_setup(st, dev); + if (ret) + return ret; + + clkin = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clkin)) + return dev_err_probe(dev, PTR_ERR(clkin), + "Failed to get clkin"); + if (!clkin) + return 0; + + rate = clk_get_rate(clkin); + if (!in_range(rate, LTC4282_CLKIN_MIN, LTC4282_CLKIN_RANGE)) + return dev_err_probe(dev, -EINVAL, + "Invalid clkin range(%lu) [%lu %lu]\n", + rate, LTC4282_CLKIN_MIN, + LTC4282_CLKIN_MAX); + + /* + * Clocks faster than 250KHZ should be reduced to 250KHZ. The clock + * frequency is divided by twice the value in the register. + */ + val = rate / (2 * LTC4282_CLKIN_MIN); + + return regmap_update_bits(st->map, LTC4282_CLK_DIV, + LTC4282_CLK_DIV_MASK, + FIELD_PREP(LTC4282_CLK_DIV_MASK, val)); +} + +static const int ltc4282_curr_lim_uv[] = { + 12500, 15625, 18750, 21875, 25000, 28125, 31250, 34375 +}; + +static int ltc4282_get_defaults(struct ltc4282_state *st, u32 *vin_mode) +{ + u32 reg_val, ilm_adjust; + int ret; + + ret = regmap_read(st->map, LTC4282_ADC_CTRL, ®_val); + if (ret) + return ret; + + st->energy_en = !FIELD_GET(LTC4282_METER_HALT_MASK, reg_val); + + ret = regmap_read(st->map, LTC4282_CTRL_MSB, ®_val); + if (ret) + return ret; + + *vin_mode = FIELD_GET(LTC4282_CTRL_VIN_MODE_MASK, reg_val); + + ret = regmap_read(st->map, LTC4282_ILIM_ADJUST, ®_val); + if (ret) + return ret; + + ilm_adjust = FIELD_GET(LTC4282_ILIM_ADJUST_MASK, reg_val); + st->vsense_max = ltc4282_curr_lim_uv[ilm_adjust]; + + st->in0_1_cache[LTC4282_CHAN_VSOURCE].en = FIELD_GET(LTC4282_VDD_MONITOR_MASK, + ilm_adjust); + if (!st->in0_1_cache[LTC4282_CHAN_VSOURCE].en) { + st->in0_1_cache[LTC4282_CHAN_VDD].en = true; + return regmap_read(st->map, LTC4282_VSOURCE_MAX, + &st->in0_1_cache[LTC4282_CHAN_VSOURCE].in_max_raw); + } + + return regmap_read(st->map, LTC4282_VSOURCE_MAX, + &st->in0_1_cache[LTC4282_CHAN_VDD].in_max_raw); +} + +/* + * Set max limits for ISENSE and Power as that depends on the max voltage on + * rsense that is defined in ILIM_ADJUST. This is specially important for power + * because for some rsense and vfsout values, if we allow the default raw 255 + * value, that would overflow long in 32bit archs when reading back the max + * power limit. + * + * Also set meaningful historic values for VDD and VSOURCE + * (0 would not mean much). + */ +static int ltc4282_set_max_limits(struct ltc4282_state *st) +{ + int ret; + + ret = ltc4282_write_voltage_byte(st, LTC4282_VSENSE_MAX, 40 * MILLI, + st->vsense_max); + if (ret) + return ret; + + /* Power is given by ISENSE * Vout. */ + st->power_max = DIV_ROUND_CLOSEST(st->vsense_max * DECA * MILLI, st->rsense) * st->vfs_out; + ret = ltc4282_write_power_byte(st, LTC4282_POWER_MAX, st->power_max); + if (ret) + return ret; + + if (st->in0_1_cache[LTC4282_CHAN_VDD].en) { + st->in0_1_cache[LTC4282_CHAN_VSOURCE].in_lowest = st->vfs_out; + return __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST, + st->vdd, 0, st->vfs_out); + } + + st->in0_1_cache[LTC4282_CHAN_VDD].in_lowest = st->vdd; + return __ltc4282_in_write_history(st, LTC4282_VSOURCE_LOWEST, + st->vfs_out, 0, st->vfs_out); +} + +static const char * const ltc4282_gpio1_modes[] = { + "power_bad", "power_good" +}; + +static const char * const ltc4282_gpio2_modes[] = { + "adc_input", "stress_fet" +}; + +static int ltc4282_gpio_setup(struct ltc4282_state *st, struct device *dev) +{ + const char *func = NULL; + int ret; + + ret = device_property_read_string(dev, "adi,gpio1-mode", &func); + if (!ret) { + ret = match_string(ltc4282_gpio1_modes, + ARRAY_SIZE(ltc4282_gpio1_modes), func); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid func(%s) for gpio1\n", + func); + + ret = regmap_update_bits(st->map, LTC4282_GPIO_CONFIG, + LTC4282_GPIO_1_CONFIG_MASK, + FIELD_PREP(LTC4282_GPIO_1_CONFIG_MASK, ret)); + if (ret) + return ret; + } + + ret = device_property_read_string(dev, "adi,gpio2-mode", &func); + if (!ret) { + ret = match_string(ltc4282_gpio2_modes, + ARRAY_SIZE(ltc4282_gpio2_modes), func); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid func(%s) for gpio2\n", + func); + if (!ret) { + /* setting the bit to 1 so the ADC to monitors GPIO2 */ + ret = regmap_set_bits(st->map, LTC4282_ILIM_ADJUST, + LTC4282_GPIO_MODE_MASK); + } else { + ret = regmap_update_bits(st->map, LTC4282_GPIO_CONFIG, + LTC4282_GPIO_2_FET_STRESS_MASK, + FIELD_PREP(LTC4282_GPIO_2_FET_STRESS_MASK, 1)); + } + + if (ret) + return ret; + } + + if (!device_property_read_bool(dev, "adi,gpio3-monitor-enable")) + return 0; + + if (func && !strcmp(func, "adc_input")) + return dev_err_probe(dev, -EINVAL, + "Cannot have both gpio2 and gpio3 muxed into the ADC"); + + return regmap_clear_bits(st->map, LTC4282_ILIM_ADJUST, + LTC4282_GPIO_MODE_MASK); +} + +static const char * const ltc4282_dividers[] = { + "external", "vdd_5_percent", "vdd_10_percent", "vdd_15_percent" +}; + +/* This maps the Vout full scale for the given Vin mode */ +static const u16 ltc4282_vfs_milli[] = { 5540, 8320, 16640, 33280 }; + +static const u16 ltc4282_vdd_milli[] = { 3300, 5000, 12000, 24000 }; + +enum { + LTC4282_VIN_3_3V, + LTC4282_VIN_5V, + LTC4282_VIN_12V, + LTC4282_VIN_24V, +}; + +static int ltc4282_setup(struct ltc4282_state *st, struct device *dev) +{ + const char *divider; + u32 val, vin_mode; + int ret; + + /* The part has an eeprom so let's get the needed defaults from it */ + ret = ltc4282_get_defaults(st, &vin_mode); + if (ret) + return ret; + + ret = device_property_read_u32(dev, "adi,rsense-nano-ohms", + &st->rsense); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read adi,rsense-nano-ohms\n"); + if (st->rsense < CENTI) + return dev_err_probe(dev, -EINVAL, + "adi,rsense-nano-ohms too small (< %lu)\n", + CENTI); + + /* + * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which + * means we need nano in the bindings. However, to make things easier to + * handle (with respect to overflows) we divide it by 100 as we don't + * really need the last two digits. + */ + st->rsense /= CENTI; + + val = vin_mode; + ret = device_property_read_u32(dev, "adi,vin-mode-microvolt", &val); + if (!ret) { + switch (val) { + case 3300000: + val = LTC4282_VIN_3_3V; + break; + case 5000000: + val = LTC4282_VIN_5V; + break; + case 12000000: + val = LTC4282_VIN_12V; + break; + case 24000000: + val = LTC4282_VIN_24V; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Invalid val(%u) for vin-mode-microvolt\n", + val); + } + + ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB, + LTC4282_CTRL_VIN_MODE_MASK, + FIELD_PREP(LTC4282_CTRL_VIN_MODE_MASK, val)); + if (ret) + return ret; + + /* Foldback mode should also be set to the input voltage */ + ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST, + LTC4282_FOLDBACK_MODE_MASK, + FIELD_PREP(LTC4282_FOLDBACK_MODE_MASK, val)); + if (ret) + return ret; + } + + st->vfs_out = ltc4282_vfs_milli[val]; + st->vdd = ltc4282_vdd_milli[val]; + + ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt", + &st->vsense_max); + if (!ret) { + int reg_val; + + switch (val) { + case 12500: + reg_val = 0; + break; + case 15625: + reg_val = 1; + break; + case 18750: + reg_val = 2; + break; + case 21875: + reg_val = 3; + break; + case 25000: + reg_val = 4; + break; + case 28125: + reg_val = 5; + break; + case 31250: + reg_val = 6; + break; + case 34375: + reg_val = 7; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Invalid val(%u) for adi,current-limit-microvolt\n", + st->vsense_max); + } + + ret = regmap_update_bits(st->map, LTC4282_ILIM_ADJUST, + LTC4282_ILIM_ADJUST_MASK, + FIELD_PREP(LTC4282_ILIM_ADJUST_MASK, reg_val)); + if (ret) + return ret; + } + + ret = ltc4282_set_max_limits(st); + if (ret) + return ret; + + ret = device_property_read_string(dev, "adi,overvoltage-dividers", + ÷r); + if (!ret) { + int div = match_string(ltc4282_dividers, + ARRAY_SIZE(ltc4282_dividers), divider); + if (div < 0) + return dev_err_probe(dev, -EINVAL, + "Invalid val(%s) for adi,overvoltage-divider\n", + divider); + + ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB, + LTC4282_CTRL_OV_MODE_MASK, + FIELD_PREP(LTC4282_CTRL_OV_MODE_MASK, div)); + } + + ret = device_property_read_string(dev, "adi,undervoltage-dividers", + ÷r); + if (!ret) { + int div = match_string(ltc4282_dividers, + ARRAY_SIZE(ltc4282_dividers), divider); + if (div < 0) + return dev_err_probe(dev, -EINVAL, + "Invalid val(%s) for adi,undervoltage-divider\n", + divider); + + ret = regmap_update_bits(st->map, LTC4282_CTRL_MSB, + LTC4282_CTRL_UV_MODE_MASK, + FIELD_PREP(LTC4282_CTRL_UV_MODE_MASK, div)); + } + + if (device_property_read_bool(dev, "adi,overcurrent-retry")) { + ret = regmap_set_bits(st->map, LTC4282_CTRL_LSB, + LTC4282_CTRL_OC_RETRY_MASK); + if (ret) + return ret; + } + + if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) { + ret = regmap_clear_bits(st->map, LTC4282_CTRL_LSB, + LTC4282_CTRL_OV_RETRY_MASK); + if (ret) + return ret; + } + + if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) { + ret = regmap_clear_bits(st->map, LTC4282_CTRL_LSB, + LTC4282_CTRL_UV_RETRY_MASK); + if (ret) + return ret; + } + + if (device_property_read_bool(dev, "adi,fault-log-enable")) { + ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL, + LTC4282_FAULT_LOG_EN_MASK); + if (ret) + return ret; + } + + if (device_property_read_bool(dev, "adi,fault-log-enable")) { + ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL, LTC4282_FAULT_LOG_EN_MASK); + if (ret) + return ret; + } + + ret = device_property_read_u32(dev, "adi,fet-bad-timeout-ms", &val); + if (!ret) { + if (val > LTC4282_FET_BAD_MAX_TIMEOUT) + return dev_err_probe(dev, -EINVAL, + "Invalid value(%u) for adi,fet-bad-timeout-ms", + val); + + ret = regmap_write(st->map, LTC4282_FET_BAD_FAULT_TIMEOUT, val); + if (ret) + return ret; + } + + return ltc4282_gpio_setup(st, dev); +} + +static bool ltc4282_readable_reg(struct device *dev, unsigned int reg) +{ + if (reg == LTC4282_RESERVED_1 || reg == LTC4282_RESERVED_2) + return false; + + return true; +} + +static bool ltc4282_writable_reg(struct device *dev, unsigned int reg) +{ + if (reg == LTC4282_STATUS_LSB || reg == LTC4282_STATUS_MSB) + return false; + if (reg == LTC4282_RESERVED_1 || reg == LTC4282_RESERVED_2) + return false; + + return true; +} + +static const struct regmap_config ltc4282_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC4282_RESERVED_3, + .readable_reg = ltc4282_readable_reg, + .writeable_reg = ltc4282_writable_reg, +}; + +static const struct hwmon_channel_info * const ltc4282_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM | + HWMON_I_MAX_ALARM | HWMON_I_ENABLE | + HWMON_I_RESET_HISTORY | HWMON_I_FAULT | + HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM | + HWMON_I_MAX_ALARM | HWMON_I_LCRIT_ALARM | + HWMON_I_CRIT_ALARM | HWMON_I_ENABLE | + HWMON_I_RESET_HISTORY | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | + HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM | + HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM | + HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | + HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM | + HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM | + HWMON_C_RESET_HISTORY | HWMON_C_LABEL), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | + HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN | + HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM | + HWMON_P_RESET_HISTORY | HWMON_P_LABEL), + HWMON_CHANNEL_INFO(energy, + HWMON_E_ENABLE), + NULL +}; + +static const struct hwmon_ops ltc4282_hwmon_ops = { + .read = ltc4282_read, + .write = ltc4282_write, + .is_visible = ltc4282_is_visible, + .read_string = ltc4282_read_labels, +}; + +static const struct hwmon_chip_info ltc2947_chip_info = { + .ops = <c4282_hwmon_ops, + .info = ltc4282_info, +}; + +/* energy attributes are 6bytes wide so we need u64 */ +static SENSOR_DEVICE_ATTR_RO(energy1_input, ltc4282_energy, 0); + +static struct attribute *ltc4282_attrs[] = { + &sensor_dev_attr_energy1_input.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(ltc4282); + +static int ltc4282_show_fault_log(void *arg, u64 *val, u32 mask) +{ + struct ltc4282_state *st = arg; + long alarm; + int ret; + + ret = ltc4282_read_alarm(st, LTC4282_FAULT_LOG, mask, &alarm); + if (ret) + return ret; + + *val = alarm; + + return 0; +} + +static int ltc4282_show_curr1_crit_fault_log(void *arg, u64 *val) +{ + return ltc4282_show_fault_log(arg, val, LTC4282_OC_FAULT_MASK); +} +DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_curr1_crit_fault_log, + ltc4282_show_curr1_crit_fault_log, NULL, "%llu\n"); + +static int ltc4282_show_in1_lcrit_fault_log(void *arg, u64 *val) +{ + return ltc4282_show_fault_log(arg, val, LTC4282_UV_FAULT_MASK); +} +DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_in1_lcrit_fault_log, + ltc4282_show_in1_lcrit_fault_log, NULL, "%llu\n"); + +static int ltc4282_show_in1_crit_fault_log(void *arg, u64 *val) +{ + return ltc4282_show_fault_log(arg, val, LTC4282_OV_FAULT_MASK); +} +DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_in1_crit_fault_log, + ltc4282_show_in1_crit_fault_log, NULL, "%llu\n"); + +static int ltc4282_show_fet_bad_fault_log(void *arg, u64 *val) +{ + return ltc4282_show_fault_log(arg, val, LTC4282_FET_BAD_FAULT_MASK); +} +DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_fet_bad_fault_log, + ltc4282_show_fet_bad_fault_log, NULL, "%llu\n"); + +static int ltc4282_show_fet_short_fault_log(void *arg, u64 *val) +{ + return ltc4282_show_fault_log(arg, val, LTC4282_FET_SHORT_FAULT_MASK); +} +DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_fet_short_fault_log, + ltc4282_show_fet_short_fault_log, NULL, "%llu\n"); + +static int ltc4282_show_power1_bad_fault_log(void *arg, u64 *val) +{ + return ltc4282_show_fault_log(arg, val, LTC4282_POWER_BAD_FAULT_MASK); +} +DEFINE_DEBUGFS_ATTRIBUTE(ltc4282_power1_bad_fault_log, + ltc4282_show_power1_bad_fault_log, NULL, "%llu\n"); + +static void ltc4282_debugfs_remove(void *dir) +{ + debugfs_remove_recursive(dir); +} + +static void ltc4282_debugfs_init(struct ltc4282_state *st, + struct i2c_client *i2c, + const struct device *hwmon) +{ + const char *debugfs_name; + struct dentry *dentry; + int ret; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_name = devm_kasprintf(&i2c->dev, GFP_KERNEL, "ltc4282-%s", + dev_name(hwmon)); + if (!debugfs_name) + return; + + dentry = debugfs_create_dir(debugfs_name, NULL); + if (IS_ERR(dentry)) + return; + + ret = devm_add_action_or_reset(&i2c->dev, ltc4282_debugfs_remove, + dentry); + if (ret) + return; + + debugfs_create_file_unsafe("power1_bad_fault_log", 0400, dentry, st, + <c4282_power1_bad_fault_log); + debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, dentry, st, + <c4282_fet_short_fault_log); + debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, dentry, st, + <c4282_fet_bad_fault_log); + debugfs_create_file_unsafe("in1_crit_fault_log", 0400, dentry, st, + <c4282_in1_crit_fault_log); + debugfs_create_file_unsafe("in1_lcrit_fault_log", 0400, dentry, st, + <c4282_in1_lcrit_fault_log); + debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, dentry, st, + <c4282_curr1_crit_fault_log); +} + +static int ltc4282_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev, *hwmon; + struct ltc4282_state *st; + int ret; + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return dev_err_probe(dev, -ENOMEM, + "Failed to allocate memory\n"); + + st->map = devm_regmap_init_i2c(i2c, <c4282_regmap_config); + if (IS_ERR(st->map)) + return dev_err_probe(dev, PTR_ERR(st->map), + "failed regmap init\n"); + + /* Soft reset */ + ret = regmap_set_bits(st->map, LTC4282_ADC_CTRL, LTC4282_RESET_MASK); + if (ret) + return ret; + + /* Yes, it's big but it is as specified in the datasheet */ + msleep(3200); + + ret = ltc428_clks_setup(st, dev); + if (ret) + return ret; + + ret = ltc4282_setup(st, dev); + if (ret) + return ret; + + mutex_init(&st->lock); + hwmon = devm_hwmon_device_register_with_info(dev, "ltc4282", st, + <c2947_chip_info, + ltc4282_groups); + if (IS_ERR(hwmon)) + return PTR_ERR(hwmon); + + ltc4282_debugfs_init(st, i2c, hwmon); + + return 0; +} + +static const struct of_device_id ltc4282_of_match[] = { + { .compatible = "adi,ltc4282" }, + {} +}; +MODULE_DEVICE_TABLE(of, ltc4282_of_match); + +static struct i2c_driver ltc4282_driver = { + .driver = { + .name = "ltc4282", + .of_match_table = ltc4282_of_match, + }, + .probe = ltc4282_probe, +}; +module_i2c_driver(ltc4282_driver); + +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("LTC4282 I2C High Current Hot Swap Controller"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From d612bf839f618b090672356889e3bbdac67aebd5 Mon Sep 17 00:00:00 2001 From: Ivor Wanders Date: Tue, 30 Jan 2024 19:58:55 -0500 Subject: hwmon: add fan speed monitoring driver for Surface devices Adds a driver that provides read only access to the fan speed for Microsoft Surface Pro devices. The fan speed is always regulated by the EC and cannot be influenced directly. Signed-off-by: Ivor Wanders Link: https://github.com/linux-surface/kernel/pull/144 Reviewed-by: Maximilian Luz Reviewed-by: Armin Wolf Link: https://lore.kernel.org/r/20240131005856.10180-2-ivor@iwanders.net [groeck: - Declare surface_fan_hwmon_is_visible() static - Add dependency on SURFACE_AGGREGATOR_BUS ] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/surface_fan.rst | 25 ++++++++++ MAINTAINERS | 8 ++++ drivers/hwmon/Kconfig | 14 ++++++ drivers/hwmon/Makefile | 1 + drivers/hwmon/surface_fan.c | 91 +++++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 Documentation/hwmon/surface_fan.rst create mode 100644 drivers/hwmon/surface_fan.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index f16c6dfaec7d..8f73badecba1 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -209,6 +209,7 @@ Hardware Monitoring Kernel Drivers smsc47m1 sparx5-temp stpddc60 + surface_fan sy7636a-hwmon tc654 tc74 diff --git a/Documentation/hwmon/surface_fan.rst b/Documentation/hwmon/surface_fan.rst new file mode 100644 index 000000000000..07942574c4f0 --- /dev/null +++ b/Documentation/hwmon/surface_fan.rst @@ -0,0 +1,25 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver surface_fan +========================= + +Supported Devices: + + * Microsoft Surface Pro 9 + +Author: Ivor Wanders + +Description +----------- + +This provides monitoring of the fan found in some Microsoft Surface Pro devices, +like the Surface Pro 9. The fan is always controlled by the onboard controller. + +Sysfs interface +--------------- + +======================= ======= ========================================= +Name Perm Description +======================= ======= ========================================= +``fan1_input`` RO Current fan speed in RPM. +======================= ======= ========================================= diff --git a/MAINTAINERS b/MAINTAINERS index df8ea2d94a0c..dd69696f3c0e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14549,6 +14549,14 @@ F: Documentation/driver-api/surface_aggregator/clients/dtx.rst F: drivers/platform/surface/surface_dtx.c F: include/uapi/linux/surface_aggregator/dtx.h +MICROSOFT SURFACE SENSOR FAN DRIVER +M: Maximilian Luz +M: Ivor Wanders +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/surface_fan.rst +F: drivers/hwmon/surface_fan.c + MICROSOFT SURFACE GPE LID SUPPORT DRIVER M: Maximilian Luz L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f6160cc70077..cdf228fe50e2 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2005,6 +2005,20 @@ config SENSORS_SFCTEMP This driver can also be built as a module. If so, the module will be called sfctemp. +config SENSORS_SURFACE_FAN + tristate "Surface Fan Driver" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + help + Driver that provides monitoring of the fan on Surface Pro devices that + have a fan, like the Surface Pro 9. + + This makes the fan's current speed accessible through the hwmon + system. It does not provide control over the fan, the firmware is + responsible for that, this driver merely provides monitoring. + + Select M or Y here, if you want to be able to read the fan's speed. + config SENSORS_ADC128D818 tristate "Texas Instruments ADC128D818" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8bfc422a29e5..a49704cd48a4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -202,6 +202,7 @@ obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o +obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o obj-$(CONFIG_SENSORS_TC74) += tc74.o diff --git a/drivers/hwmon/surface_fan.c b/drivers/hwmon/surface_fan.c new file mode 100644 index 000000000000..de3c5a2409c6 --- /dev/null +++ b/drivers/hwmon/surface_fan.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Fan driver for Surface System Aggregator Module. It provides access + * to the fan's rpm through the hwmon system. + * + * Copyright (C) 2023 Ivor Wanders + */ + +#include +#include +#include +#include +#include + +// SSAM +SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_fan_rpm_get, __le16, { + .target_category = SSAM_SSH_TC_FAN, + .command_id = 0x01, +}); + +// hwmon +static umode_t surface_fan_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + return 0444; +} + +static int surface_fan_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct ssam_device *sdev = dev_get_drvdata(dev); + int ret; + __le16 value; + + ret = __ssam_fan_rpm_get(sdev, &value); + if (ret) + return ret; + + *val = le16_to_cpu(value); + + return 0; +} + +static const struct hwmon_channel_info *const surface_fan_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + NULL +}; + +static const struct hwmon_ops surface_fan_hwmon_ops = { + .is_visible = surface_fan_hwmon_is_visible, + .read = surface_fan_hwmon_read, +}; + +static const struct hwmon_chip_info surface_fan_chip_info = { + .ops = &surface_fan_hwmon_ops, + .info = surface_fan_info, +}; + +static int surface_fan_probe(struct ssam_device *sdev) +{ + struct device *hdev; + + hdev = devm_hwmon_device_register_with_info(&sdev->dev, + "surface_fan", sdev, + &surface_fan_chip_info, + NULL); + + return PTR_ERR_OR_ZERO(hdev); +} + +static const struct ssam_device_id ssam_fan_match[] = { + { SSAM_SDEV(FAN, SAM, 0x01, 0x01) }, + {}, +}; +MODULE_DEVICE_TABLE(ssam, ssam_fan_match); + +static struct ssam_device_driver surface_fan = { + .probe = surface_fan_probe, + .match_table = ssam_fan_match, + .driver = { + .name = "surface_fan", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_ssam_device_driver(surface_fan); + +MODULE_AUTHOR("Ivor Wanders "); +MODULE_DESCRIPTION("Fan Driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 7e6707f7da317eb98c12de6a477d5cfb5057705f Mon Sep 17 00:00:00 2001 From: Charles Hsu Date: Wed, 31 Jan 2024 15:48:21 +0800 Subject: dt-bindings: Add MPQ8785 voltage regulator device Monolithic Power Systems, Inc. (MPS) synchronous step-down converter. Signed-off-by: Charles Hsu Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240131074822.2962078-1-ythsu0511@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 79dcd92c4a43..088b23ed2ae6 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -129,6 +129,8 @@ properties: - mps,mp2975 # Monolithic Power Systems Inc. multi-phase hot-swap controller mp5990 - mps,mp5990 + # Monolithic Power Systems Inc. synchronous step-down converter mpq8785 + - mps,mpq8785 # Honeywell Humidicon HIH-6130 humidity/temperature sensor - honeywell,hi6130 # IBM Common Form Factor Power Supply Versions (all versions) -- cgit v1.2.3 From f20b4a931130cb574c40563cfda0fc2cb944b4df Mon Sep 17 00:00:00 2001 From: Charles Hsu Date: Wed, 31 Jan 2024 15:48:22 +0800 Subject: hwmon: Add driver for MPS MPQ8785 Synchronous Step-Down Converter Add support for mpq8785 device from Monolithic Power Systems, Inc. (MPS) vendor. This is synchronous step-down controller. Signed-off-by: Charles Hsu Link: https://lore.kernel.org/r/20240131074822.2962078-2-ythsu0511@gmail.com [groeck: probe_new --> probe; add MODULE_IMPORT_NS(PMBUS)] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mpq8785.rst | 94 +++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/pmbus/Kconfig | 9 ++++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/mpq8785.c | 90 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 Documentation/hwmon/mpq8785.rst create mode 100644 drivers/hwmon/pmbus/mpq8785.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 8f73badecba1..9ac087dd3e78 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -164,6 +164,7 @@ Hardware Monitoring Kernel Drivers mp2975 mp5023 mp5990 + mpq8785 nct6683 nct6775 nct7802 diff --git a/Documentation/hwmon/mpq8785.rst b/Documentation/hwmon/mpq8785.rst new file mode 100644 index 000000000000..bf8176b87086 --- /dev/null +++ b/Documentation/hwmon/mpq8785.rst @@ -0,0 +1,94 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver mpq8785 +======================= + +Supported chips: + + * MPS MPQ8785 + + Prefix: 'mpq8785' + +Author: Charles Hsu + +Description +----------- + +The MPQ8785 is a fully integrated, PMBus-compatible, high-frequency, synchronous +buck converter. The MPQ8785 offers a very compact solution that achieves up to +40A output current per phase, with excellent load and line regulation over a +wide input supply range. The MPQ8785 operates at high efficiency over a wide +output current load range. + +The PMBus interface provides converter configurations and key parameters +monitoring. + +The MPQ8785 adopts MPS's proprietary multi-phase digital constant-on-time (MCOT) +control, which provides fast transient response and eases loop stabilization. +The MCOT scheme also allows multiple MPQ8785 devices to be connected in parallel +with excellent current sharing and phase interleaving for high-current +applications. + +Fully integrated protection features include over-current protection (OCP), +over-voltage protection (OVP), under-voltage protection (UVP), and +over-temperature protection (OTP). + +The MPQ8785 requires a minimal number of readily available, standard external +components, and is available in a TLGA (5mmx6mm) package. + +Device compliant with: + +- PMBus rev 1.3 interface. + +The driver exports the following attributes via the 'sysfs' files +for input voltage: + +**in1_input** + +**in1_label** + +**in1_max** + +**in1_max_alarm** + +**in1_min** + +**in1_min_alarm** + +**in1_crit** + +**in1_crit_alarm** + +The driver provides the following attributes for output voltage: + +**in2_input** + +**in2_label** + +**in2_alarm** + +The driver provides the following attributes for output current: + +**curr1_input** + +**curr1_label** + +**curr1_max** + +**curr1_max_alarm** + +**curr1_crit** + +**curr1_crit_alarm** + +The driver provides the following attributes for temperature: + +**temp1_input** + +**temp1_max** + +**temp1_max_alarm** + +**temp1_crit** + +**temp1_crit_alarm** diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 294808f5240a..557ae0c414b0 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -377,6 +377,15 @@ config SENSORS_MPQ7932 This driver can also be built as a module. If so, the module will be called mpq7932. +config SENSORS_MPQ8785 + tristate "MPS MPQ8785" + help + If you say yes here you get hardware monitoring functionality support + for power management IC MPS MPQ8785. + + This driver can also be built as a module. If so, the module will + be called mpq8785. + config SENSORS_PIM4328 tristate "Flex PIM4328 and compatibles" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index cf8a76744545..f14ecf03ad77 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_SENSORS_MP2975) += mp2975.o obj-$(CONFIG_SENSORS_MP5023) += mp5023.o obj-$(CONFIG_SENSORS_MP5990) += mp5990.o obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o +obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o diff --git a/drivers/hwmon/pmbus/mpq8785.c b/drivers/hwmon/pmbus/mpq8785.c new file mode 100644 index 000000000000..4e2549cc8120 --- /dev/null +++ b/drivers/hwmon/pmbus/mpq8785.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MPQ8785 Step-Down Converter + */ + +#include +#include +#include +#include "pmbus.h" + +static int mpq8785_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode < 0 || vout_mode == 0xff) + return vout_mode < 0 ? vout_mode : -ENODEV; + switch (vout_mode >> 5) { + case 0: + info->format[PSC_VOLTAGE_OUT] = linear; + break; + case 1: + case 2: + info->format[PSC_VOLTAGE_OUT] = direct, + info->m[PSC_VOLTAGE_OUT] = 64; + info->b[PSC_VOLTAGE_OUT] = 0; + info->R[PSC_VOLTAGE_OUT] = 1; + break; + default: + return -ENODEV; + } + + return 0; +}; + +static struct pmbus_driver_info mpq8785_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 4, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = + PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .identify = mpq8785_identify, +}; + +static int mpq8785_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &mpq8785_info); +}; + +static const struct i2c_device_id mpq8785_id[] = { + { "mpq8785", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, mpq8785_id); + +static const struct of_device_id __maybe_unused mpq8785_of_match[] = { + { .compatible = "mps,mpq8785" }, + {} +}; +MODULE_DEVICE_TABLE(of, mpq8785_of_match); + +static struct i2c_driver mpq8785_driver = { + .driver = { + .name = "mpq8785", + .of_match_table = of_match_ptr(mpq8785_of_match), + }, + .probe = mpq8785_probe, + .id_table = mpq8785_id, +}; + +module_i2c_driver(mpq8785_driver); + +MODULE_AUTHOR("Charles Hsu "); +MODULE_DESCRIPTION("PMBus driver for MPS MPQ8785"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); -- cgit v1.2.3 From 9d613d9b8a9e54cc21379a40de29294adc1e6594 Mon Sep 17 00:00:00 2001 From: Stefan Gloor Date: Wed, 31 Jan 2024 12:15:12 +0100 Subject: hwmon: (sht3x) read out sensor serial number The temperature/humidity sensors of the STS3x/SHT3x family are calibrated and factory-programmed with a unique serial number. For some sensors, this serial number can be used to obtain a calibration certificate via an API provided by the manufacturer (Sensirion). Expose the serial number via debugfs. Tested with: 2x STS31, 1x STS32, 1x SHT31 Signed-off-by: Stefan Gloor Link: https://lore.kernel.org/r/20240131111512.25321-2-code@stefan-gloor.ch Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht3x.rst | 11 ++++++++ drivers/hwmon/sht3x.c | 66 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/sht3x.rst b/Documentation/hwmon/sht3x.rst index 957c854f5d08..9585fa7c5a5d 100644 --- a/Documentation/hwmon/sht3x.rst +++ b/Documentation/hwmon/sht3x.rst @@ -65,6 +65,10 @@ When the temperature and humidity readings move back between the hysteresis values, the alert bit is set to 0 and the alert pin on the sensor is set to low. +The serial number exposed to debugfs allows for unique identification of the +sensors. For sts32, sts33 and sht33, the manufacturer provides calibration +certificates through an API. + sysfs-Interface --------------- @@ -99,3 +103,10 @@ repeatability: write or read repeatability, higher repeatability means - 1: medium repeatability - 2: high repeatability =================== ============================================================ + +debugfs-Interface +----------------- + +=================== ============================================================ +serial_number: unique serial number of the sensor in decimal +=================== ============================================================ diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c index 79657910b79e..c0d02fbcdb76 100644 --- a/drivers/hwmon/sht3x.c +++ b/drivers/hwmon/sht3x.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,9 @@ static const unsigned char sht3x_cmd_heater_off[] = { 0x30, 0x66 }; /* other commands */ static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d }; static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 }; +static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 }; + +static struct dentry *debugfs; /* delays for single-shot mode i2c commands, both in us */ #define SHT3X_SINGLE_WAIT_TIME_HPM 15000 @@ -163,12 +167,14 @@ struct sht3x_data { enum sht3x_chips chip_id; struct mutex i2c_lock; /* lock for sending i2c commands */ struct mutex data_lock; /* lock for updating driver data */ + struct dentry *sensor_dir; u8 mode; const unsigned char *command; u32 wait_time; /* in us*/ unsigned long last_update; /* last update in periodic mode*/ enum sht3x_repeatability repeatability; + u32 serial_number; /* * cached values for temperature and humidity and limits @@ -831,6 +837,40 @@ static int sht3x_write(struct device *dev, enum hwmon_sensor_types type, } } +static void sht3x_debugfs_init(struct sht3x_data *data) +{ + char name[32]; + + snprintf(name, sizeof(name), "i2c%u-%02x", + data->client->adapter->nr, data->client->addr); + data->sensor_dir = debugfs_create_dir(name, debugfs); + debugfs_create_u32("serial_number", 0444, + data->sensor_dir, &data->serial_number); +} + +static void sht3x_debugfs_remove(void *sensor_dir) +{ + debugfs_remove_recursive(sensor_dir); +} + +static int sht3x_serial_number_read(struct sht3x_data *data) +{ + int ret; + char buffer[SHT3X_RESPONSE_LENGTH]; + struct i2c_client *client = data->client; + + ret = sht3x_read_from_command(client, data, + sht3x_cmd_read_serial_number, + buffer, + SHT3X_RESPONSE_LENGTH, 0); + if (ret) + return ret; + + data->serial_number = (buffer[0] << 24) | (buffer[1] << 16) | + (buffer[3] << 8) | buffer[4]; + return ret; +} + static const struct hwmon_ops sht3x_ops = { .is_visible = sht3x_is_visible, .read = sht3x_read, @@ -899,6 +939,18 @@ static int sht3x_probe(struct i2c_client *client) if (ret) return ret; + ret = sht3x_serial_number_read(data); + if (ret) { + dev_dbg(dev, "unable to read serial number\n"); + } else { + sht3x_debugfs_init(data); + ret = devm_add_action_or_reset(dev, + sht3x_debugfs_remove, + data->sensor_dir); + if (ret) + return ret; + } + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, @@ -917,7 +969,19 @@ static struct i2c_driver sht3x_i2c_driver = { .id_table = sht3x_ids, }; -module_i2c_driver(sht3x_i2c_driver); +static int __init sht3x_init(void) +{ + debugfs = debugfs_create_dir("sht3x", NULL); + return i2c_add_driver(&sht3x_i2c_driver); +} +module_init(sht3x_init); + +static void __exit sht3x_cleanup(void) +{ + debugfs_remove_recursive(debugfs); + i2c_del_driver(&sht3x_i2c_driver); +} +module_exit(sht3x_cleanup); MODULE_AUTHOR("David Frey "); MODULE_AUTHOR("Pascal Sachs "); -- cgit v1.2.3 From f5b75bde41f23de21d3e336e25b7c4deb47b4372 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:13:55 -0500 Subject: hwmon: (adt7x10) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202071355.40666-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7310.c | 2 +- drivers/hwmon/adt7410.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/adt7310.c b/drivers/hwmon/adt7310.c index 067865f4887a..25281739aa3b 100644 --- a/drivers/hwmon/adt7310.c +++ b/drivers/hwmon/adt7310.c @@ -124,7 +124,7 @@ static int adt7310_reg_write(void *context, unsigned int reg, unsigned int val) static const struct regmap_config adt7310_regmap_config = { .reg_bits = 8, .val_bits = 16, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = adt7310_regmap_is_volatile, .reg_read = adt7310_reg_read, .reg_write = adt7310_reg_write, diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c index fd214d9b3a89..d15f64d4b6e7 100644 --- a/drivers/hwmon/adt7410.c +++ b/drivers/hwmon/adt7410.c @@ -69,7 +69,7 @@ static const struct regmap_config adt7410_regmap_config = { .reg_bits = 8, .val_bits = 16, .max_register = ADT7X10_ID, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = adt7410_regmap_is_volatile, .reg_read = adt7410_reg_read, .reg_write = adt7410_reg_write, -- cgit v1.2.3 From 9c440cf0c68fa30dc3072b8f1755f2d91863d7d6 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:14:52 -0500 Subject: hwmon: (emc1403) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202071452.40778-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/emc1403.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index 1332e4ac078c..d370efd6f986 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -385,7 +385,7 @@ static bool emc1403_regmap_is_volatile(struct device *dev, unsigned int reg) static const struct regmap_config emc1403_regmap_config = { .reg_bits = 8, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = emc1403_regmap_is_volatile, }; -- cgit v1.2.3 From ba468d4b0b1e81067405c9f68986828ca8ede876 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:15:38 -0500 Subject: hwmon: (ina3221) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202071538.40877-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ina3221.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ina3221.c b/drivers/hwmon/ina3221.c index 5ffdc94db436..2c9530b6f192 100644 --- a/drivers/hwmon/ina3221.c +++ b/drivers/hwmon/ina3221.c @@ -762,7 +762,7 @@ static const struct regmap_config ina3221_regmap_config = { .reg_bits = 8, .val_bits = 16, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_table = &ina3221_volatile_table, }; -- cgit v1.2.3 From f8fec5f317d4aacdcea51de78fd622fad33cbba5 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:16:28 -0500 Subject: hwmon: (jc42) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202071628.40990-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/jc42.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index f958e830b23c..75dc25df0f8b 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -497,7 +497,7 @@ static const struct regmap_config jc42_regmap_config = { .writeable_reg = jc42_writable_reg, .readable_reg = jc42_readable_reg, .volatile_reg = jc42_volatile_reg, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static int jc42_probe(struct i2c_client *client) -- cgit v1.2.3 From 6c224da4d79fbb60d087423066c59e8bc8f0a44f Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:18:00 -0500 Subject: hwmon: (lm83) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202071800.41113-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 5befedca6abb..b333c9bde4e6 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -165,7 +165,7 @@ static bool lm83_regmap_is_volatile(struct device *dev, unsigned int reg) static const struct regmap_config lm83_regmap_config = { .reg_bits = 8, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = lm83_regmap_is_volatile, .reg_read = lm83_regmap_reg_read, .reg_write = lm83_regmap_reg_write, -- cgit v1.2.3 From 7a04f015d47535545a57ccf27cdc5390f17f10f0 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:19:27 -0500 Subject: hwmon: (max31760) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202071927.41213-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/max31760.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/max31760.c b/drivers/hwmon/max31760.c index 1b6f71bc61cb..127e31ca3c87 100644 --- a/drivers/hwmon/max31760.c +++ b/drivers/hwmon/max31760.c @@ -60,7 +60,7 @@ static const struct regmap_config regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = 0x5B, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = max31760_volatile_reg, }; -- cgit v1.2.3 From 23c7029f376968dbc8d72fd55e60fe2e31c2629b Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:20:07 -0500 Subject: hwmon: (nct7802) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202072007.41316-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct7802.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c index 024cff151c36..a0e664d5ebfe 100644 --- a/drivers/hwmon/nct7802.c +++ b/drivers/hwmon/nct7802.c @@ -1051,7 +1051,7 @@ static bool nct7802_regmap_is_volatile(struct device *dev, unsigned int reg) static const struct regmap_config nct7802_regmap_config = { .reg_bits = 8, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = nct7802_regmap_is_volatile, }; -- cgit v1.2.3 From 3d8e02bda1327d17363446bd7a3f536f244a5905 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:20:39 -0500 Subject: hwmon: (sch5627) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202072039.41419-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/sch5627.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c index 1891d4d75aa9..33e997b5c1f5 100644 --- a/drivers/hwmon/sch5627.c +++ b/drivers/hwmon/sch5627.c @@ -116,7 +116,7 @@ static const struct regmap_config sch5627_regmap_config = { .val_bits = 8, .wr_table = &sch5627_tunables_table, .rd_table = &sch5627_tunables_table, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, .can_sleep = true, -- cgit v1.2.3 From cac78418e2f54247ea1c141f0cfa1a3f7cbb8889 Mon Sep 17 00:00:00 2001 From: Bo Liu Date: Fri, 2 Feb 2024 02:22:35 -0500 Subject: hwmon: (tmp401) convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Bo Liu Link: https://lore.kernel.org/r/20240202072235.41614-1-liubo03@inspur.com Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 91f2314568cf..df1b45a62e80 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -256,7 +256,7 @@ static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val) static const struct regmap_config tmp401_regmap_config = { .reg_bits = 8, .val_bits = 16, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = tmp401_regmap_is_volatile, .reg_read = tmp401_reg_read, .reg_write = tmp401_reg_write, -- cgit v1.2.3 From cc804e48fef1535150f7b300b81990230b8a10f0 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Tue, 30 Jan 2024 22:06:44 +0100 Subject: dt-bindings: vendor-prefixes: add Amphenol Add vendor prefix for Amphenol (https://www.amphenol-sensors.com) Acked-by: Krzysztof Kozlowski Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240130-topic-chipcap2-v6-1-260bea05cf9b@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 1a0dc04f1db4..25158559471c 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -107,6 +107,8 @@ patternProperties: description: Amlogic, Inc. "^ampere,.*": description: Ampere Computing LLC + "^amphenol,.*": + description: Amphenol Advanced Sensors "^ampire,.*": description: Ampire Co., Ltd. "^ams,.*": -- cgit v1.2.3 From 5f85c4d10ef46a5f457a9d337159ff620a0a2191 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Tue, 30 Jan 2024 22:06:45 +0100 Subject: hwmon: (core) Add support for humidity min/max alarm Add min_alarm and max_alarm attributes for humidityX to support devices that can generate these alarms. Such attributes already exist for other magnitudes such as tempX. Tested with a ChipCap 2 temperature-humidity sensor. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240130-topic-chipcap2-v6-2-260bea05cf9b@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 2 ++ include/linux/hwmon.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 18705049ad61..3b259c425ab7 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -587,6 +587,8 @@ static const char * const hwmon_humidity_attr_templates[] = { [hwmon_humidity_fault] = "humidity%d_fault", [hwmon_humidity_rated_min] = "humidity%d_rated_min", [hwmon_humidity_rated_max] = "humidity%d_rated_max", + [hwmon_humidity_min_alarm] = "humidity%d_min_alarm", + [hwmon_humidity_max_alarm] = "humidity%d_max_alarm", }; static const char * const hwmon_fan_attr_templates[] = { diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index c7885fdce88f..edf96f249eb5 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -295,6 +295,8 @@ enum hwmon_humidity_attributes { hwmon_humidity_fault, hwmon_humidity_rated_min, hwmon_humidity_rated_max, + hwmon_humidity_min_alarm, + hwmon_humidity_max_alarm, }; #define HWMON_H_ENABLE BIT(hwmon_humidity_enable) @@ -308,6 +310,8 @@ enum hwmon_humidity_attributes { #define HWMON_H_FAULT BIT(hwmon_humidity_fault) #define HWMON_H_RATED_MIN BIT(hwmon_humidity_rated_min) #define HWMON_H_RATED_MAX BIT(hwmon_humidity_rated_max) +#define HWMON_H_MIN_ALARM BIT(hwmon_humidity_min_alarm) +#define HWMON_H_MAX_ALARM BIT(hwmon_humidity_max_alarm) enum hwmon_fan_attributes { hwmon_fan_enable, -- cgit v1.2.3 From b86d76015376a89ff99acfcd783e9ae4f4b9360d Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Tue, 30 Jan 2024 22:06:46 +0100 Subject: ABI: sysfs-class-hwmon: add descriptions for humidity min/max alarms This attributes have been recently introduced and require the corresponding ABI documentation. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240130-topic-chipcap2-v6-3-260bea05cf9b@gmail.com Signed-off-by: Guenter Roeck --- Documentation/ABI/testing/sysfs-class-hwmon | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon index 6c4e68ad4a83..cfd0d0bab483 100644 --- a/Documentation/ABI/testing/sysfs-class-hwmon +++ b/Documentation/ABI/testing/sysfs-class-hwmon @@ -977,6 +977,15 @@ Description: RW +What: /sys/class/hwmon/hwmonX/humidityY_max_alarm +Description: + Maximum humidity detection + + - 0: OK + - 1: Maximum humidity detected + + RO + What: /sys/class/hwmon/hwmonX/humidityY_max_hyst Description: Humidity hysteresis value for max limit. @@ -996,6 +1005,15 @@ Description: RW +What: /sys/class/hwmon/hwmonX/humidityY_min_alarm +Description: + Minimum humidity detection + + - 0: OK + - 1: Minimum humidity detected + + RO + What: /sys/class/hwmon/hwmonX/humidityY_min_hyst Description: Humidity hysteresis value for min limit. -- cgit v1.2.3 From 8f89ac2b9bdec95e4a05612a198b4255dce2ca8a Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Tue, 30 Jan 2024 22:06:47 +0100 Subject: dt-bindings: hwmon: Add Amphenol ChipCap 2 Add device tree bindings and an example for the ChipCap 2 humidity and temperature sensor. Reviewed-by: Conor Dooley Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240130-topic-chipcap2-v6-4-260bea05cf9b@gmail.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/amphenol,chipcap2.yaml | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml diff --git a/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml b/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml new file mode 100644 index 000000000000..17351fdbefce --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/amphenol,chipcap2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ChipCap 2 humidity and temperature iio sensor + +maintainers: + - Javier Carrasco + +description: | + Relative humidity and temperature sensor on I2C bus. + + Datasheets: + https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2 + +properties: + compatible: + oneOf: + - const: amphenol,cc2d23 + - items: + - enum: + - amphenol,cc2d23s + - amphenol,cc2d25 + - amphenol,cc2d25s + - amphenol,cc2d33 + - amphenol,cc2d33s + - amphenol,cc2d35 + - amphenol,cc2d35s + - const: amphenol,cc2d23 + + reg: + maxItems: 1 + + interrupts: + items: + - description: measurement ready indicator + - description: low humidity alarm + - description: high humidity alarm + + interrupt-names: + items: + - const: ready + - const: low + - const: high + + vdd-supply: + description: + Dedicated, controllable supply-regulator to reset the device and + enter in command mode. + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + humidity@28 { + compatible = "amphenol,cc2d23s", "amphenol,cc2d23"; + reg = <0x28>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_RISING>, + <5 IRQ_TYPE_EDGE_RISING>, + <6 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "ready", "low", "high"; + vdd-supply = <®_vdd>; + }; + }; -- cgit v1.2.3 From 3af350929e752fe72f0457033d448bff9aac695a Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Tue, 30 Jan 2024 22:06:48 +0100 Subject: hwmon: Add support for Amphenol ChipCap 2 The Amphenol ChipCap 2 is a capacitive polymer humidity and temperature sensor with an integrated EEPROM and minimum/maximum humidity alarms. All device variants offer an I2C interface and depending on the part number, two different output modes: - CC2D: digital output - CC2A: analog (PDM) output This driver adds support for the digital variant (CC2D part numbers), which includes the following part numbers: - non-sleep measurement mode (CC2D23, CC2D25, CC2D33, CC2D35) - sleep measurement mode (CC2D23S, CC2D25S, CC2D33S, CC2D35S) The Chipcap 2 EEPROM can be accessed to configure a series of parameters like the minimum/maximum humidity alarm threshold and hysteresis. The EEPROM is only accessible in the command window after a power-on reset. The default window lasts 10 ms if no Start_CM command is sent. After the command window is finished (either after the mentioned timeout of after a Start_NOM command is sent), the device enters the normal operation mode and makes a first measurement automatically. Unfortunately, the device does not provide any hardware or software reset and therefore the driver must trigger power cycles to enter the command mode. A dedicated, external regulator is required for that. This driver keeps the device off until a measurement or access to the EEPROM is required, making use of the first automatic measurement to avoid different code paths for sleep and non-sleep devices. The minimum and maximum humidity alarms are configured with two registers per alarm: one stores the alarm threshold and the other one keeps the value that turns off the alarm. The alarm signals are only updated when a measurement is carried out. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240130-topic-chipcap2-v6-5-260bea05cf9b@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/chipcap2.rst | 73 ++++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 8 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/chipcap2.c | 816 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 909 insertions(+) create mode 100644 Documentation/hwmon/chipcap2.rst create mode 100644 drivers/hwmon/chipcap2.c diff --git a/Documentation/hwmon/chipcap2.rst b/Documentation/hwmon/chipcap2.rst new file mode 100644 index 000000000000..dc165becc64c --- /dev/null +++ b/Documentation/hwmon/chipcap2.rst @@ -0,0 +1,73 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver ChipCap2 +====================== + +Supported chips: + + * Amphenol CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S + + Prefix: 'chipcap2' + + Addresses scanned: - + + Datasheet: https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2 + +Author: + + - Javier Carrasco + +Description +----------- + +This driver implements support for the Amphenol ChipCap 2, a humidity and +temperature chip family. Temperature is measured in milli degrees celsius, +relative humidity is expressed as a per cent mille. The measurement ranges +are the following: + + - Relative humidity: 0 to 100000 pcm (14-bit resolution) + - Temperature: -40000 to +125000 m°C (14-bit resolution) + +The device communicates with the I2C protocol and uses the I2C address 0x28 +by default. + +Depending on the hardware configuration, up to two humidity alarms to control +minimum and maximum values are provided. Their thresholds and hystersis can be +configured via sysfs. + +Thresholds and hysteris must be provided as a per cent mille. These values +might be truncated to match the 14-bit device resolution (6.1 pcm/LSB) + +Known Issues +------------ + +The driver does not support I2C address and command window length modification. + +sysfs-Interface +--------------- + +The following list includes the sysfs attributes that the driver always provides, +their permissions and a short description: + +=============================== ======= ======================================== +Name Perm Description +=============================== ======= ======================================== +temp1_input: RO temperature input +humidity1_input: RO humidity input +=============================== ======= ======================================== + +The following list includes the sysfs attributes that the driver may provide +depending on the hardware configuration: + +=============================== ======= ======================================== +Name Perm Description +=============================== ======= ======================================== +humidity1_min: RW humidity low limit. Measurements under + this limit trigger a humidity low alarm +humidity1_max: RW humidity high limit. Measurements above + this limit trigger a humidity high alarm +humidity1_min_hyst: RW humidity low hystersis +humidity1_max_hyst: RW humidity high hystersis +humidity1_min_alarm: RO humidity low alarm indicator +humidity1_max_alarm: RO humidity high alarm indicator +=============================== ======= ======================================== diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 9ac087dd3e78..0d12254c0f9e 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -51,6 +51,7 @@ Hardware Monitoring Kernel Drivers bel-pfe bpa-rs600 bt1-pvt + chipcap2 coretemp corsair-cpro corsair-psu diff --git a/MAINTAINERS b/MAINTAINERS index dd69696f3c0e..b00657d2536f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1098,6 +1098,14 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml F: drivers/perf/amlogic/ F: include/soc/amlogic/ +AMPHENOL CHIPCAP 2 HUMIDITY-TEMPERATURE IIO DRIVER +M: Javier Carrasco +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/amphenol,chipcap2.yaml +F: Documentation/hwmon/chipcap2.rst +F: drivers/hwmon/chipcap2.c + AMPHION VPU CODEC V4L2 DRIVER M: Ming Qian M: Zhou Peng diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index cdf228fe50e2..5c85c976d795 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -452,6 +452,16 @@ config SENSORS_BT1_PVT_ALARMS the data conversion will be periodically performed and the data will be saved in the internal driver cache. +config SENSORS_CHIPCAP2 + tristate "Amphenol ChipCap 2 relative humidity and temperature sensor" + depends on I2C + help + Say yes here to build support for the Amphenol ChipCap 2 + relative humidity and temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called chipcap2. + config SENSORS_CORSAIR_CPRO tristate "Corsair Commander Pro controller" depends on HID diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index a49704cd48a4..5da0c4ce881b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o +obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c new file mode 100644 index 000000000000..a62c507b1042 --- /dev/null +++ b/drivers/hwmon/chipcap2.c @@ -0,0 +1,816 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cc2.c - Support for the Amphenol ChipCap 2 relative humidity, temperature sensor + * + * Part numbers supported: + * CC2D23, CC2D23S, CC2D25, CC2D25S, CC2D33, CC2D33S, CC2D35, CC2D35S + * + * Author: Javier Carrasco + * + * Datasheet and application notes: + * https://www.amphenol-sensors.com/en/telaire/humidity/527-humidity-sensors/3095-chipcap-2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CC2_START_CM 0xA0 +#define CC2_START_NOM 0x80 +#define CC2_R_ALARM_H_ON 0x18 +#define CC2_R_ALARM_H_OFF 0x19 +#define CC2_R_ALARM_L_ON 0x1A +#define CC2_R_ALARM_L_OFF 0x1B +#define CC2_RW_OFFSET 0x40 +#define CC2_W_ALARM_H_ON (CC2_R_ALARM_H_ON + CC2_RW_OFFSET) +#define CC2_W_ALARM_H_OFF (CC2_R_ALARM_H_OFF + CC2_RW_OFFSET) +#define CC2_W_ALARM_L_ON (CC2_R_ALARM_L_ON + CC2_RW_OFFSET) +#define CC2_W_ALARM_L_OFF (CC2_R_ALARM_L_OFF + CC2_RW_OFFSET) + +#define CC2_STATUS_FIELD GENMASK(7, 6) +#define CC2_STATUS_VALID_DATA 0x00 +#define CC2_STATUS_STALE_DATA 0x01 +#define CC2_STATUS_CMD_MODE 0x02 + +#define CC2_RESPONSE_FIELD GENMASK(1, 0) +#define CC2_RESPONSE_BUSY 0x00 +#define CC2_RESPONSE_ACK 0x01 +#define CC2_RESPONSE_NACK 0x02 + +#define CC2_ERR_CORR_EEPROM BIT(2) +#define CC2_ERR_UNCORR_EEPROM BIT(3) +#define CC2_ERR_RAM_PARITY BIT(4) +#define CC2_ERR_CONFIG_LOAD BIT(5) + +#define CC2_EEPROM_SIZE 10 +#define CC2_EEPROM_DATA_LEN 3 +#define CC2_MEASUREMENT_DATA_LEN 4 + +#define CC2_RH_DATA_FIELD GENMASK(13, 0) + +/* ensure clean off -> on transitions */ +#define CC2_POWER_CYCLE_MS 80 + +#define CC2_STARTUP_TO_DATA_MS 55 +#define CC2_RESP_START_CM_US 100 +#define CC2_RESP_EEPROM_R_US 100 +#define CC2_RESP_EEPROM_W_MS 12 +#define CC2_STARTUP_TIME_US 1250 + +#define CC2_RH_MAX (100 * 1000U) + +#define CC2_CM_RETRIES 5 + +struct cc2_rh_alarm_info { + bool low_alarm; + bool high_alarm; + bool low_alarm_visible; + bool high_alarm_visible; +}; + +struct cc2_data { + struct cc2_rh_alarm_info rh_alarm; + struct completion complete; + struct device *hwmon; + struct i2c_client *client; + struct mutex dev_access_lock; /* device access lock */ + struct regulator *regulator; + const char *name; + int irq_ready; + int irq_low; + int irq_high; + bool process_irqs; +}; + +enum cc2_chan_addr { + CC2_CHAN_TEMP = 0, + CC2_CHAN_HUMIDITY, +}; + +/* %RH as a per cent mille from a register value */ +static long cc2_rh_convert(u16 data) +{ + unsigned long tmp = (data & CC2_RH_DATA_FIELD) * CC2_RH_MAX; + + return tmp / ((1 << 14) - 1); +} + +/* convert %RH to a register value */ +static u16 cc2_rh_to_reg(long data) +{ + return data * ((1 << 14) - 1) / CC2_RH_MAX; +} + +/* temperature in milli degrees celsius from a register value */ +static long cc2_temp_convert(u16 data) +{ + unsigned long tmp = ((data >> 2) * 165 * 1000U) / ((1 << 14) - 1); + + return tmp - 40 * 1000U; +} + +static int cc2_enable(struct cc2_data *data) +{ + int ret; + + /* exclusive regulator, check in case a disable failed */ + if (regulator_is_enabled(data->regulator)) + return 0; + + /* clear any pending completion */ + try_wait_for_completion(&data->complete); + + ret = regulator_enable(data->regulator); + if (ret < 0) + return ret; + + usleep_range(CC2_STARTUP_TIME_US, CC2_STARTUP_TIME_US + 125); + + data->process_irqs = true; + + return 0; +} + +static void cc2_disable(struct cc2_data *data) +{ + int err; + + /* ignore alarms triggered by voltage toggling when powering up */ + data->process_irqs = false; + + /* exclusive regulator, check in case an enable failed */ + if (regulator_is_enabled(data->regulator)) { + err = regulator_disable(data->regulator); + if (err) + dev_dbg(&data->client->dev, "Failed to disable device"); + } +} + +static int cc2_cmd_response_diagnostic(struct device *dev, u8 status) +{ + int resp; + + if (FIELD_GET(CC2_STATUS_FIELD, status) != CC2_STATUS_CMD_MODE) { + dev_dbg(dev, "Command sent out of command window\n"); + return -ETIMEDOUT; + } + + resp = FIELD_GET(CC2_RESPONSE_FIELD, status); + switch (resp) { + case CC2_RESPONSE_ACK: + return 0; + case CC2_RESPONSE_BUSY: + return -EBUSY; + case CC2_RESPONSE_NACK: + if (resp & CC2_ERR_CORR_EEPROM) + dev_dbg(dev, "Command failed: corrected EEPROM\n"); + if (resp & CC2_ERR_UNCORR_EEPROM) + dev_dbg(dev, "Command failed: uncorrected EEPROM\n"); + if (resp & CC2_ERR_RAM_PARITY) + dev_dbg(dev, "Command failed: RAM parity\n"); + if (resp & CC2_ERR_RAM_PARITY) + dev_dbg(dev, "Command failed: configuration error\n"); + return -ENODATA; + default: + dev_dbg(dev, "Unknown command reply\n"); + return -EINVAL; + } +} + +static int cc2_read_command_status(struct i2c_client *client) +{ + u8 status; + int ret; + + ret = i2c_master_recv(client, &status, 1); + if (ret != 1) { + ret = ret < 0 ? ret : -EIO; + return ret; + } + + return cc2_cmd_response_diagnostic(&client->dev, status); +} + +/* + * The command mode is only accessible after sending the START_CM command in the + * first 10 ms after power-up. Only in case the command window is missed, + * CC2_CM_RETRIES retries are attempted before giving up and returning an error. + */ +static int cc2_command_mode_start(struct cc2_data *data) +{ + unsigned long timeout; + int i, ret; + + for (i = 0; i < CC2_CM_RETRIES; i++) { + ret = cc2_enable(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_word_data(data->client, CC2_START_CM, 0); + if (ret < 0) + return ret; + + if (data->irq_ready > 0) { + timeout = usecs_to_jiffies(2 * CC2_RESP_START_CM_US); + ret = wait_for_completion_timeout(&data->complete, + timeout); + if (!ret) + return -ETIMEDOUT; + } else { + usleep_range(CC2_RESP_START_CM_US, + 2 * CC2_RESP_START_CM_US); + } + ret = cc2_read_command_status(data->client); + if (ret != -ETIMEDOUT || i == CC2_CM_RETRIES) + break; + + /* command window missed, prepare for a retry */ + cc2_disable(data); + msleep(CC2_POWER_CYCLE_MS); + } + + return ret; +} + +/* Sending a Start_NOM command finishes the command mode immediately with no + * reply and the device enters normal operation mode + */ +static int cc2_command_mode_finish(struct cc2_data *data) +{ + int ret; + + ret = i2c_smbus_write_word_data(data->client, CC2_START_NOM, 0); + if (ret < 0) + return ret; + + return 0; +} + +static int cc2_write_reg(struct cc2_data *data, u8 reg, u16 val) +{ + unsigned long timeout; + int ret; + + ret = cc2_command_mode_start(data); + if (ret < 0) + goto disable; + + cpu_to_be16s(&val); + ret = i2c_smbus_write_word_data(data->client, reg, val); + if (ret < 0) + goto disable; + + if (data->irq_ready > 0) { + timeout = msecs_to_jiffies(2 * CC2_RESP_EEPROM_W_MS); + ret = wait_for_completion_timeout(&data->complete, timeout); + if (!ret) { + ret = -ETIMEDOUT; + goto disable; + } + } else { + msleep(CC2_RESP_EEPROM_W_MS); + } + + ret = cc2_read_command_status(data->client); + +disable: + cc2_disable(data); + + return ret; +} + +static int cc2_read_reg(struct cc2_data *data, u8 reg, u16 *val) +{ + u8 buf[CC2_EEPROM_DATA_LEN]; + unsigned long timeout; + int ret; + + ret = cc2_command_mode_start(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_word_data(data->client, reg, 0); + if (ret < 0) + return ret; + + if (data->irq_ready > 0) { + timeout = usecs_to_jiffies(2 * CC2_RESP_EEPROM_R_US); + ret = wait_for_completion_timeout(&data->complete, timeout); + if (!ret) + return -ETIMEDOUT; + + } else { + usleep_range(CC2_RESP_EEPROM_R_US, CC2_RESP_EEPROM_R_US + 10); + } + ret = i2c_master_recv(data->client, buf, CC2_EEPROM_DATA_LEN); + if (ret != CC2_EEPROM_DATA_LEN) + return ret < 0 ? ret : -EIO; + + *val = be16_to_cpup((__be16 *)&buf[1]); + + return cc2_read_command_status(data->client); +} + +static int cc2_get_reg_val(struct cc2_data *data, u8 reg, long *val) +{ + u16 reg_val; + int ret; + + ret = cc2_read_reg(data, reg, ®_val); + *val = cc2_rh_convert(reg_val); + cc2_disable(data); + + return ret; +} + +static int cc2_data_fetch(struct i2c_client *client, + enum hwmon_sensor_types type, long *val) +{ + u8 data[CC2_MEASUREMENT_DATA_LEN]; + u8 status; + int ret; + + ret = i2c_master_recv(client, data, CC2_MEASUREMENT_DATA_LEN); + if (ret != CC2_MEASUREMENT_DATA_LEN) { + ret = ret < 0 ? ret : -EIO; + return ret; + } + status = FIELD_GET(CC2_STATUS_FIELD, data[0]); + if (status == CC2_STATUS_STALE_DATA) + return -EBUSY; + + if (status != CC2_STATUS_VALID_DATA) + return -EIO; + + switch (type) { + case hwmon_humidity: + *val = cc2_rh_convert(be16_to_cpup((__be16 *)&data[0])); + break; + case hwmon_temp: + *val = cc2_temp_convert(be16_to_cpup((__be16 *)&data[2])); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cc2_read_measurement(struct cc2_data *data, + enum hwmon_sensor_types type, long *val) +{ + unsigned long timeout; + int ret; + + if (data->irq_ready > 0) { + timeout = msecs_to_jiffies(CC2_STARTUP_TO_DATA_MS * 2); + ret = wait_for_completion_timeout(&data->complete, timeout); + if (!ret) + return -ETIMEDOUT; + + } else { + msleep(CC2_STARTUP_TO_DATA_MS); + } + + ret = cc2_data_fetch(data->client, type, val); + + return ret; +} + +/* + * A measurement requires enabling the device, waiting for the automatic + * measurement to finish, reading the measurement data and disabling the device + * again. + */ +static int cc2_measurement(struct cc2_data *data, enum hwmon_sensor_types type, + long *val) +{ + int ret; + + ret = cc2_enable(data); + if (ret) + return ret; + + ret = cc2_read_measurement(data, type, val); + + cc2_disable(data); + + return ret; +} + +/* + * In order to check alarm status, the corresponding ALARM_OFF (hysteresis) + * register must be read and a new measurement must be carried out to trigger + * the alarm signals. Given that the device carries out a measurement after + * exiting the command mode, there is no need to force two power-up sequences. + * Instead, a NOM command is sent and the device is disabled after the + * measurement is read. + */ +static int cc2_read_hyst_and_measure(struct cc2_data *data, u8 reg, + long *hyst, long *measurement) +{ + u16 reg_val; + int ret; + + ret = cc2_read_reg(data, reg, ®_val); + if (ret) + goto disable; + + *hyst = cc2_rh_convert(reg_val); + + ret = cc2_command_mode_finish(data); + if (ret) + goto disable; + + ret = cc2_read_measurement(data, hwmon_humidity, measurement); + +disable: + cc2_disable(data); + + return ret; +} + +static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct cc2_data *cc2 = data; + + switch (type) { + case hwmon_humidity: + switch (attr) { + case hwmon_humidity_input: + return 0444; + case hwmon_humidity_min_alarm: + return cc2->rh_alarm.low_alarm_visible ? 0444 : 0; + case hwmon_humidity_max_alarm: + return cc2->rh_alarm.high_alarm_visible ? 0444 : 0; + case hwmon_humidity_min: + case hwmon_humidity_min_hyst: + return cc2->rh_alarm.low_alarm_visible ? 0644 : 0; + case hwmon_humidity_max: + case hwmon_humidity_max_hyst: + return cc2->rh_alarm.high_alarm_visible ? 0644 : 0; + default: + return 0; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return 0444; + default: + return 0; + } + default: + break; + } + + return 0; +} + +static irqreturn_t cc2_ready_interrupt(int irq, void *data) +{ + struct cc2_data *cc2 = data; + + if (cc2->process_irqs) + complete(&cc2->complete); + + return IRQ_HANDLED; +} + +static irqreturn_t cc2_low_interrupt(int irq, void *data) +{ + struct cc2_data *cc2 = data; + + if (cc2->process_irqs) { + hwmon_notify_event(cc2->hwmon, hwmon_humidity, + hwmon_humidity_min_alarm, CC2_CHAN_HUMIDITY); + cc2->rh_alarm.low_alarm = true; + } + + return IRQ_HANDLED; +} + +static irqreturn_t cc2_high_interrupt(int irq, void *data) +{ + struct cc2_data *cc2 = data; + + if (cc2->process_irqs) { + hwmon_notify_event(cc2->hwmon, hwmon_humidity, + hwmon_humidity_max_alarm, CC2_CHAN_HUMIDITY); + cc2->rh_alarm.high_alarm = true; + } + + return IRQ_HANDLED; +} + +static int cc2_humidity_min_alarm_status(struct cc2_data *data, long *val) +{ + long measurement, min_hyst; + int ret; + + ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_L_OFF, &min_hyst, + &measurement); + if (ret < 0) + return ret; + + if (data->rh_alarm.low_alarm) { + *val = (measurement < min_hyst) ? 1 : 0; + data->rh_alarm.low_alarm = *val; + } else { + *val = 0; + } + + return 0; +} + +static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) +{ + long measurement, max_hyst; + int ret; + + ret = cc2_read_hyst_and_measure(data, CC2_R_ALARM_H_OFF, &max_hyst, + &measurement); + if (ret < 0) + return ret; + + if (data->rh_alarm.high_alarm) { + *val = (measurement > max_hyst) ? 1 : 0; + data->rh_alarm.high_alarm = *val; + } else { + *val = 0; + } + + return 0; +} + +static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct cc2_data *data = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&data->dev_access_lock); + + switch (type) { + case hwmon_temp: + ret = cc2_measurement(data, type, val); + break; + case hwmon_humidity: + switch (attr) { + case hwmon_humidity_input: + ret = cc2_measurement(data, type, val); + break; + case hwmon_humidity_min: + ret = cc2_get_reg_val(data, CC2_R_ALARM_L_ON, val); + break; + case hwmon_humidity_min_hyst: + ret = cc2_get_reg_val(data, CC2_R_ALARM_L_OFF, val); + break; + case hwmon_humidity_max: + ret = cc2_get_reg_val(data, CC2_R_ALARM_H_ON, val); + break; + case hwmon_humidity_max_hyst: + ret = cc2_get_reg_val(data, CC2_R_ALARM_H_OFF, val); + break; + case hwmon_humidity_min_alarm: + ret = cc2_humidity_min_alarm_status(data, val); + break; + case hwmon_humidity_max_alarm: + ret = cc2_humidity_max_alarm_status(data, val); + break; + default: + ret = -EOPNOTSUPP; + } + break; + default: + ret = -EOPNOTSUPP; + } + + mutex_unlock(&data->dev_access_lock); + + return ret; +} + +static int cc2_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct cc2_data *data = dev_get_drvdata(dev); + int ret; + u16 arg; + u8 cmd; + + if (type != hwmon_humidity) + return -EOPNOTSUPP; + + if (val < 0 || val > CC2_RH_MAX) + return -EINVAL; + + mutex_lock(&data->dev_access_lock); + + switch (attr) { + case hwmon_humidity_min: + cmd = CC2_W_ALARM_L_ON; + arg = cc2_rh_to_reg(val); + ret = cc2_write_reg(data, cmd, arg); + break; + + case hwmon_humidity_min_hyst: + cmd = CC2_W_ALARM_L_OFF; + arg = cc2_rh_to_reg(val); + ret = cc2_write_reg(data, cmd, arg); + break; + + case hwmon_humidity_max: + cmd = CC2_W_ALARM_H_ON; + arg = cc2_rh_to_reg(val); + ret = cc2_write_reg(data, cmd, arg); + break; + + case hwmon_humidity_max_hyst: + cmd = CC2_W_ALARM_H_OFF; + arg = cc2_rh_to_reg(val); + ret = cc2_write_reg(data, cmd, arg); + break; + + default: + ret = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->dev_access_lock); + + return ret; +} + +static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev) +{ + int ret = 0; + + data->irq_ready = fwnode_irq_get_byname(dev_fwnode(dev), "ready"); + if (data->irq_ready > 0) { + init_completion(&data->complete); + ret = devm_request_threaded_irq(dev, data->irq_ready, NULL, + cc2_ready_interrupt, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING, + dev_name(dev), data); + } + + return ret; +} + +static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) +{ + int ret; + + data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low"); + if (data->irq_low > 0) { + ret = devm_request_threaded_irq(dev, data->irq_low, NULL, + cc2_low_interrupt, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING, + dev_name(dev), data); + if (!ret) + data->rh_alarm.low_alarm_visible = true; + } + + data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high"); + if (data->irq_high > 0) { + ret = devm_request_threaded_irq(dev, data->irq_high, NULL, + cc2_high_interrupt, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING, + dev_name(dev), data); + if (!ret) + data->rh_alarm.high_alarm_visible = true; + } + + return ret; +} + +static const struct hwmon_channel_info *cc2_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX | + HWMON_H_MIN_HYST | HWMON_H_MAX_HYST | + HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM), + NULL +}; + +static const struct hwmon_ops cc2_hwmon_ops = { + .is_visible = cc2_is_visible, + .read = cc2_read, + .write = cc2_write, +}; + +static const struct hwmon_chip_info cc2_chip_info = { + .ops = &cc2_hwmon_ops, + .info = cc2_info, +}; + +static int cc2_probe(struct i2c_client *client) +{ + struct cc2_data *data; + struct device *dev = &client->dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + + mutex_init(&data->dev_access_lock); + + data->client = client; + + data->regulator = devm_regulator_get_exclusive(dev, "vdd"); + if (IS_ERR(data->regulator)) { + dev_err_probe(dev, PTR_ERR(data->regulator), + "Failed to get regulator\n"); + return PTR_ERR(data->regulator); + } + + ret = cc2_request_ready_irq(data, dev); + if (ret) { + dev_err_probe(dev, ret, "Failed to request ready irq\n"); + return ret; + } + + ret = cc2_request_alarm_irqs(data, dev); + if (ret) { + dev_err_probe(dev, ret, "Failed to request alarm irqs\n"); + goto disable; + } + + data->hwmon = devm_hwmon_device_register_with_info(dev, client->name, + data, &cc2_chip_info, + NULL); + if (IS_ERR(data->hwmon)) { + dev_err_probe(dev, PTR_ERR(data->hwmon), + "Failed to register hwmon device\n"); + ret = PTR_ERR(data->hwmon); + } + +disable: + cc2_disable(data); + + return ret; +} + +static void cc2_remove(struct i2c_client *client) +{ + struct cc2_data *data = i2c_get_clientdata(client); + + cc2_disable(data); +} + +static const struct i2c_device_id cc2_id[] = { + { "cc2d23" }, + { "cc2d23s" }, + { "cc2d25" }, + { "cc2d25s" }, + { "cc2d33" }, + { "cc2d33s" }, + { "cc2d35" }, + { "cc2d35s" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cc2_id); + +static const struct of_device_id cc2_of_match[] = { + { .compatible = "amphenol,cc2d23" }, + { .compatible = "amphenol,cc2d23s" }, + { .compatible = "amphenol,cc2d25" }, + { .compatible = "amphenol,cc2d25s" }, + { .compatible = "amphenol,cc2d33" }, + { .compatible = "amphenol,cc2d33s" }, + { .compatible = "amphenol,cc2d35" }, + { .compatible = "amphenol,cc2d35s" }, + { }, +}; +MODULE_DEVICE_TABLE(of, cc2_of_match); + +static struct i2c_driver cc2_driver = { + .driver = { + .name = "cc2d23", + .of_match_table = cc2_of_match, + }, + .probe = cc2_probe, + .remove = cc2_remove, + .id_table = cc2_id, +}; +module_i2c_driver(cc2_driver); + +MODULE_AUTHOR("Javier Carrasco "); +MODULE_DESCRIPTION("Amphenol ChipCap 2 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 2948b88a5ba4e650e9b37705272e2a17014a6edb Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Mon, 15 Jan 2024 18:05:16 +0800 Subject: dt-bindings: vendor-prefixes: add asteralabs Add vendor prefix for Astera Labs, Inc. https://www.asteralabs.com Signed-off-by: Cosmo Chou Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20240115100518.2887549-2-chou.cosmo@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 25158559471c..c734ea4de428 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -161,6 +161,8 @@ patternProperties: description: ASPEED Technology Inc. "^asrock,.*": description: ASRock Inc. + "^asteralabs,.*": + description: Astera Labs, Inc. "^asus,.*": description: AsusTek Computer Inc. "^atheros,.*": -- cgit v1.2.3 From 684a28759f4c8bf046532615a029075559d1f873 Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Mon, 15 Jan 2024 18:05:17 +0800 Subject: dt-bindings: trivial-devices: add Astera Labs PT5161L Add dt-bindings for pt5161l temperature monitoring. Signed-off-by: Cosmo Chou Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20240115100518.2887549-3-chou.cosmo@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 088b23ed2ae6..842eb65e4c03 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -47,6 +47,8 @@ properties: - adi,lt7182s # AMS iAQ-Core VOC Sensor - ams,iaq-core + # Temperature monitoring of Astera Labs PT5161L PCIe retimer + - asteralabs,pt5161l # i2c serial eeprom (24cxx) - at,24c08 # ATSHA204 - i2c h/w symmetric crypto module -- cgit v1.2.3 From f3b4b146eb107bda47ee4a8b0927699f962e8a2f Mon Sep 17 00:00:00 2001 From: Aleksa Savic Date: Mon, 29 Jan 2024 12:19:28 +0100 Subject: hwmon: Add driver for NZXT Kraken X and Z series AIO CPU coolers This driver enables hardware monitoring support for NZXT Kraken X53/X63/X73 and Z53/Z63/Z73 all-in-one CPU liquid coolers. All models expose liquid temperature and pump speed (in RPM), as well as PWM control (natively only through a temp-PWM curve, but the driver also emulates fixed PWM control on top of that). The Z-series models additionally expose the speed and duty of an optionally connected fan, with the same PWM control capabilities. Pump and fan duty control mode can be set through pwm[1-2]_enable, where 1 is for the manual control mode and 2 is for the liquid temp to PWM curve mode. Writing a 0 disables control of the channel through the driver after setting its duty to 100%. As it is not possible to query the device for the active mode, the driver keeps track of it. The temperature of the curves relates to the fixed [20-59] C range, per device limitations, and correlating to the detected liquid temperature. Only PWM values (ranging from 0-255) can be set. The addressable RGB LEDs and LCD screen, included only on Z-series models, are not supported in this driver. Co-developed-by: Jonas Malaco Signed-off-by: Jonas Malaco Co-developed-by: Yury Zhuravlev Signed-off-by: Yury Zhuravlev Signed-off-by: Aleksa Savic Link: https://lore.kernel.org/r/20240129111932.368232-1-savicaleksa83@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/nzxt-kraken3.rst | 74 +++ MAINTAINERS | 8 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/nzxt-kraken3.c | 1008 ++++++++++++++++++++++++++++++++++ 6 files changed, 1102 insertions(+) create mode 100644 Documentation/hwmon/nzxt-kraken3.rst create mode 100644 drivers/hwmon/nzxt-kraken3.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 0d12254c0f9e..6f8a4a7524e8 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -174,6 +174,7 @@ Hardware Monitoring Kernel Drivers nsa320 ntc_thermistor nzxt-kraken2 + nzxt-kraken3 nzxt-smart2 occ oxp-sensors diff --git a/Documentation/hwmon/nzxt-kraken3.rst b/Documentation/hwmon/nzxt-kraken3.rst new file mode 100644 index 000000000000..90fd9dec15ff --- /dev/null +++ b/Documentation/hwmon/nzxt-kraken3.rst @@ -0,0 +1,74 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver nzxt-kraken3 +========================== + +Supported devices: + +* NZXT Kraken X53 +* NZXT Kraken X63 +* NZXT Kraken X73 +* NZXT Kraken Z53 +* NZXT Kraken Z63 +* NZXT Kraken Z73 + +Author: Jonas Malaco, Aleksa Savic + +Description +----------- + +This driver enables hardware monitoring support for NZXT Kraken X53/X63/X73 and +Z53/Z63/Z73 all-in-one CPU liquid coolers. All models expose liquid temperature +and pump speed (in RPM), as well as PWM control (either as a fixed value +or through a temp-PWM curve). The Z-series models additionally expose the speed +and duty of an optionally connected fan, with the same PWM control capabilities. + +Pump and fan duty control mode can be set through pwm[1-2]_enable, where 1 is +for the manual control mode and 2 is for the liquid temp to PWM curve mode. +Writing a 0 disables control of the channel through the driver after setting its +duty to 100%. + +The temperature of the curves relates to the fixed [20-59] range, correlating to +the detected liquid temperature. Only PWM values (ranging from 0-255) can be set. +If in curve mode, setting point values should be done in moderation - the devices +require complete curves to be sent for each change; they can lock up or discard +the changes if they are too numerous at once. Suggestion is to set them while +in an another mode, and then apply them by switching to curve. + +The devices can report if they are faulty. The driver supports that situation +and will issue a warning. This can also happen when the USB cable is connected, +but SATA power is not. + +The addressable RGB LEDs and LCD screen (only on Z-series models) are not +supported in this driver, but can be controlled through existing userspace tools, +such as `liquidctl`_. + +.. _liquidctl: https://github.com/liquidctl/liquidctl + +Usage Notes +----------- + +As these are USB HIDs, the driver can be loaded automatically by the kernel and +supports hot swapping. + +Possible pwm_enable values are: + +====== ========================================================================== +0 Set fan to 100% +1 Direct PWM mode (applies value in corresponding PWM entry) +2 Curve control mode (applies the temp-PWM duty curve based on coolant temp) +====== ========================================================================== + +Sysfs entries +------------- + +============================== ================================================================ +fan1_input Pump speed (in rpm) +fan2_input Fan speed (in rpm) +temp1_input Coolant temperature (in millidegrees Celsius) +pwm1 Pump duty (value between 0-255) +pwm1_enable Pump duty control mode (0: disabled, 1: manual, 2: curve) +pwm2 Fan duty (value between 0-255) +pwm2_enable Fan duty control mode (0: disabled, 1: manual, 2: curve) +temp[1-2]_auto_point[1-40]_pwm Temp-PWM duty curves (for pump and fan), related to coolant temp +============================== ================================================================ diff --git a/MAINTAINERS b/MAINTAINERS index b00657d2536f..b9906f88c1b7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15840,6 +15840,14 @@ S: Maintained F: Documentation/hwmon/nzxt-kraken2.rst F: drivers/hwmon/nzxt-kraken2.c +NZXT-KRAKEN3 HARDWARE MONITORING DRIVER +M: Jonas Malaco +M: Aleksa Savic +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/nzxt-kraken3.rst +F: drivers/hwmon/nzxt-kraken3.c + NZXT-SMART2 HARDWARE MONITORING DRIVER M: Aleksandr Mezin L: linux-hwmon@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5c85c976d795..3904bb297d61 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1695,6 +1695,16 @@ config SENSORS_NZXT_KRAKEN2 This driver can also be built as a module. If so, the module will be called nzxt-kraken2. +config SENSORS_NZXT_KRAKEN3 + tristate "NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers" + depends on USB_HID + help + If you say yes here you get support for hardware monitoring for the + NZXT Kraken X53/X63/X73, Z53/Z63/Z73 all-in-one CPU liquid coolers. + + This driver can also be built as a module. If so, the module + will be called nzxt-kraken3. + config SENSORS_NZXT_SMART2 tristate "NZXT RGB & Fan Controller/Smart Device v2" depends on USB_HID diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5da0c4ce881b..76e6dfef9f24 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -175,6 +175,7 @@ obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o +obj-$(CONFIG_SENSORS_NZXT_KRAKEN3) += nzxt-kraken3.o obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o obj-$(CONFIG_SENSORS_OXP) += oxp-sensors.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c new file mode 100644 index 000000000000..5806a3f32bcb --- /dev/null +++ b/drivers/hwmon/nzxt-kraken3.c @@ -0,0 +1,1008 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hwmon driver for NZXT Kraken X53/X63/X73 and Z53/Z63/Z73 all in one coolers. + * X53 and Z53 in code refer to all models in their respective series (shortened + * for brevity). + * + * Copyright 2021 Jonas Malaco + * Copyright 2022 Aleksa Savic + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_VENDOR_ID_NZXT 0x1e71 +#define USB_PRODUCT_ID_X53 0x2007 +#define USB_PRODUCT_ID_X53_SECOND 0x2014 +#define USB_PRODUCT_ID_Z53 0x3008 + +enum kinds { X53, Z53 } __packed; +enum pwm_enable { off, manual, curve } __packed; + +static const char *const kraken3_device_names[] = { + [X53] = "x53", + [Z53] = "z53", +}; + +#define DRIVER_NAME "nzxt_kraken3" +#define STATUS_REPORT_ID 0x75 +#define FIRMWARE_REPORT_ID 0x11 +#define STATUS_VALIDITY 2000 /* In ms, equivalent to period of four status reports */ +#define CUSTOM_CURVE_POINTS 40 /* For temps from 20C to 59C (critical temp) */ +#define PUMP_DUTY_MIN 20 /* In percent */ + +/* Sensor report offsets for Kraken X53 and Z53 */ +#define TEMP_SENSOR_START_OFFSET 15 +#define TEMP_SENSOR_END_OFFSET 16 +#define PUMP_SPEED_OFFSET 17 +#define PUMP_DUTY_OFFSET 19 + +/* Firmware version report offset for Kraken X53 and Z53 */ +#define FIRMWARE_VERSION_OFFSET 17 + +/* Sensor report offsets for Kraken Z53 */ +#define Z53_FAN_SPEED_OFFSET 23 +#define Z53_FAN_DUTY_OFFSET 25 + +/* Report offsets for control commands for Kraken X53 and Z53 */ +#define SET_DUTY_ID_OFFSET 1 + +/* Control commands and their lengths for Kraken X53 and Z53 */ + +/* Last byte sets the report interval at 0.5s */ +static const u8 set_interval_cmd[] = { 0x70, 0x02, 0x01, 0xB8, 1 }; +static const u8 finish_init_cmd[] = { 0x70, 0x01 }; +static const u8 __maybe_unused get_fw_version_cmd[] = { 0x10, 0x01 }; +static const u8 set_pump_duty_cmd_header[] = { 0x72, 0x00, 0x00, 0x00 }; +static const u8 z53_get_status_cmd[] = { 0x74, 0x01 }; + +#define SET_INTERVAL_CMD_LENGTH 5 +#define FINISH_INIT_CMD_LENGTH 2 +#define GET_FW_VERSION_CMD_LENGTH 2 +#define MAX_REPORT_LENGTH 64 +#define MIN_REPORT_LENGTH 20 +#define SET_CURVE_DUTY_CMD_HEADER_LENGTH 4 +/* 4 byte header and 40 duty offsets */ +#define SET_CURVE_DUTY_CMD_LENGTH (4 + 40) +#define Z53_GET_STATUS_CMD_LENGTH 2 + +static const char *const kraken3_temp_label[] = { + "Coolant temp", +}; + +static const char *const kraken3_fan_label[] = { + "Pump speed", + "Fan speed" +}; + +struct kraken3_channel_info { + enum pwm_enable mode; + + /* Both values are PWM */ + u16 reported_duty; + u16 fixed_duty; /* Manually set fixed duty */ + + u8 pwm_points[CUSTOM_CURVE_POINTS]; +}; + +struct kraken3_data { + struct hid_device *hdev; + struct device *hwmon_dev; + struct dentry *debugfs; + struct mutex buffer_lock; /* For locking access to buffer */ + struct mutex z53_status_request_lock; + struct completion fw_version_processed; + /* + * For X53 devices, tracks whether an initial (one) sensor report was received to + * make fancontrol not bail outright. For Z53 devices, whether a status report + * was processed after requesting one. + */ + struct completion status_report_processed; + /* For locking the above completion */ + spinlock_t status_completion_lock; + + u8 *buffer; + struct kraken3_channel_info channel_info[2]; /* Pump and fan */ + bool is_device_faulty; + + /* Sensor values */ + s32 temp_input[1]; + u16 fan_input[2]; + + enum kinds kind; + u8 firmware_version[3]; + + unsigned long updated; /* jiffies */ +}; + +static umode_t kraken3_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct kraken3_data *priv = data; + + switch (type) { + case hwmon_temp: + if (channel < 1) + return 0444; + break; + case hwmon_fan: + switch (priv->kind) { + case X53: + /* Just the pump */ + if (channel < 1) + return 0444; + break; + case Z53: + /* Pump and fan */ + if (channel < 2) + return 0444; + break; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + case hwmon_pwm_input: + switch (priv->kind) { + case X53: + /* Just the pump */ + if (channel < 1) + return 0644; + break; + case Z53: + /* Pump and fan */ + if (channel < 2) + return 0644; + break; + default: + break; + } + break; + default: + break; + } + break; + default: + break; + } + + return 0; +} + +/* + * Writes the command to the device with the rest of the report (up to 64 bytes) filled + * with zeroes. + */ +static int kraken3_write_expanded(struct kraken3_data *priv, const u8 *cmd, int cmd_length) +{ + int ret; + + mutex_lock(&priv->buffer_lock); + + memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00); + ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH); + + mutex_unlock(&priv->buffer_lock); + return ret; +} + +static int kraken3_percent_to_pwm(long val) +{ + return DIV_ROUND_CLOSEST(val * 255, 100); +} + +static int kraken3_pwm_to_percent(long val, int channel) +{ + int percent_value; + + if (val < 0 || val > 255) + return -EINVAL; + + percent_value = DIV_ROUND_CLOSEST(val * 100, 255); + + /* Bring up pump duty to min value if needed */ + if (channel == 0 && percent_value < PUMP_DUTY_MIN) + percent_value = PUMP_DUTY_MIN; + + return percent_value; +} + +static int kraken3_read_x53(struct kraken3_data *priv) +{ + int ret; + + if (completion_done(&priv->status_report_processed)) + /* + * We're here because data is stale. This means that sensor reports haven't + * been received for some time in kraken3_raw_event(). On X-series sensor data + * can't be manually requested, so return an error. + */ + return -ENODATA; + + /* + * Data needs to be read, but a sensor report wasn't yet received. It's usually + * fancontrol that requests data this early and it exits if it reads an error code. + * So, wait for the first report to be parsed (but up to STATUS_VALIDITY). + * This does not concern the Z series devices, because they send a sensor report + * only when requested. + */ + ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed, + msecs_to_jiffies(STATUS_VALIDITY)); + if (ret == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + /* The first sensor report was parsed on time and reading can continue */ + return 0; +} + +static int kraken3_read_z53(struct kraken3_data *priv) +{ + int ret = mutex_lock_interruptible(&priv->z53_status_request_lock); + + if (ret < 0) + return ret; + + if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { + /* Data is up to date */ + goto unlock_and_return; + } + + /* + * Disable interrupts for a moment to safely reinit the completion, + * as hidraw calls could have allowed one or more readers to complete. + */ + spin_lock_bh(&priv->status_completion_lock); + reinit_completion(&priv->status_report_processed); + spin_unlock_bh(&priv->status_completion_lock); + + /* Send command for getting status */ + ret = kraken3_write_expanded(priv, z53_get_status_cmd, Z53_GET_STATUS_CMD_LENGTH); + if (ret < 0) + goto unlock_and_return; + + /* Wait for completion from kraken3_raw_event() */ + ret = wait_for_completion_interruptible_timeout(&priv->status_report_processed, + msecs_to_jiffies(STATUS_VALIDITY)); + if (ret == 0) + ret = -ETIMEDOUT; + +unlock_and_return: + mutex_unlock(&priv->z53_status_request_lock); + if (ret < 0) + return ret; + + return 0; +} + +static int kraken3_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long *val) +{ + struct kraken3_data *priv = dev_get_drvdata(dev); + int ret; + + if (time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { + if (priv->kind == X53) + ret = kraken3_read_x53(priv); + else + ret = kraken3_read_z53(priv); + + if (ret < 0) + return ret; + + if (priv->is_device_faulty) + return -ENODATA; + } + + switch (type) { + case hwmon_temp: + *val = priv->temp_input[channel]; + break; + case hwmon_fan: + *val = priv->fan_input[channel]; + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + *val = priv->channel_info[channel].mode; + break; + case hwmon_pwm_input: + *val = priv->channel_info[channel].reported_duty; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int kraken3_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + *str = kraken3_temp_label[channel]; + break; + case hwmon_fan: + *str = kraken3_fan_label[channel]; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +/* Writes custom curve to device */ +static int kraken3_write_curve(struct kraken3_data *priv, u8 *curve_array, int channel) +{ + u8 fixed_duty_cmd[SET_CURVE_DUTY_CMD_LENGTH]; + int ret; + + /* Copy command header */ + memcpy(fixed_duty_cmd, set_pump_duty_cmd_header, SET_CURVE_DUTY_CMD_HEADER_LENGTH); + + /* Set the correct ID for writing pump/fan duty (0x01 or 0x02, respectively) */ + fixed_duty_cmd[SET_DUTY_ID_OFFSET] = channel + 1; + + /* Copy curve to command */ + memcpy(fixed_duty_cmd + SET_CURVE_DUTY_CMD_HEADER_LENGTH, curve_array, CUSTOM_CURVE_POINTS); + + ret = kraken3_write_expanded(priv, fixed_duty_cmd, SET_CURVE_DUTY_CMD_LENGTH); + return ret; +} + +static int kraken3_write_fixed_duty(struct kraken3_data *priv, long val, int channel) +{ + u8 fixed_curve_points[CUSTOM_CURVE_POINTS]; + int ret, percent_val, i; + + percent_val = kraken3_pwm_to_percent(val, channel); + if (percent_val < 0) + return percent_val; + + /* + * The devices can only control the duty through a curve. + * Since we're setting a fixed duty here, fill the whole curve + * (ranging from 20C to 59C) with the same duty, except for + * the last point, the critical temperature, where it's maxed + * out for safety. + */ + + /* Fill the custom curve with the fixed value we're setting */ + for (i = 0; i < CUSTOM_CURVE_POINTS - 1; i++) + fixed_curve_points[i] = percent_val; + + /* Force duty to 100% at critical temp */ + fixed_curve_points[CUSTOM_CURVE_POINTS - 1] = 100; + + /* Write the fixed duty curve to the device */ + ret = kraken3_write_curve(priv, fixed_curve_points, channel); + return ret; +} + +static int kraken3_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + struct kraken3_data *priv = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + /* Remember the last set fixed duty for channel */ + priv->channel_info[channel].fixed_duty = val; + + if (priv->channel_info[channel].mode == manual) { + ret = kraken3_write_fixed_duty(priv, val, channel); + if (ret < 0) + return ret; + + /* + * Lock onto this value and report it until next interrupt status + * report is received, so userspace tools can continue to work. + */ + priv->channel_info[channel].reported_duty = val; + } + break; + case hwmon_pwm_enable: + if (val < 0 || val > 2) + return -EINVAL; + + switch (val) { + case 0: + /* Set channel to 100%, direct duty value */ + ret = kraken3_write_fixed_duty(priv, 255, channel); + if (ret < 0) + return ret; + + /* We don't control anything anymore */ + priv->channel_info[channel].mode = off; + break; + case 1: + /* Apply the last known direct duty value */ + ret = + kraken3_write_fixed_duty(priv, + priv->channel_info[channel].fixed_duty, + channel); + if (ret < 0) + return ret; + + priv->channel_info[channel].mode = manual; + break; + case 2: + /* Apply the curve and note as enabled */ + ret = + kraken3_write_curve(priv, + priv->channel_info[channel].pwm_points, + channel); + if (ret < 0) + return ret; + + priv->channel_info[channel].mode = curve; + break; + default: + break; + } + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static ssize_t kraken3_fan_curve_pwm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr); + struct kraken3_data *priv = dev_get_drvdata(dev); + long val; + int ret; + + if (kstrtol(buf, 10, &val) < 0) + return -EINVAL; + + val = kraken3_pwm_to_percent(val, dev_attr->nr); + if (val < 0) + return val; + + priv->channel_info[dev_attr->nr].pwm_points[dev_attr->index] = val; + + if (priv->channel_info[dev_attr->nr].mode == curve) { + /* Apply the curve */ + ret = + kraken3_write_curve(priv, + priv->channel_info[dev_attr->nr].pwm_points, dev_attr->nr); + if (ret < 0) + return ret; + } + + return count; +} + +static umode_t kraken3_curve_props_are_visible(struct kobject *kobj, struct attribute *attr, + int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct kraken3_data *priv = dev_get_drvdata(dev); + + /* Only Z53 has the fan curve */ + if (index >= CUSTOM_CURVE_POINTS && priv->kind != Z53) + return 0; + + return attr->mode; +} + +/* Custom pump curve from 20C to 59C (critical temp) */ +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point1_pwm, kraken3_fan_curve_pwm, 0, 0); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point2_pwm, kraken3_fan_curve_pwm, 0, 1); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point3_pwm, kraken3_fan_curve_pwm, 0, 2); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point4_pwm, kraken3_fan_curve_pwm, 0, 3); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point5_pwm, kraken3_fan_curve_pwm, 0, 4); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point6_pwm, kraken3_fan_curve_pwm, 0, 5); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point7_pwm, kraken3_fan_curve_pwm, 0, 6); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point8_pwm, kraken3_fan_curve_pwm, 0, 7); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point9_pwm, kraken3_fan_curve_pwm, 0, 8); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point10_pwm, kraken3_fan_curve_pwm, 0, 9); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point11_pwm, kraken3_fan_curve_pwm, 0, 10); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point12_pwm, kraken3_fan_curve_pwm, 0, 11); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point13_pwm, kraken3_fan_curve_pwm, 0, 12); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point14_pwm, kraken3_fan_curve_pwm, 0, 13); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point15_pwm, kraken3_fan_curve_pwm, 0, 14); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point16_pwm, kraken3_fan_curve_pwm, 0, 15); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point17_pwm, kraken3_fan_curve_pwm, 0, 16); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point18_pwm, kraken3_fan_curve_pwm, 0, 17); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point19_pwm, kraken3_fan_curve_pwm, 0, 18); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point20_pwm, kraken3_fan_curve_pwm, 0, 19); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point21_pwm, kraken3_fan_curve_pwm, 0, 20); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point22_pwm, kraken3_fan_curve_pwm, 0, 21); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point23_pwm, kraken3_fan_curve_pwm, 0, 22); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point24_pwm, kraken3_fan_curve_pwm, 0, 23); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point25_pwm, kraken3_fan_curve_pwm, 0, 24); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point26_pwm, kraken3_fan_curve_pwm, 0, 25); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point27_pwm, kraken3_fan_curve_pwm, 0, 26); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point28_pwm, kraken3_fan_curve_pwm, 0, 27); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point29_pwm, kraken3_fan_curve_pwm, 0, 28); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point30_pwm, kraken3_fan_curve_pwm, 0, 29); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point31_pwm, kraken3_fan_curve_pwm, 0, 30); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point32_pwm, kraken3_fan_curve_pwm, 0, 31); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point33_pwm, kraken3_fan_curve_pwm, 0, 32); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point34_pwm, kraken3_fan_curve_pwm, 0, 33); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point35_pwm, kraken3_fan_curve_pwm, 0, 34); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point36_pwm, kraken3_fan_curve_pwm, 0, 35); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point37_pwm, kraken3_fan_curve_pwm, 0, 36); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point38_pwm, kraken3_fan_curve_pwm, 0, 37); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point39_pwm, kraken3_fan_curve_pwm, 0, 38); +static SENSOR_DEVICE_ATTR_2_WO(temp1_auto_point40_pwm, kraken3_fan_curve_pwm, 0, 39); + +/* Custom fan curve from 20C to 59C (critical temp) */ +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point1_pwm, kraken3_fan_curve_pwm, 1, 0); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point2_pwm, kraken3_fan_curve_pwm, 1, 1); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point3_pwm, kraken3_fan_curve_pwm, 1, 2); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point4_pwm, kraken3_fan_curve_pwm, 1, 3); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point5_pwm, kraken3_fan_curve_pwm, 1, 4); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point6_pwm, kraken3_fan_curve_pwm, 1, 5); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point7_pwm, kraken3_fan_curve_pwm, 1, 6); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point8_pwm, kraken3_fan_curve_pwm, 1, 7); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point9_pwm, kraken3_fan_curve_pwm, 1, 8); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point10_pwm, kraken3_fan_curve_pwm, 1, 9); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point11_pwm, kraken3_fan_curve_pwm, 1, 10); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point12_pwm, kraken3_fan_curve_pwm, 1, 11); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point13_pwm, kraken3_fan_curve_pwm, 1, 12); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point14_pwm, kraken3_fan_curve_pwm, 1, 13); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point15_pwm, kraken3_fan_curve_pwm, 1, 14); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point16_pwm, kraken3_fan_curve_pwm, 1, 15); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point17_pwm, kraken3_fan_curve_pwm, 1, 16); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point18_pwm, kraken3_fan_curve_pwm, 1, 17); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point19_pwm, kraken3_fan_curve_pwm, 1, 18); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point20_pwm, kraken3_fan_curve_pwm, 1, 19); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point21_pwm, kraken3_fan_curve_pwm, 1, 20); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point22_pwm, kraken3_fan_curve_pwm, 1, 21); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point23_pwm, kraken3_fan_curve_pwm, 1, 22); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point24_pwm, kraken3_fan_curve_pwm, 1, 23); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point25_pwm, kraken3_fan_curve_pwm, 1, 24); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point26_pwm, kraken3_fan_curve_pwm, 1, 25); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point27_pwm, kraken3_fan_curve_pwm, 1, 26); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point28_pwm, kraken3_fan_curve_pwm, 1, 27); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point29_pwm, kraken3_fan_curve_pwm, 1, 28); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point30_pwm, kraken3_fan_curve_pwm, 1, 29); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point31_pwm, kraken3_fan_curve_pwm, 1, 30); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point32_pwm, kraken3_fan_curve_pwm, 1, 31); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point33_pwm, kraken3_fan_curve_pwm, 1, 32); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point34_pwm, kraken3_fan_curve_pwm, 1, 33); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point35_pwm, kraken3_fan_curve_pwm, 1, 34); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point36_pwm, kraken3_fan_curve_pwm, 1, 35); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point37_pwm, kraken3_fan_curve_pwm, 1, 36); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point38_pwm, kraken3_fan_curve_pwm, 1, 37); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point39_pwm, kraken3_fan_curve_pwm, 1, 38); +static SENSOR_DEVICE_ATTR_2_WO(temp2_auto_point40_pwm, kraken3_fan_curve_pwm, 1, 39); + +static struct attribute *kraken3_curve_attrs[] = { + /* Pump control curve */ + &sensor_dev_attr_temp1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point6_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point7_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point8_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point9_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point10_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point11_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point12_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point13_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point14_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point15_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point16_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point17_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point18_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point19_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point20_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point21_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point22_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point23_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point24_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point25_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point26_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point27_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point28_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point29_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point30_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point31_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point32_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point33_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point34_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point35_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point36_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point37_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point38_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point39_pwm.dev_attr.attr, + &sensor_dev_attr_temp1_auto_point40_pwm.dev_attr.attr, + /* Fan control curve (Z53 only) */ + &sensor_dev_attr_temp2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point6_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point7_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point8_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point9_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point10_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point11_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point12_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point13_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point14_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point15_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point16_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point17_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point18_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point19_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point20_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point21_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point22_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point23_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point24_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point25_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point26_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point27_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point28_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point29_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point30_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point31_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point32_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point33_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point34_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point35_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point36_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point37_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point38_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point39_pwm.dev_attr.attr, + &sensor_dev_attr_temp2_auto_point40_pwm.dev_attr.attr, + NULL +}; + +static const struct attribute_group kraken3_curves_group = { + .attrs = kraken3_curve_attrs, + .is_visible = kraken3_curve_props_are_visible +}; + +static const struct attribute_group *kraken3_groups[] = { + &kraken3_curves_group, + NULL +}; + +static const struct hwmon_ops kraken3_hwmon_ops = { + .is_visible = kraken3_is_visible, + .read = kraken3_read, + .read_string = kraken3_read_string, + .write = kraken3_write +}; + +static const struct hwmon_channel_info *kraken3_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_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_ENABLE), + NULL +}; + +static const struct hwmon_chip_info kraken3_chip_info = { + .ops = &kraken3_hwmon_ops, + .info = kraken3_info, +}; + +static int kraken3_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct kraken3_data *priv = hid_get_drvdata(hdev); + int i; + + if (size < MIN_REPORT_LENGTH) + return 0; + + if (report->id == FIRMWARE_REPORT_ID) { + /* Read firmware version */ + for (i = 0; i < 3; i++) + priv->firmware_version[i] = data[FIRMWARE_VERSION_OFFSET + i]; + + if (!completion_done(&priv->fw_version_processed)) + complete_all(&priv->fw_version_processed); + + return 0; + } + + if (report->id != STATUS_REPORT_ID) + return 0; + + if (data[TEMP_SENSOR_START_OFFSET] == 0xff && data[TEMP_SENSOR_END_OFFSET] == 0xff) { + hid_err_once(hdev, + "firmware or device is possibly damaged (is SATA power connected?), not parsing reports\n"); + + /* + * Mark first X-series device report as received, + * as well as all for Z-series, if faulty. + */ + spin_lock(&priv->status_completion_lock); + if (priv->kind != X53 || !completion_done(&priv->status_report_processed)) { + priv->is_device_faulty = true; + complete_all(&priv->status_report_processed); + } + spin_unlock(&priv->status_completion_lock); + + return 0; + } + + /* Received normal data */ + priv->is_device_faulty = false; + + /* Temperature and fan sensor readings */ + priv->temp_input[0] = + data[TEMP_SENSOR_START_OFFSET] * 1000 + data[TEMP_SENSOR_END_OFFSET] * 100; + + priv->fan_input[0] = get_unaligned_le16(data + PUMP_SPEED_OFFSET); + priv->channel_info[0].reported_duty = kraken3_percent_to_pwm(data[PUMP_DUTY_OFFSET]); + + spin_lock(&priv->status_completion_lock); + if (priv->kind == X53 && !completion_done(&priv->status_report_processed)) { + /* Mark first X-series device report as received */ + complete_all(&priv->status_report_processed); + } else if (priv->kind == Z53) { + /* Additional readings for Z53 */ + priv->fan_input[1] = get_unaligned_le16(data + Z53_FAN_SPEED_OFFSET); + priv->channel_info[1].reported_duty = + kraken3_percent_to_pwm(data[Z53_FAN_DUTY_OFFSET]); + + if (!completion_done(&priv->status_report_processed)) + complete_all(&priv->status_report_processed); + } + spin_unlock(&priv->status_completion_lock); + + priv->updated = jiffies; + + return 0; +} + +static int kraken3_init_device(struct hid_device *hdev) +{ + struct kraken3_data *priv = hid_get_drvdata(hdev); + int ret; + + /* Set the polling interval */ + ret = kraken3_write_expanded(priv, set_interval_cmd, SET_INTERVAL_CMD_LENGTH); + if (ret < 0) + return ret; + + /* Finalize the init process */ + ret = kraken3_write_expanded(priv, finish_init_cmd, FINISH_INIT_CMD_LENGTH); + if (ret < 0) + return ret; + + return 0; +} + +static int kraken3_get_fw_ver(struct hid_device *hdev) +{ + struct kraken3_data *priv = hid_get_drvdata(hdev); + int ret; + + ret = kraken3_write_expanded(priv, get_fw_version_cmd, GET_FW_VERSION_CMD_LENGTH); + if (ret < 0) + return ret; + + ret = wait_for_completion_interruptible_timeout(&priv->fw_version_processed, + msecs_to_jiffies(STATUS_VALIDITY)); + if (ret == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + return 0; +} + +static int __maybe_unused kraken3_reset_resume(struct hid_device *hdev) +{ + int ret; + + ret = kraken3_init_device(hdev); + if (ret) + hid_err(hdev, "req init (reset_resume) failed with %d\n", ret); + + return ret; +} + +static int firmware_version_show(struct seq_file *seqf, void *unused) +{ + struct kraken3_data *priv = seqf->private; + + seq_printf(seqf, "%u.%u.%u\n", priv->firmware_version[0], priv->firmware_version[1], + priv->firmware_version[2]); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(firmware_version); + +static void kraken3_debugfs_init(struct kraken3_data *priv) +{ + char name[64]; + + if (!priv->firmware_version[0]) + return; /* Nothing to display in debugfs */ + + scnprintf(name, sizeof(name), "%s_%s-%s", DRIVER_NAME, kraken3_device_names[priv->kind], + dev_name(&priv->hdev->dev)); + + priv->debugfs = debugfs_create_dir(name, NULL); + debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); +} + +static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct kraken3_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); + + /* + * Initialize ->updated to STATUS_VALIDITY seconds in the past, making + * the initial empty data invalid for kraken3_read without the need for + * a special case there. + */ + priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "hid parse failed with %d\n", ret); + return ret; + } + + /* Enable hidraw so existing user-space tools can continue to work */ + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "hid hw start failed with %d\n", ret); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "hid hw open failed with %d\n", ret); + goto fail_and_stop; + } + + switch (hdev->product) { + case USB_PRODUCT_ID_X53: + case USB_PRODUCT_ID_X53_SECOND: + priv->kind = X53; + break; + case USB_PRODUCT_ID_Z53: + priv->kind = Z53; + break; + default: + break; + } + + priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL); + if (!priv->buffer) { + ret = -ENOMEM; + goto fail_and_close; + } + + mutex_init(&priv->buffer_lock); + mutex_init(&priv->z53_status_request_lock); + init_completion(&priv->fw_version_processed); + init_completion(&priv->status_report_processed); + spin_lock_init(&priv->status_completion_lock); + + hid_device_io_start(hdev); + ret = kraken3_init_device(hdev); + if (ret < 0) { + hid_err(hdev, "device init failed with %d\n", ret); + goto fail_and_close; + } + + ret = kraken3_get_fw_ver(hdev); + if (ret < 0) + hid_warn(hdev, "fw version request failed with %d\n", ret); + + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, + kraken3_device_names[priv->kind], priv, + &kraken3_chip_info, kraken3_groups); + if (IS_ERR(priv->hwmon_dev)) { + ret = PTR_ERR(priv->hwmon_dev); + hid_err(hdev, "hwmon registration failed with %d\n", ret); + goto fail_and_close; + } + + kraken3_debugfs_init(priv); + + return 0; + +fail_and_close: + hid_hw_close(hdev); +fail_and_stop: + hid_hw_stop(hdev); + return ret; +} + +static void kraken3_remove(struct hid_device *hdev) +{ + struct kraken3_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 kraken3_table[] = { + /* NZXT Kraken X53/X63/X73 have two possible product IDs */ + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_X53_SECOND) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, kraken3_table); + +static struct hid_driver kraken3_driver = { + .name = DRIVER_NAME, + .id_table = kraken3_table, + .probe = kraken3_probe, + .remove = kraken3_remove, + .raw_event = kraken3_raw_event, +#ifdef CONFIG_PM + .reset_resume = kraken3_reset_resume, +#endif +}; + +static int __init kraken3_init(void) +{ + return hid_register_driver(&kraken3_driver); +} + +static void __exit kraken3_exit(void) +{ + hid_unregister_driver(&kraken3_driver); +} + +/* When compiled into the kernel, initialize after the HID bus */ +late_initcall(kraken3_init); +module_exit(kraken3_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jonas Malaco "); +MODULE_AUTHOR("Aleksa Savic "); +MODULE_DESCRIPTION("Hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 coolers"); -- cgit v1.2.3 From ed3e03790c5c9f29f032dde9bb784e198984a759 Mon Sep 17 00:00:00 2001 From: Aleksa Savic Date: Mon, 8 Jan 2024 10:44:50 +0100 Subject: hwmon: Add driver for ASUS ROG RYUJIN II 360 AIO cooler This driver exposes hardware sensors of the ASUS ROG RYUJIN II 360 all-in-one CPU liquid cooler, which communicates through a proprietary USB HID protocol. Report offsets were initially discovered in [1] by Florian Freudiger. Available sensors are pump, internal and external (controller) fan speed in RPM, their duties in PWM, as well as coolant temperature. Attaching external fans to the controller is optional and allows them to be controlled from the device. If not connected, the fan-related sensors will report zeroes. The controller is a separate hardware unit that comes bundled with the AIO and connects to it to allow fan control. The addressable LCD screen is not supported in this driver and should be controlled through userspace tools. [1]: https://github.com/liquidctl/liquidctl/pull/653 Tested-by: Florian Freudiger Signed-off-by: Aleksa Savic Link: https://lore.kernel.org/r/20240108094453.22986-1-savicaleksa83@gmail.com [groeck: Add HID dependency] Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_rog_ryujin.rst | 47 +++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 6 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/asus_rog_ryujin.c | 609 ++++++++++++++++++++++++++++++++ 6 files changed, 674 insertions(+) create mode 100644 Documentation/hwmon/asus_rog_ryujin.rst create mode 100644 drivers/hwmon/asus_rog_ryujin.c diff --git a/Documentation/hwmon/asus_rog_ryujin.rst b/Documentation/hwmon/asus_rog_ryujin.rst new file mode 100644 index 000000000000..9f77da070022 --- /dev/null +++ b/Documentation/hwmon/asus_rog_ryujin.rst @@ -0,0 +1,47 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver asus_rog_ryujin +============================= + +Supported devices: + +* ASUS ROG RYUJIN II 360 + +Author: Aleksa Savic + +Description +----------- + +This driver enables hardware monitoring support for the listed ASUS ROG RYUJIN +all-in-one CPU liquid coolers. Available sensors are pump, internal and external +(controller) fan speed in RPM, their duties in PWM, as well as coolant temperature. + +Attaching external fans to the controller is optional and allows them to be +controlled from the device. If not connected, the fan-related sensors will +report zeroes. The controller is a separate hardware unit that comes bundled +with the AIO and connects to it to allow fan control. + +The addressable LCD screen is not supported in this driver and should +be controlled through userspace tools. + +Usage notes +----------- + +As these are USB HIDs, the driver can be loaded automatically by the kernel and +supports hot swapping. + +Sysfs entries +------------- + +=========== ============================================= +fan1_input Pump speed (in rpm) +fan2_input Internal fan speed (in rpm) +fan3_input External (controller) fan 1 speed (in rpm) +fan4_input External (controller) fan 2 speed (in rpm) +fan5_input External (controller) fan 3 speed (in rpm) +fan6_input External (controller) fan 4 speed (in rpm) +temp1_input Coolant temperature (in millidegrees Celsius) +pwm1 Pump duty +pwm2 Internal fan duty +pwm3 External (controller) fan duty +=========== ============================================= diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 6f8a4a7524e8..c19f53d9b3ab 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -46,6 +46,7 @@ Hardware Monitoring Kernel Drivers asc7621 aspeed-pwm-tacho asus_ec_sensors + asus_rog_ryujin asus_wmi_sensors bcm54140 bel-pfe diff --git a/MAINTAINERS b/MAINTAINERS index b9906f88c1b7..8fc0ca8e881c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3181,6 +3181,12 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git F: drivers/platform/x86/asus-tf103c-dock.c +ASUS ROG RYUJIN AIO HARDWARE MONITOR DRIVER +M: Aleksa Savic +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/asus_rog_ryujin.c + ASUS WIRELESS RADIO CONTROL DRIVER M: João Paulo Rechi Vita L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3904bb297d61..e4b24ad93961 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -301,6 +301,16 @@ config SENSORS_ASC7621 This driver can also be built as a module. If so, the module will be called asc7621. +config SENSORS_ASUS_ROG_RYUJIN + tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver" + depends on HID + help + If you say yes here you get support for the fans and sensors of + the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler. + + This driver can also be built as a module. If so, the module + will be called asus_rog_ryujin. + config SENSORS_AXI_FAN_CONTROL tristate "Analog Devices FAN Control HDL Core driver" help diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 76e6dfef9f24..e3faee7be51a 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o +obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c new file mode 100644 index 000000000000..f8b20346a995 --- /dev/null +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -0,0 +1,609 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. + * + * Copyright 2024 Aleksa Savic + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "asus_rog_ryujin" + +#define USB_VENDOR_ID_ASUS_ROG 0x0b05 +#define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ + +#define STATUS_VALIDITY 1500 /* ms */ +#define MAX_REPORT_LENGTH 65 + +/* Cooler status report offsets */ +#define RYUJIN_TEMP_SENSOR_1 3 +#define RYUJIN_TEMP_SENSOR_2 4 +#define RYUJIN_PUMP_SPEED 5 +#define RYUJIN_INTERNAL_FAN_SPEED 7 + +/* Cooler duty report offsets */ +#define RYUJIN_PUMP_DUTY 4 +#define RYUJIN_INTERNAL_FAN_DUTY 5 + +/* Controller status (speeds) report offsets */ +#define RYUJIN_CONTROLLER_SPEED_1 5 +#define RYUJIN_CONTROLLER_SPEED_2 7 +#define RYUJIN_CONTROLLER_SPEED_3 9 +#define RYUJIN_CONTROLLER_SPEED_4 3 + +/* Controller duty report offsets */ +#define RYUJIN_CONTROLLER_DUTY 4 + +/* Control commands and their inner offsets */ +#define RYUJIN_CMD_PREFIX 0xEC + +static const u8 get_cooler_status_cmd[] = { RYUJIN_CMD_PREFIX, 0x99 }; +static const u8 get_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x9A }; +static const u8 get_controller_speed_cmd[] = { RYUJIN_CMD_PREFIX, 0xA0 }; +static const u8 get_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0xA1 }; + +#define RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET 3 +#define RYUJIN_SET_COOLER_FAN_DUTY_OFFSET 4 +static const u8 set_cooler_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x1A, 0x00, 0x00, 0x00 }; + +#define RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET 4 +static const u8 set_controller_duty_cmd[] = { RYUJIN_CMD_PREFIX, 0x21, 0x00, 0x00, 0x00 }; + +/* Command lengths */ +#define GET_CMD_LENGTH 2 /* Same length for all get commands */ +#define SET_CMD_LENGTH 5 /* Same length for all set commands */ + +/* Command response headers */ +#define RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE 0x19 +#define RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE 0x1A +#define RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE 0x20 +#define RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE 0x21 + +static const char *const rog_ryujin_temp_label[] = { + "Coolant temp" +}; + +static const char *const rog_ryujin_speed_label[] = { + "Pump speed", + "Internal fan speed", + "Controller fan 1 speed", + "Controller fan 2 speed", + "Controller fan 3 speed", + "Controller fan 4 speed", +}; + +struct rog_ryujin_data { + struct hid_device *hdev; + struct device *hwmon_dev; + /* For locking access to buffer */ + struct mutex buffer_lock; + /* For queueing multiple readers */ + struct mutex status_report_request_mutex; + /* For reinitializing the completions below */ + spinlock_t status_report_request_lock; + struct completion cooler_status_received; + struct completion controller_status_received; + struct completion cooler_duty_received; + struct completion controller_duty_received; + struct completion cooler_duty_set; + struct completion controller_duty_set; + + /* Sensor data */ + s32 temp_input[1]; + u16 speed_input[6]; /* Pump, internal fan and four controller fan speeds in RPM */ + u8 duty_input[3]; /* Pump, internal fan and controller fan duty in PWM */ + + u8 *buffer; + unsigned long updated; /* jiffies */ +}; + +static int rog_ryujin_percent_to_pwm(u16 val) +{ + return DIV_ROUND_CLOSEST(val * 255, 100); +} + +static int rog_ryujin_pwm_to_percent(long val) +{ + return DIV_ROUND_CLOSEST(val * 100, 255); +} + +static umode_t rog_ryujin_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + case hwmon_temp_input: + return 0444; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_label: + case hwmon_fan_input: + return 0444; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return 0644; + default: + break; + } + break; + default: + break; + } + + return 0; +} + +/* Writes the command to the device with the rest of the report filled with zeroes */ +static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length) +{ + int ret; + + mutex_lock(&priv->buffer_lock); + + memcpy_and_pad(priv->buffer, MAX_REPORT_LENGTH, cmd, cmd_length, 0x00); + ret = hid_hw_output_report(priv->hdev, priv->buffer, MAX_REPORT_LENGTH); + + mutex_unlock(&priv->buffer_lock); + return ret; +} + +/* Assumes priv->status_report_request_mutex is locked */ +static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) +{ + int ret; + + /* + * Disable raw event parsing for a moment to safely reinitialize the + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ + spin_lock_bh(&priv->status_report_request_lock); + reinit_completion(status_completion); + spin_unlock_bh(&priv->status_report_request_lock); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); + if (ret < 0) + return ret; + + ret = wait_for_completion_interruptible_timeout(status_completion, + msecs_to_jiffies(STATUS_VALIDITY)); + if (ret == 0) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + + return 0; +} + +static int rog_ryujin_get_status(struct rog_ryujin_data *priv) +{ + int ret = mutex_lock_interruptible(&priv->status_report_request_mutex); + + if (ret < 0) + return ret; + + if (!time_after(jiffies, priv->updated + msecs_to_jiffies(STATUS_VALIDITY))) { + /* Data is up to date */ + goto unlock_and_return; + } + + /* Retrieve cooler status */ + ret = + rog_ryujin_execute_cmd(priv, get_cooler_status_cmd, GET_CMD_LENGTH, + &priv->cooler_status_received); + if (ret < 0) + goto unlock_and_return; + + /* Retrieve controller status (speeds) */ + ret = + rog_ryujin_execute_cmd(priv, get_controller_speed_cmd, GET_CMD_LENGTH, + &priv->controller_status_received); + if (ret < 0) + goto unlock_and_return; + + /* Retrieve cooler duty */ + ret = + rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH, + &priv->cooler_duty_received); + if (ret < 0) + goto unlock_and_return; + + /* Retrieve controller duty */ + ret = + rog_ryujin_execute_cmd(priv, get_controller_duty_cmd, GET_CMD_LENGTH, + &priv->controller_duty_received); + if (ret < 0) + goto unlock_and_return; + + priv->updated = jiffies; + +unlock_and_return: + mutex_unlock(&priv->status_report_request_mutex); + if (ret < 0) + return ret; + + return 0; +} + +static int rog_ryujin_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct rog_ryujin_data *priv = dev_get_drvdata(dev); + int ret = rog_ryujin_get_status(priv); + + if (ret < 0) + return ret; + + switch (type) { + case hwmon_temp: + *val = priv->temp_input[channel]; + break; + case hwmon_fan: + *val = priv->speed_input[channel]; + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + *val = priv->duty_input[channel]; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; /* unreachable */ + } + + return 0; +} + +static int rog_ryujin_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + *str = rog_ryujin_temp_label[channel]; + break; + case hwmon_fan: + *str = rog_ryujin_speed_label[channel]; + break; + default: + return -EOPNOTSUPP; /* unreachable */ + } + + return 0; +} + +static int rog_ryujin_write_fixed_duty(struct rog_ryujin_data *priv, int channel, int val) +{ + u8 set_cmd[SET_CMD_LENGTH]; + int ret; + + if (channel < 2) { + /* + * Retrieve cooler duty since both pump and internal fan are set + * together, then write back with one of them modified. + */ + ret = mutex_lock_interruptible(&priv->status_report_request_mutex); + if (ret < 0) + return ret; + ret = + rog_ryujin_execute_cmd(priv, get_cooler_duty_cmd, GET_CMD_LENGTH, + &priv->cooler_duty_received); + if (ret < 0) + goto unlock_and_return; + + memcpy(set_cmd, set_cooler_duty_cmd, SET_CMD_LENGTH); + + /* Cooler duties are set as 0-100% */ + val = rog_ryujin_pwm_to_percent(val); + + if (channel == 0) { + /* Cooler pump duty */ + set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = val; + set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = + rog_ryujin_pwm_to_percent(priv->duty_input[1]); + } else if (channel == 1) { + /* Cooler internal fan duty */ + set_cmd[RYUJIN_SET_COOLER_PUMP_DUTY_OFFSET] = + rog_ryujin_pwm_to_percent(priv->duty_input[0]); + set_cmd[RYUJIN_SET_COOLER_FAN_DUTY_OFFSET] = val; + } + + ret = rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, &priv->cooler_duty_set); +unlock_and_return: + mutex_unlock(&priv->status_report_request_mutex); + if (ret < 0) + return ret; + } else { + /* + * Controller fan duty (channel == 2). No need to retrieve current + * duty, so just send the command. + */ + memcpy(set_cmd, set_controller_duty_cmd, SET_CMD_LENGTH); + set_cmd[RYUJIN_SET_CONTROLLER_FAN_DUTY_OFFSET] = val; + + ret = + rog_ryujin_execute_cmd(priv, set_cmd, SET_CMD_LENGTH, + &priv->controller_duty_set); + if (ret < 0) + return ret; + } + + /* Lock onto this value until next refresh cycle */ + priv->duty_input[channel] = val; + + return 0; +} + +static int rog_ryujin_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + struct rog_ryujin_data *priv = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + if (val < 0 || val > 255) + return -EINVAL; + + ret = rog_ryujin_write_fixed_duty(priv, channel, val); + if (ret < 0) + return ret; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static const struct hwmon_ops rog_ryujin_hwmon_ops = { + .is_visible = rog_ryujin_is_visible, + .read = rog_ryujin_read, + .read_string = rog_ryujin_read_string, + .write = rog_ryujin_write +}; + +static const struct hwmon_channel_info *rog_ryujin_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_F_INPUT | HWMON_F_LABEL, + 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_INPUT, + HWMON_PWM_INPUT), + NULL +}; + +static const struct hwmon_chip_info rog_ryujin_chip_info = { + .ops = &rog_ryujin_hwmon_ops, + .info = rog_ryujin_info, +}; + +static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) +{ + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); + + if (data[0] != RYUJIN_CMD_PREFIX) + return 0; + + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = + data[RYUJIN_TEMP_SENSOR_1] * 1000 + data[RYUJIN_TEMP_SENSOR_2] * 100; + priv->speed_input[0] = get_unaligned_le16(data + RYUJIN_PUMP_SPEED); + priv->speed_input[1] = get_unaligned_le16(data + RYUJIN_INTERNAL_FAN_SPEED); + + if (!completion_done(&priv->cooler_status_received)) + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); + priv->speed_input[3] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_2); + priv->speed_input[4] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_3); + priv->speed_input[5] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_4); + + if (!completion_done(&priv->controller_status_received)) + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { + /* + * We received a report with zeroes for duty in both places. + * The device returns this as a confirmation that setting values + * is successful. If we initiated a write, mark it as complete. + */ + if (!completion_done(&priv->cooler_duty_set)) + complete_all(&priv->cooler_duty_set); + else if (!completion_done(&priv->cooler_duty_received)) + /* + * We didn't initiate a write, but received both zeroes. + * This means that either both duties are actually zero, + * or that we received a success report caused by userspace. + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; + return 0; + } +read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); + priv->duty_input[1] = rog_ryujin_percent_to_pwm(data[RYUJIN_INTERNAL_FAN_DUTY]); + + if (!completion_done(&priv->cooler_duty_received)) + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { + /* + * We received a report with a zero for duty. The device returns this as + * a confirmation that setting the controller duty value was successful. + * If we initiated a write, mark it as complete. + */ + if (!completion_done(&priv->controller_duty_set)) + complete_all(&priv->controller_duty_set); + else if (!completion_done(&priv->controller_duty_received)) + /* + * We didn't initiate a write, but received a zero for duty. + * This means that either the duty is actually zero, or that + * we received a success report caused by userspace. + * We're expecting a report, so parse it. + */ + goto read_controller_duty; + return 0; + } +read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; + + if (!completion_done(&priv->controller_duty_received)) + complete_all(&priv->controller_duty_received); + } + + return 0; +} + +static int rog_ryujin_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct rog_ryujin_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); + + /* + * Initialize priv->updated to STATUS_VALIDITY seconds in the past, making + * the initial empty data invalid for rog_ryujin_read() without the need for + * a special case there. + */ + priv->updated = jiffies - msecs_to_jiffies(STATUS_VALIDITY); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "hid parse failed with %d\n", ret); + return ret; + } + + /* Enable hidraw so existing user-space tools can continue to work */ + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "hid hw start failed with %d\n", ret); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "hid hw open failed with %d\n", ret); + goto fail_and_stop; + } + + priv->buffer = devm_kzalloc(&hdev->dev, MAX_REPORT_LENGTH, GFP_KERNEL); + if (!priv->buffer) { + ret = -ENOMEM; + goto fail_and_close; + } + + mutex_init(&priv->status_report_request_mutex); + mutex_init(&priv->buffer_lock); + spin_lock_init(&priv->status_report_request_lock); + init_completion(&priv->cooler_status_received); + init_completion(&priv->controller_status_received); + init_completion(&priv->cooler_duty_received); + init_completion(&priv->controller_duty_received); + init_completion(&priv->cooler_duty_set); + init_completion(&priv->controller_duty_set); + + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "rog_ryujin", + priv, &rog_ryujin_chip_info, NULL); + if (IS_ERR(priv->hwmon_dev)) { + ret = PTR_ERR(priv->hwmon_dev); + hid_err(hdev, "hwmon registration failed with %d\n", ret); + goto fail_and_close; + } + + return 0; + +fail_and_close: + hid_hw_close(hdev); +fail_and_stop: + hid_hw_stop(hdev); + return ret; +} + +static void rog_ryujin_remove(struct hid_device *hdev) +{ + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); + + hwmon_device_unregister(priv->hwmon_dev); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id rog_ryujin_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, rog_ryujin_table); + +static struct hid_driver rog_ryujin_driver = { + .name = "rog_ryujin", + .id_table = rog_ryujin_table, + .probe = rog_ryujin_probe, + .remove = rog_ryujin_remove, + .raw_event = rog_ryujin_raw_event, +}; + +static int __init rog_ryujin_init(void) +{ + return hid_register_driver(&rog_ryujin_driver); +} + +static void __exit rog_ryujin_exit(void) +{ + hid_unregister_driver(&rog_ryujin_driver); +} + +/* When compiled into the kernel, initialize after the HID bus */ +late_initcall(rog_ryujin_init); +module_exit(rog_ryujin_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Aleksa Savic "); +MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); -- cgit v1.2.3 From c8c2074020a878ca6c6106a2cf224b536247e11a Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:37 +0800 Subject: hwmon: (coretemp) Introduce enum for attr index Introduce enum coretemp_attr_index to better describe the index of each sensor attribute. No functional change. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-5-rui.zhang@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index b8fc8d1ef20d..32f99cf6308b 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -43,10 +43,18 @@ MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius"); #define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */ #define NUM_REAL_CORES 512 /* Number of Real cores per cpu */ #define CORETEMP_NAME_LENGTH 28 /* String Length of attrs */ -#define MAX_CORE_ATTRS 4 /* Maximum no of basic attrs */ -#define TOTAL_ATTRS (MAX_CORE_ATTRS + 1) #define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO) +enum coretemp_attr_index { + ATTR_LABEL, + ATTR_CRIT_ALARM, + ATTR_TEMP, + ATTR_TJMAX, + ATTR_TTARGET, + MAX_CORE_ATTRS = ATTR_TJMAX + 1, /* Maximum no of basic attrs */ + TOTAL_ATTRS = ATTR_TTARGET + 1 /* Maximum no of possible attrs */ +}; + #ifdef CONFIG_SMP #define for_each_sibling(i, cpu) \ for_each_cpu(i, topology_sibling_cpumask(cpu)) -- cgit v1.2.3 From 25f8e01baa05dfeb1b477112b1fec94f768f27b4 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:38 +0800 Subject: hwmon: (coretemp) Remove unnecessary dependency of array index When sensor_device_attribute pointer is available, use container_of() to get the temp_data address. This removes the unnecessary dependency of cached index in pdata->core_data[]. No functional change. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-6-rui.zhang@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 32f99cf6308b..9a7bfc046c72 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -342,7 +342,7 @@ static ssize_t show_label(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct platform_data *pdata = dev_get_drvdata(dev); - struct temp_data *tdata = pdata->core_data[attr->index]; + struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_LABEL]); if (tdata->is_pkg_data) return sprintf(buf, "Package id %u\n", pdata->pkg_id); @@ -355,8 +355,7 @@ static ssize_t show_crit_alarm(struct device *dev, { u32 eax, edx; struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct platform_data *pdata = dev_get_drvdata(dev); - struct temp_data *tdata = pdata->core_data[attr->index]; + struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_CRIT_ALARM]); mutex_lock(&tdata->update_lock); rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx); @@ -369,8 +368,7 @@ static ssize_t show_tjmax(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct platform_data *pdata = dev_get_drvdata(dev); - struct temp_data *tdata = pdata->core_data[attr->index]; + struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_TJMAX]); int tjmax; mutex_lock(&tdata->update_lock); @@ -384,8 +382,7 @@ static ssize_t show_ttarget(struct device *dev, struct device_attribute *devattr, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct platform_data *pdata = dev_get_drvdata(dev); - struct temp_data *tdata = pdata->core_data[attr->index]; + struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_TTARGET]); int ttarget; mutex_lock(&tdata->update_lock); @@ -402,8 +399,7 @@ static ssize_t show_temp(struct device *dev, { u32 eax, edx; struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct platform_data *pdata = dev_get_drvdata(dev); - struct temp_data *tdata = pdata->core_data[attr->index]; + struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_TEMP]); int tjmax; mutex_lock(&tdata->update_lock); @@ -426,8 +422,7 @@ static ssize_t show_temp(struct device *dev, return sprintf(buf, "%d\n", tdata->temp); } -static int create_core_attrs(struct temp_data *tdata, struct device *dev, - int index) +static int create_core_attrs(struct temp_data *tdata, struct device *dev) { int i; static ssize_t (*const rd_ptr[TOTAL_ATTRS]) (struct device *dev, @@ -452,7 +447,6 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev, tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i]; tdata->sd_attrs[i].dev_attr.attr.mode = 0444; tdata->sd_attrs[i].dev_attr.show = rd_ptr[i]; - tdata->sd_attrs[i].index = index; tdata->attrs[i] = &tdata->sd_attrs[i].dev_attr.attr; } tdata->attr_group.attrs = tdata->attrs; @@ -557,7 +551,7 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu, pdata->core_data[index] = tdata; /* Create sysfs interfaces */ - err = create_core_attrs(tdata, pdata->hwmon_dev, index); + err = create_core_attrs(tdata, pdata->hwmon_dev); if (err) goto exit_free; -- cgit v1.2.3 From 18d8f5583388f38094b3b17ad3149d2a93a23646 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:39 +0800 Subject: hwmon: (coretemp) Replace sensor_device_attribute with device_attribute Replace sensor_device_attribute with device_attribute because sensor_device_attribute->index is no longer used. No functional change. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-7-rui.zhang@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 9a7bfc046c72..cdd1e069d5c1 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -85,7 +85,7 @@ struct temp_data { u32 status_reg; int attr_size; bool is_pkg_data; - struct sensor_device_attribute sd_attrs[TOTAL_ATTRS]; + struct device_attribute sd_attrs[TOTAL_ATTRS]; char attr_name[TOTAL_ATTRS][CORETEMP_NAME_LENGTH]; struct attribute *attrs[TOTAL_ATTRS + 1]; struct attribute_group attr_group; @@ -340,9 +340,8 @@ static struct platform_device **zone_devices; static ssize_t show_label(struct device *dev, struct device_attribute *devattr, char *buf) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct platform_data *pdata = dev_get_drvdata(dev); - struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_LABEL]); + struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_LABEL]); if (tdata->is_pkg_data) return sprintf(buf, "Package id %u\n", pdata->pkg_id); @@ -354,8 +353,8 @@ static ssize_t show_crit_alarm(struct device *dev, struct device_attribute *devattr, char *buf) { u32 eax, edx; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_CRIT_ALARM]); + struct temp_data *tdata = container_of(devattr, struct temp_data, + sd_attrs[ATTR_CRIT_ALARM]); mutex_lock(&tdata->update_lock); rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx); @@ -367,8 +366,7 @@ static ssize_t show_crit_alarm(struct device *dev, static ssize_t show_tjmax(struct device *dev, struct device_attribute *devattr, char *buf) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_TJMAX]); + struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TJMAX]); int tjmax; mutex_lock(&tdata->update_lock); @@ -381,8 +379,7 @@ static ssize_t show_tjmax(struct device *dev, static ssize_t show_ttarget(struct device *dev, struct device_attribute *devattr, char *buf) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_TTARGET]); + struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TTARGET]); int ttarget; mutex_lock(&tdata->update_lock); @@ -398,8 +395,7 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, char *buf) { u32 eax, edx; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct temp_data *tdata = container_of(attr, struct temp_data, sd_attrs[ATTR_TEMP]); + struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_TEMP]); int tjmax; mutex_lock(&tdata->update_lock); @@ -443,11 +439,11 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev) snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH, "temp%d_%s", attr_no, suffixes[i]); - sysfs_attr_init(&tdata->sd_attrs[i].dev_attr.attr); - tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i]; - tdata->sd_attrs[i].dev_attr.attr.mode = 0444; - tdata->sd_attrs[i].dev_attr.show = rd_ptr[i]; - tdata->attrs[i] = &tdata->sd_attrs[i].dev_attr.attr; + sysfs_attr_init(&tdata->sd_attrs[i].attr); + tdata->sd_attrs[i].attr.name = tdata->attr_name[i]; + tdata->sd_attrs[i].attr.mode = 0444; + tdata->sd_attrs[i].show = rd_ptr[i]; + tdata->attrs[i] = &tdata->sd_attrs[i].attr; } tdata->attr_group.attrs = tdata->attrs; return sysfs_create_group(&dev->kobj, &tdata->attr_group); -- cgit v1.2.3 From 87eb801925a0a391dce0d7519eb1d45a9d7e0953 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:40 +0800 Subject: hwmon: (coretemp) Remove redundant pdata->cpu_map[] pdata->cpu_map[] saves the mapping between cpu core id and the index in pdata->core_data[]. This is used to find the temp_data structure using cpu_core_id, by traversing the pdata->cpu_map[] array. But the same goal can be achieved by traversing the pdata->core_temp[] array directly. Remove redundant pdata->cpu_map[]. No functional change. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-8-rui.zhang@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index cdd1e069d5c1..29ee8e0c0fe9 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -96,7 +96,6 @@ struct temp_data { struct platform_data { struct device *hwmon_dev; u16 pkg_id; - u16 cpu_map[NUM_REAL_CORES]; struct ida ida; struct cpumask cpumask; struct temp_data *core_data[MAX_CORE_DATA]; @@ -517,7 +516,6 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu, if (index < 0) return index; - pdata->cpu_map[index] = topology_core_id(cpu); index += BASE_SYSFS_ATTR_NO; } @@ -696,7 +694,7 @@ static int coretemp_cpu_offline(unsigned int cpu) struct platform_device *pdev = coretemp_get_pdev(cpu); struct platform_data *pd; struct temp_data *tdata; - int i, indx = -1, target; + int i, target; /* No need to tear down any interfaces for suspend */ if (cpuhp_tasks_frozen) @@ -707,18 +705,16 @@ static int coretemp_cpu_offline(unsigned int cpu) if (!pd->hwmon_dev) return 0; - for (i = 0; i < NUM_REAL_CORES; i++) { - if (pd->cpu_map[i] == topology_core_id(cpu)) { - indx = i + BASE_SYSFS_ATTR_NO; + for (i = BASE_SYSFS_ATTR_NO; i < MAX_CORE_DATA; i++) { + if (pd->core_data[i] && pd->core_data[i]->cpu_core_id == topology_core_id(cpu)) break; - } } /* Too many cores and this core is not populated, just return */ - if (indx < 0) + if (i == MAX_CORE_DATA) return 0; - tdata = pd->core_data[indx]; + tdata = pd->core_data[i]; cpumask_clear_cpu(cpu, &pd->cpumask); @@ -729,7 +725,7 @@ static int coretemp_cpu_offline(unsigned int cpu) */ target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu)); if (target >= nr_cpu_ids) { - coretemp_remove_core(pd, indx); + coretemp_remove_core(pd, i); } else if (tdata && tdata->cpu == cpu) { mutex_lock(&tdata->update_lock); tdata->cpu = target; -- cgit v1.2.3 From b0b01414a26105b68cbce308df076ec66c72bddd Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:41 +0800 Subject: hwmon: (coretemp) Abstract core_temp helpers coretemp driver has an obscure and fragile logic for handling package and core temperature data. Place the logic in newly introduced helpers for further optimizations. No functional change. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-9-rui.zhang@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 118 +++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 29ee8e0c0fe9..a19799a302a2 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -81,6 +81,7 @@ struct temp_data { int tjmax; unsigned long last_updated; unsigned int cpu; + unsigned int index; u32 cpu_core_id; u32 status_reg; int attr_size; @@ -474,14 +475,36 @@ static struct platform_device *coretemp_get_pdev(unsigned int cpu) return NULL; } -static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag) +static struct temp_data * +init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) { struct temp_data *tdata; + int index; tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL); if (!tdata) return NULL; + /* + * Get the index of tdata in pdata->core_data[] + * tdata for package: pdata->core_data[1] + * tdata for core: pdata->core_data[2] .. pdata->core_data[NUM_REAL_CORES + 1] + */ + if (pkg_flag) { + index = PKG_SYSFS_ATTR_NO; + } else { + index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL); + if (index < 0) { + kfree(tdata); + return NULL; + } + index += BASE_SYSFS_ATTR_NO; + } + /* Index in pdata->core_data[] */ + tdata->index = index; + + pdata->core_data[index] = tdata; + tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS : MSR_IA32_THERM_STATUS; tdata->is_pkg_data = pkg_flag; @@ -492,6 +515,30 @@ static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag) return tdata; } +static void destroy_temp_data(struct platform_data *pdata, struct temp_data *tdata) +{ + pdata->core_data[tdata->index] = NULL; + if (!tdata->is_pkg_data) + ida_free(&pdata->ida, tdata->index - BASE_SYSFS_ATTR_NO); + kfree(tdata); +} + +static struct temp_data *get_temp_data(struct platform_data *pdata, int cpu) +{ + int i; + + /* cpu < 0 means get pkg temp_data */ + if (cpu < 0) + return pdata->core_data[PKG_SYSFS_ATTR_NO]; + + for (i = BASE_SYSFS_ATTR_NO; i < MAX_CORE_DATA; i++) { + if (pdata->core_data[i] && + pdata->core_data[i]->cpu_core_id == topology_core_id(cpu)) + return pdata->core_data[i]; + } + return NULL; +} + static int create_core_data(struct platform_device *pdev, unsigned int cpu, int pkg_flag) { @@ -499,36 +546,19 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu, struct platform_data *pdata = platform_get_drvdata(pdev); struct cpuinfo_x86 *c = &cpu_data(cpu); u32 eax, edx; - int err, index; + int err; if (!housekeeping_cpu(cpu, HK_TYPE_MISC)) return 0; - /* - * Get the index of tdata in pdata->core_data[] - * tdata for package: pdata->core_data[1] - * tdata for core: pdata->core_data[2] .. pdata->core_data[NUM_REAL_CORES + 1] - */ - if (pkg_flag) { - index = PKG_SYSFS_ATTR_NO; - } else { - index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL); - if (index < 0) - return index; - - index += BASE_SYSFS_ATTR_NO; - } - - tdata = init_temp_data(cpu, pkg_flag); - if (!tdata) { - err = -ENOMEM; - goto ida_free; - } + tdata = init_temp_data(pdata, cpu, pkg_flag); + if (!tdata) + return -ENOMEM; /* Test if we can access the status register */ err = rdmsr_safe_on_cpu(cpu, tdata->status_reg, &eax, &edx); if (err) - goto exit_free; + goto err; /* Make sure tdata->tjmax is a valid indicator for dynamic/static tjmax */ get_tjmax(tdata, &pdev->dev); @@ -542,20 +572,15 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu, if (get_ttarget(tdata, &pdev->dev) >= 0) tdata->attr_size++; - pdata->core_data[index] = tdata; - /* Create sysfs interfaces */ err = create_core_attrs(tdata, pdata->hwmon_dev); if (err) - goto exit_free; + goto err; return 0; -exit_free: - pdata->core_data[index] = NULL; - kfree(tdata); -ida_free: - if (!pkg_flag) - ida_free(&pdata->ida, index - BASE_SYSFS_ATTR_NO); + +err: + destroy_temp_data(pdata, tdata); return err; } @@ -566,10 +591,8 @@ coretemp_add_core(struct platform_device *pdev, unsigned int cpu, int pkg_flag) dev_err(&pdev->dev, "Adding Core %u failed\n", cpu); } -static void coretemp_remove_core(struct platform_data *pdata, int indx) +static void coretemp_remove_core(struct platform_data *pdata, struct temp_data *tdata) { - struct temp_data *tdata = pdata->core_data[indx]; - /* if we errored on add then this is already gone */ if (!tdata) return; @@ -577,11 +600,7 @@ static void coretemp_remove_core(struct platform_data *pdata, int indx) /* Remove the sysfs attributes */ sysfs_remove_group(&pdata->hwmon_dev->kobj, &tdata->attr_group); - kfree(pdata->core_data[indx]); - pdata->core_data[indx] = NULL; - - if (indx >= BASE_SYSFS_ATTR_NO) - ida_free(&pdata->ida, indx - BASE_SYSFS_ATTR_NO); + destroy_temp_data(pdata, tdata); } static int coretemp_device_add(int zoneid) @@ -694,7 +713,7 @@ static int coretemp_cpu_offline(unsigned int cpu) struct platform_device *pdev = coretemp_get_pdev(cpu); struct platform_data *pd; struct temp_data *tdata; - int i, target; + int target; /* No need to tear down any interfaces for suspend */ if (cpuhp_tasks_frozen) @@ -705,16 +724,7 @@ static int coretemp_cpu_offline(unsigned int cpu) if (!pd->hwmon_dev) return 0; - for (i = BASE_SYSFS_ATTR_NO; i < MAX_CORE_DATA; i++) { - if (pd->core_data[i] && pd->core_data[i]->cpu_core_id == topology_core_id(cpu)) - break; - } - - /* Too many cores and this core is not populated, just return */ - if (i == MAX_CORE_DATA) - return 0; - - tdata = pd->core_data[i]; + tdata = get_temp_data(pd, cpu); cpumask_clear_cpu(cpu, &pd->cpumask); @@ -725,7 +735,7 @@ static int coretemp_cpu_offline(unsigned int cpu) */ target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu)); if (target >= nr_cpu_ids) { - coretemp_remove_core(pd, i); + coretemp_remove_core(pd, tdata); } else if (tdata && tdata->cpu == cpu) { mutex_lock(&tdata->update_lock); tdata->cpu = target; @@ -735,10 +745,10 @@ static int coretemp_cpu_offline(unsigned int cpu) /* * If all cores in this pkg are offline, remove the interface. */ - tdata = pd->core_data[PKG_SYSFS_ATTR_NO]; + tdata = get_temp_data(pd, -1); if (cpumask_empty(&pd->cpumask)) { if (tdata) - coretemp_remove_core(pd, PKG_SYSFS_ATTR_NO); + coretemp_remove_core(pd, tdata); hwmon_device_unregister(pd->hwmon_dev); pd->hwmon_dev = NULL; return 0; -- cgit v1.2.3 From 326241f71f3d8de3c62236b559ce3f2f201aec4d Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:42 +0800 Subject: hwmon: (coretemp) Split package temp_data and core temp_data Saving package temp_data and core temp_data in one array with different offsets is fragile. Split them and clean up crabbed maths and macros. This also fixes a problem that pdata->core_data[0] was never used. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-10-rui.zhang@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index a19799a302a2..1a3b5ae0baca 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -39,11 +39,8 @@ static int force_tjmax; module_param_named(tjmax, force_tjmax, int, 0444); MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius"); -#define PKG_SYSFS_ATTR_NO 1 /* Sysfs attribute for package temp */ -#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */ #define NUM_REAL_CORES 512 /* Number of Real cores per cpu */ #define CORETEMP_NAME_LENGTH 28 /* String Length of attrs */ -#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO) enum coretemp_attr_index { ATTR_LABEL, @@ -99,7 +96,8 @@ struct platform_data { u16 pkg_id; struct ida ida; struct cpumask cpumask; - struct temp_data *core_data[MAX_CORE_DATA]; + struct temp_data *pkg_data; + struct temp_data *core_data[NUM_REAL_CORES]; struct device_attribute name_attr; }; @@ -479,31 +477,21 @@ static struct temp_data * init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) { struct temp_data *tdata; - int index; tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL); if (!tdata) return NULL; - /* - * Get the index of tdata in pdata->core_data[] - * tdata for package: pdata->core_data[1] - * tdata for core: pdata->core_data[2] .. pdata->core_data[NUM_REAL_CORES + 1] - */ if (pkg_flag) { - index = PKG_SYSFS_ATTR_NO; + pdata->pkg_data = tdata; } else { - index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL); - if (index < 0) { + tdata->index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL); + if (tdata->index < 0) { kfree(tdata); return NULL; } - index += BASE_SYSFS_ATTR_NO; + pdata->core_data[tdata->index] = tdata; } - /* Index in pdata->core_data[] */ - tdata->index = index; - - pdata->core_data[index] = tdata; tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS : MSR_IA32_THERM_STATUS; @@ -517,9 +505,12 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) static void destroy_temp_data(struct platform_data *pdata, struct temp_data *tdata) { - pdata->core_data[tdata->index] = NULL; - if (!tdata->is_pkg_data) - ida_free(&pdata->ida, tdata->index - BASE_SYSFS_ATTR_NO); + if (tdata->is_pkg_data) { + pdata->pkg_data = NULL; + } else { + pdata->core_data[tdata->index] = NULL; + ida_free(&pdata->ida, tdata->index); + } kfree(tdata); } @@ -529,9 +520,9 @@ static struct temp_data *get_temp_data(struct platform_data *pdata, int cpu) /* cpu < 0 means get pkg temp_data */ if (cpu < 0) - return pdata->core_data[PKG_SYSFS_ATTR_NO]; + return pdata->pkg_data; - for (i = BASE_SYSFS_ATTR_NO; i < MAX_CORE_DATA; i++) { + for (i = 0; i < NUM_REAL_CORES; i++) { if (pdata->core_data[i] && pdata->core_data[i]->cpu_core_id == topology_core_id(cpu)) return pdata->core_data[i]; -- cgit v1.2.3 From 18b24a5f9ca3f180f0be72b98f170f043a5d8961 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:43 +0800 Subject: hwmon: (coretemp) Remove redundant temp_data->is_pkg_data temp_data->index saves the index in pdata->core_data[]. It is not used by package temp_data. Use temp_data->index as the indicator of package temp_data and remove redundant temp_data->is_pkg_data. No functional change. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-11-rui.zhang@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 1a3b5ae0baca..e548f2145449 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -70,19 +70,16 @@ enum coretemp_attr_index { * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, * from where the temperature values should be read. * @attr_size: Total number of pre-core attrs displayed in the sysfs. - * @is_pkg_data: If this is 1, the temp_data holds pkgtemp data. - * Otherwise, temp_data holds coretemp data. */ struct temp_data { int temp; int tjmax; unsigned long last_updated; unsigned int cpu; - unsigned int index; + int index; u32 cpu_core_id; u32 status_reg; int attr_size; - bool is_pkg_data; struct device_attribute sd_attrs[TOTAL_ATTRS]; char attr_name[TOTAL_ATTRS][CORETEMP_NAME_LENGTH]; struct attribute *attrs[TOTAL_ATTRS + 1]; @@ -149,6 +146,11 @@ static const struct tjmax_model tjmax_model_table[] = { */ }; +static bool is_pkg_temp_data(struct temp_data *tdata) +{ + return tdata->index < 0; +} + static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) { /* The 100C is default for both mobile and non mobile CPUs */ @@ -341,7 +343,7 @@ static ssize_t show_label(struct device *dev, struct platform_data *pdata = dev_get_drvdata(dev); struct temp_data *tdata = container_of(devattr, struct temp_data, sd_attrs[ATTR_LABEL]); - if (tdata->is_pkg_data) + if (is_pkg_temp_data(tdata)) return sprintf(buf, "Package id %u\n", pdata->pkg_id); return sprintf(buf, "Core %u\n", tdata->cpu_core_id); @@ -433,7 +435,7 @@ static int create_core_attrs(struct temp_data *tdata, struct device *dev) * The attr number is always core id + 2 * The Pkgtemp will always show up as temp1_*, if available */ - int attr_no = tdata->is_pkg_data ? 1 : tdata->cpu_core_id + 2; + int attr_no = is_pkg_temp_data(tdata) ? 1 : tdata->cpu_core_id + 2; snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH, "temp%d_%s", attr_no, suffixes[i]); @@ -484,6 +486,8 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) if (pkg_flag) { pdata->pkg_data = tdata; + /* Use tdata->index as indicator of package temp data */ + tdata->index = -1; } else { tdata->index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL); if (tdata->index < 0) { @@ -495,7 +499,6 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS : MSR_IA32_THERM_STATUS; - tdata->is_pkg_data = pkg_flag; tdata->cpu = cpu; tdata->cpu_core_id = topology_core_id(cpu); tdata->attr_size = MAX_CORE_ATTRS; @@ -505,7 +508,7 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) static void destroy_temp_data(struct platform_data *pdata, struct temp_data *tdata) { - if (tdata->is_pkg_data) { + if (is_pkg_temp_data(tdata)) { pdata->pkg_data = NULL; } else { pdata->core_data[tdata->index] = NULL; -- cgit v1.2.3 From 1a793caf6f6991716cb07583ed7c27de84ef0cba Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 2 Feb 2024 17:21:44 +0800 Subject: hwmon: (coretemp) Use dynamic allocated memory for core temp_data The total memory needed for saving per core temperature data depends on the number of cores in a package. Using static allocated memory wastes memories on systems with low per package core count. Improve the code to use dynamic allocated memory so that it can be improved further when per package core count information becomes available. No functional change intended. Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20240202092144.71180-12-rui.zhang@intel.com [groeck: Fixed continuation line alignment] Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index e548f2145449..30402de2c889 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -91,10 +91,11 @@ struct temp_data { struct platform_data { struct device *hwmon_dev; u16 pkg_id; + int nr_cores; struct ida ida; struct cpumask cpumask; struct temp_data *pkg_data; - struct temp_data *core_data[NUM_REAL_CORES]; + struct temp_data **core_data; struct device_attribute name_attr; }; @@ -480,6 +481,20 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) { struct temp_data *tdata; + if (!pdata->core_data) { + /* + * TODO: + * The information of actual possible cores in a package is broken for now. + * Will replace hardcoded NUM_REAL_CORES with actual per package core count + * when this information becomes available. + */ + pdata->nr_cores = NUM_REAL_CORES; + pdata->core_data = kcalloc(pdata->nr_cores, sizeof(struct temp_data *), + GFP_KERNEL); + if (!pdata->core_data) + return NULL; + } + tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL); if (!tdata) return NULL; @@ -489,7 +504,7 @@ init_temp_data(struct platform_data *pdata, unsigned int cpu, int pkg_flag) /* Use tdata->index as indicator of package temp data */ tdata->index = -1; } else { - tdata->index = ida_alloc_max(&pdata->ida, NUM_REAL_CORES - 1, GFP_KERNEL); + tdata->index = ida_alloc_max(&pdata->ida, pdata->nr_cores - 1, GFP_KERNEL); if (tdata->index < 0) { kfree(tdata); return NULL; @@ -510,6 +525,9 @@ static void destroy_temp_data(struct platform_data *pdata, struct temp_data *tda { if (is_pkg_temp_data(tdata)) { pdata->pkg_data = NULL; + kfree(pdata->core_data); + pdata->core_data = NULL; + pdata->nr_cores = 0; } else { pdata->core_data[tdata->index] = NULL; ida_free(&pdata->ida, tdata->index); @@ -525,7 +543,7 @@ static struct temp_data *get_temp_data(struct platform_data *pdata, int cpu) if (cpu < 0) return pdata->pkg_data; - for (i = 0; i < NUM_REAL_CORES; i++) { + for (i = 0; i < pdata->nr_cores; i++) { if (pdata->core_data[i] && pdata->core_data[i]->cpu_core_id == topology_core_id(cpu)) return pdata->core_data[i]; -- cgit v1.2.3 From 1b2ca93cd0592b1fcbc6f8b64e02552bc15f4bb4 Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Tue, 6 Feb 2024 20:54:20 +0800 Subject: hwmon: Add driver for Astera Labs PT5161L retimer This driver implements support for temperature monitoring of Astera Labs PT5161L series PCIe retimer chips. This driver implementation originates from the CSDK available at Link: https://github.com/facebook/openbmc/tree/helium/common/recipes-lib/retimer-v2.14 The communication protocol utilized is based on the I2C/SMBus standard. Signed-off-by: Cosmo Chou Link: https://lore.kernel.org/r/20240206125420.3884300-2-chou.cosmo@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/pt5161l.rst | 42 +++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/pt5161l.c | 667 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 728 insertions(+) create mode 100644 Documentation/hwmon/pt5161l.rst create mode 100644 drivers/hwmon/pt5161l.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index c19f53d9b3ab..c4af2a894c42 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -190,6 +190,7 @@ Hardware Monitoring Kernel Drivers pmbus powerz powr1220 + pt5161l pxe1610 pwm-fan q54sj108a2 diff --git a/Documentation/hwmon/pt5161l.rst b/Documentation/hwmon/pt5161l.rst new file mode 100644 index 000000000000..1b97336991ea --- /dev/null +++ b/Documentation/hwmon/pt5161l.rst @@ -0,0 +1,42 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver pt5161l +===================== + +Supported chips: + + * Astera Labs PT5161L + + Prefix: 'pt5161l' + + Addresses scanned: I2C 0x20 - 0x27 + + Datasheet: Not publicly available. + +Authors: Cosmo Chou + +Description +----------- + +This driver implements support for temperature monitoring of Astera Labs +PT5161L series PCIe retimer chips. + +This driver implementation originates from the CSDK available at +https://github.com/facebook/openbmc/tree/helium/common/recipes-lib/retimer-v2.14 +The communication protocol utilized is based on the I2C/SMBus standard. + +Sysfs entries +---------------- + +================ ============================================== +temp1_input Measured temperature (in millidegrees Celsius) +================ ============================================== + +Debugfs entries +---------------- + +================ =============================== +fw_load_status Firmware load status +fw_ver Firmware version of the retimer +heartbeat_status Heartbeat status +================ =============================== diff --git a/MAINTAINERS b/MAINTAINERS index 8fc0ca8e881c..afe08a63f7a9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17698,6 +17698,13 @@ F: fs/pstore/ F: include/linux/pstore* K: \b(pstore|ramoops) +PT5161L HARDWARE MONITOR DRIVER +M: Cosmo Chou +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/pt5161l.rst +F: drivers/hwmon/pt5161l.c + PTP HARDWARE CLOCK SUPPORT M: Richard Cochran L: netdev@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e4b24ad93961..56260821d658 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1755,6 +1755,16 @@ source "drivers/hwmon/peci/Kconfig" source "drivers/hwmon/pmbus/Kconfig" +config SENSORS_PT5161L + tristate "Astera Labs PT5161L PCIe retimer hardware monitoring" + depends on I2C + help + If you say yes here you get support for temperature monitoring + on the Astera Labs PT5161L PCIe retimer. + + This driver can also be built as a module. If so, the module + will be called pt5161l. + config SENSORS_PWM_FAN tristate "PWM fan" depends on (PWM && OF) || COMPILE_TEST diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e3faee7be51a..f45c31aff009 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -184,6 +184,7 @@ obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o +obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o diff --git a/drivers/hwmon/pt5161l.c b/drivers/hwmon/pt5161l.c new file mode 100644 index 000000000000..60361e39c474 --- /dev/null +++ b/drivers/hwmon/pt5161l.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Aries current average temp ADC code CSR */ +#define ARIES_CURRENT_AVG_TEMP_ADC_CSR 0x42c + +/* Device Load check register */ +#define ARIES_CODE_LOAD_REG 0x605 +/* Value indicating FW was loaded properly, [3:1] = 3'b111 */ +#define ARIES_LOAD_CODE 0xe + +/* Main Micro Heartbeat register */ +#define ARIES_MM_HEARTBEAT_ADDR 0x923 + +/* Reg offset to specify Address for MM assisted accesses */ +#define ARIES_MM_ASSIST_REG_ADDR_OFFSET 0xd99 +/* Reg offset to specify Command for MM assisted accesses */ +#define ARIES_MM_ASSIST_CMD_OFFSET 0xd9d +/* Reg offset to MM SPARE 0 used specify Address[7:0] */ +#define ARIES_MM_ASSIST_SPARE_0_OFFSET 0xd9f +/* Reg offset to MM SPARE 3 used specify Data Byte 0 */ +#define ARIES_MM_ASSIST_SPARE_3_OFFSET 0xda2 +/* Wide register reads */ +#define ARIES_MM_RD_WIDE_REG_2B 0x1d +#define ARIES_MM_RD_WIDE_REG_3B 0x1e +#define ARIES_MM_RD_WIDE_REG_4B 0x1f +#define ARIES_MM_RD_WIDE_REG_5B 0x20 + +/* Time delay between checking MM status of EEPROM write (microseconds) */ +#define ARIES_MM_STATUS_TIME 5000 + +/* AL Main SRAM DMEM offset (A0) */ +#define AL_MAIN_SRAM_DMEM_OFFSET (64 * 1024) +/* SRAM read command */ +#define AL_TG_RD_LOC_IND_SRAM 0x16 + +/* Offset for main micro FW info */ +#define ARIES_MAIN_MICRO_FW_INFO (96 * 1024 - 128) +/* FW Info (Major) offset location in struct */ +#define ARIES_MM_FW_VERSION_MAJOR 0 +/* FW Info (Minor) offset location in struct */ +#define ARIES_MM_FW_VERSION_MINOR 1 +/* FW Info (Build no.) offset location in struct */ +#define ARIES_MM_FW_VERSION_BUILD 2 + +#define ARIES_TEMP_CAL_CODE_DEFAULT 84 + +/* Struct defining FW version loaded on an Aries device */ +struct pt5161l_fw_ver { + u8 major; + u8 minor; + u16 build; +}; + +/* Each client has this additional data */ +struct pt5161l_data { + struct i2c_client *client; + struct dentry *debugfs; + struct pt5161l_fw_ver fw_ver; + struct mutex lock; /* for atomic I2C transactions */ + bool init_done; + bool code_load_okay; /* indicate if code load reg value is expected */ + bool mm_heartbeat_okay; /* indicate if Main Micro heartbeat is good */ + bool mm_wide_reg_access; /* MM assisted wide register access */ +}; + +static struct dentry *pt5161l_debugfs_dir; + +/* + * Write multiple data bytes to Aries over I2C + */ +static int pt5161l_write_block_data(struct pt5161l_data *data, u32 address, + u8 len, u8 *val) +{ + struct i2c_client *client = data->client; + int ret; + u8 remain_len = len; + u8 xfer_len, curr_len; + u8 buf[16]; + u8 cmd = 0x0F; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ + u8 config = 0x40; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ + + while (remain_len > 0) { + if (remain_len > 4) { + curr_len = 4; + remain_len -= 4; + } else { + curr_len = remain_len; + remain_len = 0; + } + + buf[0] = config | (curr_len - 1) << 1 | ((address >> 16) & 0x1); + buf[1] = (address >> 8) & 0xff; + buf[2] = address & 0xff; + memcpy(&buf[3], val, curr_len); + + xfer_len = 3 + curr_len; + ret = i2c_smbus_write_block_data(client, cmd, xfer_len, buf); + if (ret) + return ret; + + val += curr_len; + address += curr_len; + } + + return 0; +} + +/* + * Read multiple data bytes from Aries over I2C + */ +static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address, + u8 len, u8 *val) +{ + struct i2c_client *client = data->client; + int ret, tries; + u8 remain_len = len; + u8 curr_len; + u8 wbuf[16], rbuf[24]; + u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ + u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ + + while (remain_len > 0) { + if (remain_len > 16) { + curr_len = 16; + remain_len -= 16; + } else { + curr_len = remain_len; + remain_len = 0; + } + + wbuf[0] = config | (curr_len - 1) << 1 | + ((address >> 16) & 0x1); + wbuf[1] = (address >> 8) & 0xff; + wbuf[2] = address & 0xff; + + for (tries = 0; tries < 3; tries++) { + ret = i2c_smbus_write_block_data(client, (cmd | 0x2), 3, + wbuf); + if (ret) + return ret; + + ret = i2c_smbus_read_block_data(client, (cmd | 0x1), + rbuf); + if (ret == curr_len) + break; + } + if (tries >= 3) + return ret; + + memcpy(val, rbuf, curr_len); + val += curr_len; + address += curr_len; + } + + return 0; +} + +static int pt5161l_read_wide_reg(struct pt5161l_data *data, u32 address, + u8 width, u8 *val) +{ + int ret, tries; + u8 buf[8]; + u8 status; + + /* + * Safely access wide registers using mailbox method to prevent + * risking conflict with Aries firmware; otherwise fallback to + * legacy, less secure method. + */ + if (data->mm_wide_reg_access) { + buf[0] = address & 0xff; + buf[1] = (address >> 8) & 0xff; + buf[2] = (address >> 16) & 0x1; + ret = pt5161l_write_block_data(data, + ARIES_MM_ASSIST_SPARE_0_OFFSET, + 3, buf); + if (ret) + return ret; + + /* Set command based on width */ + switch (width) { + case 2: + buf[0] = ARIES_MM_RD_WIDE_REG_2B; + break; + case 3: + buf[0] = ARIES_MM_RD_WIDE_REG_3B; + break; + case 4: + buf[0] = ARIES_MM_RD_WIDE_REG_4B; + break; + case 5: + buf[0] = ARIES_MM_RD_WIDE_REG_5B; + break; + default: + return -EINVAL; + } + ret = pt5161l_write_block_data(data, ARIES_MM_ASSIST_CMD_OFFSET, + 1, buf); + if (ret) + return ret; + + status = 0xff; + for (tries = 0; tries < 100; tries++) { + ret = pt5161l_read_block_data(data, + ARIES_MM_ASSIST_CMD_OFFSET, + 1, &status); + if (ret) + return ret; + + if (status == 0) + break; + + usleep_range(ARIES_MM_STATUS_TIME, + ARIES_MM_STATUS_TIME + 1000); + } + if (status != 0) + return -ETIMEDOUT; + + ret = pt5161l_read_block_data(data, + ARIES_MM_ASSIST_SPARE_3_OFFSET, + width, val); + if (ret) + return ret; + } else { + return pt5161l_read_block_data(data, address, width, val); + } + + return 0; +} + +/* + * Read multiple (up to eight) data bytes from micro SRAM over I2C + */ +static int +pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data *data, + u32 address, u8 len, u8 *val) +{ + int ret, tries; + u8 buf[8]; + u8 i, status; + u32 uind_offs = ARIES_MM_ASSIST_REG_ADDR_OFFSET; + u32 eeprom_base, eeprom_addr; + + /* No multi-byte indirect support here. Hence read a byte at a time */ + eeprom_base = address - AL_MAIN_SRAM_DMEM_OFFSET; + for (i = 0; i < len; i++) { + eeprom_addr = eeprom_base + i; + buf[0] = eeprom_addr & 0xff; + buf[1] = (eeprom_addr >> 8) & 0xff; + buf[2] = (eeprom_addr >> 16) & 0xff; + ret = pt5161l_write_block_data(data, uind_offs, 3, buf); + if (ret) + return ret; + + buf[0] = AL_TG_RD_LOC_IND_SRAM; + ret = pt5161l_write_block_data(data, uind_offs + 4, 1, buf); + if (ret) + return ret; + + status = 0xff; + for (tries = 0; tries < 255; tries++) { + ret = pt5161l_read_block_data(data, uind_offs + 4, 1, + &status); + if (ret) + return ret; + + if (status == 0) + break; + } + if (status != 0) + return -ETIMEDOUT; + + ret = pt5161l_read_block_data(data, uind_offs + 3, 1, buf); + if (ret) + return ret; + + val[i] = buf[0]; + } + + return 0; +} + +/* + * Check firmware load status + */ +static int pt5161l_fw_load_check(struct pt5161l_data *data) +{ + int ret; + u8 buf[8]; + + ret = pt5161l_read_block_data(data, ARIES_CODE_LOAD_REG, 1, buf); + if (ret) + return ret; + + if (buf[0] < ARIES_LOAD_CODE) { + dev_dbg(&data->client->dev, + "Code Load reg unexpected. Not all modules are loaded %x\n", + buf[0]); + data->code_load_okay = false; + } else { + data->code_load_okay = true; + } + + return 0; +} + +/* + * Check main micro heartbeat + */ +static int pt5161l_heartbeat_check(struct pt5161l_data *data) +{ + int ret, tries; + u8 buf[8]; + u8 heartbeat; + bool hb_changed = false; + + ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, buf); + if (ret) + return ret; + + heartbeat = buf[0]; + for (tries = 0; tries < 100; tries++) { + ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, + buf); + if (ret) + return ret; + + if (buf[0] != heartbeat) { + hb_changed = true; + break; + } + } + data->mm_heartbeat_okay = hb_changed; + + return 0; +} + +/* + * Check the status of firmware + */ +static int pt5161l_fwsts_check(struct pt5161l_data *data) +{ + int ret; + u8 buf[8]; + u8 major = 0, minor = 0; + u16 build = 0; + + ret = pt5161l_fw_load_check(data); + if (ret) + return ret; + + ret = pt5161l_heartbeat_check(data); + if (ret) + return ret; + + if (data->code_load_okay && data->mm_heartbeat_okay) { + ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + + ARIES_MM_FW_VERSION_MAJOR, + 1, &major); + if (ret) + return ret; + + ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + + ARIES_MM_FW_VERSION_MINOR, + 1, &minor); + if (ret) + return ret; + + ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + + ARIES_MM_FW_VERSION_BUILD, + 2, buf); + if (ret) + return ret; + build = buf[1] << 8 | buf[0]; + } + data->fw_ver.major = major; + data->fw_ver.minor = minor; + data->fw_ver.build = build; + + return 0; +} + +static int pt5161l_fw_is_at_least(struct pt5161l_data *data, u8 major, u8 minor, + u16 build) +{ + u32 ver = major << 24 | minor << 16 | build; + u32 curr_ver = data->fw_ver.major << 24 | data->fw_ver.minor << 16 | + data->fw_ver.build; + + if (curr_ver >= ver) + return true; + + return false; +} + +static int pt5161l_init_dev(struct pt5161l_data *data) +{ + int ret; + + mutex_lock(&data->lock); + ret = pt5161l_fwsts_check(data); + mutex_unlock(&data->lock); + if (ret) + return ret; + + /* Firmware 2.2.0 enables safe access to wide registers */ + if (pt5161l_fw_is_at_least(data, 2, 2, 0)) + data->mm_wide_reg_access = true; + + data->init_done = true; + + return 0; +} + +static int pt5161l_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct pt5161l_data *data = dev_get_drvdata(dev); + int ret; + u8 buf[8]; + long adc_code; + + switch (attr) { + case hwmon_temp_input: + if (!data->init_done) { + ret = pt5161l_init_dev(data); + if (ret) + return ret; + } + + mutex_lock(&data->lock); + ret = pt5161l_read_wide_reg(data, + ARIES_CURRENT_AVG_TEMP_ADC_CSR, 4, + buf); + mutex_unlock(&data->lock); + if (ret) { + dev_dbg(dev, "Read adc_code failed %d\n", ret); + return ret; + } + + adc_code = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]; + if (adc_code == 0 || adc_code >= 0x3ff) { + dev_dbg(dev, "Invalid adc_code %lx\n", adc_code); + return -EIO; + } + + *val = 110000 + + ((adc_code - (ARIES_TEMP_CAL_CODE_DEFAULT + 250)) * + -320); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static umode_t pt5161l_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + switch (attr) { + case hwmon_temp_input: + return 0444; + default: + break; + } + + return 0; +} + +static const struct hwmon_channel_info *pt5161l_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_ops pt5161l_hwmon_ops = { + .is_visible = pt5161l_is_visible, + .read = pt5161l_read, +}; + +static const struct hwmon_chip_info pt5161l_chip_info = { + .ops = &pt5161l_hwmon_ops, + .info = pt5161l_info, +}; + +static ssize_t pt5161l_debugfs_read_fw_ver(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt5161l_data *data = file->private_data; + int ret; + char ver[32]; + + mutex_lock(&data->lock); + ret = pt5161l_fwsts_check(data); + mutex_unlock(&data->lock); + if (ret) + return ret; + + ret = snprintf(ver, sizeof(ver), "%u.%u.%u\n", data->fw_ver.major, + data->fw_ver.minor, data->fw_ver.build); + + return simple_read_from_buffer(buf, count, ppos, ver, ret); +} + +static const struct file_operations pt5161l_debugfs_ops_fw_ver = { + .read = pt5161l_debugfs_read_fw_ver, + .open = simple_open, +}; + +static ssize_t pt5161l_debugfs_read_fw_load_sts(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + struct pt5161l_data *data = file->private_data; + int ret; + bool status = false; + char health[16]; + + mutex_lock(&data->lock); + ret = pt5161l_fw_load_check(data); + mutex_unlock(&data->lock); + if (ret == 0) + status = data->code_load_okay; + + ret = snprintf(health, sizeof(health), "%s\n", + status ? "normal" : "abnormal"); + + return simple_read_from_buffer(buf, count, ppos, health, ret); +} + +static const struct file_operations pt5161l_debugfs_ops_fw_load_sts = { + .read = pt5161l_debugfs_read_fw_load_sts, + .open = simple_open, +}; + +static ssize_t pt5161l_debugfs_read_hb_sts(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pt5161l_data *data = file->private_data; + int ret; + bool status = false; + char health[16]; + + mutex_lock(&data->lock); + ret = pt5161l_heartbeat_check(data); + mutex_unlock(&data->lock); + if (ret == 0) + status = data->mm_heartbeat_okay; + + ret = snprintf(health, sizeof(health), "%s\n", + status ? "normal" : "abnormal"); + + return simple_read_from_buffer(buf, count, ppos, health, ret); +} + +static const struct file_operations pt5161l_debugfs_ops_hb_sts = { + .read = pt5161l_debugfs_read_hb_sts, + .open = simple_open, +}; + +static int pt5161l_init_debugfs(struct pt5161l_data *data) +{ + data->debugfs = debugfs_create_dir(dev_name(&data->client->dev), + pt5161l_debugfs_dir); + + debugfs_create_file("fw_ver", 0444, data->debugfs, data, + &pt5161l_debugfs_ops_fw_ver); + + debugfs_create_file("fw_load_status", 0444, data->debugfs, data, + &pt5161l_debugfs_ops_fw_load_sts); + + debugfs_create_file("heartbeat_status", 0444, data->debugfs, data, + &pt5161l_debugfs_ops_hb_sts); + + return 0; +} + +static int pt5161l_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct pt5161l_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->lock); + pt5161l_init_dev(data); + dev_set_drvdata(dev, data); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &pt5161l_chip_info, + NULL); + + pt5161l_init_debugfs(data); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static void pt5161l_remove(struct i2c_client *client) +{ + struct pt5161l_data *data = i2c_get_clientdata(client); + + debugfs_remove_recursive(data->debugfs); +} + +static const struct of_device_id __maybe_unused pt5161l_of_match[] = { + { .compatible = "asteralabs,pt5161l" }, + {}, +}; +MODULE_DEVICE_TABLE(of, pt5161l_of_match); + +static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = { + { "PT5161L", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match); + +static const struct i2c_device_id pt5161l_id[] = { + { "pt5161l", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pt5161l_id); + +static struct i2c_driver pt5161l_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "pt5161l", + .of_match_table = of_match_ptr(pt5161l_of_match), + .acpi_match_table = ACPI_PTR(pt5161l_acpi_match), + }, + .probe = pt5161l_probe, + .remove = pt5161l_remove, + .id_table = pt5161l_id, +}; + +static int __init pt5161l_init(void) +{ + pt5161l_debugfs_dir = debugfs_create_dir("pt5161l", NULL); + return i2c_add_driver(&pt5161l_driver); +} + +static void __exit pt5161l_exit(void) +{ + i2c_del_driver(&pt5161l_driver); + debugfs_remove_recursive(pt5161l_debugfs_dir); +} + +module_init(pt5161l_init); +module_exit(pt5161l_exit); + +MODULE_AUTHOR("Cosmo Chou "); +MODULE_DESCRIPTION("Hwmon driver for Astera Labs Aries PCIe retimer"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From f16fb6d23b68699eed97fe1edee0d8eecde14a67 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 7 Feb 2024 22:17:08 +0100 Subject: hwmon: (chipcap2) fix uninitialized variable in cc2_get_reg_val() The reg_val variable in cc2_get_reg_val() might be used without a known value if cc2_read_reg() fails. That leads to a useless data conversion because the returned error means the read operation failed and the data is not relevant. That makes its initial value irrelevant as well, so skip the data conversion instead. If no error happens, a value is assigned to reg_val and the data conversion is required. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-hwmon/294e4634-89d4-415e-a723-b208d8770d7c@gmail.com/T/#t Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240207-chipcap2_init_vars-v1-1-08cafe43e20e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index a62c507b1042..3b604fc5d8ae 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -324,7 +324,9 @@ static int cc2_get_reg_val(struct cc2_data *data, u8 reg, long *val) int ret; ret = cc2_read_reg(data, reg, ®_val); - *val = cc2_rh_convert(reg_val); + if (!ret) + *val = cc2_rh_convert(reg_val); + cc2_disable(data); return ret; -- cgit v1.2.3 From efd49b8eef659e0aa962ec1dbf18740e34735219 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Wed, 7 Feb 2024 22:17:09 +0100 Subject: hwmon: (chipcap2) fix return path in cc2_request_alarm_irqs() The return path can be improved by returning upon first failure. The current implementation would try to register the second interrupt even if the first one failed, which is unnecessary. Moreover, if no irqs are available, the return value should be zero (the driver supports the use case with no interrupts). Currently the initial value is unassigned and that may lead to returning an unknown value if stack variables are not automatically set to zero and no irqs were provided. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-hwmon/294e4634-89d4-415e-a723-b208d8770d7c@gmail.com/T/#t Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20240207-chipcap2_init_vars-v1-2-08cafe43e20e@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/chipcap2.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c index 3b604fc5d8ae..6ccceae21f70 100644 --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -670,7 +670,7 @@ static int cc2_request_ready_irq(struct cc2_data *data, struct device *dev) static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) { - int ret; + int ret = 0; data->irq_low = fwnode_irq_get_byname(dev_fwnode(dev), "low"); if (data->irq_low > 0) { @@ -679,8 +679,10 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) IRQF_ONESHOT | IRQF_TRIGGER_RISING, dev_name(dev), data); - if (!ret) - data->rh_alarm.low_alarm_visible = true; + if (ret) + return ret; + + data->rh_alarm.low_alarm_visible = true; } data->irq_high = fwnode_irq_get_byname(dev_fwnode(dev), "high"); @@ -690,8 +692,10 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) IRQF_ONESHOT | IRQF_TRIGGER_RISING, dev_name(dev), data); - if (!ret) - data->rh_alarm.high_alarm_visible = true; + if (ret) + return ret; + + data->rh_alarm.high_alarm_visible = true; } return ret; -- cgit v1.2.3 From 692cf83bc3c10c8af4dce0d9d2596c72b1037f6c Mon Sep 17 00:00:00 2001 From: Sebastian Kranz Date: Fri, 9 Feb 2024 10:01:23 +0100 Subject: hwmon: (oxp-sensors) Add support for Ayaneo Air Plus 7320u. Add support for handheld AYANEO AIR Plus with the same EC registers to add proper fan control. Functionality was tested successfully. Signed-off-by: Sebastian Kranz Link: https://lore.kernel.org/r/20240209090157.3232-1-tklightforce@googlemail.com [groeck: Fixed up commit message] Signed-off-by: Guenter Roeck --- Documentation/hwmon/oxp-sensors.rst | 1 + drivers/hwmon/oxp-sensors.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Documentation/hwmon/oxp-sensors.rst b/Documentation/hwmon/oxp-sensors.rst index 3adeb7406243..55b1ef61625e 100644 --- a/Documentation/hwmon/oxp-sensors.rst +++ b/Documentation/hwmon/oxp-sensors.rst @@ -33,6 +33,7 @@ Currently the driver supports the following handhelds: - AOK ZOE A1 PRO - Aya Neo 2 - Aya Neo AIR + - Aya Neo AIR Plus (Mendocino) - Aya Neo AIR Pro - Aya Neo Geek - OneXPlayer AMD diff --git a/drivers/hwmon/oxp-sensors.c b/drivers/hwmon/oxp-sensors.c index ea9602063eab..8d3b0f86cc57 100644 --- a/drivers/hwmon/oxp-sensors.c +++ b/drivers/hwmon/oxp-sensors.c @@ -43,6 +43,7 @@ enum oxp_board { aok_zoe_a1 = 1, aya_neo_2, aya_neo_air, + aya_neo_air_plus_mendo, aya_neo_air_pro, aya_neo_geek, oxp_mini_amd, @@ -98,6 +99,13 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = (void *)aya_neo_air, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"), + }, + .driver_data = (void *)aya_neo_air_plus_mendo, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"), @@ -332,6 +340,7 @@ static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type, switch (board) { case aya_neo_2: case aya_neo_air: + case aya_neo_air_plus_mendo: case aya_neo_air_pro: case aya_neo_geek: case oxp_mini_amd: @@ -374,6 +383,7 @@ static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type, switch (board) { case aya_neo_2: case aya_neo_air: + case aya_neo_air_plus_mendo: case aya_neo_air_pro: case aya_neo_geek: case oxp_mini_amd: -- cgit v1.2.3 From 1b5239f70fcd2d7fe86f0f5473006c2896db07a8 Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Wed, 14 Feb 2024 15:36:43 +0100 Subject: hwmon: (axi-fan-control) Use device firmware agnostic API Don't directly use OF and use device property APIs. In addition, this makes the probe() code neater and also allow us to move the of_device_id table to it's natural place. While at it, make sure to explicitly include mod_devicetable.h for the of_device_id table. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240214-axi-fan-control-no-of-v1-1-43ca656fe2e3@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index 19b9bf3d75ef..8dfe3b6c5a17 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -13,8 +13,9 @@ #include #include #include -#include +#include #include +#include /* register map */ #define ADI_REG_RSTN 0x0080 @@ -368,12 +369,12 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data) } static int axi_fan_control_init(struct axi_fan_control_data *ctl, - const struct device_node *np) + const struct device *dev) { int ret; /* get fan pulses per revolution */ - ret = of_property_read_u32(np, "pulses-per-revolution", &ctl->ppr); + ret = device_property_read_u32(dev, "pulses-per-revolution", &ctl->ppr); if (ret) return ret; @@ -443,25 +444,16 @@ static struct attribute *axi_fan_control_attrs[] = { }; 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[] = { - { .compatible = "adi,axi-fan-control-1.00.a", - .data = (void *)&version_1_0_0}, - {}, -}; -MODULE_DEVICE_TABLE(of, axi_fan_control_of_match); - static int axi_fan_control_probe(struct platform_device *pdev) { struct axi_fan_control_data *ctl; struct clk *clk; - const struct of_device_id *id; + const unsigned int *id; const char *name = "axi_fan_control"; u32 version; int ret; - id = of_match_node(axi_fan_control_of_match, pdev->dev.of_node); + id = device_get_match_data(&pdev->dev); if (!id) return -EINVAL; @@ -485,18 +477,18 @@ static int axi_fan_control_probe(struct platform_device *pdev) version = axi_ioread(ADI_AXI_REG_VERSION, ctl); if (ADI_AXI_PCORE_VER_MAJOR(version) != - ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data))) { + ADI_AXI_PCORE_VER_MAJOR((*id))) { dev_err(&pdev->dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", - ADI_AXI_PCORE_VER_MAJOR((*(u32 *)id->data)), - ADI_AXI_PCORE_VER_MINOR((*(u32 *)id->data)), - ADI_AXI_PCORE_VER_PATCH((*(u32 *)id->data)), + ADI_AXI_PCORE_VER_MAJOR(*id), + ADI_AXI_PCORE_VER_MINOR(*id), + ADI_AXI_PCORE_VER_PATCH(*id), ADI_AXI_PCORE_VER_MAJOR(version), ADI_AXI_PCORE_VER_MINOR(version), ADI_AXI_PCORE_VER_PATCH(version)); return -ENODEV; } - ret = axi_fan_control_init(ctl, pdev->dev.of_node); + ret = axi_fan_control_init(ctl, &pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize device\n"); return ret; @@ -527,6 +519,15 @@ static int axi_fan_control_probe(struct platform_device *pdev) return 0; } +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[] = { + { .compatible = "adi,axi-fan-control-1.00.a", + .data = (void *)&version_1_0_0}, + {}, +}; +MODULE_DEVICE_TABLE(of, axi_fan_control_of_match); + static struct platform_driver axi_fan_control_driver = { .driver = { .name = "axi_fan_control_driver", -- cgit v1.2.3 From 0b5f91d47d72d706360777fe03e40eab2f51a94a Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Wed, 14 Feb 2024 15:36:44 +0100 Subject: hwmon: (axi-fan-control) Make use of sysfs_emit() Use sysfs_emit() instead of directly call sprintf(). Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240214-axi-fan-control-no-of-v1-2-43ca656fe2e3@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index 8dfe3b6c5a17..efd42a4f5951 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -84,7 +84,7 @@ static ssize_t axi_fan_control_show(struct device *dev, struct device_attribute temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230; - return sprintf(buf, "%u\n", temp); + return sysfs_emit(buf, "%u\n", temp); } static ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da, -- cgit v1.2.3 From ec823656c1e0e5f49e92ed86cee9fb26585da18e Mon Sep 17 00:00:00 2001 From: Nuno Sa Date: Wed, 14 Feb 2024 15:36:45 +0100 Subject: hwmon: (axi-fan-control) Make use of dev_err_probe() Use dev_err_probe() to slightly simplify printing errors during probe. No functional changes intended. Signed-off-by: Nuno Sa Link: https://lore.kernel.org/r/20240214-axi-fan-control-no-of-v1-3-43ca656fe2e3@analog.com Signed-off-by: Guenter Roeck --- drivers/hwmon/axi-fan-control.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index efd42a4f5951..35c862eb158b 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -466,10 +466,9 @@ static int axi_fan_control_probe(struct platform_device *pdev) return PTR_ERR(ctl->base); clk = devm_clk_get_enabled(&pdev->dev, NULL); - if (IS_ERR(clk)) { - dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk)); - return PTR_ERR(clk); - } + if (IS_ERR(clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), + "clk_get failed\n"); ctl->clk_rate = clk_get_rate(clk); if (!ctl->clk_rate) @@ -477,22 +476,20 @@ static int axi_fan_control_probe(struct platform_device *pdev) version = axi_ioread(ADI_AXI_REG_VERSION, ctl); if (ADI_AXI_PCORE_VER_MAJOR(version) != - ADI_AXI_PCORE_VER_MAJOR((*id))) { - dev_err(&pdev->dev, "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", - ADI_AXI_PCORE_VER_MAJOR(*id), - ADI_AXI_PCORE_VER_MINOR(*id), - ADI_AXI_PCORE_VER_PATCH(*id), - ADI_AXI_PCORE_VER_MAJOR(version), - ADI_AXI_PCORE_VER_MINOR(version), - ADI_AXI_PCORE_VER_PATCH(version)); - return -ENODEV; - } + ADI_AXI_PCORE_VER_MAJOR((*id))) + return dev_err_probe(&pdev->dev, -ENODEV, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(*id), + ADI_AXI_PCORE_VER_MINOR(*id), + ADI_AXI_PCORE_VER_PATCH(*id), + ADI_AXI_PCORE_VER_MAJOR(version), + ADI_AXI_PCORE_VER_MINOR(version), + ADI_AXI_PCORE_VER_PATCH(version)); ret = axi_fan_control_init(ctl, &pdev->dev); - if (ret) { - dev_err(&pdev->dev, "Failed to initialize device\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to initialize device\n"); ctl->hdev = devm_hwmon_device_register_with_info(&pdev->dev, name, @@ -511,10 +508,9 @@ static int axi_fan_control_probe(struct platform_device *pdev) axi_fan_control_irq_handler, IRQF_ONESHOT | IRQF_TRIGGER_HIGH, pdev->driver_override, ctl); - if (ret) { - dev_err(&pdev->dev, "failed to request an irq, %d", ret); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to request an irq\n"); return 0; } -- cgit v1.2.3 From d88cef1d17f62f9a7bcefb8f37d222c8fcc89f6c Mon Sep 17 00:00:00 2001 From: Okan Akyuz Date: Fri, 16 Feb 2024 10:52:12 +0300 Subject: hwmon: (max6620) Update broken Datasheet URL in driver documentation The URL for the MAX6620 datasheet has changed. Update it to reflect the current location. Signed-off-by: Okan Akyuz Link: https://lore.kernel.org/r/20240216075212.69118-1-okanakyuz@okanakyuz.com [groeck: Updated subject and patch description] Signed-off-by: Guenter Roeck --- Documentation/hwmon/max6620.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/hwmon/max6620.rst b/Documentation/hwmon/max6620.rst index 84c1c44d3de4..d70173bf0242 100644 --- a/Documentation/hwmon/max6620.rst +++ b/Documentation/hwmon/max6620.rst @@ -11,7 +11,7 @@ Supported chips: Addresses scanned: none - Datasheet: http://pdfserv.maxim-ic.com/en/ds/MAX6620.pdf + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max6620.pdf Authors: - L\. Grunenberg -- cgit v1.2.3 From 4adee4e1a354bd318205afd3f8defc99299fb47a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 17 Feb 2024 08:16:51 -0800 Subject: MAINTAINERS: Drop redundant hwmon entries I am listed as maintainer of several individual hardware monitoring drivers and for the hardware monitoring subsystem itself. That is redundant and just bloats the MAINTAINERS file. Drop all the redundant entries. Signed-off-by: Guenter Roeck --- MAINTAINERS | 108 ------------------------------------------------------------ 1 file changed, 108 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index afe08a63f7a9..9c14c97d1056 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10493,22 +10493,6 @@ L: linux-fbdev@vger.kernel.org S: Orphan F: drivers/video/fbdev/imsttfb.c -INA209 HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml -F: Documentation/hwmon/ina209.rst -F: drivers/hwmon/ina209.c - -INA2XX HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/hwmon/ina2xx.rst -F: drivers/hwmon/ina2xx.c -F: include/linux/platform_data/ina2xx.h - INDEX OF FURTHER KERNEL DOCUMENTATION M: Carlos Bilbao S: Maintained @@ -11484,14 +11468,6 @@ S: Maintained F: arch/x86/include/asm/jailhouse_para.h F: arch/x86/kernel/jailhouse.c -JC42.4 TEMPERATURE SENSOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/hwmon/jedec,jc42.yaml -F: Documentation/hwmon/jc42.rst -F: drivers/hwmon/jc42.c - JFS FILESYSTEM M: Dave Kleikamp L: jfs-discussion@lists.sourceforge.net @@ -12557,13 +12533,6 @@ F: Documentation/hwmon/lm90.rst F: drivers/hwmon/lm90.c F: include/dt-bindings/thermal/lm90.h -LM95234 HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/hwmon/lm95234.rst -F: drivers/hwmon/lm95234.c - LME2510 MEDIA DRIVER M: Malcolm Priestley L: linux-media@vger.kernel.org @@ -12767,13 +12736,6 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml F: drivers/iio/temperature/ltc2983.c -LTC4261 HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/hwmon/ltc4261.rst -F: drivers/hwmon/ltc4261.c - LTC4282 HARDWARE MONITOR DRIVER M: Nuno Sa L: linux-hwmon@vger.kernel.org @@ -13129,13 +13091,6 @@ S: Maintained F: Documentation/hwmon/max15301.rst F: drivers/hwmon/pmbus/max15301.c -MAX16065 HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/hwmon/max16065.rst -F: drivers/hwmon/max16065.c - MAX2175 SDR TUNER DRIVER M: Ramesh Shanmugasundaram L: linux-media@vger.kernel.org @@ -13160,15 +13115,6 @@ S: Orphan F: Documentation/hwmon/max6650.rst F: drivers/hwmon/max6650.c -MAX6697 HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/hwmon/max6697.txt -F: Documentation/hwmon/max6697.rst -F: drivers/hwmon/max6697.c -F: include/linux/platform_data/max6697.h - MAX9286 QUAD GMSL DESERIALIZER DRIVER M: Jacopo Mondi M: Kieran Bingham @@ -15075,15 +15021,6 @@ M: Samuel Mendoza-Jonas S: Maintained F: net/ncsi/ -NCT6775 HARDWARE MONITOR DRIVER - CORE & PLATFORM DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/hwmon/nct6775.rst -F: drivers/hwmon/nct6775-core.c -F: drivers/hwmon/nct6775-platform.c -F: drivers/hwmon/nct6775.h - NCT6775 HARDWARE MONITOR DRIVER - I2C DRIVER M: Zev Weiss L: linux-hwmon@vger.kernel.org @@ -17450,35 +17387,6 @@ S: Maintained F: Documentation/hwmon/pm6764tr.rst F: drivers/hwmon/pmbus/pm6764tr.c -PMBUS HARDWARE MONITORING DRIVERS -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -W: http://hwmon.wiki.kernel.org/ -W: http://www.roeck-us.net/linux/drivers/ -T: git git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git -F: Documentation/devicetree/bindings/hwmon/ltc2978.txt -F: Documentation/devicetree/bindings/hwmon/max31785.txt -F: Documentation/hwmon/adm1275.rst -F: Documentation/hwmon/ibm-cffps.rst -F: Documentation/hwmon/ir35221.rst -F: Documentation/hwmon/lm25066.rst -F: Documentation/hwmon/ltc2978.rst -F: Documentation/hwmon/ltc3815.rst -F: Documentation/hwmon/max16064.rst -F: Documentation/hwmon/max20751.rst -F: Documentation/hwmon/max31785.rst -F: Documentation/hwmon/max34440.rst -F: Documentation/hwmon/max8688.rst -F: Documentation/hwmon/pmbus-core.rst -F: Documentation/hwmon/pmbus.rst -F: Documentation/hwmon/tps40422.rst -F: Documentation/hwmon/ucd9000.rst -F: Documentation/hwmon/ucd9200.rst -F: Documentation/hwmon/zl6100.rst -F: drivers/hwmon/pmbus/ -F: include/linux/pmbus.h - PMC SIERRA MaxRAID DRIVER L: linux-scsi@vger.kernel.org S: Orphan @@ -22182,22 +22090,6 @@ F: drivers/mmc/host/renesas_sdhi* F: drivers/mmc/host/tmio_mmc* F: include/linux/mfd/tmio.h -TMP401 HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml -F: Documentation/hwmon/tmp401.rst -F: drivers/hwmon/tmp401.c - -TMP464 HARDWARE MONITOR DRIVER -M: Guenter Roeck -L: linux-hwmon@vger.kernel.org -S: Maintained -F: Documentation/devicetree/bindings/hwmon/ti,tmp464.yaml -F: Documentation/hwmon/tmp464.rst -F: drivers/hwmon/tmp464.c - TMP513 HARDWARE MONITOR DRIVER M: Eric Tremblay L: linux-hwmon@vger.kernel.org -- cgit v1.2.3 From 22d409ead1f4f388e368812731f2f8b8892ed615 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 21 Feb 2024 07:51:58 -0800 Subject: dt-bindings: hwmon: nuvoton,nct6775: Add compatible value for NCT6799 While NCT6799 is mostly compatible to NCT6798, it needs a separate compatible entry because it is not completely compatible and does require chip specific code in the driver. Signed-off-by: Guenter Roeck Acked-by: Conor Dooley Acked-by: Zev Weiss Link: https://lore.kernel.org/r/20240221155158.2234898-1-linux@roeck-us.net Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml b/Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml index 358b262431fc..e3db642878d4 100644 --- a/Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml +++ b/Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml @@ -25,6 +25,7 @@ properties: - nuvoton,nct6796 - nuvoton,nct6797 - nuvoton,nct6798 + - nuvoton,nct6799 reg: maxItems: 1 -- cgit v1.2.3 From f1ed8af54bba07c81af97a4c999686d2ccafbfb3 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Fri, 23 Feb 2024 16:21:05 +0000 Subject: dt-bindings: hwmon/pmbus: ti,lm25066: document regulators All devices documented in the lm25066 binding are intended for use with a regulator, be that for purely monitoring purposes (lm25056) or, for the other devices, as the controller of that regulator. The binding does not currently allow regulator child nodes, so add one. Each of these devices interacts with only a single regulator and documentation refers to it as "Vout", hence the choice of child node name. Acked-by: Zev Weiss Signed-off-by: Conor Dooley Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240223-tingling-mutt-dd55dd87ff5e@spud Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml index da8292bc32f5..4373e9c86e54 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml @@ -34,6 +34,18 @@ properties: Shunt (sense) resistor value in micro-Ohms default: 1000 + regulators: + type: object + + properties: + vout: + $ref: /schemas/regulator/regulator.yaml# + type: object + + unevaluatedProperties: false + + additionalProperties: false + required: - compatible - reg -- cgit v1.2.3 From bad582f9879812bcf74adb520e005051eb021cfb Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Fri, 23 Feb 2024 16:21:06 +0000 Subject: regulator: dt-bindings: promote infineon buck converters to their own binding These devices are regulators may need to make use of the common regulator properties, but these are not permitted while only documented in trivial-devices.yaml Signed-off-by: Conor Dooley Reviewed-by: Krzysztof Kozlowski Reviewed-by: Mark Brown Link: https://lore.kernel.org/r/20240223-blabber-obnoxious-353e519541a6@spud [groeck: Dropped empty line at end of patch] Signed-off-by: Guenter Roeck --- .../bindings/regulator/infineon,ir38060.yaml | 45 ++++++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 8 ---- 2 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 Documentation/devicetree/bindings/regulator/infineon,ir38060.yaml diff --git a/Documentation/devicetree/bindings/regulator/infineon,ir38060.yaml b/Documentation/devicetree/bindings/regulator/infineon,ir38060.yaml new file mode 100644 index 000000000000..e6ffbc2a2298 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/infineon,ir38060.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/regulator/infineon,ir38060.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Infineon Buck Regulators with PMBUS interfaces + +maintainers: + - Not Me. + +allOf: + - $ref: regulator.yaml# + +properties: + compatible: + enum: + - infineon,ir38060 + - infineon,ir38064 + - infineon,ir38164 + - infineon,ir38263 + + reg: + maxItems: 1 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@34 { + compatible = "infineon,ir38060"; + reg = <0x34>; + + regulator-min-microvolt = <437500>; + regulator-max-microvolt = <1387500>; + }; + }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 842eb65e4c03..2210964faaf6 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -143,14 +143,6 @@ properties: - ibm,cffps2 # Infineon IR36021 digital POL buck controller - infineon,ir36021 - # Infineon IR38060 Voltage Regulator - - infineon,ir38060 - # Infineon IR38064 Voltage Regulator - - infineon,ir38064 - # Infineon IR38164 Voltage Regulator - - infineon,ir38164 - # Infineon IR38263 Voltage Regulator - - infineon,ir38263 # Infineon IRPS5401 Voltage Regulator (PMIC) - infineon,irps5401 # Infineon TLV493D-A1B6 I2C 3D Magnetic Sensor -- cgit v1.2.3 From 8be143b978717ce953c1c42cc57c36cd543334a4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 23 Feb 2024 16:21:07 +0000 Subject: hwmon: (pmbus/tda38640) Use PMBUS_REGULATOR_ONE to declare regulator If a chip only provides a single regulator, it should be named 'vout' and not 'vout0'. Declare regulator using PMBUS_REGULATOR_ONE() to make that happen. Cc: Conor Dooley Cc: Naresh Solanki Cc: Patrick Rudolph Signed-off-by: Guenter Roeck Signed-off-by: Conor Dooley Link: https://lore.kernel.org/r/20240223-catnap-companion-c42fdd8ad110@spud Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/tda38640.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/tda38640.c b/drivers/hwmon/pmbus/tda38640.c index 09cd114b1736..c31889a036f0 100644 --- a/drivers/hwmon/pmbus/tda38640.c +++ b/drivers/hwmon/pmbus/tda38640.c @@ -15,7 +15,7 @@ #include "pmbus.h" static const struct regulator_desc __maybe_unused tda38640_reg_desc[] = { - PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR_ONE("vout"), }; struct tda38640_data { -- cgit v1.2.3 From 8f832d23ead3108eb74afffb5def676f8cebf35c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 23 Feb 2024 16:21:08 +0000 Subject: hwmon: (pmbus/lm25066) Use PMBUS_REGULATOR_ONE to declare regulator If a chip only provides a single regulator, it should be named 'vout' and not 'vout0'. Declare regulator using PMBUS_REGULATOR_ONE() to make that happen. Cc: Conor Dooley Cc: Naresh Solanki Cc: Zev Weiss Signed-off-by: Guenter Roeck Signed-off-by: Conor Dooley Link: https://lore.kernel.org/r/20240223-player-buckskin-01405c5889c4@spud Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/lm25066.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 3a20df5a43ec..cfffa4cdc0df 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -437,7 +437,7 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, #if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) static const struct regulator_desc lm25066_reg_desc[] = { - PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR_ONE("vout"), }; #endif -- cgit v1.2.3 From cb7222997e9c64326f4e58d0aad02007225e4975 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 23 Feb 2024 16:21:09 +0000 Subject: hwmon: (pmbus/ir38064) Use PMBUS_REGULATOR_ONE to declare regulator If a chip only provides a single regulator, it should be named 'vout' and not 'vout0'. Declare regulator using PMBUS_REGULATOR_ONE() to make that happen. Cc: Conor Dooley Cc: Naresh Solanki Cc: Patrick Rudolph Signed-off-by: Guenter Roeck Signed-off-by: Conor Dooley Link: https://lore.kernel.org/r/20240223-harmless-covenant-9cd3d4f1cfd2@spud Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ir38064.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 04185be3fdb6..69e18cb468f6 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -22,7 +22,7 @@ #if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) static const struct regulator_desc ir38064_reg_desc[] = { - PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR_ONE("vout"), }; #endif /* CONFIG_SENSORS_IR38064_REGULATOR */ -- cgit v1.2.3 From d590900b62f0a502931b331f2e7a1748580f403f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 24 Feb 2024 11:14:50 +0100 Subject: dt-bindings: hwmon: add common properties Add a common hwmon schema for two properties: 1. "label", because Linux hwmon core code parses it. 2. "shunt-resistor-micro-ohms", because several devices already use it. Acked-by: Guenter Roeck Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240224-dt-bindings-hwmon-common-v2-1-b446eecf5480@linaro.org Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/hwmon-common.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/hwmon-common.yaml diff --git a/Documentation/devicetree/bindings/hwmon/hwmon-common.yaml b/Documentation/devicetree/bindings/hwmon/hwmon-common.yaml new file mode 100644 index 000000000000..dc86b5c72cf2 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/hwmon-common.yaml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/hwmon-common.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Hardware Monitoring Devices Common Properties + +maintainers: + - Guenter Roeck + +properties: + label: + description: A descriptive name for this device. + + shunt-resistor-micro-ohms: + description: The value of current sense resistor. + +additionalProperties: true -- cgit v1.2.3 From 67db0ea3ece240b85896a4e4054af1e277815960 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 24 Feb 2024 11:14:51 +0100 Subject: dt-bindings: hwmon: ti,ina2xx: use common hwmon schema Reference common hwmon schema which allows to drop the "label" property. Acked-by: Rob Herring Acked-by: Guenter Roeck Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240224-dt-bindings-hwmon-common-v2-2-b446eecf5480@linaro.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index a099bb71415e..df86c2c92037 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -36,9 +36,6 @@ properties: "#io-channel-cells": const: 1 - label: - description: A descriptive name for this device. - shunt-resistor: description: Shunt resistor value in micro-Ohm. @@ -73,7 +70,10 @@ required: - compatible - reg -additionalProperties: false +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From 09458a0e7c8b780f762897e92e9020c9c016b9e9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 24 Feb 2024 11:14:52 +0100 Subject: dt-bindings: hwmon: adi,adm1275: use common hwmon schema Reference common hwmon schema which allows to drop the "shunt-resistor-micro-ohms" property. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240224-dt-bindings-hwmon-common-v2-3-b446eecf5480@linaro.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml index ab87f51c5aef..b68061294964 100644 --- a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml @@ -33,10 +33,6 @@ properties: reg: maxItems: 1 - shunt-resistor-micro-ohms: - description: - Shunt resistor value in micro-Ohm. - adi,volt-curr-sample-average: description: | Number of samples to be used to report voltage and current values. @@ -50,6 +46,7 @@ properties: enum: [1, 2, 4, 8, 16, 32, 64, 128] allOf: + - $ref: hwmon-common.yaml# - if: properties: compatible: @@ -107,7 +104,7 @@ required: - compatible - reg -additionalProperties: false +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From 2988ce56b5610a2802d0974371de53c004ea4888 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 24 Feb 2024 11:14:53 +0100 Subject: dt-bindings: hwmon: lltc,ltc4286: use common hwmon schema Reference common hwmon schema which allows to drop the "shunt-resistor-micro-ohms" property. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240224-dt-bindings-hwmon-common-v2-4-b446eecf5480@linaro.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml b/Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml index 98ca163d3486..853df9fef6c8 100644 --- a/Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml +++ b/Documentation/devicetree/bindings/hwmon/lltc,ltc4286.yaml @@ -25,15 +25,14 @@ properties: The default is 102.4 volts. type: boolean - shunt-resistor-micro-ohms: - description: - Resistor value micro-ohms. - required: - compatible - reg -additionalProperties: false +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From a095d8c0230b16eb6ea824925d670215624a0590 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 24 Feb 2024 11:14:54 +0100 Subject: dt-bindings: hwmon: reference common hwmon schema Reference common hwmon schema in the bindings which have and customize the "shunt-resistor-micro-ohms" property. While this does not make the binding smaller, it brings common definition of that property and also allows generic "label" (parsed by Linux hwmon core code). Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240224-dt-bindings-hwmon-common-v2-5-b446eecf5480@linaro.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml | 5 ++++- Documentation/devicetree/bindings/hwmon/adi,ltc2945.yaml | 5 ++++- Documentation/devicetree/bindings/hwmon/lltc,ltc4151.yaml | 5 ++++- Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml | 5 ++++- Documentation/devicetree/bindings/hwmon/ti,tmp513.yaml | 5 ++++- Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml | 5 ++++- 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml index 2e45364d0543..be7e9e91a3a8 100644 --- a/Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1177.yaml @@ -46,7 +46,10 @@ required: - compatible - reg -additionalProperties: false +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/hwmon/adi,ltc2945.yaml b/Documentation/devicetree/bindings/hwmon/adi,ltc2945.yaml index 5cb66e97e816..6401b0a9aff4 100644 --- a/Documentation/devicetree/bindings/hwmon/adi,ltc2945.yaml +++ b/Documentation/devicetree/bindings/hwmon/adi,ltc2945.yaml @@ -31,7 +31,10 @@ required: - compatible - reg -additionalProperties: false +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/hwmon/lltc,ltc4151.yaml b/Documentation/devicetree/bindings/hwmon/lltc,ltc4151.yaml index e62aff670478..8f0095bb7f6e 100644 --- a/Documentation/devicetree/bindings/hwmon/lltc,ltc4151.yaml +++ b/Documentation/devicetree/bindings/hwmon/lltc,ltc4151.yaml @@ -25,7 +25,10 @@ required: - compatible - reg -additionalProperties: false +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml index 4373e9c86e54..a20f140dc79a 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/ti,lm25066.yaml @@ -50,7 +50,10 @@ required: - compatible - reg -additionalProperties: false +allOf: + - $ref: /schemas/hwmon/hwmon-common.yaml# + +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/hwmon/ti,tmp513.yaml b/Documentation/devicetree/bindings/hwmon/ti,tmp513.yaml index cdd1489e0c54..227858e76058 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,tmp513.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,tmp513.yaml @@ -72,7 +72,10 @@ required: - compatible - reg -additionalProperties: false +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml b/Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml index ebc8d466c1aa..f58248c29e22 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml @@ -35,7 +35,10 @@ required: - compatible - reg -additionalProperties: false +allOf: + - $ref: hwmon-common.yaml# + +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From 5b1d7a0f904b0785d1f6e12770648410093744ac Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sun, 25 Feb 2024 21:28:41 +0100 Subject: hwmon: (sis5595) drop unused DIV_TO_REG function 'DIV_TO_REG' function is not used: sis5595.c:159:18: error: unused function 'DIV_TO_REG' [-Werror,-Wunused-function] Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240225202841.60740-1-krzysztof.kozlowski@linaro.org Signed-off-by: Guenter Roeck --- drivers/hwmon/sis5595.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/sis5595.c b/drivers/hwmon/sis5595.c index 641be1f7f9cd..e73b1522f3ce 100644 --- a/drivers/hwmon/sis5595.c +++ b/drivers/hwmon/sis5595.c @@ -153,13 +153,9 @@ static inline s8 TEMP_TO_REG(long val) } /* - * FAN DIV: 1, 2, 4, or 8 (defaults to 2) - * REG: 0, 1, 2, or 3 (respectively) (defaults to 1) + * FAN DIV: 1, 2, 4, or 8 + * REG: 0, 1, 2, or 3 (respectively) */ -static inline u8 DIV_TO_REG(int val) -{ - return val == 8 ? 3 : val == 4 ? 2 : val == 1 ? 0 : 1; -} #define DIV_FROM_REG(val) (1 << (val)) /* -- cgit v1.2.3 From 78cc80d834ed3b792605bc9c2215ed9e63825459 Mon Sep 17 00:00:00 2001 From: Théo Lebrun Date: Thu, 29 Feb 2024 19:10:50 +0100 Subject: dt-bindings: hwmon: lm75: use common hwmon schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference common hwmon schema which has the generic "label" property, parsed by Linux hwmon subsystem. To: Jean Delvare To: Guenter Roeck Cc: linux-hwmon@vger.kernel.org Signed-off-by: Théo Lebrun Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240229-mbly-i2c-v2-2-b32ed18c098c@bootlin.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/lm75.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/hwmon/lm75.yaml b/Documentation/devicetree/bindings/hwmon/lm75.yaml index ed269e428a3d..29bd7460cc26 100644 --- a/Documentation/devicetree/bindings/hwmon/lm75.yaml +++ b/Documentation/devicetree/bindings/hwmon/lm75.yaml @@ -57,6 +57,7 @@ required: - reg allOf: + - $ref: hwmon-common.yaml# - if: not: properties: @@ -71,7 +72,7 @@ allOf: properties: interrupts: false -additionalProperties: false +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From 3f003fda98a7a8d5f399057d92e6ed56b468657c Mon Sep 17 00:00:00 2001 From: Josua Mayer Date: Thu, 7 Mar 2024 12:06:58 +0100 Subject: hwmon: (amc6821) add of_match table Add of_match table for "ti,amc6821" compatible string. This fixes automatic driver loading by userspace when using device-tree, and if built as a module like major linux distributions do. While devices probe just fine with i2c_device_id table, userspace can't match the "ti,amc6821" compatible string from dt with the plain "amc6821" device id. As a result, the kernel module can not be loaded. Cc: stable@vger.kernel.org Signed-off-by: Josua Mayer Link: https://lore.kernel.org/r/20240307-amc6821-of-match-v1-1-5f40464a3110@solid-run.com [groeck: Cleaned up patch description] Signed-off-by: Guenter Roeck --- drivers/hwmon/amc6821.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index 2a7a4b6b0094..9b02b304c2f5 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -934,10 +934,21 @@ static const struct i2c_device_id amc6821_id[] = { MODULE_DEVICE_TABLE(i2c, amc6821_id); +static const struct of_device_id __maybe_unused amc6821_of_match[] = { + { + .compatible = "ti,amc6821", + .data = (void *)amc6821, + }, + { } +}; + +MODULE_DEVICE_TABLE(of, amc6821_of_match); + static struct i2c_driver amc6821_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "amc6821", + .of_match_table = of_match_ptr(amc6821_of_match), }, .probe = amc6821_probe, .id_table = amc6821_id, -- cgit v1.2.3 From c71d2502edf0a22684928ed3fdae8e1617609985 Mon Sep 17 00:00:00 2001 From: Naresh Solanki Date: Thu, 7 Mar 2024 17:03:24 +0530 Subject: dt-bindings: hwmon: tda38640: Add interrupt & regulator properties tda38640 has a single regulator output along with CAT_FAULT# pin to report internal events. Hence add properties for regulator & interrupt. Signed-off-by: Naresh Solanki Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20240307113325.3800181-1-naresh.solanki@9elements.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/pmbus/infineon,tda38640.yaml | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/infineon,tda38640.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/infineon,tda38640.yaml index ded1c115764b..5c4e52b472ad 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/infineon,tda38640.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/infineon,tda38640.yaml @@ -30,6 +30,23 @@ properties: unconnected(has internal pull-down). type: boolean + interrupts: + maxItems: 1 + + regulators: + type: object + description: + list of regulators provided by this controller. + + properties: + vout: + $ref: /schemas/regulator/regulator.yaml# + type: object + + unevaluatedProperties: false + + additionalProperties: false + required: - compatible - reg @@ -38,6 +55,7 @@ additionalProperties: false examples: - | + #include i2c { #address-cells = <1>; #size-cells = <0>; @@ -45,5 +63,15 @@ examples: tda38640@40 { compatible = "infineon,tda38640"; reg = <0x40>; + + interrupt-parent = <&smb_pex_cpu0_event>; + interrupts = <10 IRQ_TYPE_LEVEL_LOW>; + + regulators { + pvnn_main_cpu0: vout { + regulator-name = "pvnn_main_cpu0"; + regulator-enable-ramp-delay = <200>; + }; + }; }; }; -- cgit v1.2.3 From 3b0ac1f90c44178b295432e1bf7ef05c2ca20014 Mon Sep 17 00:00:00 2001 From: Naresh Solanki Date: Wed, 21 Feb 2024 18:40:23 +0800 Subject: dt-bindings: hwmon: fan: Add fan binding to schema Add common fan properties bindings to a schema. Bindings for fan controllers can reference the common schema for the fan child nodes: patternProperties: "^fan@[0-2]": type: object $ref: fan-common.yaml# unevaluatedProperties: false Reviewed-by: Rob Herring Signed-off-by: Naresh Solanki Signed-off-by: Billy Tsai Link: https://lore.kernel.org/r/20240221104025.1306227-2-billy_tsai@aspeedtech.com Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/fan-common.yaml | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/fan-common.yaml diff --git a/Documentation/devicetree/bindings/hwmon/fan-common.yaml b/Documentation/devicetree/bindings/hwmon/fan-common.yaml new file mode 100644 index 000000000000..0fb738081699 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/fan-common.yaml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/fan-common.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Common Fan Properties + +maintainers: + - Naresh Solanki + - Billy Tsai + +properties: + max-rpm: + description: + Max RPM supported by fan. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 100000 + + min-rpm: + description: + Min RPM supported by fan. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 1000 + + pulses-per-revolution: + description: + The number of pulse from fan sensor per revolution. + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 4 + + tach-div: + description: + Divisor for the tach sampling clock, which determines the sensitivity of the tach pin. + $ref: /schemas/types.yaml#/definitions/uint32 + + target-rpm: + description: + The default desired fan speed in RPM. + $ref: /schemas/types.yaml#/definitions/uint32 + + fan-driving-mode: + description: + Select the driving mode of the fan.(DC, PWM and so on) + $ref: /schemas/types.yaml#/definitions/string + enum: [ dc, pwm ] + + pwms: + description: + PWM provider. + maxItems: 1 + + "#cooling-cells": + const: 2 + + cooling-levels: + description: + The control value which correspond to thermal cooling states. + $ref: /schemas/types.yaml#/definitions/uint32-array + + tach-ch: + description: + The tach channel used for the fan. + $ref: /schemas/types.yaml#/definitions/uint8-array + + label: + description: + Optional fan label + + fan-supply: + description: + Power supply for fan. + + reg: + maxItems: 1 + +additionalProperties: true + +... -- cgit v1.2.3 From df9d235c300d143d319ff8373988ef213ec19024 Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Wed, 21 Feb 2024 18:40:24 +0800 Subject: dt-bindings: hwmon: Support Aspeed g6 PWM TACH Control Document the compatible for aspeed,ast2600-pwm-tach device, which can support up to 16 PWM outputs and 16 fan tach input. Signed-off-by: Billy Tsai Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20240221104025.1306227-3-billy_tsai@aspeedtech.com Signed-off-by: Guenter Roeck --- .../bindings/hwmon/aspeed,g6-pwm-tach.yaml | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/aspeed,g6-pwm-tach.yaml diff --git a/Documentation/devicetree/bindings/hwmon/aspeed,g6-pwm-tach.yaml b/Documentation/devicetree/bindings/hwmon/aspeed,g6-pwm-tach.yaml new file mode 100644 index 000000000000..9e5ed901ae54 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/aspeed,g6-pwm-tach.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2023 Aspeed, Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/aspeed,g6-pwm-tach.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ASPEED G6 PWM and Fan Tach controller + +maintainers: + - Billy Tsai + +description: | + The ASPEED PWM controller can support up to 16 PWM outputs. + The ASPEED Fan Tacho controller can support up to 16 fan tach input. + They are independent hardware blocks, which are different from the + previous version of the ASPEED chip. + +properties: + compatible: + enum: + - aspeed,ast2600-pwm-tach + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + "#pwm-cells": + const: 3 + +patternProperties: + "^fan-[0-9]+$": + $ref: fan-common.yaml# + unevaluatedProperties: false + required: + - tach-ch + +required: + - reg + - clocks + - resets + - "#pwm-cells" + - compatible + +additionalProperties: false + +examples: + - | + #include + pwm_tach: pwm-tach-controller@1e610000 { + compatible = "aspeed,ast2600-pwm-tach"; + reg = <0x1e610000 0x100>; + clocks = <&syscon ASPEED_CLK_AHB>; + resets = <&syscon ASPEED_RESET_PWM>; + #pwm-cells = <3>; + + fan-0 { + tach-ch = /bits/ 8 <0x0>; + pwms = <&pwm_tach 0 40000 0>; + }; + + fan-1 { + tach-ch = /bits/ 8 <0x1 0x2>; + pwms = <&pwm_tach 1 40000 0>; + }; + }; -- cgit v1.2.3 From 7e1449cd15d1096157d1a9923b82e37602fb7eb0 Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Wed, 21 Feb 2024 18:40:25 +0800 Subject: hwmon: (aspeed-g6-pwm-tacho): Support for ASPEED g6 PWM/Fan tach The driver support two functions: PWM and Tachometer. The PWM feature can handle up to 16 output ports, while the Tachometer can monitor to up to 16 input ports as well. This driver implements them by exposing two kernel subsystems: PWM and HWMON. The PWM subsystem can be utilized alongside existing drivers for controlling elements such as fans (pwm-fan.c), beepers (pwm-beeper.c) and so on. Through the HWMON subsystem, the driver provides sysfs interfaces for fan. Signed-off-by: Billy Tsai Link: https://lore.kernel.org/r/20240221104025.1306227-4-billy_tsai@aspeedtech.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/aspeed-g6-pwm-tach.rst | 26 ++ Documentation/hwmon/index.rst | 1 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/aspeed-g6-pwm-tach.c | 549 +++++++++++++++++++++++++++++ 5 files changed, 588 insertions(+) create mode 100644 Documentation/hwmon/aspeed-g6-pwm-tach.rst create mode 100644 drivers/hwmon/aspeed-g6-pwm-tach.c diff --git a/Documentation/hwmon/aspeed-g6-pwm-tach.rst b/Documentation/hwmon/aspeed-g6-pwm-tach.rst new file mode 100644 index 000000000000..17398fe397fe --- /dev/null +++ b/Documentation/hwmon/aspeed-g6-pwm-tach.rst @@ -0,0 +1,26 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver aspeed-g6-pwm-tach +================================= + +Supported chips: + ASPEED AST2600 + +Authors: + + +Description: +------------ +This driver implements support for ASPEED AST2600 Fan Tacho controller. +The controller supports up to 16 tachometer inputs. + +The driver provides the following sensor accesses in sysfs: + +=============== ======= ====================================================== +fanX_input ro provide current fan rotation value in RPM as reported + by the fan to the device. +fanX_div rw Fan divisor: Supported value are power of 4 (1, 4, 16 + 64, ... 4194304) + The larger divisor, the less rpm accuracy and the less + affected by fan signal glitch. +=============== ======= ====================================================== diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index c4af2a894c42..1ca7a4fe1f8f 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -44,6 +44,7 @@ Hardware Monitoring Kernel Drivers aquacomputer_d5next asb100 asc7621 + aspeed-g6-pwm-tach aspeed-pwm-tacho asus_ec_sensors asus_rog_ryujin diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 56260821d658..83945397b6eb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -422,6 +422,17 @@ config SENSORS_ASPEED This driver can also be built as a module. If so, the module will be called aspeed_pwm_tacho. +config SENSORS_ASPEED_G6 + tristate "ASPEED g6 PWM and Fan tach driver" + depends on ARCH_ASPEED || COMPILE_TEST + depends on PWM + help + This driver provides support for ASPEED G6 PWM and Fan Tach + controllers. + + This driver can also be built as a module. If so, the module + will be called aspeed_pwm_tacho. + config SENSORS_ATXP1 tristate "Attansic ATXP1 VID controller" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index f45c31aff009..5c31808f6378 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o +obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o diff --git a/drivers/hwmon/aspeed-g6-pwm-tach.c b/drivers/hwmon/aspeed-g6-pwm-tach.c new file mode 100644 index 000000000000..597b3b019d49 --- /dev/null +++ b/drivers/hwmon/aspeed-g6-pwm-tach.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Aspeed Technology Inc. + * + * PWM/TACH controller driver for Aspeed ast2600 SoCs. + * This drivers doesn't support earlier version of the IP. + * + * The hardware operates in time quantities of length + * Q := (DIV_L + 1) << DIV_H / input-clk + * The length of a PWM period is (DUTY_CYCLE_PERIOD + 1) * Q. + * The maximal value for DUTY_CYCLE_PERIOD is used here to provide + * a fine grained selection for the duty cycle. + * + * This driver uses DUTY_CYCLE_RISING_POINT = 0, so from the start of a + * period the output is active until DUTY_CYCLE_FALLING_POINT * Q. Note + * that if DUTY_CYCLE_RISING_POINT = DUTY_CYCLE_FALLING_POINT the output is + * always active. + * + * Register usage: + * PIN_ENABLE: When it is unset the pwm controller will emit inactive level to the external. + * Use to determine whether the PWM channel is enabled or disabled + * CLK_ENABLE: When it is unset the pwm controller will assert the duty counter reset and + * emit inactive level to the PIN_ENABLE mux after that the driver can still change the pwm period + * and duty and the value will apply when CLK_ENABLE be set again. + * Use to determine whether duty_cycle bigger than 0. + * PWM_ASPEED_CTRL_INVERSE: When it is toggled the output value will inverse immediately. + * PWM_ASPEED_DUTY_CYCLE_FALLING_POINT/PWM_ASPEED_DUTY_CYCLE_RISING_POINT: When these two + * values are equal it means the duty cycle = 100%. + * + * The glitch may generate at: + * - Enabled changing when the duty_cycle bigger than 0% and less than 100%. + * - Polarity changing when the duty_cycle bigger than 0% and less than 100%. + * + * Limitations: + * - When changing both duty cycle and period, we cannot prevent in + * software that the output might produce a period with mixed + * settings. + * - Disabling the PWM doesn't complete the current period. + * + * Improvements: + * - When only changing one of duty cycle or period, our pwm controller will not + * generate the glitch, the configure will change at next cycle of pwm. + * This improvement can disable/enable through PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The channel number of Aspeed pwm controller */ +#define PWM_ASPEED_NR_PWMS 16 +/* PWM Control Register */ +#define PWM_ASPEED_CTRL(ch) ((ch) * 0x10 + 0x00) +#define PWM_ASPEED_CTRL_LOAD_SEL_RISING_AS_WDT BIT(19) +#define PWM_ASPEED_CTRL_DUTY_LOAD_AS_WDT_ENABLE BIT(18) +#define PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE BIT(17) +#define PWM_ASPEED_CTRL_CLK_ENABLE BIT(16) +#define PWM_ASPEED_CTRL_LEVEL_OUTPUT BIT(15) +#define PWM_ASPEED_CTRL_INVERSE BIT(14) +#define PWM_ASPEED_CTRL_OPEN_DRAIN_ENABLE BIT(13) +#define PWM_ASPEED_CTRL_PIN_ENABLE BIT(12) +#define PWM_ASPEED_CTRL_CLK_DIV_H GENMASK(11, 8) +#define PWM_ASPEED_CTRL_CLK_DIV_L GENMASK(7, 0) + +/* PWM Duty Cycle Register */ +#define PWM_ASPEED_DUTY_CYCLE(ch) ((ch) * 0x10 + 0x04) +#define PWM_ASPEED_DUTY_CYCLE_PERIOD GENMASK(31, 24) +#define PWM_ASPEED_DUTY_CYCLE_POINT_AS_WDT GENMASK(23, 16) +#define PWM_ASPEED_DUTY_CYCLE_FALLING_POINT GENMASK(15, 8) +#define PWM_ASPEED_DUTY_CYCLE_RISING_POINT GENMASK(7, 0) + +/* PWM fixed value */ +#define PWM_ASPEED_FIXED_PERIOD FIELD_MAX(PWM_ASPEED_DUTY_CYCLE_PERIOD) + +/* The channel number of Aspeed tach controller */ +#define TACH_ASPEED_NR_TACHS 16 +/* TACH Control Register */ +#define TACH_ASPEED_CTRL(ch) (((ch) * 0x10) + 0x08) +#define TACH_ASPEED_IER BIT(31) +#define TACH_ASPEED_INVERS_LIMIT BIT(30) +#define TACH_ASPEED_LOOPBACK BIT(29) +#define TACH_ASPEED_ENABLE BIT(28) +#define TACH_ASPEED_DEBOUNCE_MASK GENMASK(27, 26) +#define TACH_ASPEED_DEBOUNCE_BIT 26 +#define TACH_ASPEED_IO_EDGE_MASK GENMASK(25, 24) +#define TACH_ASPEED_IO_EDGE_BIT 24 +#define TACH_ASPEED_CLK_DIV_T_MASK GENMASK(23, 20) +#define TACH_ASPEED_CLK_DIV_BIT 20 +#define TACH_ASPEED_THRESHOLD_MASK GENMASK(19, 0) +/* [27:26] */ +#define DEBOUNCE_3_CLK 0x00 +#define DEBOUNCE_2_CLK 0x01 +#define DEBOUNCE_1_CLK 0x02 +#define DEBOUNCE_0_CLK 0x03 +/* [25:24] */ +#define F2F_EDGES 0x00 +#define R2R_EDGES 0x01 +#define BOTH_EDGES 0x02 +/* [23:20] */ +/* divisor = 4 to the nth power, n = register value */ +#define DEFAULT_TACH_DIV 1024 +#define DIV_TO_REG(divisor) (ilog2(divisor) >> 1) + +/* TACH Status Register */ +#define TACH_ASPEED_STS(ch) (((ch) * 0x10) + 0x0C) + +/*PWM_TACH_STS */ +#define TACH_ASPEED_ISR BIT(31) +#define TACH_ASPEED_PWM_OUT BIT(25) +#define TACH_ASPEED_PWM_OEN BIT(24) +#define TACH_ASPEED_DEB_INPUT BIT(23) +#define TACH_ASPEED_RAW_INPUT BIT(22) +#define TACH_ASPEED_VALUE_UPDATE BIT(21) +#define TACH_ASPEED_FULL_MEASUREMENT BIT(20) +#define TACH_ASPEED_VALUE_MASK GENMASK(19, 0) +/********************************************************** + * Software setting + *********************************************************/ +#define DEFAULT_FAN_PULSE_PR 2 + +struct aspeed_pwm_tach_data { + struct device *dev; + void __iomem *base; + struct clk *clk; + struct reset_control *reset; + unsigned long clk_rate; + struct pwm_chip chip; + bool tach_present[TACH_ASPEED_NR_TACHS]; + u32 tach_divisor; +}; + +static inline struct aspeed_pwm_tach_data * +aspeed_pwm_chip_to_data(struct pwm_chip *chip) +{ + return container_of(chip, struct aspeed_pwm_tach_data, chip); +} + +static int aspeed_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip); + u32 hwpwm = pwm->hwpwm; + bool polarity, pin_en, clk_en; + u32 duty_pt, val; + u64 div_h, div_l, duty_cycle_period, dividend; + + val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm)); + polarity = FIELD_GET(PWM_ASPEED_CTRL_INVERSE, val); + pin_en = FIELD_GET(PWM_ASPEED_CTRL_PIN_ENABLE, val); + clk_en = FIELD_GET(PWM_ASPEED_CTRL_CLK_ENABLE, val); + div_h = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_H, val); + div_l = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_L, val); + val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm)); + duty_pt = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, val); + duty_cycle_period = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_PERIOD, val); + /* + * This multiplication doesn't overflow, the upper bound is + * 1000000000 * 256 * 256 << 15 = 0x1dcd650000000000 + */ + dividend = (u64)NSEC_PER_SEC * (div_l + 1) * (duty_cycle_period + 1) + << div_h; + state->period = DIV_ROUND_UP_ULL(dividend, priv->clk_rate); + + if (clk_en && duty_pt) { + dividend = (u64)NSEC_PER_SEC * (div_l + 1) * duty_pt + << div_h; + state->duty_cycle = DIV_ROUND_UP_ULL(dividend, priv->clk_rate); + } else { + state->duty_cycle = clk_en ? state->period : 0; + } + state->polarity = polarity ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL; + state->enabled = pin_en; + return 0; +} + +static int aspeed_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip); + u32 hwpwm = pwm->hwpwm, duty_pt, val; + u64 div_h, div_l, divisor, expect_period; + bool clk_en; + + expect_period = div64_u64(ULLONG_MAX, (u64)priv->clk_rate); + expect_period = min(expect_period, state->period); + dev_dbg(chip->dev, "expect period: %lldns, duty_cycle: %lldns", + expect_period, state->duty_cycle); + /* + * Pick the smallest value for div_h so that div_l can be the biggest + * which results in a finer resolution near the target period value. + */ + divisor = (u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1) * + (FIELD_MAX(PWM_ASPEED_CTRL_CLK_DIV_L) + 1); + div_h = order_base_2(DIV64_U64_ROUND_UP(priv->clk_rate * expect_period, divisor)); + if (div_h > 0xf) + div_h = 0xf; + + divisor = ((u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1)) << div_h; + div_l = div64_u64(priv->clk_rate * expect_period, divisor); + + if (div_l == 0) + return -ERANGE; + + div_l -= 1; + + if (div_l > 255) + div_l = 255; + + dev_dbg(chip->dev, "clk source: %ld div_h %lld, div_l : %lld\n", + priv->clk_rate, div_h, div_l); + /* duty_pt = duty_cycle * (PERIOD + 1) / period */ + duty_pt = div64_u64(state->duty_cycle * priv->clk_rate, + (u64)NSEC_PER_SEC * (div_l + 1) << div_h); + dev_dbg(chip->dev, "duty_cycle = %lld, duty_pt = %d\n", + state->duty_cycle, duty_pt); + + /* + * Fixed DUTY_CYCLE_PERIOD to its max value to get a + * fine-grained resolution for duty_cycle at the expense of a + * coarser period resolution. + */ + val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm)); + val &= ~PWM_ASPEED_DUTY_CYCLE_PERIOD; + val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_PERIOD, + PWM_ASPEED_FIXED_PERIOD); + writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm)); + + if (duty_pt == 0) { + /* emit inactive level and assert the duty counter reset */ + clk_en = 0; + } else { + clk_en = 1; + if (duty_pt >= (PWM_ASPEED_FIXED_PERIOD + 1)) + duty_pt = 0; + val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm)); + val &= ~(PWM_ASPEED_DUTY_CYCLE_RISING_POINT | + PWM_ASPEED_DUTY_CYCLE_FALLING_POINT); + val |= FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, duty_pt); + writel(val, priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm)); + } + + val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm)); + val &= ~(PWM_ASPEED_CTRL_CLK_DIV_H | PWM_ASPEED_CTRL_CLK_DIV_L | + PWM_ASPEED_CTRL_PIN_ENABLE | PWM_ASPEED_CTRL_CLK_ENABLE | + PWM_ASPEED_CTRL_INVERSE); + val |= FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_H, div_h) | + FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_L, div_l) | + FIELD_PREP(PWM_ASPEED_CTRL_PIN_ENABLE, state->enabled) | + FIELD_PREP(PWM_ASPEED_CTRL_CLK_ENABLE, clk_en) | + FIELD_PREP(PWM_ASPEED_CTRL_INVERSE, state->polarity); + writel(val, priv->base + PWM_ASPEED_CTRL(hwpwm)); + + return 0; +} + +static const struct pwm_ops aspeed_pwm_ops = { + .apply = aspeed_pwm_apply, + .get_state = aspeed_pwm_get_state, +}; + +static void aspeed_tach_ch_enable(struct aspeed_pwm_tach_data *priv, u8 tach_ch, + bool enable) +{ + if (enable) + writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) | + TACH_ASPEED_ENABLE, + priv->base + TACH_ASPEED_CTRL(tach_ch)); + else + writel(readl(priv->base + TACH_ASPEED_CTRL(tach_ch)) & + ~TACH_ASPEED_ENABLE, + priv->base + TACH_ASPEED_CTRL(tach_ch)); +} + +static int aspeed_tach_val_to_rpm(struct aspeed_pwm_tach_data *priv, u32 tach_val) +{ + u64 rpm; + u32 tach_div; + + tach_div = tach_val * priv->tach_divisor * DEFAULT_FAN_PULSE_PR; + + dev_dbg(priv->dev, "clk %ld, tach_val %d , tach_div %d\n", + priv->clk_rate, tach_val, tach_div); + + rpm = (u64)priv->clk_rate * 60; + do_div(rpm, tach_div); + + return (int)rpm; +} + +static int aspeed_get_fan_tach_ch_rpm(struct aspeed_pwm_tach_data *priv, + u8 fan_tach_ch) +{ + u32 val; + + val = readl(priv->base + TACH_ASPEED_STS(fan_tach_ch)); + + if (!(val & TACH_ASPEED_FULL_MEASUREMENT)) + return 0; + val = FIELD_GET(TACH_ASPEED_VALUE_MASK, val); + return aspeed_tach_val_to_rpm(priv, val); +} + +static int aspeed_tach_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev); + u32 reg_val; + + switch (attr) { + case hwmon_fan_input: + *val = aspeed_get_fan_tach_ch_rpm(priv, channel); + break; + case hwmon_fan_div: + reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel)); + reg_val = FIELD_GET(TACH_ASPEED_CLK_DIV_T_MASK, reg_val); + *val = BIT(reg_val << 1); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int aspeed_tach_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct aspeed_pwm_tach_data *priv = dev_get_drvdata(dev); + u32 reg_val; + + switch (attr) { + case hwmon_fan_div: + if (!is_power_of_2(val) || (ilog2(val) % 2) || + DIV_TO_REG(val) > 0xb) + return -EINVAL; + priv->tach_divisor = val; + reg_val = readl(priv->base + TACH_ASPEED_CTRL(channel)); + reg_val &= ~TACH_ASPEED_CLK_DIV_T_MASK; + reg_val |= FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK, + DIV_TO_REG(priv->tach_divisor)); + writel(reg_val, priv->base + TACH_ASPEED_CTRL(channel)); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static umode_t aspeed_tach_dev_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct aspeed_pwm_tach_data *priv = drvdata; + + if (!priv->tach_present[channel]) + return 0; + switch (attr) { + case hwmon_fan_input: + return 0444; + case hwmon_fan_div: + return 0644; + } + return 0; +} + +static const struct hwmon_ops aspeed_tach_ops = { + .is_visible = aspeed_tach_dev_is_visible, + .read = aspeed_tach_hwmon_read, + .write = aspeed_tach_hwmon_write, +}; + +static const struct hwmon_channel_info *aspeed_tach_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV, + HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV, + HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV, + HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV, + HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV, + HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV, + HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV, + HWMON_F_INPUT | HWMON_F_DIV, HWMON_F_INPUT | HWMON_F_DIV), + NULL +}; + +static const struct hwmon_chip_info aspeed_tach_chip_info = { + .ops = &aspeed_tach_ops, + .info = aspeed_tach_info, +}; + +static void aspeed_present_fan_tach(struct aspeed_pwm_tach_data *priv, u8 *tach_ch, int count) +{ + u8 ch, index; + u32 val; + + for (index = 0; index < count; index++) { + ch = tach_ch[index]; + priv->tach_present[ch] = true; + priv->tach_divisor = DEFAULT_TACH_DIV; + + val = readl(priv->base + TACH_ASPEED_CTRL(ch)); + val &= ~(TACH_ASPEED_INVERS_LIMIT | TACH_ASPEED_DEBOUNCE_MASK | + TACH_ASPEED_IO_EDGE_MASK | TACH_ASPEED_CLK_DIV_T_MASK | + TACH_ASPEED_THRESHOLD_MASK); + val |= (DEBOUNCE_3_CLK << TACH_ASPEED_DEBOUNCE_BIT) | + F2F_EDGES | + FIELD_PREP(TACH_ASPEED_CLK_DIV_T_MASK, + DIV_TO_REG(priv->tach_divisor)); + writel(val, priv->base + TACH_ASPEED_CTRL(ch)); + + aspeed_tach_ch_enable(priv, ch, true); + } +} + +static int aspeed_create_fan_monitor(struct device *dev, + struct device_node *child, + struct aspeed_pwm_tach_data *priv) +{ + int ret, count; + u8 *tach_ch; + + count = of_property_count_u8_elems(child, "tach-ch"); + if (count < 1) + return -EINVAL; + tach_ch = devm_kcalloc(dev, count, sizeof(*tach_ch), GFP_KERNEL); + if (!tach_ch) + return -ENOMEM; + ret = of_property_read_u8_array(child, "tach-ch", tach_ch, count); + if (ret) + return ret; + + aspeed_present_fan_tach(priv, tach_ch, count); + + return 0; +} + +static void aspeed_pwm_tach_reset_assert(void *data) +{ + struct reset_control *rst = data; + + reset_control_assert(rst); +} + +static int aspeed_pwm_tach_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev, *hwmon; + int ret; + struct device_node *child; + struct aspeed_pwm_tach_data *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = dev; + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "Couldn't get clock\n"); + priv->clk_rate = clk_get_rate(priv->clk); + priv->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->reset)) + return dev_err_probe(dev, PTR_ERR(priv->reset), + "Couldn't get reset control\n"); + + ret = reset_control_deassert(priv->reset); + if (ret) + return dev_err_probe(dev, ret, + "Couldn't deassert reset control\n"); + ret = devm_add_action_or_reset(dev, aspeed_pwm_tach_reset_assert, + priv->reset); + if (ret) + return ret; + + priv->chip.dev = dev; + priv->chip.ops = &aspeed_pwm_ops; + priv->chip.npwm = PWM_ASPEED_NR_PWMS; + + ret = devm_pwmchip_add(dev, &priv->chip); + if (ret) + return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + for_each_child_of_node(dev->of_node, child) { + ret = aspeed_create_fan_monitor(dev, child, priv); + if (ret) { + of_node_put(child); + dev_warn(dev, "Failed to create fan %d", ret); + return 0; + } + } + + hwmon = devm_hwmon_device_register_with_info(dev, "aspeed_tach", priv, + &aspeed_tach_chip_info, NULL); + ret = PTR_ERR_OR_ZERO(hwmon); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register hwmon device\n"); + + of_platform_populate(dev->of_node, NULL, NULL, dev); + + return 0; +} + +static int aspeed_pwm_tach_remove(struct platform_device *pdev) +{ + struct aspeed_pwm_tach_data *priv = platform_get_drvdata(pdev); + + reset_control_assert(priv->reset); + + return 0; +} + +static const struct of_device_id aspeed_pwm_tach_match[] = { + { + .compatible = "aspeed,ast2600-pwm-tach", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, aspeed_pwm_tach_match); + +static struct platform_driver aspeed_pwm_tach_driver = { + .probe = aspeed_pwm_tach_probe, + .remove = aspeed_pwm_tach_remove, + .driver = { + .name = "aspeed-g6-pwm-tach", + .of_match_table = aspeed_pwm_tach_match, + }, +}; + +module_platform_driver(aspeed_pwm_tach_driver); + +MODULE_AUTHOR("Billy Tsai "); +MODULE_DESCRIPTION("Aspeed ast2600 PWM and Fan Tach device driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 8debe3c1295ef36958dae77487eed9cf6584c008 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sat, 9 Mar 2024 22:20:25 +0100 Subject: hwmon: (dell-smm) Add XPS 9315 to fan control whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user reported that on this machine, disabling BIOS fan control is necessary in order to change the fan speed. Signed-off-by: Armin Wolf Acked-by: Pali Rohár Link: https://lore.kernel.org/r/20240309212025.13758-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 6d8c0f328b7b..4fa837e65a61 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1450,10 +1450,15 @@ struct i8k_fan_control_data { }; enum i8k_fan_controls { + I8K_FAN_30A3_31A3, I8K_FAN_34A3_35A3, }; static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = { + [I8K_FAN_30A3_31A3] = { + .manual_fan = 0x30a3, + .auto_fan = 0x31a3, + }, [I8K_FAN_34A3_35A3] = { .manual_fan = 0x34a3, .auto_fan = 0x35a3, @@ -1517,6 +1522,14 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = { }, .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], }, + { + .ident = "Dell XPS 9315", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 9315"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3], + }, { } }; -- cgit v1.2.3