diff --git a/.github/workflows/top-level.yml b/.github/workflows/top-level.yml new file mode 100644 index 00000000000000..1cb128ce3caeb2 --- /dev/null +++ b/.github/workflows/top-level.yml @@ -0,0 +1,100 @@ +name: Kernel build + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + checks: + uses: analogdevicesinc/linux/.github/workflows/checks.yml@ci + if: (github.event_name != 'push' || github.repository_owner == 'analogdevicesinc') + secrets: inherit + permissions: + contents: read + build_gcc_x86_64: + uses: analogdevicesinc/linux/.github/workflows/build.yml@ci + needs: [checks] + if: needs.checks.outputs.fatal != 'true' + secrets: inherit + permissions: + contents: read + with: + compiler: "gcc" + arch: "x86" + defconfig: "adi_ci_defconfig" + build_llvm_x86_64: + uses: analogdevicesinc/linux/.github/workflows/build.yml@ci + needs: [checks] + if: needs.checks.outputs.fatal != 'true' + secrets: inherit + permissions: + contents: read + with: + compiler: "llvm" + arch: "x86" + defconfig: "adi_ci_defconfig" + checks: true + build_gcc_aarch64: + uses: analogdevicesinc/linux/.github/workflows/build.yml@ci + needs: [checks] + if: needs.checks.outputs.fatal != 'true' + secrets: inherit + permissions: + contents: read + with: + compiler: "gcc" + arch: "arm64" + defconfig: "adi_ci_defconfig" + build_gcc_arm: + uses: analogdevicesinc/linux/.github/workflows/build.yml@ci + if: (github.event_name != 'push' || github.repository_owner == 'analogdevicesinc') + secrets: inherit + permissions: + contents: read + with: + compiler: "gcc" + arch: "arm" + defconfig: "adi_ci_defconfig" + checks: true + assert_checks: + runs-on: ${{ vars.RUNNER != '' && fromJSON(vars.RUNNER) || 'ubuntu-slim' }} + permissions: + contents: read + needs: + - checks + - build_gcc_x86_64 + - build_llvm_x86_64 + - build_gcc_aarch64 + - build_gcc_arm + + steps: + - name: Assert + env: + job_checks: ${{ needs.checks.outputs.summary }} + job_build_gcc_x86_64: ${{ needs.build_gcc_x86_64.outputs.summary }} + job_build_llvm_x86_64: ${{ needs.build_llvm_x86_64.outputs.summary }} + job_build_gcc_aarch64: ${{ needs.build_gcc_aarch64.outputs.summary }} + job_build_gcc_arm: ${{ needs.build_gcc_arm.outputs.summary }} + run: | + curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -L -o runner_env.sh \ + https://raw.githubusercontent.com/analogdevicesinc/linux/ci/ci/runner_env.sh + source ./runner_env.sh + assert_job_summary + + deploy_cloudsmith_checks: + needs: [assert_checks] + if: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' }} + uses: analogdevicesinc/linux/.github/workflows/upload-to-cloudsmith.yml@ci + secrets: + CLOUDSMITH_SERVICE_SLUG: ${{ secrets.CLOUDSMITH_SERVICE_SLUG }} + CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} + permissions: + id-token: write + contents: read + actions: read + with: + artifacts: > + adi_ci_defconfig-* diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon index b185bdfc7186a7..34df6d7bd5f85e 100644 --- a/Documentation/ABI/testing/sysfs-class-hwmon +++ b/Documentation/ABI/testing/sysfs-class-hwmon @@ -61,6 +61,18 @@ Description: take drastic action such as power down or reset. At the very least, it should report a fault. +What: /sys/class/hwmon/hwmonX/inY_lemergency +Description: + Voltage emergency min value. + + Unit: millivolt + + RW + + If voltage drops to or below this limit, the system is + expected to take drastic action such as immediate power + down or reset. At the very least, it should report a fault. + What: /sys/class/hwmon/hwmonX/inY_max Description: Voltage max value. @@ -81,6 +93,18 @@ Description: take drastic action such as power down or reset. At the very least, it should report a fault. +What: /sys/class/hwmon/hwmonX/inY_emergency +Description: + Voltage emergency max value. + + Unit: millivolt + + RW + + If voltage reaches or exceeds this limit, the system is expected + to take drastic action such as immediate power down or reset. + At the very least, it should report a fault. + What: /sys/class/hwmon/hwmonX/inY_input Description: Voltage input value. @@ -647,6 +671,18 @@ Description: RW +What: /sys/class/hwmon/hwmonX/currY_emergency +Description: + Current emergency high value. + + Unit: milliampere + + RW + + If a current reaches or exceeds this limit, the system is + expected to take drastic action such as immediate power down + or reset. At the very least, it should report a fault. + What: /sys/class/hwmon/hwmonX/currY_input Description: Current input value diff --git a/Documentation/devicetree/bindings/hwmon/axiado,ax3000-tsadc.yaml b/Documentation/devicetree/bindings/hwmon/axiado,ax3000-tsadc.yaml new file mode 100644 index 00000000000000..4b56e4fd9ef97d --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/axiado,ax3000-tsadc.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/axiado,ax3000-tsadc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Axiado AX3000 and AX3005 TSADC + +maintainers: + - Petar Stepanovic + - Akhila Kavi + - Prasad Bolisetty + +description: | + The Axiado AX3000 and AX3005 Temperature Sensor ADC (TSADC) controllers + monitor the on-die temperature of the SoC. + +properties: + compatible: + oneOf: + - const: axiado,ax3000-tsadc + - items: + - const: axiado,ax3005-tsadc + - const: axiado,ax3000-tsadc + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - clocks + +additionalProperties: false + +examples: + - | + temperature-sensor@33021000 { + compatible = "axiado,ax3000-tsadc"; + reg = <0x33021000 0x100>; + clocks = <&refclk>; + }; diff --git a/Documentation/devicetree/bindings/hwmon/national,lm63.yaml b/Documentation/devicetree/bindings/hwmon/national,lm63.yaml new file mode 100644 index 00000000000000..4c8d0a15fbd12a --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/national,lm63.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/national,lm63.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LM63-compatible temperature sensors with integrated fan control + +maintainers: + - Guenter Roeck + +description: | + The LM63 family provides local and remote temperature monitoring together + with fan speed monitoring and PWM fan control. Supported devices include + the National Semiconductor LM63, LM64 and LM96163, and the Sensylink + CTF2301. + +allOf: + - $ref: hwmon-common.yaml# + +properties: + compatible: + enum: + - national,lm63 + - national,lm64 + - national,lm96163 + - sensylink,ctf2301 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + "#cooling-cells": + const: 2 + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@4c { + compatible = "national,lm63"; + reg = <0x4c>; + #cooling-cells = <2>; + }; + }; diff --git a/Documentation/devicetree/bindings/hwmon/national,lm90.yaml b/Documentation/devicetree/bindings/hwmon/national,lm90.yaml index 164068ba069d73..a8b2a24501b354 100644 --- a/Documentation/devicetree/bindings/hwmon/national,lm90.yaml +++ b/Documentation/devicetree/bindings/hwmon/national,lm90.yaml @@ -113,6 +113,23 @@ allOf: properties: ti,extended-range-enable: false + - if: + not: + properties: + compatible: + contains: + enum: + - adi,adt7481 + - dallas,max6695 + - dallas,max6696 + then: + patternProperties: + "^channel@[0-1]$": + properties: + reg: + enum: [0, 1] + "channel@2": false + - if: properties: compatible: @@ -134,6 +151,11 @@ allOf: "^channel@([0-2])$": properties: temperature-offset-millicelsius: false + else: + patternProperties: + "channel@0": + properties: + temperature-offset-millicelsius: false - if: properties: @@ -149,7 +171,7 @@ allOf: - onnn,nct1008 then: patternProperties: - "^channel@([0-2])$": + "^channel@([1-2])$": properties: temperature-offset-millicelsius: maximum: 127750 @@ -172,7 +194,7 @@ allOf: - winbond,w83l771 then: patternProperties: - "^channel@([0-2])$": + "^channel@([1-2])$": properties: temperature-offset-millicelsius: maximum: 127875 @@ -186,7 +208,7 @@ allOf: - ti,tmp461 then: patternProperties: - "^channel@([0-2])$": + "^channel@([1-2])$": properties: temperature-offset-millicelsius: maximum: 127937 diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20826.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20826.yaml new file mode 100644 index 00000000000000..e3bb51d6d1d642 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20826.yaml @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/pmbus/adi,max20826.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX20826 PMBus Voltage Regulator + +maintainers: + - Nuno Sá + +description: + Analog Devices MAX20826 and similar devices are Dual-Output Multiphase + High-Current Controller. + +properties: + compatible: + enum: + - adi,max20826 + - adi,max20855b + - adi,max20908 + - adi,max20912 + - adi,max20916 + + reg: + maxItems: 1 + + vdd3p3-supply: + description: + 3.3V bias supply. It is the input of the internal 1.8V linear regulator + which supplies the controller. + + avren-gpios: + description: GPIO pin to enable/disable the output voltage on Rail A. + maxItems: 1 + + bvren-gpios: + description: GPIO pin to enable/disable the output voltage on Rail B. + maxItems: 1 + + adi,rail-a-high-speed: + type: boolean + description: + The output voltage of Rail A is controlled by the high-speed processor + voltage-control interface the device powered up in (PWMVID, SVID, SVI3 or + AVSBus). If not given, PMBus controls the output voltage of Rail A. + + adi,rail-b-high-speed: + type: boolean + description: + Same as adi,rail-a-high-speed but for Rail B. + + regulators: + type: object + description: + List of regulators provided by this controller. vout0 is Rail A and + vout1 is Rail B. + + patternProperties: + "^vout[0-1]$": + $ref: /schemas/regulator/regulator.yaml# + type: object + unevaluatedProperties: false + + additionalProperties: false + +required: + - compatible + - reg + - vdd3p3-supply + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + regulator@64 { + compatible = "adi,max20826"; + reg = <0x64>; + + vdd3p3-supply = <&vdd3p3>; + + avren-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>; + bvren-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>; + + adi,rail-a-high-speed; + + regulators { + vout0 { + regulator-name = "vcore_a"; + }; + + vout1 { + regulator-name = "vcore_b"; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml index f4115870e45094..63ccb67576df8b 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/ti,tps25990.yaml @@ -5,18 +5,20 @@ $id: http://devicetree.org/schemas/hwmon/pmbus/ti,tps25990.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Texas Instruments TPS25990 Stackable eFuse +title: Texas Instruments Stackable eFuses maintainers: - Jerome Brunet description: - The TI TPS25990 is an integrated, high-current circuit + The TI TPS25990 and TPS1689 are integrated, high-current circuit protection and power management device with PMBUS interface properties: compatible: - const: ti,tps25990 + enum: + - ti,tps1689 + - ti,tps25990 reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml b/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml index 96b2e4969f78a1..c0b747a9ce0de4 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml @@ -4,15 +4,21 @@ $id: http://devicetree.org/schemas/hwmon/ti,tmp102.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: TMP102 temperature sensor +title: TMP102/TMP103/TMP110/TMP113 temperature sensor maintainers: - Krzysztof Kozlowski properties: compatible: - enum: - - ti,tmp102 + oneOf: + - items: + - enum: + - ti,tmp110 + - ti,tmp113 + - const: ti,tmp102 + - const: ti,tmp102 + - const: ti,tmp103 interrupts: maxItems: 1 @@ -25,10 +31,10 @@ properties: A descriptive name for this channel, like "ambient" or "psu". "#thermal-sensor-cells": - const: 1 + enum: [0, 1] vcc-supply: - description: Power supply for tmp102 + description: Power supply for the sensor required: - compatible diff --git a/Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml b/Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml index 0e8ddf0ad7890d..f283b567d5b6cd 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,tmp401.yaml @@ -50,6 +50,9 @@ properties: minimum: 0 maximum: 15 + "#thermal-sensor-cells": + const: 1 + allOf: - if: properties: diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 2de8eb09cb7d14..5dd6a1aca44090 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -368,16 +368,10 @@ properties: - mps,mpq82d00 # Murata D1U74T-W power supply unit - murata,d1u74t - # Temperature sensor with integrated fan control - - national,lm63 - # Temperature sensor with integrated fan control - - national,lm64 # Temperature sensor - national,lm95235 # Temperature sensor - national,lm95245 - # Temperature sensor with integrated fan control - - national,lm96163 # Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor - national,lm80 # Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor @@ -427,6 +421,8 @@ properties: - sensirion,sht21 - sensirion,sht25 - sensirion,sht4x + # Sensirion temperature sensor with I2C interface + - sensirion,sts40 # Sensortek 3 axis accelerometer - sensortek,stk8312 # Sensortek 3 axis accelerometer @@ -504,16 +500,18 @@ properties: - ti,lm74 # Temperature sensor with integrated fan control - ti,lm96000 - # Low Power Digital Temperature Sensor with SMBUS/Two Wire Serial Interface - - ti,tmp103 # Thermometer with SPI interface - ti,tmp121 - ti,tmp122 - ti,tmp125 # TI DC-DC converter on PMBus - ti,tps40400 + # TI Dual channel DCAP+ multiphase controller TPS53622 + - ti,tps53622 # TI DCAP+ multiphase controller - ti,tps53647 + # TI Dual channel DCAP+ multiphase controller TPS53659 + - ti,tps53659 # TI DCAP+ multiphase controller - ti,tps53667 # TI Dual channel DCAP+ multiphase controller TPS53676 with AVSBus @@ -526,6 +524,8 @@ properties: - ti,tps53685 # TI Dual channel DCAP+ multiphase controller TPS53688 - ti,tps53688 + # TI Dual channel DCAP+ multiphase controller TPS536C7 + - ti,tps536c7 # TI DC-DC converters on PMBus - ti,tps544b20 - ti,tps544b25 diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ba200296937343..4a5cf436eb143f 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1495,6 +1495,8 @@ patternProperties: description: Sensirion AG "^sensortek,.*": description: Sensortek Technology Corporation + "^sensylink,.*": + description: Sensylink Microelectronics Technology Co., Ltd. "^sercomm,.*": description: Sercomm (Suzhou) Corporation "^sff,.*": diff --git a/Documentation/hwmon/arctic_fan_controller.rst b/Documentation/hwmon/arctic_fan_controller.rst index b5be88ae464d7d..60ac09a9cea3a9 100644 --- a/Documentation/hwmon/arctic_fan_controller.rst +++ b/Documentation/hwmon/arctic_fan_controller.rst @@ -1,4 +1,4 @@ -.. SPDX-License-Identifier: GPL-2.0-or-later +.. SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause Kernel driver arctic_fan_controller ===================================== @@ -29,18 +29,16 @@ Usage notes Since it is a USB device, hotplug is supported. The device is autodetected. The device does not support GET_REPORT, so the driver cannot read back the -current hardware PWM state at probe time. The cached PWM values (readable -via pwm[1-10]) start at 0 and reflect only values that have been -successfully written. Because each OUT report carries all 10 channel values, -writing a single channel also sends the cached values for all other channels. -Users should set all channels to the desired values before relying on the -cached state. - -On system suspend, the device may lose power and reset its PWM channels to -hardware defaults. The driver clears its cached duty values on resume so -that reads reflect the unknown hardware state rather than stale pre-suspend -values. Userspace is responsible for re-applying the desired duty cycles -after resume. +current hardware PWM state at probe time. Each OUT report carries all 10 +channels, so pwm[1-10] is a host cache. It starts at the MCU factory +default of 40% (sysfs 102). After a successful write, the cache reflects +that value. Users should set all channels to the desired values before +relying on the cached state. + +On system suspend, the device may lose power and reset PWM to the factory +default. The driver restores the cache to 40% on resume. If the device +kept power across suspend, userspace should re-apply the desired duty +cycles. Sysfs entries ------------- @@ -51,6 +49,6 @@ pwm[1-10] PWM duty cycle (0-255). Write: sends an OUT report setting the duty cycle (scaled from 0-255 to 0-100% for the device); the cached value is updated only after the device ACKs the command with a success status. Read: returns the last - successfully written value; initialized to 0 at driver load - and after resume (hardware state unknown). + successfully written value; initialized to 102 (40%) at + driver load and after resume (MCU factory default). ================ ============================================================== diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 2c68a343a2edde..7032c40731d900 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -45,6 +45,7 @@ Supported boards: * ROG STRIX X570-E GAMING WIFI II * ROG STRIX X570-F GAMING * ROG STRIX X570-I GAMING + * ROG STRIX X670E-A GAMING WIFI * ROG STRIX X670E-E GAMING WIFI * ROG STRIX X670E-I GAMING WIFI * ROG STRIX X870-F GAMING WIFI @@ -54,6 +55,7 @@ Supported boards: * ROG STRIX X870E-H GAMING WIFI7 * ROG STRIX Z390-E GAMING * ROG STRIX Z390-F GAMING + * ROG STRIX Z490-A GAMING * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 * ROG STRIX Z690-E GAMING WIFI diff --git a/Documentation/hwmon/asus_rog_ryujin.rst b/Documentation/hwmon/asus_rog_ryujin.rst index b0d7ce8dd921f0..3ebab230e8cafa 100644 --- a/Documentation/hwmon/asus_rog_ryujin.rst +++ b/Documentation/hwmon/asus_rog_ryujin.rst @@ -6,6 +6,7 @@ Kernel driver asus_rog_ryujin Supported devices: * ASUS ROG RYUJIN II 360 +* ASUS ROG RYUJIN III 360 * ASUS ROG RYUJIN III EXTREME * ASUS ROG RYUJIN III EVA EDITION * ASUS ROG RYUJIN III WHITE EDITION diff --git a/Documentation/hwmon/axiado-tsadc.rst b/Documentation/hwmon/axiado-tsadc.rst new file mode 100644 index 00000000000000..b8fd0aeacc1f8d --- /dev/null +++ b/Documentation/hwmon/axiado-tsadc.rst @@ -0,0 +1,41 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver axiado-tsadc +========================== + +Supported chips: + + * Axiado AX3000 and AX3005 on-chip temperature sensor (TSADC) + + Prefix: 'axiado_tsadc' + + Addresses scanned: - + + Datasheet: Not publicly available + +Authors: + + - Petar Stepanovic + +Description +----------- + +This driver supports the Temperature Sensor ADC (TSADC) block found on Axiado +AX3000 and AX3005 SoCs. The block monitors the on-die temperature of the SoC. + +An SoC may instantiate several TSADC blocks at different locations on the die. +Each block is described by its own device tree node and is registered as a +separate hwmon device with a single temperature channel. + +The sensor is configured for continuous conversion at probe time, averaging +16 samples per conversion. The averaged 12-bit ADC code is converted to +millidegrees Celsius using a lookup table with linear interpolation between +entries. The table covers -40 to +125 degrees Celsius; readings outside that +range are clamped to the nearest supported temperature. + +sysfs entries +------------- + +======================= ======================================================= +temp1_input Die temperature in millidegrees Celsius (read-only) +======================= ======================================================= diff --git a/Documentation/hwmon/honor-fmi.rst b/Documentation/hwmon/honor-fmi.rst new file mode 100644 index 00000000000000..a42a1dd532e86b --- /dev/null +++ b/Documentation/hwmon/honor-fmi.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver honor-fmi +======================= + +Supported systems: + + * HONOR FMI-XX + +Author: Nikita Dubrovskih + +Description +----------- + +The driver provides read-only monitoring of the fan speed on the HONOR FMI-XX. +The system firmware implements a ``GFNS`` ACPI method which returns the speed +of one of two firmware fan channels in RPM. Embedded Controller access and +serialization are handled by the firmware method. + +The driver does not expose fan control or direct Embedded Controller access. + +Sysfs entries +------------- + +The following attributes are supported: + +======================= ======= ============================================= +Name Perm Description +======================= ======= ============================================= +``fan1_input`` RO Fan channel 0 speed in RPM +``fan2_input`` RO Fan channel 1 speed in RPM +======================= ======= ============================================= diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 9955a525436ae6..21ad419fcbf137 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -51,6 +51,7 @@ Hardware Monitoring Kernel Drivers asus_ec_sensors asus_rog_ryujin asus_wmi_sensors + axiado-tsadc bcm54140 bel-pfe bpa-rs600 @@ -90,6 +91,7 @@ Hardware Monitoring Kernel Drivers gxp-fan-ctrl hac300s hih6130 + honor-fmi hp-wmi-sensors hs3001 htu31 @@ -165,6 +167,7 @@ Hardware Monitoring Kernel Drivers max197 max20730 max20751 + max20826 max20830 max20860a max31722 @@ -186,6 +189,7 @@ Hardware Monitoring Kernel Drivers mcp3021 mcp9982 menf21bmc + minisforum-um780xtx mlxreg-fan mp2856 mp2869 diff --git a/Documentation/hwmon/it87.rst b/Documentation/hwmon/it87.rst index fc1c90b023ae6e..c33ba8a0749a8a 100644 --- a/Documentation/hwmon/it87.rst +++ b/Documentation/hwmon/it87.rst @@ -11,6 +11,14 @@ Supported chips: Datasheet: Not publicly available + * IT8613E + + Prefix: 'it8613' + + Addresses scanned: from Super I/O config space (8 I/O ports) + + Datasheet: Not publicly available + * IT8620E Prefix: 'it8620' diff --git a/Documentation/hwmon/lm63.rst b/Documentation/hwmon/lm63.rst index 9e27367d74058f..c40bd0dfe591c4 100644 --- a/Documentation/hwmon/lm63.rst +++ b/Documentation/hwmon/lm63.rst @@ -33,6 +33,16 @@ Supported chips: http://www.national.com/pf/LM/LM96163.html + * Sensylink CTF2301 + + Prefix: 'ctf2301' + + Addresses scanned: none + + Datasheet: https://www.sensylink.com/upload/1/net.sensylink.portal/1689557281035.pdf + + Register description: https://github.com/TroyMitchell911/ctf2301-datasheet + Author: Jean Delvare @@ -62,6 +72,8 @@ value have to be masked out. The value is still 16 bit in width. All temperature values are given in degrees Celsius. Resolution is 1.0 degree for the local temperature, 0.125 degree for the remote temperature. +The CTF2301 local temperature input and limit have a resolution of 0.0625 +degree. The fan speed is measured using a tachometer. Contrary to most chips which store the value in an 8-bit register and have a selectable clock divider @@ -93,3 +105,13 @@ support these GPIO lines at present. The LM96163 is an enhanced version of LM63 with improved temperature accuracy and better PWM resolution. For LM96163, the external temperature sensor type is configurable as CPU embedded diode(1) or 3904 transistor(2). + +The CTF2301 is register-compatible with the LM63 family and provides 12 fan +control lookup table entries. It supports 8-bit PWM resolution when configured +for a 22.5 kHz PWM frequency. + +Device tree nodes may declare ``#cooling-cells`` to register the fan controller +with the thermal framework. The cooling state is mapped to the existing +``pwm1`` range from 0 to 255. The driver keeps the current automatic or manual +fan control mode during probe and switches to manual control when the thermal +framework first requests a cooling state. diff --git a/Documentation/hwmon/max20826.rst b/Documentation/hwmon/max20826.rst new file mode 100644 index 00000000000000..bcabbc64c0398a --- /dev/null +++ b/Documentation/hwmon/max20826.rst @@ -0,0 +1,139 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver max20826 +====================== + +Supported chips: + + * Analog Devices MAX20826 + + Prefix: 'max20826' + + Addresses scanned: - + + Datasheet: Datasheet is not publicly available. + + * Analog Devices MAX20855B + + Prefix: 'max20855b' + + Addresses scanned: - + + Datasheet: Datasheet is not publicly available. + + * Analog Devices MAX20908 + + Prefix: 'max20908' + + Addresses scanned: - + + Datasheet: Datasheet is not publicly available. + + * Analog Devices MAX20912 + + Prefix: 'max20912' + + Addresses scanned: - + + Datasheet: Datasheet is not publicly available. + + * Analog Devices MAX20916 + + Prefix: 'max20916' + + Addresses scanned: - + + Datasheet: Datasheet is not publicly available. + +Author: + + - Nuno Sá + + +Description +----------- + +This driver supports hardware monitoring for Analog Devices MAX20826, +MAX20855B, MAX20908, MAX20912, and MAX20916 multiphase voltage regulator +controllers with PMBus interface. + +The devices are dual-loop, multiphase controllers. Depending on the device and +configuration, the high-speed processor voltage-control interface can be +Nvidia PWMVID, Intel SVID, AMD SVI3, or AVSBus. PMBus is used for monitoring, +configuration, status, and fault reporting. + +The driver detects whether the device uses PMBus page mode or direct address +mode. In direct address mode, rail B is accessed at the rail A I2C address plus +one. If rail B is present, the driver exposes a second PMBus page. + +The driver detects the active number of phases and exposes per-phase input and +output current attributes through the PMBus virtual phase support. + +Usage Notes +----------- + +This driver does not auto-detect devices. You will have to instantiate the +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for +details. + +The optional ``avren`` and ``bvren`` GPIOs may be provided to control the rail A +and rail B hardware enable pins. If regulator support is enabled, the rails are +also registered through the PMBus regulator framework. + +The source of the output voltage of each rail, i.e. either the high-speed +processor voltage-control interface or PMBus, is normally selected once, before +the processor boots. Hence it is described in firmware with the +``adi,rail-a-high-speed`` and ``adi,rail-b-high-speed`` properties. If the +property is not given for a rail, PMBus controls its output voltage. + +Sysfs entries +------------- + +The following attributes are supported. Limits, alarms, and per-phase entries +are exposed depending on device capabilities, rail configuration, and detected +phase count. + +=========================== ================================================ +in1_label "vin" +in1_input Measured input voltage +in1_alarm Input voltage alarm +in[2-3]_label "vout[1-2]" +in[2-3]_input Measured output voltage +in[2-3]_alarm Output voltage alarm +currX_label "iinN", "iinN.P", "ioutN", or "ioutN.P" +currX_input Measured input/output current +currX_alarm Current alarm +powerX_label "pinN" or "poutN" +powerX_input Measured input/output power +temp[1-2]_input Measured temperature +temp[1-2]_alarm Temperature alarm +=========================== ================================================ + +Notes +----- + +``N`` is the rail number, starting at 1. ``P`` is the phase number, starting at +0. The exact ``currX`` indices depend on the number of present rails and on the +detected phase count. + +Debugfs entries +--------------- + +In addition to the entries provided by the PMBus core, the following entries are +available for debug purposes only. They are not part of any ABI and may change +or go away at any time. + +=========================== ================================================ +in[2-3]_high_speed_en Enable high-speed voltage-control interface +in_high_speed_bus Active high-speed voltage-control interface +=========================== ================================================ + +``in[2-3]_high_speed_en`` is a per-rail read/write entry matching the standard +hwmon voltage channels for the output rails. ``in2_high_speed_en`` controls rail +A / ``vout1``. ``in3_high_speed_en`` controls rail B / ``vout2`` and is only +present if rail B is detected. Writing 1 selects the high-speed voltage-control +interface for that rail; writing 0 selects PMBus voltage control. + +``in_high_speed_bus`` is a read-only entry reporting the high-speed interface +used by the device, for example ``Nvidia PWMVID``, ``Intel SVID``, ``AMD SVI3``, +or ``AVSBus``. diff --git a/Documentation/hwmon/max34440.rst b/Documentation/hwmon/max34440.rst index 866f22b88e9b36..a4848da495e6d8 100644 --- a/Documentation/hwmon/max34440.rst +++ b/Documentation/hwmon/max34440.rst @@ -27,6 +27,22 @@ Supported chips: Datasheet: - + * ADI ADPM12300 + + Prefixes: 'adpm12300' + + Addresses scanned: - + + Datasheet: - + + * ADI ADPM12886 + + Prefixes: 'adpm12886' + + Addresses scanned: - + + Datasheet: - + * Maxim MAX34440 Prefixes: 'max34440' @@ -105,11 +121,12 @@ This driver supports multiple devices: hardware monitoring for Maxim MAX34440 PMBus 6-Channel Power-Supply Manager, MAX34441 PMBus 5-Channel Power-Supply Manager and Intelligent Fan Controller, and MAX34446 PMBus Power-Supply Data Logger; PMBus Voltage Monitor and Sequencers for MAX34451, MAX34452, MAX34460, -and MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, and ADPM12250. The -MAX34451 and MAX34452 support monitoring voltage or current of 16 channels based -on GIN pins. The MAX34460 supports 12 voltage channels, and the MAX34461 supports -16 voltage channels. The ADPM12160, ADPM12200, and ADPM12250 also monitor both -input and output of voltage and current. +and MAX34461; PMBus DC/DC Power Module ADPM12160, ADPM12200, ADPM12250, +ADPM12300, and ADPM12886. The MAX34451 and MAX34452 support monitoring voltage +or current of 16 channels based on GIN pins. The MAX34460 supports 12 voltage +channels, and the MAX34461 supports 16 voltage channels. The ADPM12160, ADPM12200, +ADPM12250, ADPM12300, and ADPM12886 also monitor both input and output of voltage +and current. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers. @@ -167,8 +184,8 @@ in[1-6]_reset_history Write any value to reset history. .. note:: - MAX34446 only supports in[1-4]. - - ADPM12160, ADPM12200, and ADPM12250 only supports in[1-2]. Label is "vin1" - and "vout1" respectively. + - ADPM12160, ADPM12200, ADPM12250, ADPM12300, and ADPM12886 only supports in[1-2]. + Label is "vin1" and "vout1" respectively. Curr ~~~~ @@ -190,9 +207,10 @@ curr[1-6]_reset_history Write any value to reset history. - in6 and curr6 attributes only exist for MAX34440. - MAX34446 only supports curr[1-4]. - - For ADPM12160, ADPM12200, and ADPM12250, curr[1] is "iin1" + - For ADPM12160, ADPM12200, ADPM12250, ADPM12300, and ADPM12886, curr[1] is "iin1" - For ADPM12160, and ADPM12200 curr[2-6] are "iout[1-5]". - - For ADPM12250, curr[2-4] are "iout[1-3]". + - For ADPM12250, and ADPM12300 curr[2-4] are "iout[1-3]". + - For ADPM12886 curr[2-3] are "iout[1-2]". Power ~~~~~ @@ -228,7 +246,7 @@ temp[1-8]_reset_history Write any value to reset history. .. note:: - temp7 and temp8 attributes only exist for MAX34440. - MAX34446 only supports temp[1-3]. - - ADPM12160, ADPM12200, and ADPM12250 only supports temp[1]. + - ADPM12160, ADPM12200, ADPM12250, ADPM12300, and ADPM12886 only supports temp[1]. .. note:: diff --git a/Documentation/hwmon/minisforum-um780xtx.rst b/Documentation/hwmon/minisforum-um780xtx.rst new file mode 100644 index 00000000000000..8237d525a9c7d3 --- /dev/null +++ b/Documentation/hwmon/minisforum-um780xtx.rst @@ -0,0 +1,68 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +==================================== +Kernel driver minisforum-um780xtx +==================================== + +Supported systems: + + * Minisforum UM780 XTX + + * DMI product name: ``Venus series`` + * Mainboard: ``F7BSD``, revision ``1.1`` + * BIOS version: ``1.06`` + +Author: Sebastián Peyrott + +Description +----------- + +This driver exposes hardware monitoring and fan-control data cached by the +IT5571E embedded controller. It uses the ACPI EC transport and only binds to +the exact system and firmware identity listed above. + +The two temperature channels are the values used by the EC's fan-control +loops. Their physical sensor placement is not known. The CPU channel is the +filtered AMD SB-TSI temperature, while the system channel is an external +thermistor input. + +The fan tachometers are read through OEM EC commands. Since each byte is +returned by a separate command, the driver uses a high-low-high sequence and +retries if the high byte changes. + +Sysfs entries +------------- + +========================== ============================================== +``temp1_input`` CPU-fan control temperature +``temp2_input`` System-fan control temperature +``fan1_input`` CPU fan speed in RPM +``fan2_input`` System fan speed in RPM +``pwm1_enable`` CPU profile: 2 is OEM B1, 3 is OEM B2 +``pwm2_auto_point1_temp`` Off-to-low system-fan transition temperature +``pwm2_auto_point2_temp`` Low-to-high system-fan transition temperature +========================== ============================================== + +CPU fan profiles +---------------- + +Values 2 and 3 of ``pwm1_enable`` select the two complete automatic profiles +implemented by the firmware. Writing either value reloads the whole CPU fan +curve, including firmware state which is not visible through the ACPI EC +window. Other values are rejected. + +System fan thresholds +--------------------- + +The two writable system-fan temperatures are rounded to whole degrees Celsius +and clamped to preserve strict ordering between both visible thresholds and +the firmware's internal third threshold. The third threshold does not select +a new PWM target and is therefore not exposed as another auto point. + +Resume state preservation +------------------------- + +Fan settings are held in EC RAM. On the supported firmware, initialization +during s2idle resume reloads the factory system-fan curve. The driver retains +the last coherent CPU profile and system-fan thresholds selected through its +interface and restores them synchronously at resume. diff --git a/Documentation/hwmon/nct6775.rst b/Documentation/hwmon/nct6775.rst index 41b21bed7e278d..d2dedade0606a4 100644 --- a/Documentation/hwmon/nct6775.rst +++ b/Documentation/hwmon/nct6775.rst @@ -16,6 +16,38 @@ Supported chips: Datasheet: Available from the Nuvoton web site + * Nuvoton NCT6112D/NCT6114D/NCT6116D + + Prefix: 'nct6116' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Available from the Nuvoton web site + + * Nuvoton NCT6122D/NCT6126D + + Prefix: 'nct6126' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Available from the Nuvoton web site + + * Nuvoton NCT6112D/NCT6114D/NCT6116D + + Prefix: 'nct6116' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Available from the Nuvoton web site + + * Nuvoton NCT6122D/NCT6126D + + Prefix: 'nct6126' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Available from the Nuvoton web site + * Nuvoton NCT5572D/NCT6771F/NCT6772F/NCT6775F/W83677HG-I Prefix: 'nct6775' diff --git a/Documentation/hwmon/sht4x.rst b/Documentation/hwmon/sht4x.rst index ba094ad0e2816f..b9564632a1be2d 100644 --- a/Documentation/hwmon/sht4x.rst +++ b/Documentation/hwmon/sht4x.rst @@ -15,6 +15,16 @@ Supported Chips: English: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT4x_Datasheet.pdf + * Sensirion STS4X + + Prefix: 'sts4x' + + Addresses scanned: None + + Datasheet: + + English: https://sensirion.com/resource/datasheet/sts4x + Author: Navin Sankar Velliangiri @@ -22,9 +32,10 @@ Description ----------- This driver implements support for the Sensirion SHT4x chip, a humidity -and temperature sensor. Temperature is measured in degree celsius, relative -humidity is expressed as a percentage. In sysfs interface, all values are -scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500. +and temperature sensor, and the Sensirion STS4x chip, a temperature sensor. +Temperature is measured in degree celsius, relative humidity is expressed as a +percentage (on SHT4x only). In sysfs interface, all values are scaled by 1000, +i.e. the value for 31.5 degrees celsius is 31500. Usage Notes ----------- diff --git a/Documentation/hwmon/socfpga-hwmon.rst b/Documentation/hwmon/socfpga-hwmon.rst index e5da42556a627a..664103a38195a3 100644 --- a/Documentation/hwmon/socfpga-hwmon.rst +++ b/Documentation/hwmon/socfpga-hwmon.rst @@ -7,6 +7,7 @@ Supported chips: * Altera Stratix 10 SoC FPGA * Altera Agilex SoC FPGA + * Altera Agilex 5 SoC FPGA Authors: - Nazim Amirul @@ -32,3 +33,54 @@ driver based on the service layer compatible string: * intel,stratix10-svc * intel,agilex-svc + * intel,agilex5-svc + +Channel mappings are fixed in the driver (not described in DT). The tables +below list the SDM page/channel encodings used for each family. + +Temperature channels +~~~~~~~~~~~~~~~~~~~~ + +========== ==== ======= ================================= +Family Page Channel Label +========== ==== ======= ================================= +Stratix 10 0 0 Main Die SDM +Agilex 0 0 Main Die SDM +Agilex 1 0 Main Die corner bottom left max +Agilex 2 0 Main Die corner top left max +Agilex 3 0 Main Die corner bottom right max +Agilex 4 0 Main Die corner top right max +Agilex 5 0 0 Main Die SDM +Agilex 5 1 0 Main Die corner bottom left max +Agilex 5 3 0 Main Die corner bottom right max +Agilex 5 4 0 Main Die corner top right max +========== ==== ======= ================================= + +Agilex 5 omits SDM temperature channel 2 (top-left corner on Agilex) +because that sensor is not present in hardware. The remaining sensors keep +the same channel numbers as Agilex. + +Voltage channels +~~~~~~~~~~~~~~~~ + +========== ==== ======= ================= +Family Page Channel Label +========== ==== ======= ================= +Stratix 10 0 2 0.8V VCC +Stratix 10 0 3 1.8V VCCIO_SDM +Stratix 10 0 6 0.9V VCCERAM +Agilex 0 2 0.8V VCC +Agilex 0 3 1.8V VCCIO_SDM +Agilex 0 4 1.8V VCCPT +Agilex 0 5 1.2V VCCCRCORE +Agilex 0 6 0.9V VCCH +Agilex 0 7 0.8V VCCL +Agilex 5 0 2 0.8V VCC +Agilex 5 0 3 1.8V VCCIO_SDM +Agilex 5 0 4 1.8V VCCPT +Agilex 5 0 5 1.2V VCCCRCORE +Agilex 5 0 6 0.9V VCCH +Agilex 5 0 7 0.8V VCCL +========== ==== ======= ================= + +Agilex 5 reuses the Agilex voltage SDM page/channel layout and labels. diff --git a/Documentation/hwmon/sysfs-interface.rst b/Documentation/hwmon/sysfs-interface.rst index 94e1bbce172a33..c5ac4d3665dc43 100644 --- a/Documentation/hwmon/sysfs-interface.rst +++ b/Documentation/hwmon/sysfs-interface.rst @@ -47,7 +47,7 @@ There is only one value per file, unlike the older /proc specification. The common scheme for files naming is: _. Usual types for sensor chips are "in" (voltage), "temp" (temperature) and "fan" (fan). Usual items are "input" (measured value), "max" (high -threshold, "min" (low threshold). Numbering usually starts from 1, +threshold), "min" (low threshold). Numbering usually starts from 1, except for voltages which start from 0 (because most data sheets use this). A number is always used for elements that can be present more than once, even if there is a single element of the given type on the @@ -121,12 +121,18 @@ Voltages `in[0-*]_lcrit` Voltage critical min value. +`in[0-*]_lemergency` + Voltage emergency min value. + `in[0-*]_max` Voltage max value. `in[0-*]_crit` Voltage critical max value. +`in[0-*]_emergency` + Voltage emergency max value. + `in[0-*]_input` Voltage input value. @@ -332,6 +338,9 @@ Currents `curr[1-*]_crit` Current critical high value. +`curr[1-*]_emergency` + Current emergency high value. + `curr[1-*]_input` Current input value. @@ -527,12 +536,15 @@ implementation. +-------------------------------+-----------------------+ | **`in[0-*]_min_alarm`, | Limit alarm | | `in[0-*]_max_alarm`, | | +| `in[0-*]_lemergency_alarm`, | | | `in[0-*]_lcrit_alarm`, | - 0: no alarm | | `in[0-*]_crit_alarm`, | - 1: alarm | +| `in[0-*]_emergency_alarm`, | | | `curr[1-*]_min_alarm`, | | | `curr[1-*]_max_alarm`, | RO | | `curr[1-*]_lcrit_alarm`, | | | `curr[1-*]_crit_alarm`, | | +| `curr[1-*]_emergency_alarm`, | | | `power[1-*]_cap_alarm`, | | | `power[1-*]_max_alarm`, | | | `power[1-*]_crit_alarm`, | | diff --git a/Documentation/hwmon/tps25990.rst b/Documentation/hwmon/tps25990.rst index 04faec780d2628..e8bc9a550bda39 100644 --- a/Documentation/hwmon/tps25990.rst +++ b/Documentation/hwmon/tps25990.rst @@ -9,26 +9,31 @@ Supported chips: Prefix: 'tps25990' - * Datasheet + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 - Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps25990 + * TI TPS1689 + + Prefix: 'tps1689' + + Datasheet: Publicly available at Texas Instruments website: https://www.ti.com/lit/gpn/tps1689 Author: Jerome Brunet + Stoyan Bogdanov Description ----------- -This driver implements support for TI TPS25990 eFuse. +This driver implements support for TI TPS25990 and TI TPS1689 eFuse chips. This is an integrated, high-current circuit protection and power management device with PMBUS interface -Device compliant with: +Devices are compliant with: - PMBus rev 1.3 interface. -Device supports direct format for reading input voltages, +Devices supports direct format for reading input voltages, output voltage, input current, input power and temperature. Due to the specificities of the chip, all history reset attributes diff --git a/Documentation/hwmon/tps53679.rst b/Documentation/hwmon/tps53679.rst index dd5e4a37375d11..3d584197b3a03c 100644 --- a/Documentation/hwmon/tps53679.rst +++ b/Documentation/hwmon/tps53679.rst @@ -3,6 +3,14 @@ Kernel driver tps53679 Supported chips: + * Texas Instruments TPS53622 + + Prefix: 'tps53622' + + Addresses scanned: - + + Datasheet: https://www.ti.com/lit/gpn/TPS53622 + * Texas Instruments TPS53647 Prefix: 'tps53647' @@ -11,6 +19,14 @@ Supported chips: Datasheet: https://www.ti.com/lit/gpn/tps53647 + * Texas Instruments TPS53659 + + Prefix: 'tps53659' + + Addresses scanned: - + + Datasheet: https://www.ti.com/lit/gpn/TPS53659 + * Texas Instruments TPS53667 Prefix: 'tps53667' @@ -59,6 +75,14 @@ Supported chips: Datasheet: Available under NDA + * Texas Instruments TPS536C7 + + Prefix: 'tps536c7' + + Addresses scanned: - + + Datasheet: https://www.ti.com/lit/gpn/TPS536C7 + Authors: Vadim Pasternak @@ -69,7 +93,12 @@ Description ----------- Chips in this series are multi-phase step-down converters with one or two -output channels and up to 8 phases per channel. +output channels and up to 12 phases in total, depending on the device. + +For TPS536C7 only aggregate per-channel telemetry is exposed; the +individual per-phase output currents the hardware can report are not. +Attributes for the second output channel are present only when channel B +is configured. Usage Notes @@ -108,7 +137,7 @@ in1_crit_alarm Input voltage critical high alarm. in[N]_label "vout[1-2]" - TPS53647, TPS53667: N=2 - - TPS53679, TPS53588: N=2,3 + - TPS53622, TPS53659, TPS53679, TPS53688, TPS536C7: N=2,3 in[N]_input Measured output voltage. @@ -135,7 +164,7 @@ in[N]_crit_alarm Output voltage critical high alarm. temp[N]_input Measured temperature. - TPS53647, TPS53667: N=1 - - TPS53679, TPS53681, TPS53588: N=1,2 + - TPS53622, TPS53659, TPS53679, TPS53681, TPS53688, TPS536C7: N=1,2 temp[N]_max Maximum temperature. @@ -152,7 +181,7 @@ power1_input Measured input power. power[N]_label "pout[1-2]". - TPS53647, TPS53667: N=2 - - TPS53676, TPS53679, TPS53681, TPS53588: N=2,3 + - TPS53622, TPS53659, TPS53676, TPS53679, TPS53681, TPS53688, TPS536C7: N=2,3 power[N]_input Measured output power. @@ -175,7 +204,7 @@ curr[N]_label "iout[1-2]" or "iout1.[0-5]". telemetry supported on TPS53676 and TPS53681 only. - TPS53647, TPS53667: N=2 - - TPS53679, TPS53588: N=2,3 + - TPS53622, TPS53659, TPS53679, TPS53688, TPS536C7: N=2,3 - TPS53676: N=2-8 - TPS53681: N=2-9 diff --git a/Documentation/hwmon/yogafan.rst b/Documentation/hwmon/yogafan.rst index 6395c94f4a6b06..239c7b5761caf6 100644 --- a/Documentation/hwmon/yogafan.rst +++ b/Documentation/hwmon/yogafan.rst @@ -85,27 +85,30 @@ immediately to ensure the user knows the fan has stopped. :: - MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier - ---------------------------------------------------------------------------------------------------- - 82N7 | Yoga 14cACN | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 - 80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 - 83E2 / 83DN | Yoga Pro 7/9 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 - 82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 - 81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 - 82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 - 82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 - 82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 - 82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 - 82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1 - 83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 - 81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 - 83KF | XiaoXinPro 13ARE | 0x06/0xFE | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 - 82KU | IdeaPad 3 15ALC6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 - 83RU | Legion Pro 7 16 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 - 83KF | Yoga Pro 7 14IAH | 0x06 | \_SB.PC00.LPCB.EC0.FANS | 8-bit | 100 - 83BS | Yoga 7 16ARP8 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 - *Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100 - ---------------------------------------------------------------------------------------------------- + MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier + ------------------------------------------------------------------------------------------------------- + 82N7 | Yoga 14cACN 2021 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 + 81TD | Yoga 740-15IML | 0x06 | \_SB.PCI0.LPCB.EC0.FANS | 8-bit | 100 + 82KU | IdeaPad 3 15ALC6 Ub | 0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 + 80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 83E2 | Yoga Pro 7 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 + 83DN | Yoga Pro 9 16IMH9 | 0x06/0xFE | \_SB.PC00.LPCB.EC0.FANS/FA2S | 8-bit | 100 + 82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100 + 81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 + 82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 + 82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1 + 82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1 + 82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1 + 83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 83KF | XiaoXinPro 13ARE | 0x06/0xFE | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 + 82KU | IdeaPad 3 15ALC6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 + 83RU | Legion Pro 7 16 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 + 83KF | Yoga Pro 7 14IAH | 0x06 | \_SB.PC00.LPCB.EC0.FANS | 8-bit | 100 + 83BS | Yoga 7 16ARP8 | 0x03/0x06 | \_SB.PCI0.LPC0.EC0.FANS/FA2S | 8-bit | 100 + *Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100 + ------------------------------------------------------------------------------------------------------- METHODOLOGY & IDENTIFICATION: @@ -158,6 +161,7 @@ Contributors & DSDT Providers: - **Sarbajit Sarkar** (Lenovo LOQ 15IAX9) - **HinataKato** (XiaoXin Pro 13ARE 2020 - 83KF) - **PenPenIsGod** (IdeaPad 3 15ALC6 - 82KU & Legion Pro 7 16AFR10H - 83RU) +- **wizard-28** (IdeaPad 3 15ALC6 Ub - 82KU) - **unlockxiaom** (Legion Pro 7 16AFR10H - 83RU, Yoga Pro 7 14IAH10 - 83KF, ThinkCentre M80q) - **Phani Pavan K** (Yoga Pro 7 14IAH10 - 83KF) - **Splarkszter** (Yoga 7 16ARP8 - 83BS) diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c24..2cd3e36bc97319 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2177,7 +2177,7 @@ F: drivers/net/arcnet/ F: include/uapi/linux/if_arcnet.h ARCTIC FAN CONTROLLER DRIVER -M: Aureo Serrano de Souza +M: ARCTIC (HK) L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/hwmon/arctic_fan_controller.rst @@ -4508,6 +4508,16 @@ F: Documentation/devicetree/bindings/spi/axiado,ax3000-spi.yaml F: drivers/spi/spi-axiado.c F: drivers/spi/spi-axiado.h +AXIADO TSADC DRIVER +M: Petar Stepanovic +M: Akhila Kavi +M: Prasad Bolisetty +L: linux-hwmon@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/hwmon/axiado,ax3000-tsadc.yaml +F: Documentation/hwmon/axiado-tsadc.rst +F: drivers/hwmon/axiado-tsadc.c + AYANEO PLATFORM EC DRIVER M: Antheas Kapenekakis L: platform-driver-x86@vger.kernel.org @@ -11959,6 +11969,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml F: drivers/iio/pressure/mprls0025pa* +HONOR FMI-XX HARDWARE MONITOR DRIVER +M: Nikita Dubrovskih +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/honor-fmi.rst +F: drivers/hwmon/honor-fmi.c + HP BIOSCFG DRIVER M: Jorge Lopez L: platform-driver-x86@vger.kernel.org @@ -15987,6 +16004,15 @@ F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max17616.yaml F: Documentation/hwmon/max17616.rst F: drivers/hwmon/pmbus/max17616.c +MAX20826 HARDWARE MONITOR CONTROLLER DRIVER +M: Nuno Sá +L: linux-hwmon@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/hwmon/pmbus/adi,max20826.yaml +F: Documentation/hwmon/max20826.rst +F: drivers/hwmon/pmbus/max20826.c + MAX20830 HARDWARE MONITOR DRIVER M: Alexis Czezar Torreno L: linux-hwmon@vger.kernel.org @@ -18177,6 +18203,13 @@ F: include/linux/min_heap.h F: lib/min_heap.c F: lib/tests/min_heap_kunit.c +MINISFORUM UM780 XTX EC HARDWARE MONITOR DRIVER +M: Sebastián Peyrott +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/minisforum-um780xtx.rst +F: drivers/hwmon/minisforum-um780xtx.c + MIPI CCS, SMIA AND SMIA++ IMAGE SENSOR DRIVER M: Sakari Ailus L: linux-media@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index fecff8610ea8ba..3d8b2c958b2982 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -311,6 +311,17 @@ config SENSORS_AXI_FAN_CONTROL This driver can also be built as a module. If so, the module will be called axi-fan-control +config SENSORS_AXIADO_TSADC + tristate "Axiado AX3000 TSADC temperature sensor" + depends on ARCH_AXIADO || COMPILE_TEST + help + If you say yes here you get support for the on-chip temperature + sensor (TSADC) found on Axiado AX3000 and AX3005 SoCs. The die + temperature is reported through the hwmon sysfs interface. + + This driver can also be built as a module. If so, the module + will be called axiado-tsadc. + config SENSORS_K8TEMP tristate "AMD Athlon64/FX or Opteron temperature sensor" depends on X86 && PCI @@ -1490,6 +1501,22 @@ config SENSORS_MENF21BMC_HWMON This driver can also be built as a module. If so the module will be called menf21bmc_hwmon. +config SENSORS_MINISFORUM_UM780XTX + tristate "Minisforum UM780 XTX EC hardware monitoring" + depends on ACPI_EC && DMI && X86 + help + If you say yes here you get support for the hardware monitoring + features of the embedded controller in the Minisforum UM780 XTX. + This includes CPU and system fan speeds, their control temperatures, + two CPU fan profiles, system fan temperature thresholds, and + preservation of the selected settings across suspend and resume. + + This driver only binds to explicitly supported board and firmware + versions. + + This driver can also be built as a module. If so, the module + will be called minisforum-um780xtx. + config SENSORS_MR75203 tristate "Moortec Semiconductor MR75203 PVT Controller" select REGMAP_MMIO @@ -1521,10 +1548,10 @@ config SENSORS_LM63 depends on I2C help If you say yes here you get support for the National - Semiconductor LM63, LM64, and LM96163 remote diode digital temperature - sensors with integrated fan control. Such chips are found - on the Tyan S4882 (Thunder K8QS Pro) motherboard, among - others. + Semiconductor LM63, LM64, LM96163, and Sensylink CTF2301 + remote diode digital temperature sensors with integrated fan + control. Such chips are found on the Tyan S4882 (Thunder K8QS + Pro) motherboard, among others. This driver can also be built as a module. If so, the module will be called lm63. @@ -2827,6 +2854,16 @@ config SENSORS_ASUS_EC This driver can also be built as a module. If so, the module will be called asus_ec_sensors. +config SENSORS_HONOR_FMI + tristate "HONOR FMI-XX fan monitor" + depends on X86 && ACPI + help + If you say yes here, you get support for fan speed monitoring on + the HONOR FMI-XX laptop through its firmware ACPI method. + + This driver can also be built as a module. If so, the module + will be called honor-fmi. + config SENSORS_HP_WMI tristate "HP WMI Sensors" depends on ACPI_WMI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 1229b6b3996d79..f84f9df27466a2 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o obj-$(CONFIG_SENSORS_ASUS_EC) += asus-ec-sensors.o obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o +obj-$(CONFIG_SENSORS_HONOR_FMI) += honor-fmi.o obj-$(CONFIG_SENSORS_HP_WMI) += hp-wmi-sensors.o # Native drivers @@ -59,6 +60,7 @@ 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 +obj-$(CONFIG_SENSORS_AXIADO_TSADC) += axiado-tsadc.o obj-$(CONFIG_SENSORS_CGBC) += cgbc-hwmon.o obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o @@ -180,6 +182,7 @@ obj-$(CONFIG_SENSORS_TC654) += tc654.o obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o +obj-$(CONFIG_SENSORS_MINISFORUM_UM780XTX) += minisforum-um780xtx.o obj-$(CONFIG_SENSORS_MR75203) += mr75203.o obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o obj-$(CONFIG_SENSORS_NCT6694) += nct6694-hwmon.o diff --git a/drivers/hwmon/abituguru.c b/drivers/hwmon/abituguru.c index ba8c68ae45953b..3bb959d16dbf53 100644 --- a/drivers/hwmon/abituguru.c +++ b/drivers/hwmon/abituguru.c @@ -475,7 +475,7 @@ abituguru_detect_bank1_sensor_type(struct abituguru_data *data, u8 val, test_flag, buf[3]; int i, ret = -ENODEV; /* error is the most common used retval :| */ - /* If overriden by the user return the user selected type */ + /* If overridden by the user return the user selected type */ if (bank1_types[sensor_addr] >= ABIT_UGURU_IN_SENSOR && bank1_types[sensor_addr] <= ABIT_UGURU_NC) { ABIT_UGURU_DEBUG(2, "assuming sensor type %d for bank1 sensor " @@ -1262,7 +1262,7 @@ static int abituguru_probe(struct platform_device *pdev) /* * El weirdo probe order, to keep the sysfs order identical to the - * BIOS and window-appliction listing order. + * BIOS and window-application listing order. */ static const u8 probe_order[ABIT_UGURU_MAX_BANK1_SENSORS] = { 0x00, 0x01, 0x03, 0x04, 0x0A, 0x08, 0x0E, 0x02, diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 8a539e8d1334b3..d8e8715094055d 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -54,8 +54,8 @@ static int can_cap_in_hardware(void) } static const struct acpi_device_id power_meter_ids[] = { - {"ACPI000D", 0}, - {"", 0}, + { .id = "ACPI000D" }, + { } }; MODULE_DEVICE_TABLE(acpi, power_meter_ids); diff --git a/drivers/hwmon/adm1177.c b/drivers/hwmon/adm1177.c index dc4d8a214d1b48..85ad4a3009bd46 100644 --- a/drivers/hwmon/adm1177.c +++ b/drivers/hwmon/adm1177.c @@ -105,7 +105,7 @@ static int adm1177_read(struct device *dev, enum hwmon_sensor_types type, return ret; dummy = (data[0] << 4) | (data[2] >> 4); /* - * convert to millivolts based on resistor devision + * convert to millivolts based on resistor division * (V_fullscale / 4096) * raw */ if (st->vrange_high) diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index d003ee3ebf062e..f99d38e82d0bb4 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -338,7 +338,7 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, data->oldconfig = config; /* - * Set to 16 bit resolution, continous conversion and comparator mode. + * Set to 16 bit resolution, continuous conversion and comparator mode. */ data->config = data->oldconfig; data->config &= ~(ADT7X10_MODE_MASK | ADT7X10_CT_POLARITY | diff --git a/drivers/hwmon/arctic_fan_controller.c b/drivers/hwmon/arctic_fan_controller.c index dbe84cd93c0837..9eb83a149cd63d 100644 --- a/drivers/hwmon/arctic_fan_controller.c +++ b/drivers/hwmon/arctic_fan_controller.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-2-Clause /* * Linux hwmon driver for ARCTIC Fan Controller * @@ -35,6 +35,8 @@ * Measured over 500 iterations: max ~563 ms. Keep 1 s as margin. */ #define ARCTIC_ACK_TIMEOUT_MS 1000 +/* MCU factory default; 40% of 0-255 is 102. */ +#define ARCTIC_PWM_DEFAULT 102 struct arctic_fan_data { struct hid_device *hdev; @@ -164,7 +166,7 @@ static int arctic_fan_write(struct device *dev, enum hwmon_sensor_types type, /* * Build the buffer and arm write_pending under in_report_lock so that - * reset_resume() cannot clear pwm_duty[] between the pwm_duty[] read + * reset_resume() cannot replace pwm_duty[] between the pwm_duty[] read * and the buffer write, and raw_event() cannot deliver a stale ACK * from a previous write into this write's completion. * @@ -256,14 +258,14 @@ static int arctic_fan_reset_resume(struct hid_device *hdev) unsigned long flags; /* - * The device resets its PWM channels to hardware defaults on power - * loss during suspend. Clear the cached duty values so they reflect - * the unknown hardware state, consistent with probe-time behaviour - * (the device has no GET_REPORT support). Hold in_report_lock so - * this does not race with a concurrent pwm read or write callback. + * The device resets its PWM channels to the MCU factory default + * (40%) on power loss during suspend. Restore the cache to that + * same default, consistent with probe-time behaviour (the device + * has no GET_REPORT support). Hold in_report_lock so this does + * not race with a concurrent pwm read or write callback. */ spin_lock_irqsave(&priv->in_report_lock, flags); - memset(priv->pwm_duty, 0, sizeof(priv->pwm_duty)); + memset(priv->pwm_duty, ARCTIC_PWM_DEFAULT, sizeof(priv->pwm_duty)); spin_unlock_irqrestore(&priv->in_report_lock, flags); return 0; } @@ -288,6 +290,8 @@ static int arctic_fan_probe(struct hid_device *hdev, priv->hdev = hdev; spin_lock_init(&priv->in_report_lock); init_completion(&priv->in_report_received); + /* Same MCU factory default as reset_resume(); see ARCTIC_PWM_DEFAULT above. */ + memset(priv->pwm_duty, ARCTIC_PWM_DEFAULT, sizeof(priv->pwm_duty)); hid_set_drvdata(hdev, priv); ret = hid_hw_start(hdev, HID_CONNECT_DRIVER); @@ -371,4 +375,4 @@ module_hid_driver(arctic_fan_driver); MODULE_AUTHOR("Aureo Serrano de Souza "); MODULE_DESCRIPTION("HID hwmon driver for ARCTIC Fan Controller"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index e43645e884fdb6..19f423e45ce5e4 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -962,6 +962,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x570_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-I GAMING", &board_info_strix_x570_i_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-A GAMING WIFI", + &board_info_strix_x670e_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-E GAMING WIFI", &board_info_strix_x670e_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-I GAMING WIFI", @@ -980,6 +982,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-A GAMING", + &board_info_strix_z490_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", &board_info_strix_z490_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-A GAMING WIFI D4", diff --git a/drivers/hwmon/asus_atk0110.c b/drivers/hwmon/asus_atk0110.c index 92afb64c09df8d..868c4e14d26acf 100644 --- a/drivers/hwmon/asus_atk0110.c +++ b/drivers/hwmon/asus_atk0110.c @@ -141,8 +141,8 @@ typedef ssize_t (*sysfs_show_func)(struct device *dev, struct device_attribute *attr, char *buf); static const struct acpi_device_id atk_ids[] = { - {ATK_HID, 0}, - {"", 0}, + { .id = ATK_HID }, + { } }; MODULE_DEVICE_TABLE(acpi, atk_ids); diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c index e297557ca34638..5ced69e94ead45 100644 --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -20,6 +20,7 @@ #define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb #define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade #define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada +#define USB_PRODUCT_ID_RYUJIN_III 0x1aa2 struct rog_ryujin_device_info { u8 temp_offset; @@ -613,6 +614,8 @@ static const struct hid_device_id rog_ryujin_table[] = { .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE), .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III), + .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, { } }; diff --git a/drivers/hwmon/axiado-tsadc.c b/drivers/hwmon/axiado-tsadc.c new file mode 100644 index 00000000000000..c111df6f00447f --- /dev/null +++ b/drivers/hwmon/axiado-tsadc.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2026 Axiado Corporation + * + * Driver for the Axiado on-chip temperature sensor (TSADC) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* TSADC register offsets */ +#define AXIADO_TSADC_MODE 0x0000 +#define AXIADO_TSADC_FUNC_CTRL 0x0004 +#define AXIADO_TSADC_AVG_DOUT 0x000c +#define AXIADO_TSADC_INTR_MASK 0x0010 +#define AXIADO_TSADC_ANA_REG 0x0020 +#define AXIADO_TSADC_SEL 0x0028 +#define AXIADO_TSADC_TSEN_EN 0x002c +#define AXIADO_TSADC_EN 0x0030 + +/* TSADC mode register: continuous conversion */ +#define AXIADO_TSADC_MODE_CONT 0x2 + +/* + * TSADC functional control register: bit 0 starts the conversion and + * bits [31:16] specify the number of samples. Average over 16 samples. + */ +#define AXIADO_TSADC_FUNC_CTRL_START BIT(0) +#define AXIADO_TSADC_FUNC_CTRL_NUM_SAMPLES GENMASK(31, 16) +#define AXIADO_TSADC_FUNC_CTRL_CLR 0x0 +#define AXIADO_TSADC_FUNC_CTRL_SET \ + (FIELD_PREP(AXIADO_TSADC_FUNC_CTRL_NUM_SAMPLES, 16) | \ + AXIADO_TSADC_FUNC_CTRL_START) + +/* TSADC average output register: 12-bit ADC result */ +#define AXIADO_TSADC_AVG_DOUT_MASK GENMASK(11, 0) + +/* Interrupt mask bits */ +#define AXIADO_TSADC_INTR_BIST_DONE BIT(0) +#define AXIADO_TSADC_INTR_LOOP_DONE BIT(1) +#define AXIADO_TSADC_INTR_THRESHOLD BIT(2) +#define AXIADO_TSADC_INTR_MASK_ALL \ + (AXIADO_TSADC_INTR_BIST_DONE | \ + AXIADO_TSADC_INTR_LOOP_DONE | \ + AXIADO_TSADC_INTR_THRESHOLD) + +/* TSADC analog block configuration required for temperature measurement */ +#define AXIADO_TSADC_ANA_REG_INIT 0x6 + +/* TSADC SEL: select temperature measurement */ +#define AXIADO_TSADC_SEL_TEMP 0x00 + +/* TSADC EN / TSEN_EN field values */ +#define AXIADO_TSADC_DIS 0x00 +#define AXIADO_TSADC_ENA 0x01 + +struct axiado_tsadc { + void __iomem *regs; +}; + +/* + * Lookup table: raw ADC output (12-bit) vs temperature in millidegrees + * Celsius. Raw values decrease as temperature increases, from -40°C + * to 125°C in 5°C steps. + */ +struct axiado_tsadc_point { + u32 raw; + int temp_mc; +}; + +static const struct axiado_tsadc_point axiado_tsadc_table[] = { + { .raw = 2776, .temp_mc = -40000 }, + { .raw = 2748, .temp_mc = -35000 }, + { .raw = 2721, .temp_mc = -30000 }, + { .raw = 2694, .temp_mc = -25000 }, + { .raw = 2667, .temp_mc = -20000 }, + { .raw = 2640, .temp_mc = -15000 }, + { .raw = 2612, .temp_mc = -10000 }, + { .raw = 2584, .temp_mc = -5000 }, + { .raw = 2556, .temp_mc = 0 }, + { .raw = 2528, .temp_mc = 5000 }, + { .raw = 2500, .temp_mc = 10000 }, + { .raw = 2472, .temp_mc = 15000 }, + { .raw = 2444, .temp_mc = 20000 }, + { .raw = 2416, .temp_mc = 25000 }, + { .raw = 2389, .temp_mc = 30000 }, + { .raw = 2362, .temp_mc = 35000 }, + { .raw = 2335, .temp_mc = 40000 }, + { .raw = 2307, .temp_mc = 45000 }, + { .raw = 2279, .temp_mc = 50000 }, + { .raw = 2251, .temp_mc = 55000 }, + { .raw = 2223, .temp_mc = 60000 }, + { .raw = 2195, .temp_mc = 65000 }, + { .raw = 2167, .temp_mc = 70000 }, + { .raw = 2138, .temp_mc = 75000 }, + { .raw = 2110, .temp_mc = 80000 }, + { .raw = 2081, .temp_mc = 85000 }, + { .raw = 2052, .temp_mc = 90000 }, + { .raw = 2024, .temp_mc = 95000 }, + { .raw = 1996, .temp_mc = 100000 }, + { .raw = 1967, .temp_mc = 105000 }, + { .raw = 1938, .temp_mc = 110000 }, + { .raw = 1909, .temp_mc = 115000 }, + { .raw = 1880, .temp_mc = 120000 }, + { .raw = 1852, .temp_mc = 125000 }, +}; + +/* Convert raw ADC code to millidegrees Celsius with linear interpolation. */ +static int axiado_tsadc_raw_to_mc(u32 raw, long *val) +{ + int i, n = ARRAY_SIZE(axiado_tsadc_table); + long num, denom; + + if (raw >= axiado_tsadc_table[0].raw) { + *val = axiado_tsadc_table[0].temp_mc; + return 0; + } + + if (raw <= axiado_tsadc_table[n - 1].raw) { + *val = axiado_tsadc_table[n - 1].temp_mc; + return 0; + } + + /* Find i such that axiado_tsadc_table[i].raw >= raw > axiado_tsadc_table[i+1].raw */ + for (i = 0; i < n - 1; i++) { + if (raw >= axiado_tsadc_table[i + 1].raw) + break; + } + + if (raw == axiado_tsadc_table[i].raw) { + *val = axiado_tsadc_table[i].temp_mc; + return 0; + } + + /* Linear interpolation within the interval */ + num = (long)(axiado_tsadc_table[i + 1].temp_mc - axiado_tsadc_table[i].temp_mc) * + (axiado_tsadc_table[i].raw - raw); + denom = axiado_tsadc_table[i].raw - axiado_tsadc_table[i + 1].raw; + *val = axiado_tsadc_table[i].temp_mc + num / denom; + return 0; +} + +static int axiado_tsadc_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct axiado_tsadc *tsadc = dev_get_drvdata(dev); + u32 raw; + + if (type != hwmon_temp) + return -EOPNOTSUPP; + + switch (attr) { + case hwmon_temp_input: + if (ioread32(tsadc->regs + AXIADO_TSADC_SEL) != AXIADO_TSADC_SEL_TEMP || + ioread32(tsadc->regs + AXIADO_TSADC_EN) != AXIADO_TSADC_ENA) + return -ENODATA; + + raw = ioread32(tsadc->regs + AXIADO_TSADC_AVG_DOUT) & + AXIADO_TSADC_AVG_DOUT_MASK; + + return axiado_tsadc_raw_to_mc(raw, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t axiado_tsadc_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + if (type == hwmon_temp) { + switch (attr) { + case hwmon_temp_input: + return 0444; + default: + break; + } + } + + return 0; +} + +static const struct hwmon_channel_info * const axiado_tsadc_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_ops axiado_tsadc_hwmon_ops = { + .is_visible = axiado_tsadc_is_visible, + .read = axiado_tsadc_read, +}; + +static const struct hwmon_chip_info axiado_tsadc_chip_info = { + .ops = &axiado_tsadc_hwmon_ops, + .info = axiado_tsadc_info, +}; + +static void axiado_tsadc_disable(void *data) +{ + struct axiado_tsadc *tsadc = data; + + iowrite32(AXIADO_TSADC_DIS, tsadc->regs + AXIADO_TSADC_TSEN_EN); + iowrite32(AXIADO_TSADC_DIS, tsadc->regs + AXIADO_TSADC_EN); +} + +static int axiado_tsadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct axiado_tsadc *tsadc; + struct device *hwmon_dev; + struct clk *refclk; + int ret; + + tsadc = devm_kzalloc(dev, sizeof(*tsadc), GFP_KERNEL); + if (!tsadc) + return -ENOMEM; + + tsadc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tsadc->regs)) + return dev_err_probe(dev, PTR_ERR(tsadc->regs), + "failed to map registers\n"); + + refclk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(refclk)) + return dev_err_probe(dev, PTR_ERR(refclk), + "failed to enable reference clock\n"); + + /* Mask all interrupts before setup */ + iowrite32(AXIADO_TSADC_INTR_MASK_ALL, tsadc->regs + AXIADO_TSADC_INTR_MASK); + + iowrite32(AXIADO_TSADC_ANA_REG_INIT, tsadc->regs + AXIADO_TSADC_ANA_REG); + iowrite32(AXIADO_TSADC_ENA, tsadc->regs + AXIADO_TSADC_EN); + iowrite32(AXIADO_TSADC_SEL_TEMP, tsadc->regs + AXIADO_TSADC_SEL); + iowrite32(AXIADO_TSADC_MODE_CONT, tsadc->regs + AXIADO_TSADC_MODE); + iowrite32(AXIADO_TSADC_FUNC_CTRL_CLR, tsadc->regs + AXIADO_TSADC_FUNC_CTRL); + iowrite32(AXIADO_TSADC_FUNC_CTRL_SET, tsadc->regs + AXIADO_TSADC_FUNC_CTRL); + + /* Enable the bandgap circuit once the block is fully configured */ + iowrite32(AXIADO_TSADC_ENA, tsadc->regs + AXIADO_TSADC_TSEN_EN); + + ret = devm_add_action_or_reset(dev, axiado_tsadc_disable, tsadc); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "axiado_tsadc", + tsadc, + &axiado_tsadc_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id axiado_tsadc_match[] = { + { .compatible = "axiado,ax3000-tsadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_tsadc_match); + +static struct platform_driver axiado_tsadc_driver = { + .driver = { + .name = "axiado-tsadc", + .of_match_table = axiado_tsadc_match, + }, + .probe = axiado_tsadc_probe, +}; +module_platform_driver(axiado_tsadc_driver); + +MODULE_DESCRIPTION("Axiado TSADC temperature sensor driver"); +MODULE_AUTHOR("Axiado Corporation"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 20920d4bd81e27..99a58ed263ab51 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1543,6 +1543,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 Latitude 5420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5420"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3], + }, { .ident = "Dell Latitude 5480", .matches = { @@ -1631,6 +1639,22 @@ static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = { }, .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], }, + { + .ident = "Dell Precision 3650 Tower", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 3650 Tower"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3], + }, + { + .ident = "Dell Optiplex 7090", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7090"), + }, + .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3], + }, { .ident = "Dell XPS 9315", .matches = { diff --git a/drivers/hwmon/emc2103.c b/drivers/hwmon/emc2103.c index 27dc149a3ed995..4adc23ffaf720d 100644 --- a/drivers/hwmon/emc2103.c +++ b/drivers/hwmon/emc2103.c @@ -32,7 +32,7 @@ static const u8 REG_TEMP_MAX[4] = { 0x34, 0x30, 0x31, 0x32 }; #define REG_PRODUCT_ID 0xfd #define REG_MFG_ID 0xfe -/* equation 4 from datasheet: rpm = (3932160 * multipler) / count */ +/* equation 4 from datasheet: rpm = (3932160 * multiplier) / count */ #define FAN_RPM_FACTOR 3932160 /* diff --git a/drivers/hwmon/hih6130.c b/drivers/hwmon/hih6130.c index 7984be1e706d55..6a79aab5d1285a 100644 --- a/drivers/hwmon/hih6130.c +++ b/drivers/hwmon/hih6130.c @@ -168,7 +168,7 @@ static ssize_t hih6130_temperature_show(struct device *dev, ret = hih6130_update_measurements(dev); if (ret < 0) return ret; - return sprintf(buf, "%d\n", hih6130->temperature); + return sysfs_emit(buf, "%d\n", hih6130->temperature); } /** @@ -189,7 +189,7 @@ static ssize_t hih6130_humidity_show(struct device *dev, ret = hih6130_update_measurements(dev); if (ret < 0) return ret; - return sprintf(buf, "%d\n", hih6130->humidity); + return sysfs_emit(buf, "%d\n", hih6130->humidity); } /* sysfs attributes */ diff --git a/drivers/hwmon/honor-fmi.c b/drivers/hwmon/honor-fmi.c new file mode 100644 index 00000000000000..3421e40a132f83 --- /dev/null +++ b/drivers/hwmon/honor-fmi.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Read-only fan monitoring for the HONOR FMI-XX. + * + * The firmware-provided \GFNS ACPI method accepts a three-byte buffer. + * Byte 2 selects fan 0 or 1. It returns a status byte followed by a + * little-endian 16-bit fan speed in RPM. The method owns all Embedded + * Controller access and serialization; this driver deliberately exposes no + * fan control interface. + */ + +#include +#include +#include +#include +#include +#include + +#define HONOR_FMI_GFNS_RESULT_SIZE 3 + +struct honor_fmi_data { + acpi_handle gfns; +}; + +static const struct dmi_system_id honor_fmi_dmi_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HONOR"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "FMI-XX"), + }, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, honor_fmi_dmi_table); + +static int honor_fmi_read_rpm(struct honor_fmi_data *data, int channel, + long *rpm) +{ + union acpi_object input = { + .buffer = { + .type = ACPI_TYPE_BUFFER, + .length = 3, + }, + }; + struct acpi_object_list arguments = { + .count = 1, + .pointer = &input, + }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *result; + u8 input_bytes[3] = { 0, 0, channel }; + acpi_status status; + int ret = 0; + + input.buffer.pointer = input_bytes; + + status = acpi_evaluate_object(data->gfns, NULL, &arguments, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + result = output.pointer; + if (!result || result->type != ACPI_TYPE_BUFFER || + result->buffer.length < HONOR_FMI_GFNS_RESULT_SIZE) { + ret = -EPROTO; + goto out_free; + } + + if (result->buffer.pointer[0]) { + ret = -EIO; + goto out_free; + } + + *rpm = result->buffer.pointer[1] | + (result->buffer.pointer[2] << 8); + +out_free: + kfree(output.pointer); + return ret; +} + +static umode_t honor_fmi_is_visible(const void *data, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + return 0444; +} + +static int honor_fmi_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + struct honor_fmi_data *data = dev_get_drvdata(dev); + + return honor_fmi_read_rpm(data, channel, value); +} + +static const struct hwmon_ops honor_fmi_hwmon_ops = { + .is_visible = honor_fmi_is_visible, + .read = honor_fmi_read, +}; + +static const struct hwmon_channel_info * const honor_fmi_hwmon_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), + NULL +}; + +static const struct hwmon_chip_info honor_fmi_chip_info = { + .ops = &honor_fmi_hwmon_ops, + .info = honor_fmi_hwmon_info, +}; + +static int honor_fmi_probe(struct platform_device *pdev) +{ + struct honor_fmi_data *data; + struct device *hwmon_dev; + acpi_status status; + + if (!dmi_check_system(honor_fmi_dmi_table)) + return -ENODEV; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + status = acpi_get_handle(NULL, "\\GFNS", &data->gfns); + if (ACPI_FAILURE(status)) + return dev_err_probe(&pdev->dev, -ENODEV, + "firmware does not provide \\GFNS\n"); + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "honor_fmi", + data, + &honor_fmi_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct platform_driver honor_fmi_driver = { + .probe = honor_fmi_probe, + .driver = { + .name = "honor-fmi-hwmon", + }, +}; + +static struct platform_device *honor_fmi_device; + +static int __init honor_fmi_init(void) +{ + int ret; + + if (!dmi_check_system(honor_fmi_dmi_table)) + return -ENODEV; + + ret = platform_driver_register(&honor_fmi_driver); + if (ret) + return ret; + + honor_fmi_device = platform_device_register_simple("honor-fmi-hwmon", + PLATFORM_DEVID_NONE, + NULL, 0); + if (IS_ERR(honor_fmi_device)) { + platform_driver_unregister(&honor_fmi_driver); + return PTR_ERR(honor_fmi_device); + } + + return 0; +} + +static void __exit honor_fmi_exit(void) +{ + platform_device_unregister(honor_fmi_device); + platform_driver_unregister(&honor_fmi_driver); +} + +module_init(honor_fmi_init); +module_exit(honor_fmi_exit); + +MODULE_AUTHOR("Nikita Dubrovskih "); +MODULE_DESCRIPTION("HONOR FMI-XX fan speed monitor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 10d2df3efdfaee..3f32b0e83b532a 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -625,6 +625,8 @@ static const char * const hwmon_in_attr_templates[] = { [hwmon_in_max] = "in%d_max", [hwmon_in_lcrit] = "in%d_lcrit", [hwmon_in_crit] = "in%d_crit", + [hwmon_in_lemergency] = "in%d_lemergency", + [hwmon_in_emergency] = "in%d_emergency", [hwmon_in_average] = "in%d_average", [hwmon_in_lowest] = "in%d_lowest", [hwmon_in_highest] = "in%d_highest", @@ -635,6 +637,8 @@ static const char * const hwmon_in_attr_templates[] = { [hwmon_in_max_alarm] = "in%d_max_alarm", [hwmon_in_lcrit_alarm] = "in%d_lcrit_alarm", [hwmon_in_crit_alarm] = "in%d_crit_alarm", + [hwmon_in_lemergency_alarm] = "in%d_lemergency_alarm", + [hwmon_in_emergency_alarm] = "in%d_emergency_alarm", [hwmon_in_rated_min] = "in%d_rated_min", [hwmon_in_rated_max] = "in%d_rated_max", [hwmon_in_beep] = "in%d_beep", @@ -648,6 +652,7 @@ static const char * const hwmon_curr_attr_templates[] = { [hwmon_curr_max] = "curr%d_max", [hwmon_curr_lcrit] = "curr%d_lcrit", [hwmon_curr_crit] = "curr%d_crit", + [hwmon_curr_emergency] = "curr%d_emergency", [hwmon_curr_average] = "curr%d_average", [hwmon_curr_lowest] = "curr%d_lowest", [hwmon_curr_highest] = "curr%d_highest", @@ -658,6 +663,7 @@ static const char * const hwmon_curr_attr_templates[] = { [hwmon_curr_max_alarm] = "curr%d_max_alarm", [hwmon_curr_lcrit_alarm] = "curr%d_lcrit_alarm", [hwmon_curr_crit_alarm] = "curr%d_crit_alarm", + [hwmon_curr_emergency_alarm] = "curr%d_emergency_alarm", [hwmon_curr_rated_min] = "curr%d_rated_min", [hwmon_curr_rated_max] = "curr%d_rated_max", [hwmon_curr_beep] = "curr%d_beep", diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 87edb1b6048bb5..2757d428a23dba 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -36,6 +36,7 @@ * IT8790E Super I/O chip w/LPC interface * IT8792E Super I/O chip w/LPC interface * IT87952E Super I/O chip w/LPC interface + * IT8613E Super I/O chip w/LPC interface * Sis950 A clone of the IT8705F * * Copyright (C) 2001 Chris Gauthron @@ -65,7 +66,7 @@ enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732, it8771, it8772, it8781, it8782, it8783, it8786, it8790, - it8792, it8603, it8620, it8622, it8628, it8689, it87952 }; + it8792, it8603, it8613, it8620, it8622, it8628, it8689, it87952 }; static struct platform_device *it87_pdev[2]; @@ -159,6 +160,7 @@ static inline void superio_exit(int ioreg, bool noexit) #define IT8786E_DEVID 0x8786 #define IT8790E_DEVID 0x8790 #define IT8603E_DEVID 0x8603 +#define IT8613E_DEVID 0x8613 #define IT8620E_DEVID 0x8620 #define IT8622E_DEVID 0x8622 #define IT8623E_DEVID 0x8623 @@ -252,6 +254,7 @@ static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 }; #define IT87_REG_FAN_MAIN_CTRL 0x13 #define IT87_REG_FAN_CTL 0x14 static const u8 IT87_REG_PWM[] = { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf }; +static const u8 IT87_REG_PWM_8665[] = { 0x15, 0x16, 0x17, 0x1e, 0x1f, 0x92 }; static const u8 IT87_REG_PWM_DUTY[] = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab }; static const u8 IT87_REG_VIN[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, @@ -283,6 +286,7 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 }; #define NUM_TEMP 6 #define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET) #define NUM_TEMP_LIMIT 3 +#define IT87_PWM_OLD_NUM_TEMP 3 #define NUM_FAN ARRAY_SIZE(IT87_REG_FAN) #define NUM_FAN_DIV 3 #define NUM_PWM ARRAY_SIZE(IT87_REG_PWM) @@ -292,6 +296,8 @@ struct it87_devices { const char *name; const char * const model; u32 features; + const u8 *reg_pwm; + u8 num_temp_map; u8 peci_mask; u8 old_peci_mask; u8 smbus_bitmap; /* SMBus enable bits in extra config register */ @@ -328,6 +334,8 @@ struct it87_devices { #define FEAT_FOUR_PWM BIT(21) /* Supports four fan controls */ #define FEAT_FOUR_TEMP BIT(22) #define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */ +#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */ +#define FEAT_11MV_ADC BIT(25) static const struct it87_devices it87_devices[] = { [it87] = { @@ -335,12 +343,16 @@ static const struct it87_devices it87_devices[] = { .model = "IT87F", .features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF, /* may need to overwrite */ + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, }, [it8712] = { .name = "it8712", .model = "IT8712F", .features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF, /* may need to overwrite */ + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, }, [it8716] = { .name = "it8716", @@ -348,6 +360,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, }, [it8718] = { .name = "it8718", @@ -355,6 +369,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .old_peci_mask = 0x4, }, [it8720] = { @@ -363,6 +379,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .old_peci_mask = 0x4, }, [it8721] = { @@ -372,6 +390,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x05, .old_peci_mask = 0x02, /* Actually reports PCH */ }, @@ -382,6 +402,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, }, [it8732] = { @@ -391,6 +413,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI | FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS | FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, .old_peci_mask = 0x02, /* Actually reports PCH */ }, @@ -404,6 +428,8 @@ static const struct it87_devices it87_devices[] = { /* 12mV ADC (OHM) */ /* 16 bit fans (OHM) */ /* three fans, always 16 bit (guesswork) */ + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, }, [it8772] = { @@ -416,6 +442,8 @@ static const struct it87_devices it87_devices[] = { /* 12mV ADC (HWSensors4, OHM) */ /* 16 bit fans (HWSensors4, OHM) */ /* three fans, always 16 bit (datasheet) */ + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, }, [it8781] = { @@ -424,6 +452,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .old_peci_mask = 0x4, }, [it8782] = { @@ -432,6 +462,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .old_peci_mask = 0x4, }, [it8783] = { @@ -440,6 +472,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .old_peci_mask = 0x4, }, [it8786] = { @@ -448,6 +482,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, }, [it8790] = { @@ -456,6 +492,8 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, }, [it8792] = { @@ -465,6 +503,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI | FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF | FEAT_NOCONF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, .old_peci_mask = 0x02, /* Actually reports PCH */ }, @@ -474,6 +514,20 @@ static const struct it87_devices it87_devices[] = { .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL | FEAT_AVCC3 | FEAT_PWM_FREQ2, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 4, + .peci_mask = 0x07, + }, + [it8613] = { + .name = "it8613", + .model = "IT8613E", + /* Only three temperature inputs are currently known. */ + .features = FEAT_NEWER_AUTOPWM | FEAT_11MV_ADC | FEAT_16BIT_FANS + | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS + | FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2 + | FEAT_AVCC3 | FEAT_NEW_TEMPMAP, + .reg_pwm = IT87_REG_PWM_8665, + .num_temp_map = 6, .peci_mask = 0x07, }, [it8620] = { @@ -483,6 +537,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS | FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, }, [it8622] = { @@ -492,6 +548,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS | FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2 | FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP, + .reg_pwm = IT87_REG_PWM_8665, + .num_temp_map = 4, .peci_mask = 0x07, .smbus_bitmap = BIT(1) | BIT(2), }, @@ -502,6 +560,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS | FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, }, [it8689] = { @@ -511,6 +571,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3 | FEAT_FANCTL_ONOFF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .smbus_bitmap = BIT(1) | BIT(2), }, [it87952] = { @@ -520,6 +582,8 @@ static const struct it87_devices it87_devices[] = { | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI | FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF | FEAT_NOCONF, + .reg_pwm = IT87_REG_PWM, + .num_temp_map = 3, .peci_mask = 0x07, .old_peci_mask = 0x02, /* Actually reports PCH */ }, @@ -528,6 +592,7 @@ static const struct it87_devices it87_devices[] = { #define has_16bit_fans(data) ((data)->features & FEAT_16BIT_FANS) #define has_12mv_adc(data) ((data)->features & FEAT_12MV_ADC) #define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC) +#define has_11mv_adc(data) ((data)->features & FEAT_11MV_ADC) #define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM) #define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM) #define has_temp_offset(data) ((data)->features & FEAT_TEMP_OFFSET) @@ -558,8 +623,10 @@ static const struct it87_devices it87_devices[] = { #define has_vin3_5v(data) ((data)->features & FEAT_VIN3_5V) #define has_noconf(data) ((data)->features & FEAT_NOCONF) #define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \ - FEAT_10_9MV_ADC)) + FEAT_10_9MV_ADC | \ + FEAT_11MV_ADC)) #define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF) +#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP) struct it87_sio_data { int sioaddr; @@ -589,6 +656,8 @@ struct it87_data { int sioaddr; enum chips type; u32 features; + const u8 *reg_pwm; + u8 num_temp_map; u8 peci_mask; u8 old_peci_mask; @@ -635,7 +704,9 @@ struct it87_data { u8 has_pwm; /* Bitfield, pwm control enabled */ u8 pwm_ctrl[NUM_PWM]; /* Register value */ u8 pwm_duty[NUM_PWM]; /* Manual PWM value set by user */ - u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping (bits 1-0) */ + u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping */ + u8 pwm_temp_map_mask; + u8 pwm_temp_map_shift; /* Automatic fan speed control registers */ u8 auto_pwm[NUM_AUTO_PWM][4]; /* [nr][3] is hard-coded */ @@ -658,6 +729,8 @@ static int adc_lsb(const struct it87_data *data, int nr) lsb = 120; else if (has_10_9mv_adc(data)) lsb = 109; + else if (has_11mv_adc(data)) + lsb = 110; else lsb = 160; if (data->in_scaled & BIT(nr)) @@ -717,6 +790,77 @@ static int pwm_from_reg(const struct it87_data *data, u8 reg) return (reg & 0x7f) << 1; } +static inline u8 pwm_temp_map_get(const struct it87_data *data, u8 ctrl) +{ + return (ctrl >> data->pwm_temp_map_shift) & + data->pwm_temp_map_mask; +} + +static inline u8 pwm_temp_map_set(const struct it87_data *data, u8 ctrl, + u8 map) +{ + ctrl &= ~(data->pwm_temp_map_mask << data->pwm_temp_map_shift); + return ctrl | ((map & data->pwm_temp_map_mask) + << data->pwm_temp_map_shift); +} + +static inline u8 pwm_num_temp_map(const struct it87_data *data) +{ + return data->num_temp_map; +} + +static inline bool uses_global_temp_map(const struct it87_data *data) +{ + return has_new_tempmap(data) || + pwm_num_temp_map(data) != IT87_PWM_OLD_NUM_TEMP; +} + +static unsigned int pwm_temp_channel(const struct it87_data *data, + int nr, u8 map) +{ + if (uses_global_temp_map(data)) { + u8 num = pwm_num_temp_map(data); + + if (map >= num) + map = 0; + return map; + } + + if (map >= IT87_PWM_OLD_NUM_TEMP) + map = 0; + + if (nr >= IT87_PWM_OLD_NUM_TEMP) + map += IT87_PWM_OLD_NUM_TEMP; + + return map; +} + +static int pwm_temp_map_from_channel(const struct it87_data *data, int nr, + unsigned int channel, u8 *map) +{ + if (uses_global_temp_map(data)) { + u8 num = pwm_num_temp_map(data); + + if (channel >= num) + return -EINVAL; + *map = channel; + return 0; + } + + if (nr >= IT87_PWM_OLD_NUM_TEMP) { + if (channel < IT87_PWM_OLD_NUM_TEMP || + channel >= 2 * IT87_PWM_OLD_NUM_TEMP) + return -EINVAL; + channel -= IT87_PWM_OLD_NUM_TEMP; + } else { + if (channel >= IT87_PWM_OLD_NUM_TEMP) + return -EINVAL; + } + + *map = channel; + return 0; +} + static int DIV_TO_REG(int val) { int answer = 0; @@ -728,6 +872,11 @@ static int DIV_TO_REG(int val) #define DIV_FROM_REG(val) BIT(val) +static inline u16 it87_reg_pwm(const struct it87_data *data, int nr) +{ + return data->reg_pwm[nr]; +} + /* * PWM base frequencies. The frequency has to be divided by either 128 or 256, * depending on the chip type, to calculate the actual PWM frequency. @@ -808,16 +957,23 @@ static void it87_write_value(struct it87_data *data, u8 reg, u8 value) static void it87_update_pwm_ctrl(struct it87_data *data, int nr) { - data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]); + data->pwm_ctrl[nr] = it87_read_value(data, it87_reg_pwm(data, nr)); if (has_newer_autopwm(data)) { - data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03; + data->pwm_temp_map[nr] = + pwm_temp_map_get(data, data->pwm_ctrl[nr]); + if (uses_global_temp_map(data) && + data->pwm_temp_map[nr] >= pwm_num_temp_map(data)) + data->pwm_temp_map[nr] = 0; data->pwm_duty[nr] = it87_read_value(data, IT87_REG_PWM_DUTY[nr]); - } else { - if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */ - data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03; - else /* Manual mode */ - data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f; + } else if (data->pwm_ctrl[nr] & 0x80) { /* Automatic mode */ + data->pwm_temp_map[nr] = + pwm_temp_map_get(data, data->pwm_ctrl[nr]); + if (uses_global_temp_map(data) && + data->pwm_temp_map[nr] >= pwm_num_temp_map(data)) + data->pwm_temp_map[nr] = 0; + } else { /* Manual mode */ + data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f; } if (has_old_autopwm(data)) { @@ -1567,27 +1723,32 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, data->pwm_duty[nr]); /* and set manual mode */ if (has_newer_autopwm(data)) { - ctrl = (data->pwm_ctrl[nr] & 0x7c) | - data->pwm_temp_map[nr]; + ctrl = pwm_temp_map_set(data, + data->pwm_ctrl[nr] & + ~0x80, + data->pwm_temp_map[nr]); } else { ctrl = data->pwm_duty[nr]; } data->pwm_ctrl[nr] = ctrl; - it87_write_value(data, IT87_REG_PWM[nr], ctrl); + it87_write_value(data, it87_reg_pwm(data, nr), ctrl); } } else { u8 ctrl; if (has_newer_autopwm(data)) { - ctrl = (data->pwm_ctrl[nr] & 0x7c) | - data->pwm_temp_map[nr]; + ctrl = pwm_temp_map_set(data, + data->pwm_ctrl[nr] & ~0x80, + data->pwm_temp_map[nr]); if (val != 1) ctrl |= 0x80; } else { - ctrl = (val == 1 ? data->pwm_duty[nr] : 0x80); + ctrl = val == 1 ? data->pwm_duty[nr] : + pwm_temp_map_set(data, 0x80, + data->pwm_temp_map[nr]); } data->pwm_ctrl[nr] = ctrl; - it87_write_value(data, IT87_REG_PWM[nr], ctrl); + it87_write_value(data, it87_reg_pwm(data, nr), ctrl); if (has_fanctl_onoff(data) && nr < 3) { /* set SmartGuardian mode */ @@ -1638,7 +1799,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, */ if (!(data->pwm_ctrl[nr] & 0x80)) { data->pwm_ctrl[nr] = data->pwm_duty[nr]; - it87_write_value(data, IT87_REG_PWM[nr], + it87_write_value(data, it87_reg_pwm(data, nr), data->pwm_ctrl[nr]); } } @@ -1693,18 +1854,14 @@ static ssize_t show_pwm_temp_map(struct device *dev, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); struct it87_data *data = it87_update_device(dev); int nr = sensor_attr->index; - int map; + unsigned int channel; if (IS_ERR(data)) return PTR_ERR(data); - map = data->pwm_temp_map[nr]; - if (map >= 3) - map = 0; /* Should never happen */ - if (nr >= 3) /* pwm channels 3..6 map to temp4..6 */ - map += 3; + channel = pwm_temp_channel(data, nr, data->pwm_temp_map[nr]); - return sprintf(buf, "%d\n", (int)BIT(map)); + return sprintf(buf, "%d\n", (int)BIT(channel)); } static ssize_t set_pwm_temp_map(struct device *dev, @@ -1716,42 +1873,33 @@ static ssize_t set_pwm_temp_map(struct device *dev, int nr = sensor_attr->index; long val; int err; - u8 reg; + unsigned int channel; + u8 map; - if (kstrtol(buf, 10, &val) < 0) + if (kstrtol(buf, 10, &val) < 0 || val <= 0 || !is_power_of_2(val)) return -EINVAL; - if (nr >= 3) - val -= 3; - - switch (val) { - case BIT(0): - reg = 0x00; - break; - case BIT(1): - reg = 0x01; - break; - case BIT(2): - reg = 0x02; - break; - default: + channel = __ffs(val); + if (pwm_temp_map_from_channel(data, nr, channel, &map)) return -EINVAL; - } err = it87_lock(data); if (err) return err; it87_update_pwm_ctrl(data, nr); - data->pwm_temp_map[nr] = reg; + data->pwm_temp_map[nr] = map; /* - * If we are in automatic mode, write the temp mapping immediately; - * otherwise, just store it for later use. + * Newer controllers keep the duty cycle in a separate register, so + * their temperature mapping can be updated in any mode. On older + * controllers, defer the update until automatic mode is enabled. */ - if (data->pwm_ctrl[nr] & 0x80) { - data->pwm_ctrl[nr] = (data->pwm_ctrl[nr] & 0xfc) | - data->pwm_temp_map[nr]; - it87_write_value(data, IT87_REG_PWM[nr], data->pwm_ctrl[nr]); + if (has_newer_autopwm(data) || (data->pwm_ctrl[nr] & 0x80)) { + data->pwm_ctrl[nr] = pwm_temp_map_set(data, + data->pwm_ctrl[nr], + data->pwm_temp_map[nr]); + it87_write_value(data, it87_reg_pwm(data, nr), + data->pwm_ctrl[nr]); } it87_unlock(data); return count; @@ -2790,6 +2938,9 @@ static int __init it87_find(int sioaddr, unsigned short *address, case IT8623E_DEVID: sio_data->type = it8603; break; + case IT8613E_DEVID: + sio_data->type = it8613; + break; case IT8620E_DEVID: sio_data->type = it8620; break; @@ -2967,6 +3118,43 @@ static int __init it87_find(int sioaddr, unsigned short *address, sio_data->skip_in |= BIT(5); /* No VIN5 */ sio_data->skip_in |= BIT(6); /* No VIN6 */ + sio_data->beep_pin = superio_inb(sioaddr, + IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8613) { + int reg27, reg29, reg2a; + + superio_select(sioaddr, GPIO); + + /* Check for pwm3, fan3, pwm5, fan5 */ + reg27 = superio_inb(sioaddr, IT87_SIO_GPIO3_REG); + if (!(reg27 & BIT(1))) + sio_data->skip_fan |= BIT(4); + if (reg27 & BIT(3)) + sio_data->skip_pwm |= BIT(4); + if (reg27 & BIT(6)) + sio_data->skip_pwm |= BIT(2); + if (reg27 & BIT(7)) + sio_data->skip_fan |= BIT(2); + + /* Check for pwm2, fan2 */ + reg29 = superio_inb(sioaddr, IT87_SIO_GPIO5_REG); + if (reg29 & BIT(1)) + sio_data->skip_pwm |= BIT(1); + if (reg29 & BIT(2)) + sio_data->skip_fan |= BIT(1); + + /* Check for pwm4, fan4 */ + reg2a = superio_inb(sioaddr, IT87_SIO_PINX1_REG); + if (!(reg2a & BIT(0)) || (reg29 & BIT(7))) { + sio_data->skip_fan |= BIT(3); + sio_data->skip_pwm |= BIT(3); + } + + sio_data->skip_pwm |= BIT(0); /* No pwm1 */ + sio_data->skip_fan |= BIT(0); /* No fan1 */ + sio_data->skip_in |= BIT(3); /* No VIN3 */ + sio_data->skip_in |= BIT(6); /* No VIN6 */ + sio_data->beep_pin = superio_inb(sioaddr, IT87_SIO_BEEP_PIN_REG) & 0x3f; } else if (sio_data->type == it8620 || sio_data->type == it8628) { @@ -3350,7 +3538,10 @@ static void it87_init_device(struct platform_device *pdev) * manual duty cycle. */ for (i = 0; i < NUM_AUTO_PWM; i++) { - data->pwm_temp_map[i] = i; + if (uses_global_temp_map(data)) + data->pwm_temp_map[i] = 0; + else + data->pwm_temp_map[i] = i % IT87_PWM_OLD_NUM_TEMP; data->pwm_duty[i] = 0x7f; /* Full speed */ data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */ } @@ -3461,6 +3652,7 @@ static int it87_probe(struct platform_device *pdev) struct resource *res; struct device *dev = &pdev->dev; struct it87_sio_data *sio_data = dev_get_platdata(dev); + const struct it87_devices *chip; int enable_pwm_interface; struct device *hwmon_dev; int err; @@ -3483,9 +3675,19 @@ static int it87_probe(struct platform_device *pdev) data->type = sio_data->type; data->smbus_bitmap = sio_data->smbus_bitmap; data->ec_special_config = sio_data->ec_special_config; - data->features = it87_devices[sio_data->type].features; - data->peci_mask = it87_devices[sio_data->type].peci_mask; - data->old_peci_mask = it87_devices[sio_data->type].old_peci_mask; + chip = &it87_devices[sio_data->type]; + data->features = chip->features; + data->reg_pwm = chip->reg_pwm; + data->peci_mask = chip->peci_mask; + data->old_peci_mask = chip->old_peci_mask; + data->num_temp_map = chip->num_temp_map; + if (has_new_tempmap(data)) { + data->pwm_temp_map_mask = 0x07; + data->pwm_temp_map_shift = 3; + } else { + data->pwm_temp_map_mask = 0x03; + data->pwm_temp_map_shift = 0; + } /* * IT8705F Datasheet 0.4.1, 3h == Version G. * IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J. diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 3e7e63edc6a300..5414b7841864b2 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -94,6 +94,7 @@ MODULE_PARM_DESC(force, "force loading on processors with erratum 319"); * Defining locally as IDs are not shared. */ #define PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3 0x12cb +#define PCI_DEVICE_ID_AMD_1AH_M80H_DF_F3 0x1243 #define PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3 0x127b struct k10temp_data { @@ -582,6 +583,7 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M60H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M70H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M80H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, {} diff --git a/drivers/hwmon/lm63.c b/drivers/hwmon/lm63.c index da09770c05e7c3..607758bf7c58c2 100644 --- a/drivers/hwmon/lm63.c +++ b/drivers/hwmon/lm63.c @@ -35,13 +35,14 @@ #include #include #include +#include #include /* * Addresses to scan * Address is fully defined internally and cannot be changed except for * LM64 which has one pin dedicated to address selection. - * LM63 and LM96163 have address 0x4c. + * LM63, LM96163, and CTF2301 have address 0x4c. * LM64 can have address 0x18 or 0x4e. */ @@ -70,6 +71,9 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END }; #define LM63_REG_LOCAL_TEMP 0x00 #define LM63_REG_LOCAL_HIGH 0x05 +#define CTF2301_REG_LOCAL_TEMP_LSB 0x15 +#define CTF2301_REG_LOCAL_HIGH_LSB 0x06 + #define LM63_REG_REMOTE_TEMP_MSB 0x01 #define LM63_REG_REMOTE_TEMP_LSB 0x10 #define LM63_REG_REMOTE_OFFSET_MSB 0x11 @@ -94,6 +98,7 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END }; #define LM63_PWM_BASE_FAST_HZ 180000 #define LM63_PWM_BASE_SLOW_HZ 700 +#define LM63_MAX_PWM 255 #define LM63_MAX_CONVRATE 9 @@ -121,6 +126,9 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END }; 127000), 1000) #define TEMP8U_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val((val), 0, \ 255000), 1000) +#define TEMP12_FROM_REG(reg) DIV_ROUND_CLOSEST((s16)(reg) * 1000, 256) +#define TEMP12_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \ + 127937) * 16, 1000) * 16) #define TEMP11_FROM_REG(reg) ((reg) / 32 * 125) #define TEMP11_TO_REG(val) (DIV_ROUND_CLOSEST(clamp_val((val), -128000, \ 127875), 125) * 32) @@ -132,7 +140,11 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END }; #define UPDATE_INTERVAL(max, rate) \ ((1000 << (LM63_MAX_CONVRATE - (rate))) / (max)) -enum chips { lm63, lm64, lm96163 }; +enum chips { ctf2301, lm63, lm64, lm96163 }; + +static const unsigned int ctf2301_update_intervals[] = { + 20000, 10000, 4902, 2463, 1000, 615, 307, 154, 107 +}; /* * Client data (each client gets its own) @@ -164,6 +176,8 @@ struct lm63_data { 1: local high limit 2: remote critical limit 3-14: lookup table */ + s16 temp1_input; /* local input for chips with extended resolution */ + s16 temp1_max; /* local limit for chips with extended resolution */ s16 temp11[4]; /* 0: remote input 1: remote low limit 2: remote high limit @@ -229,6 +243,7 @@ static struct lm63_data *lm63_update_device(struct device *dev) struct lm63_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; unsigned long next_update; + int msb, lsb; mutex_lock(&data->update_lock); @@ -253,10 +268,26 @@ static struct lm63_data *lm63_update_device(struct device *dev) data->pwm1[0] = i2c_smbus_read_byte_data(client, LM63_REG_PWM_VALUE); - data->temp8[0] = i2c_smbus_read_byte_data(client, - LM63_REG_LOCAL_TEMP); - data->temp8[1] = i2c_smbus_read_byte_data(client, - LM63_REG_LOCAL_HIGH); + if (data->kind == ctf2301) { + msb = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_TEMP); + lsb = i2c_smbus_read_byte_data(client, + CTF2301_REG_LOCAL_TEMP_LSB); + if (msb >= 0 && lsb >= 0) + data->temp1_input = ((u16)(u8)msb << 8) | lsb; + + msb = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_HIGH); + lsb = i2c_smbus_read_byte_data(client, + CTF2301_REG_LOCAL_HIGH_LSB); + if (msb >= 0 && lsb >= 0) + data->temp1_max = ((u16)(u8)msb << 8) | lsb; + } else { + data->temp8[0] = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_TEMP); + data->temp8[1] = i2c_smbus_read_byte_data(client, + LM63_REG_LOCAL_HIGH); + } /* order matters for temp2_input */ data->temp11[0] = i2c_smbus_read_byte_data(client, @@ -276,7 +307,7 @@ static struct lm63_data *lm63_update_device(struct device *dev) | i2c_smbus_read_byte_data(client, LM63_REG_REMOTE_OFFSET_LSB); - if (data->kind == lm96163) + if (data->kind == lm96163 || data->kind == ctf2301) data->temp11u = (i2c_smbus_read_byte_data(client, LM96163_REG_REMOTE_TEMP_U_MSB) << 8) | i2c_smbus_read_byte_data(client, @@ -366,6 +397,49 @@ static ssize_t set_fan(struct device *dev, struct device_attribute *dummy, return count; } +static int lm63_pwm_from_reg(struct lm63_data *data, u8 reg) +{ + if (data->pwm_highres) + return reg; + + return reg >= 2 * data->pwm1_freq ? + LM63_MAX_PWM : + (reg * LM63_MAX_PWM + data->pwm1_freq) / (2 * data->pwm1_freq); +} + +static u8 lm63_pwm_to_reg(struct lm63_data *data, unsigned long val) +{ + val = clamp_val(val, 0, LM63_MAX_PWM); + + if (data->pwm_highres) + return val; + + return (val * data->pwm1_freq * 2 + LM63_MAX_PWM / 2) / LM63_MAX_PWM; +} + +static void lm63_update_pwm_highres(struct lm63_data *data, + int config_enhanced) +{ + data->pwm_highres = !(data->config_fan & 0x08) && + data->pwm1_freq == 8 && + (config_enhanced & 0x10); +} + +static int lm63_write_pwm(struct lm63_data *data, int nr, unsigned long val) +{ + struct i2c_client *client = data->client; + u8 reg = nr ? LM63_REG_LUT_PWM(nr - 1) : LM63_REG_PWM_VALUE; + u8 regval = lm63_pwm_to_reg(data, val); + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, regval); + if (ret) + return ret; + data->pwm1[nr] = regval; + + return 0; +} + static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -375,12 +449,7 @@ static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr, int pwm; mutex_lock(&data->update_lock); - if (data->pwm_highres) - pwm = data->pwm1[nr]; - else - pwm = data->pwm1[nr] >= 2 * data->pwm1_freq ? - 255 : (data->pwm1[nr] * 255 + data->pwm1_freq) / - (2 * data->pwm1_freq); + pwm = lm63_pwm_from_reg(data, data->pwm1[nr]); mutex_unlock(&data->update_lock); return sprintf(buf, "%d\n", pwm); @@ -391,11 +460,9 @@ static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct lm63_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; int nr = attr->index; unsigned long val; int err; - u8 reg; if (!(data->config_fan & 0x20)) /* register is read-only */ return -EPERM; @@ -404,15 +471,11 @@ static ssize_t set_pwm1(struct device *dev, struct device_attribute *devattr, if (err) return err; - reg = nr ? LM63_REG_LUT_PWM(nr - 1) : LM63_REG_PWM_VALUE; - val = clamp_val(val, 0, 255); - mutex_lock(&data->update_lock); - data->pwm1[nr] = data->pwm_highres ? val : - (val * data->pwm1_freq * 2 + 127) / 255; - i2c_smbus_write_byte_data(client, reg, data->pwm1[nr]); + err = lm63_write_pwm(data, nr, val); mutex_unlock(&data->update_lock); - return count; + + return err ? err : count; } static ssize_t pwm1_enable_show(struct device *dev, @@ -485,7 +548,9 @@ static ssize_t pwm1_freq_store(struct device *dev, struct lm63_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; unsigned long val, pfr_fast, pfr_slow, err_fast, err_slow, pfr; + u8 config_fan; bool slow_clock; + int config_enhanced = 0; int ret; ret = kstrtoul(buf, 10, &val); @@ -511,35 +576,58 @@ static ssize_t pwm1_freq_store(struct device *dev, mutex_lock(&data->update_lock); ret = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG_FAN); - if (ret < 0) { - mutex_unlock(&data->update_lock); - return ret; - } - data->config_fan = ret; + if (ret < 0) + goto unlock; + config_fan = ret; + data->config_fan = config_fan; - if (!(data->config_fan & 0x20)) { /* register is read-only */ - mutex_unlock(&data->update_lock); - return -EPERM; + if (!(config_fan & 0x20)) { /* register is read-only */ + ret = -EPERM; + goto unlock; } - if (data->kind == lm96163) { - ret = i2c_smbus_read_byte_data(client, LM96163_REG_CONFIG_ENHANCED); - if (ret < 0) { - mutex_unlock(&data->update_lock); - return ret; + if (data->kind == lm96163 || data->kind == ctf2301) { + ret = i2c_smbus_read_byte_data(client, + LM96163_REG_CONFIG_ENHANCED); + if (ret < 0) + goto unlock; + config_enhanced = ret; + lm63_update_pwm_highres(data, config_enhanced); + + if (data->kind == ctf2301) { + if (!slow_clock && pfr == 8) + config_enhanced |= 0x10; + else + config_enhanced &= ~0x10; + ret = i2c_smbus_write_byte_data(client, + LM96163_REG_CONFIG_ENHANCED, + config_enhanced); + if (ret) + goto unlock; + lm63_update_pwm_highres(data, config_enhanced); } - data->pwm_highres = !slow_clock && pfr == 8 && (ret & 0x10); } if (slow_clock) - data->config_fan |= 0x08; + config_fan |= 0x08; else - data->config_fan &= ~0x08; - i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN, data->config_fan); - i2c_smbus_write_byte_data(client, LM63_REG_PWM_FREQ, pfr); + config_fan &= ~0x08; + ret = i2c_smbus_write_byte_data(client, LM63_REG_PWM_FREQ, pfr); + if (ret) + goto unlock; data->pwm1_freq = pfr; + lm63_update_pwm_highres(data, config_enhanced); + ret = i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN, + config_fan); + if (ret) + goto unlock; + + data->config_fan = config_fan; + lm63_update_pwm_highres(data, config_enhanced); +unlock: mutex_unlock(&data->update_lock); - return count; + + return ret ? ret : count; } /* @@ -554,7 +642,17 @@ static ssize_t show_local_temp8(struct device *dev, { struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct lm63_data *data = lm63_update_device(dev); - return sprintf(buf, "%d\n", TEMP8_FROM_REG(data->temp8[attr->index])); + int temp; + + mutex_lock(&data->update_lock); + if (data->kind == ctf2301) + temp = TEMP12_FROM_REG(attr->index ? data->temp1_max : + data->temp1_input); + else + temp = TEMP8_FROM_REG(data->temp8[attr->index]); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); } static ssize_t show_remote_temp8(struct device *dev, @@ -594,6 +692,21 @@ static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr, return err; mutex_lock(&data->update_lock); + if (data->kind == ctf2301 && nr == 1) { + temp = TEMP12_TO_REG(val); + err = i2c_smbus_write_byte_data(client, LM63_REG_LOCAL_HIGH, + temp >> 8); + if (!err) + err = i2c_smbus_write_byte_data(client, + CTF2301_REG_LOCAL_HIGH_LSB, + temp & 0xf0); + if (!err) + data->temp1_max = temp; + mutex_unlock(&data->update_lock); + + return err ? err : count; + } + switch (nr) { case 2: reg = LM63_REG_REMOTE_TCRIT; @@ -610,10 +723,12 @@ static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr, reg = LM63_REG_LUT_TEMP(nr - 3); temp = lut_temp_to_reg(data, val); } - data->temp8[nr] = temp; - i2c_smbus_write_byte_data(client, reg, temp); + err = i2c_smbus_write_byte_data(client, reg, temp); + if (!err) + data->temp8[nr] = temp; mutex_unlock(&data->update_lock); - return count; + + return err ? err : count; } static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr, @@ -787,11 +902,40 @@ static ssize_t temp2_crit_hyst_store(struct device *dev, * Set conversion rate. * client->update_lock must be held when calling this function. */ -static void lm63_set_convrate(struct lm63_data *data, unsigned int interval) +static unsigned int lm63_update_interval(struct lm63_data *data, u8 rate) +{ + if (data->kind == ctf2301) + return ctf2301_update_intervals[min_t(unsigned int, rate, + ARRAY_SIZE(ctf2301_update_intervals) - 1)]; + + return UPDATE_INTERVAL(data->max_convrate_hz, rate); +} + +static int lm63_set_convrate(struct lm63_data *data, unsigned int interval) { struct i2c_client *client = data->client; - unsigned int update_interval; - int i; + unsigned int update_interval, error, best_error = ~0U; + unsigned int best = 0; + unsigned int i; + int ret; + + if (data->kind == ctf2301) { + for (i = 0; i < ARRAY_SIZE(ctf2301_update_intervals); i++) { + error = abs_diff(interval, ctf2301_update_intervals[i]); + if (error < best_error) { + best_error = error; + best = i; + } + } + + ret = i2c_smbus_write_byte_data(client, LM63_REG_CONVRATE, + best); + if (ret) + return ret; + data->update_interval = ctf2301_update_intervals[best]; + + return 0; + } /* Shift calculations to avoid rounding errors */ interval <<= 6; @@ -803,8 +947,12 @@ static void lm63_set_convrate(struct lm63_data *data, unsigned int interval) if (interval >= update_interval * 3 / 4) break; - i2c_smbus_write_byte_data(client, LM63_REG_CONVRATE, i); + ret = i2c_smbus_write_byte_data(client, LM63_REG_CONVRATE, i); + if (ret) + return ret; data->update_interval = UPDATE_INTERVAL(data->max_convrate_hz, i); + + return 0; } static ssize_t update_interval_show(struct device *dev, @@ -828,10 +976,10 @@ static ssize_t update_interval_store(struct device *dev, return err; mutex_lock(&data->update_lock); - lm63_set_convrate(data, clamp_val(val, 0, 100000)); + err = lm63_set_convrate(data, clamp_val(val, 0, 100000)); mutex_unlock(&data->update_lock); - return count; + return err ? err : count; } static ssize_t temp2_type_show(struct device *dev, @@ -996,6 +1144,75 @@ static DEVICE_ATTR_RO(alarms); static DEVICE_ATTR_RW(update_interval); +static int lm63_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = LM63_MAX_PWM; + + return 0; +} + +static int lm63_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct lm63_data *data = cdev->devdata; + int ret; + + mutex_lock(&data->update_lock); + ret = i2c_smbus_read_byte_data(data->client, LM63_REG_PWM_VALUE); + if (ret < 0) + goto unlock; + + data->pwm1[0] = ret; + *state = lm63_pwm_from_reg(data, data->pwm1[0]); +unlock: + mutex_unlock(&data->update_lock); + + return ret < 0 ? ret : 0; +} + +static int lm63_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct lm63_data *data = cdev->devdata; + int config_fan; + int ret; + + if (state > LM63_MAX_PWM) + return -EINVAL; + + mutex_lock(&data->update_lock); + config_fan = i2c_smbus_read_byte_data(data->client, + LM63_REG_CONFIG_FAN); + if (config_fan < 0) { + ret = config_fan; + goto unlock; + } + data->config_fan = config_fan; + + if (!(config_fan & 0x20)) { + config_fan |= 0x20; + ret = i2c_smbus_write_byte_data(data->client, + LM63_REG_CONFIG_FAN, + config_fan); + if (ret) + goto unlock; + data->config_fan = config_fan; + } + + ret = lm63_write_pwm(data, 0, state); +unlock: + mutex_unlock(&data->update_lock); + + return ret; +} + +static const struct thermal_cooling_device_ops lm63_cooling_ops = { + .get_max_state = lm63_get_max_state, + .get_cur_state = lm63_get_cur_state, + .set_cur_state = lm63_set_cur_state, +}; + static struct attribute *lm63_attributes[] = { &sensor_dev_attr_pwm1.dev_attr.attr, &dev_attr_pwm1_enable.attr, @@ -1078,8 +1295,8 @@ static const struct attribute_group lm63_group_extra_lut = { * On LM63, temp2_crit can be set only once, which should be job * of the bootloader. * On LM64, temp2_crit can always be set. - * On LM96163, temp2_crit can be set if bit 1 of the configuration - * register is true. + * On LM96163 and CTF2301, temp2_crit can be set if bit 1 of the + * configuration register is true. */ static umode_t lm63_attribute_mode(struct kobject *kobj, struct attribute *attr, int index) @@ -1089,7 +1306,8 @@ static umode_t lm63_attribute_mode(struct kobject *kobj, if (attr == &sensor_dev_attr_temp2_crit.dev_attr.attr && (data->kind == lm64 || - (data->kind == lm96163 && (data->config & 0x02)))) + ((data->kind == lm96163 || data->kind == ctf2301) && + (data->config & 0x02)))) return attr->mode | S_IWUSR; return attr->mode; @@ -1164,29 +1382,48 @@ static int lm63_detect(struct i2c_client *client, * Ideally we shouldn't have to initialize anything, since the BIOS * should have taken care of everything */ -static void lm63_init_client(struct lm63_data *data) +static int lm63_init_client(struct lm63_data *data) { struct i2c_client *client = data->client; struct device *dev = &client->dev; u8 convrate; + int ret; - data->config = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1); - data->config_fan = i2c_smbus_read_byte_data(client, - LM63_REG_CONFIG_FAN); + ret = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1); + if (ret < 0) + return ret; + data->config = ret; + ret = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG_FAN); + if (ret < 0) + return ret; + data->config_fan = ret; /* Start converting if needed */ if (data->config & 0x40) { /* standby */ dev_dbg(dev, "Switching to operational mode\n"); data->config &= 0xA7; - i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1, - data->config); + ret = i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1, + data->config); + if (ret) + return ret; } /* Tachometer is always enabled on LM64 */ if (data->kind == lm64) data->config |= 0x04; + if (data->kind == ctf2301 && !client->irq && !(data->config & 0x04)) { + dev_dbg(dev, "Configuring ALERT/TACH pin for tachometer input\n"); + data->config |= 0x04; + ret = i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1, + data->config); + if (ret) + return ret; + } /* We may need pwm1_freq before ever updating the client data */ - data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ) & 0x1f; + ret = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ); + if (ret < 0) + return ret; + data->pwm1_freq = ret & 0x1f; if (data->pwm1_freq == 0) data->pwm1_freq = 1; @@ -1199,25 +1436,38 @@ static void lm63_init_client(struct lm63_data *data) case lm96163: data->max_convrate_hz = LM96163_MAX_CONVRATE_HZ; data->lut_size = 12; - data->trutherm - = i2c_smbus_read_byte_data(client, - LM96163_REG_TRUTHERM) & 0x02; + ret = i2c_smbus_read_byte_data(client, LM96163_REG_TRUTHERM); + if (ret < 0) + return ret; + data->trutherm = ret & 0x02; + break; + case ctf2301: + data->lut_size = 12; break; } - convrate = i2c_smbus_read_byte_data(client, LM63_REG_CONVRATE); - if (unlikely(convrate > LM63_MAX_CONVRATE)) + ret = i2c_smbus_read_byte_data(client, LM63_REG_CONVRATE); + if (ret < 0) + return ret; + convrate = ret; + if (data->kind == ctf2301 && + convrate >= ARRAY_SIZE(ctf2301_update_intervals)) + convrate = ARRAY_SIZE(ctf2301_update_intervals) - 1; + else if (unlikely(convrate > LM63_MAX_CONVRATE)) convrate = LM63_MAX_CONVRATE; - data->update_interval = UPDATE_INTERVAL(data->max_convrate_hz, - convrate); + data->update_interval = lm63_update_interval(data, convrate); /* - * For LM96163, check if high resolution PWM + * For LM96163 and CTF2301, check if high resolution PWM * and unsigned temperature format is enabled. */ - if (data->kind == lm96163) { - u8 config_enhanced - = i2c_smbus_read_byte_data(client, - LM96163_REG_CONFIG_ENHANCED); + if (data->kind == lm96163 || data->kind == ctf2301) { + u8 config_enhanced; + + ret = i2c_smbus_read_byte_data(client, + LM96163_REG_CONFIG_ENHANCED); + if (ret < 0) + return ret; + config_enhanced = ret; if (config_enhanced & 0x20) data->lut_temp_highres = true; if ((config_enhanced & 0x10) @@ -1238,6 +1488,8 @@ static void lm63_init_client(struct lm63_data *data) dev_dbg(dev, "PWM output active %s, %s mode\n", (data->config_fan & 0x10) ? "low" : "high", (data->config_fan & 0x20) ? "manual" : "auto"); + + return 0; } static const struct i2c_device_id lm63_id[]; @@ -1245,9 +1497,11 @@ static const struct i2c_device_id lm63_id[]; static int lm63_probe(struct i2c_client *client) { struct device *dev = &client->dev; + struct thermal_cooling_device *cdev; struct device *hwmon_dev; struct lm63_data *data; int groups = 0; + int ret; data = devm_kzalloc(dev, sizeof(struct lm63_data), GFP_KERNEL); if (!data) @@ -1262,7 +1516,9 @@ static int lm63_probe(struct i2c_client *client) data->temp2_offset = 16000; /* Initialize chip */ - lm63_init_client(data); + ret = lm63_init_client(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize device\n"); /* Register sysfs hooks */ data->groups[groups++] = &lm63_group; @@ -1272,11 +1528,26 @@ static int lm63_probe(struct i2c_client *client) if (data->kind == lm96163) { data->groups[groups++] = &lm63_group_temp2_type; data->groups[groups++] = &lm63_group_extra_lut; + } else if (data->kind == ctf2301) { + data->groups[groups++] = &lm63_group_extra_lut; } hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, data, data->groups); - return PTR_ERR_OR_ZERO(hwmon_dev); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + if (IS_ENABLED(CONFIG_THERMAL_OF) && + of_property_present(dev->of_node, "#cooling-cells")) { + cdev = devm_thermal_of_cooling_device_register(dev, 0, + client->name, + data, + &lm63_cooling_ops); + if (IS_ERR(cdev)) + return PTR_ERR(cdev); + } + + return 0; } /* @@ -1287,6 +1558,7 @@ static const struct i2c_device_id lm63_id[] = { { .name = "lm63", .driver_data = lm63 }, { .name = "lm64", .driver_data = lm64 }, { .name = "lm96163", .driver_data = lm96163 }, + { .name = "ctf2301", .driver_data = ctf2301 }, { } }; MODULE_DEVICE_TABLE(i2c, lm63_id); @@ -1304,6 +1576,10 @@ static const struct of_device_id __maybe_unused lm63_of_match[] = { .compatible = "national,lm96163", .data = (void *)lm96163 }, + { + .compatible = "sensylink,ctf2301", + .data = (void *)ctf2301 + }, { }, }; MODULE_DEVICE_TABLE(of, lm63_of_match); diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index c56e164d61c185..27ef127ccc2c47 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -1445,7 +1445,7 @@ static void lm85_init_client(struct i2c_client *client) static int lm85_is_fake(struct i2c_client *client) { /* - * Differenciate between real LM96000 and Winbond WPCD377I. The latter + * Differentiate between real LM96000 and Winbond WPCD377I. The latter * emulate the former except that it has no hardware monitoring function * so the readings are always 0. */ diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 1c603272538aa1..3faeb2c6ab01b2 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -2712,6 +2712,11 @@ static int lm90_probe_channel(struct i2c_client *client, return -EINVAL; } + if (id == 2 && !(data->flags & LM90_HAVE_TEMP3)) { + dev_err(dev, "channel %d is not supported for this chip in %pfw\n", id, child); + return -EINVAL; + } + err = fwnode_property_read_string(child, "label", &data->channel_label[id]); if (err == -ENODATA || err == -EILSEQ) { dev_err(dev, "invalid label property in %pfw\n", child); diff --git a/drivers/hwmon/minisforum-um780xtx.c b/drivers/hwmon/minisforum-um780xtx.c new file mode 100644 index 00000000000000..f5d6e18cb651f5 --- /dev/null +++ b/drivers/hwmon/minisforum-um780xtx.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Minisforum UM780 XTX (F7BSD) embedded-controller hwmon driver. + * + * Copyright (C) 2026 Sebastián Peyrott + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UM780XTX_EC_TEMP_SYS 0x05 +#define UM780XTX_EC_TEMP_CPU 0x09 +#define UM780XTX_EC_CPU_PROFILE 0x2f +#define UM780XTX_EC_SYS_POINT1 0x31 +#define UM780XTX_EC_SYS_POINT2 0x34 +#define UM780XTX_EC_SYS_POINT3 0x37 + +#define UM780XTX_EC_PROFILE_B1 0xb1 +#define UM780XTX_EC_PROFILE_B2 0xb2 + +#define UM780XTX_EC_FAN1_LO 0xb6 +#define UM780XTX_EC_FAN1_HI 0xb7 +#define UM780XTX_EC_FAN2_LO 0xb9 +#define UM780XTX_EC_FAN2_HI 0xba + +#define UM780XTX_EC_MAX_RPM 9000 +#define UM780XTX_EC_RPM_RETRIES 3 + +static struct platform_device *um780xtx_pdev; + +struct um780xtx_data { + struct device *hwmon_dev; + u8 saved_profile; + u8 saved_sys_point1; + u8 saved_sys_point2; + u8 sys_point3; +}; + +static const struct dmi_system_id um780xtx_dmi_table[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, + "Micro Computer (HK) Tech Limited"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Venus series"), + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, + "Shenzhen Meigao Electronic Equipment Co.,Ltd"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "F7BSD"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, um780xtx_dmi_table); + +static bool um780xtx_firmware_match(void) +{ + const char *board_version = dmi_get_system_info(DMI_BOARD_VERSION); + const char *bios_version = dmi_get_system_info(DMI_BIOS_VERSION); + + return board_version && bios_version && + !strcmp(board_version, "1.1") && !strcmp(bios_version, "1.06"); +} + +static int um780xtx_oem_read(u8 command, u8 *value) +{ + return ec_transaction(command, NULL, 0, value, 1); +} + +static int um780xtx_read_rpm(u8 command_hi, u8 command_lo, long *rpm) +{ + u8 hi_before; + u8 hi_after; + u8 lo; + unsigned int value; + int attempt; + int ret; + + for (attempt = 0; attempt < UM780XTX_EC_RPM_RETRIES; attempt++) { + ret = um780xtx_oem_read(command_hi, &hi_before); + if (ret) + return ret; + ret = um780xtx_oem_read(command_lo, &lo); + if (ret) + return ret; + ret = um780xtx_oem_read(command_hi, &hi_after); + if (ret) + return ret; + if (hi_before != hi_after) + continue; + + value = (hi_after << 8) | lo; + if (value > UM780XTX_EC_MAX_RPM) + continue; + + *rpm = value; + return 0; + } + + return -EIO; +} + +static int um780xtx_read_profile(long *mode) +{ + u8 profile; + int ret; + + ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile); + if (ret) + return ret; + if (profile == UM780XTX_EC_PROFILE_B1) { + *mode = 2; + return 0; + } + if (profile == UM780XTX_EC_PROFILE_B2) { + *mode = 3; + return 0; + } + + return -ENODATA; +} + +static int um780xtx_write_profile(struct um780xtx_data *data, long mode) +{ + u8 expected; + u8 profile; + int ret; + + if (mode != 2 && mode != 3) + return -EINVAL; + expected = mode == 2 ? UM780XTX_EC_PROFILE_B1 : UM780XTX_EC_PROFILE_B2; + + ret = ec_transaction(expected, NULL, 0, NULL, 0); + if (ret) + return ret; + ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile); + if (ret) + return ret; + if (profile != expected) + return -EIO; + + data->saved_profile = profile; + return 0; +} + +static const u8 um780xtx_sys_point_offsets[] = { + UM780XTX_EC_SYS_POINT1, + UM780XTX_EC_SYS_POINT2, +}; + +static ssize_t um780xtx_sys_point_temp_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + u8 value; + int ret; + + guard(hwmon_lock)(dev); + ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &value); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", value * 1000); +} + +static ssize_t um780xtx_sys_point_temp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct um780xtx_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + u8 points[2]; + u8 readback; + long value; + int ret; + + ret = kstrtol(buf, 10, &value); + if (ret) + return ret; + + guard(hwmon_lock)(dev); + /* Validate and clamp against the live peer threshold. */ + ret = ec_read(UM780XTX_EC_SYS_POINT1, &points[0]); + if (ret) + return ret; + ret = ec_read(UM780XTX_EC_SYS_POINT2, &points[1]); + if (ret) + return ret; + if (points[0] >= points[1] || points[1] >= data->sys_point3) + return -EIO; + + value = DIV_ROUND_CLOSEST(value, 1000); + if (!sattr->index) + value = clamp_val(value, 0, points[1] - 1); + else + value = clamp_val(value, points[0] + 1, + data->sys_point3 - 1); + points[sattr->index] = value; + + ret = ec_write(um780xtx_sys_point_offsets[sattr->index], + points[sattr->index]); + if (ret) + return ret; + ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &readback); + if (ret) + return ret; + if (readback != points[sattr->index]) + return -EIO; + + data->saved_sys_point1 = points[0]; + data->saved_sys_point2 = points[1]; + return count; +} + +static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point1_temp, + um780xtx_sys_point_temp, 0); +static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point2_temp, + um780xtx_sys_point_temp, 1); + +static struct attribute *um780xtx_extra_attrs[] = { + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + NULL +}; + +static const struct attribute_group um780xtx_extra_group = { + .attrs = um780xtx_extra_attrs, +}; + +static const struct attribute_group *um780xtx_extra_groups[] = { + &um780xtx_extra_group, + NULL +}; + +static umode_t um780xtx_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_temp || type == hwmon_fan) + return 0444; + if (type == hwmon_pwm && attr == hwmon_pwm_enable) + return 0644; + return 0; +} + +static int um780xtx_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *value) +{ + u8 raw; + int ret; + + if (type == hwmon_temp) { + ret = ec_read(channel ? UM780XTX_EC_TEMP_SYS : + UM780XTX_EC_TEMP_CPU, &raw); + if (ret) + return ret; + *value = raw * 1000L; + return 0; + } + if (type == hwmon_fan) { + if (!channel) + ret = um780xtx_read_rpm(UM780XTX_EC_FAN1_HI, + UM780XTX_EC_FAN1_LO, value); + else + ret = um780xtx_read_rpm(UM780XTX_EC_FAN2_HI, + UM780XTX_EC_FAN2_LO, value); + return ret; + } + if (type == hwmon_pwm) + return um780xtx_read_profile(value); + + return -EOPNOTSUPP; +} + +static int um780xtx_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long value) +{ + struct um780xtx_data *data = dev_get_drvdata(dev); + + if (type == hwmon_pwm && attr == hwmon_pwm_enable) + return um780xtx_write_profile(data, value); + + return -EOPNOTSUPP; +} + +static int um780xtx_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + static const char * const labels[] = { "CPU", "SYS" }; + + if (type == hwmon_temp && attr == hwmon_temp_label) + *str = labels[channel]; + else if (type == hwmon_fan && attr == hwmon_fan_label) + *str = labels[channel]; + else + return -EOPNOTSUPP; + return 0; +} + +static const struct hwmon_ops um780xtx_hwmon_ops = { + .is_visible = um780xtx_is_visible, + .read = um780xtx_read, + .write = um780xtx_write, + .read_string = um780xtx_read_string, +}; + +static const struct hwmon_channel_info * const um780xtx_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE), + NULL +}; + +static const struct hwmon_chip_info um780xtx_chip_info = { + .ops = &um780xtx_hwmon_ops, + .info = um780xtx_info, +}; + +static int um780xtx_write_sys_point(u8 offset, u8 value) +{ + u8 readback; + int ret; + + ret = ec_write(offset, value); + if (ret) + return ret; + ret = ec_read(offset, &readback); + if (ret) + return ret; + + return readback == value ? 0 : -EIO; +} + +static int um780xtx_restore_sys_points(struct um780xtx_data *data, + u8 current_point2) +{ + u8 point1 = data->saved_sys_point1; + u8 point2 = data->saved_sys_point2; + int ret; + + /* Keep strict ordering valid after each individual EC write. */ + if (point1 >= current_point2) { + ret = um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT2, point2); + if (ret) + return ret; + return um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT1, + point1); + } + + ret = um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT1, point1); + if (ret) + return ret; + return um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT2, point2); +} + +static int um780xtx_read_initial_state(struct um780xtx_data *data) +{ + u8 profile; + u8 point1; + u8 point2; + int ret; + + ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile); + if (ret) + return ret; + if (profile != UM780XTX_EC_PROFILE_B1 && + profile != UM780XTX_EC_PROFILE_B2) + return -EINVAL; + + ret = ec_read(UM780XTX_EC_SYS_POINT1, &point1); + if (ret) + return ret; + ret = ec_read(UM780XTX_EC_SYS_POINT2, &point2); + if (ret) + return ret; + if (point1 >= point2 || point2 >= data->sys_point3) + return -EINVAL; + + data->saved_profile = profile; + data->saved_sys_point1 = point1; + data->saved_sys_point2 = point2; + return 0; +} + +static int um780xtx_restore_state(struct um780xtx_data *data) +{ + u8 readback; + u8 point2; + int ret; + + ret = ec_transaction(data->saved_profile, NULL, 0, NULL, 0); + if (ret) + return ret; + ret = ec_read(UM780XTX_EC_CPU_PROFILE, &readback); + if (ret) + return ret; + if (readback != data->saved_profile) + return -EIO; + + ret = ec_read(UM780XTX_EC_SYS_POINT2, &point2); + if (ret) + return ret; + + return um780xtx_restore_sys_points(data, point2); +} + +static int um780xtx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct um780xtx_data *data; + struct device *hwmon; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + platform_set_drvdata(pdev, data); + + ret = ec_read(UM780XTX_EC_SYS_POINT3, &data->sys_point3); + if (ret) + return dev_err_probe(dev, ret, + "failed to read system fan limit\n"); + ret = um780xtx_read_initial_state(data); + if (ret) + return dev_err_probe(dev, ret, + "failed to read initial fan settings\n"); + + hwmon = devm_hwmon_device_register_with_info(dev, "um780xtx_ec", data, + &um780xtx_chip_info, + um780xtx_extra_groups); + if (IS_ERR(hwmon)) + return PTR_ERR(hwmon); + data->hwmon_dev = hwmon; + + return 0; +} + +static int um780xtx_resume(struct device *dev) +{ + struct um780xtx_data *data = dev_get_drvdata(dev); + + guard(hwmon_lock)(data->hwmon_dev); + return um780xtx_restore_state(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(um780xtx_pm_ops, NULL, um780xtx_resume); + +static struct platform_driver um780xtx_driver = { + .driver = { + .name = "um780xtx-ec-hwmon", + .pm = pm_sleep_ptr(&um780xtx_pm_ops), + }, + .probe = um780xtx_probe, +}; + +static int __init um780xtx_init(void) +{ + int ret; + + if (!dmi_check_system(um780xtx_dmi_table) || + !um780xtx_firmware_match() || !ec_get_handle()) + return -ENODEV; + ret = platform_driver_register(&um780xtx_driver); + if (ret) + return ret; + + um780xtx_pdev = platform_device_register_simple("um780xtx-ec-hwmon", + PLATFORM_DEVID_NONE, + NULL, 0); + if (IS_ERR(um780xtx_pdev)) { + ret = PTR_ERR(um780xtx_pdev); + platform_driver_unregister(&um780xtx_driver); + return ret; + } + return 0; +} + +static void __exit um780xtx_exit(void) +{ + platform_device_unregister(um780xtx_pdev); + platform_driver_unregister(&um780xtx_driver); +} + +module_init(um780xtx_init); +module_exit(um780xtx_exit); + +MODULE_AUTHOR("Sebastián Peyrott "); +MODULE_DESCRIPTION("Minisforum UM780 XTX EC hwmon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c index 759ea3d1286e35..cf27b9c7a89ed4 100644 --- a/drivers/hwmon/nct6775-core.c +++ b/drivers/hwmon/nct6775-core.c @@ -21,6 +21,8 @@ * Chip #vin #fan #pwm #temp chip IDs man ID * nct6106d 9 3 3 6+3 0xc450 0xc1 0x5ca3 * nct6116d 9 5 5 3+3 0xd280 0xc1 0x5ca3 + * nct6126d 10 5 5 3+3 0xd283 0xc1 0x5ca3 + * (0xd284) * nct6775f 9 4 3 6+3 0xb470 0xc1 0x5ca3 * nct6776f 9 5 3 6+3 0xc330 0xc1 0x5ca3 * nct6779d 15 5 5 2+6 0xc560 0xc1 0x5ca3 @@ -66,6 +68,7 @@ static const char * const nct6775_device_names[] = { [nct6106] = "nct6106", [nct6116] = "nct6116", + [nct6126] = "nct6126", [nct6775] = "nct6775", [nct6776] = "nct6776", [nct6779] = "nct6779", @@ -897,6 +900,31 @@ static const s8 NCT6116_BEEP_BITS[NUM_BEEP_BITS] = { static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b }; +/* NCT6122D/NCT6126D specific data */ + +static const u16 NCT6126_REG_IN[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; +static const u16 NCT6126_REG_IN_MAX[] = { + 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 0xa0, 0xa2 }; +static const u16 NCT6126_REG_IN_MIN[] = { + 0x91, 0x93, 0x95, 0x97, 0x99, 0x9b, 0x9d, 0x9f, 0xa1, 0xa3 }; + +static const s8 NCT6126_ALARM_BITS[NUM_ALARM_BITS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 48, -1, /* intr0-intr1 */ +}; + +static const s8 NCT6126_BEEP_BITS[NUM_BEEP_BITS] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, /* in0-in11 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* in12-in23 */ + 24, 25, 26, 27, 28, -1, -1, -1, -1, -1, -1, -1, /* fan1-fan12 */ + 16, 17, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* temp1-temp12 */ + 34, -1, 32 /* intr0-intr1, beep_en */ +}; + static enum pwm_enable reg_to_pwm_enable(int pwm, int mode) { if (mode == 0 && pwm == 255) @@ -998,6 +1026,14 @@ static const u16 scale_in_6798[NUM_IN] = { 800, 800, 800, 1600, 800 }; +/* + * NCT6126 scaling: + * CPUVCORE, VIN0, AVSB, 3VCC, VIN1, VIN2, VHIF, 3VSB, VBAT, VTT + */ +static const u16 scale_in_6126[10] = { + 800, 800, 1600, 1600, 800, 800, 1600, 1600, 1600, 800 +}; + static inline long in_from_reg(u8 reg, u8 nr, const u16 *scales) { return DIV_ROUND_CLOSEST(reg * scales[nr], 100); @@ -1156,6 +1192,7 @@ bool nct6775_reg_is_word_sized(struct nct6775_data *data, u16 reg) reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0x111 || reg == 0x121 || reg == 0x131; case nct6116: + case nct6126: return reg == 0x20 || reg == 0x22 || reg == 0x24 || reg == 0x26 || reg == 0x28 || reg == 0x59 || reg == 0x5b || reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0xe6 || @@ -1538,6 +1575,7 @@ static int nct6775_update_pwm_limits(struct device *dev) break; case nct6106: case nct6116: + case nct6126: case nct6779: case nct6791: case nct6792: @@ -3203,6 +3241,7 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, break; /* always enabled, nothing to do */ case nct6106: case nct6116: + case nct6126: case nct6779: case nct6791: case nct6792: @@ -3677,6 +3716,83 @@ int nct6775_probe(struct device *dev, struct nct6775_data *data, reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H; + break; + case nct6126: + data->in_num = 10; + data->scale_in = scale_in_6126; + data->pwm_num = 5; + data->auto_pwm_num = 4; + data->temp_fixed_num = 3; + data->num_temp_alarms = 3; + data->num_temp_beeps = 3; + + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + + data->temp_label = nct6776_temp_label; + data->temp_mask = NCT6776_TEMP_MASK; + data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; + + data->REG_VBAT = NCT6106_REG_VBAT; + data->REG_DIODE = NCT6106_REG_DIODE; + data->DIODE_MASK = NCT6106_DIODE_MASK; + data->REG_VIN = NCT6126_REG_IN; + data->REG_IN_MINMAX[0] = NCT6126_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6126_REG_IN_MAX; + data->REG_TARGET = NCT6116_REG_TARGET; + data->REG_FAN = NCT6116_REG_FAN; + data->REG_FAN_MODE = NCT6116_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6116_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6116_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6116_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6116_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6116_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6116_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6116_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6116_REG_PWM; + data->REG_PWM[1] = NCT6116_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6116_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6106_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6106_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6106_REG_PWM_READ; + data->REG_PWM_MODE = NCT6106_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6106_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6116_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6116_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6116_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE = + NCT6116_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6116_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK = + NCT6106_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6116_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6106_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6116_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6106_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6106_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6106_REG_ALARM; + data->ALARM_BITS = NCT6126_ALARM_BITS; + data->REG_BEEP = NCT6106_REG_BEEP; + data->BEEP_BITS = NCT6126_BEEP_BITS; + data->REG_TSI_TEMP = NCT6116_REG_TSI_TEMP; + + reg_temp = NCT6106_REG_TEMP; + reg_temp_mon = NCT6106_REG_TEMP_MON; + num_reg_temp = 3; + num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP); + reg_temp_over = NCT6106_REG_TEMP_OVER; + reg_temp_hyst = NCT6106_REG_TEMP_HYST; + reg_temp_config = NCT6106_REG_TEMP_CONFIG; + num_reg_temp_config = 3; + reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6106_REG_TEMP_CRIT; + reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; + reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H; + break; case nct6775: data->in_num = 9; diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index d394376e430686..d359c28f3e0f32 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -25,6 +25,7 @@ enum sensor_access { access_direct, access_asuswmi }; static const char * const nct6775_sio_names[] __initconst = { [nct6106] = "NCT6106D", [nct6116] = "NCT6116D", + [nct6126] = "NCT6122D/NCT6126D", [nct6775] = "NCT6775F", [nct6776] = "NCT6776D/F", [nct6779] = "NCT6779D", @@ -76,6 +77,9 @@ MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); #define SIO_NCT6106_ID 0xc450 #define SIO_NCT6116_ID 0xd280 +#define SIO_NCT6122_ID 0xd2a0 +#define SIO_NCT6126_A_ID 0xd283 +#define SIO_NCT6126_B_ID 0xd284 #define SIO_NCT6775_ID 0xb470 #define SIO_NCT6776_ID 0xc330 #define SIO_NCT6779_ID 0xc560 @@ -565,6 +569,25 @@ nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio pwm3pin = fan3pin && (cr24 & 0x08); pwm4pin = fan4pin; pwm5pin = fan5pin; + } else if (data->kind == nct6126) { + int cr1a = sio_data->sio_inb(sio_data, 0x1a); + int cr1b = sio_data->sio_inb(sio_data, 0x1b); + int cr24 = sio_data->sio_inb(sio_data, 0x24); + int cr2a = sio_data->sio_inb(sio_data, 0x2a); + int cr2b = sio_data->sio_inb(sio_data, 0x2b); + int cr2f = sio_data->sio_inb(sio_data, 0x2f); + + fan3pin = !(cr2b & 0x10); + fan4pin = (cr2b & 0x80) || /* pin 1(2) */ + (!(cr2f & 0x10) && (cr1a & 0x04)); /* pin 65(66) */ + fan5pin = (cr2b & 0x80) || /* pin 126(127) */ + (!(cr1b & 0x03) && (cr2a & 0x02)); /* pin 94(96) */ + + pwm3pin = fan3pin && (cr24 & 0x08); + pwm4pin = fan4pin; + pwm5pin = fan5pin; + + fan4min = fan4pin; } else { /* * NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, @@ -861,6 +884,7 @@ static int nct6775_platform_probe_init(struct nct6775_data *data) break; case nct6106: case nct6116: + case nct6126: case nct6779: case nct6791: case nct6792: @@ -892,6 +916,7 @@ static int nct6775_platform_probe_init(struct nct6775_data *data) switch (data->kind) { case nct6106: case nct6116: + case nct6126: tmp |= 0xe0; break; case nct6775: @@ -1006,7 +1031,13 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) sio_data->kind = nct6106; break; case SIO_NCT6116_ID: - sio_data->kind = nct6116; + if (val == SIO_NCT6126_A_ID || val == SIO_NCT6126_B_ID) + sio_data->kind = nct6126; + else + sio_data->kind = nct6116; + break; + case SIO_NCT6122_ID: + sio_data->kind = nct6126; break; case SIO_NCT6775_ID: sio_data->kind = nct6775; diff --git a/drivers/hwmon/nct6775.h b/drivers/hwmon/nct6775.h index 296eff99d00383..d18ac91be49718 100644 --- a/drivers/hwmon/nct6775.h +++ b/drivers/hwmon/nct6775.h @@ -4,8 +4,8 @@ #include -enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, - nct6793, nct6795, nct6796, nct6797, nct6798, nct6799 }; +enum kinds { nct6106, nct6116, nct6126, nct6775, nct6776, nct6779, nct6791, + nct6792, nct6793, nct6795, nct6796, nct6797, nct6798, nct6799 }; enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; #define NUM_TEMP 12 /* Max number of temp attribute sets w/ limits*/ diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index eb71a0ac044b26..fa921b115ee820 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -393,6 +393,25 @@ config SENSORS_MAX20751 This driver can also be built as a module. If so, the module will be called max20751. +config SENSORS_MAX20826 + tristate "Analog Devices MAX20826 and similar devices" + help + If you say yes here you get hardware monitoring support for Analog + Devices MAX20826 and similar devices. + + This driver can also be built as a module. If so, the module will + be called max20826. + +config SENSORS_MAX20826_REGULATOR + bool "Regulator support for Analog Devices MAX20826 and similar devices" + depends on SENSORS_MAX20826 && REGULATOR + help + If you say yes here you get regulator support for Analog Devices + MAX20826 and similar sensors. + + This enables the MAX20826 to be used as a regulator device, + providing voltage control through the regulator framework. + config SENSORS_MAX20830 tristate "Analog Devices MAX20830 and compatibles" help @@ -435,8 +454,8 @@ config SENSORS_MAX34440 help If you say yes here you get hardware monitoring support for Maxim MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and - MAX34461. Other compatible devices are ADPM12160, ADPM12200, and - ADPM12250. + MAX34461. Other compatible devices are ADPM12160, ADPM12200, + ADPM12250, ADPM12300, and ADPM12886. This driver can also be built as a module. If so, the module will be called max34440. @@ -790,10 +809,11 @@ config SENSORS_TPS40422 be called tps40422. config SENSORS_TPS53679 - tristate "TI TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, TPS53688" + tristate "TI TPS536xx family" help If you say yes here you get hardware monitoring support for TI - TPS53647, TPS53667, TPS53676, TPS53679, TPS53681, and TPS53688. + TPS53622, TPS53647, TPS53659, TPS53667, TPS53676, TPS53679, TPS53681, + TPS53685, TPS53688, and TPS536C7. This driver can also be built as a module. If so, the module will be called tps53679. diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index e288fe72a43726..e3fc23f8840160 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_SENSORS_MAX16601) += max16601.o obj-$(CONFIG_SENSORS_MAX17616) += max17616.o obj-$(CONFIG_SENSORS_MAX20730) += max20730.o obj-$(CONFIG_SENSORS_MAX20751) += max20751.o +obj-$(CONFIG_SENSORS_MAX20826) += max20826.o obj-$(CONFIG_SENSORS_MAX20830) += max20830.o obj-$(CONFIG_SENSORS_MAX20860A) += max20860a.o obj-$(CONFIG_SENSORS_MAX31785) += max31785.o diff --git a/drivers/hwmon/pmbus/ltc4286.c b/drivers/hwmon/pmbus/ltc4286.c index 8715d380784a02..8190706d1bebb9 100644 --- a/drivers/hwmon/pmbus/ltc4286.c +++ b/drivers/hwmon/pmbus/ltc4286.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -16,6 +17,85 @@ #define LTC4286_MFR_ID_SIZE 3 +struct ltc4286_data { + struct pmbus_driver_info info; + u32 rsense; + bool vrange_low_enable; +}; + +#define to_ltc4286_data(x) container_of((x), struct ltc4286_data, info) + +static ssize_t ltc4286_rsense_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct ltc4286_data *data = to_ltc4286_data(info); + u32 rsense; + int ret; + + ret = pmbus_lock_interruptible(client); + if (ret) + return ret; + + rsense = data->rsense; + + pmbus_unlock(client); + + return sysfs_emit(buf, "%u\n", rsense); +} + +static ssize_t ltc4286_rsense_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + const struct pmbus_driver_info *info_ro = pmbus_get_driver_info(client); + struct ltc4286_data *data = to_ltc4286_data(info_ro); + struct pmbus_driver_info *info = &data->info; + u32 rsense; + int ret; + + ret = kstrtou32(buf, 10, &rsense); + if (ret) + return ret; + + if (rsense == 0) + return -EINVAL; + + if (rsense > (INT_MAX / 1024)) + return -EINVAL; + + ret = pmbus_lock_interruptible(client); + if (ret) + return ret; + + data->rsense = rsense; + info->m[PSC_CURRENT_OUT] = 1024 * rsense; + info->m[PSC_POWER] = data->vrange_low_enable ? 4 * rsense : rsense; + + pmbus_unlock(client); + + return count; +} + +static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ltc4286_rsense, 0); + +static struct attribute *ltc4286_attrs[] = { + &sensor_dev_attr_shunt_resistor.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ltc4286_group = { + .attrs = ltc4286_attrs, +}; + +static const struct attribute_group *ltc4286_attribute_groups[] = { + <c4286_group, + NULL, +}; + /* * Initialize the MBR as default settings which is referred to LTC4286 datasheet * (March 22, 2022 version) table 3 page 16 @@ -55,6 +135,7 @@ static struct pmbus_driver_info ltc4286_info = { .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, + .groups = ltc4286_attribute_groups, }; static const struct i2c_device_id ltc4286_id[] = { @@ -69,6 +150,7 @@ static int ltc4286_probe(struct i2c_client *client) int ret; const struct i2c_device_id *mid; u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct ltc4286_data *data; struct pmbus_driver_info *info; u32 rsense; int vrange_nval, vrange_oval; @@ -114,11 +196,14 @@ static int ltc4286_probe(struct i2c_client *client) if (rsense > (INT_MAX / 1024)) return -EINVAL; - info = devm_kmemdup(&client->dev, <c4286_info, sizeof(*info), - GFP_KERNEL); - if (!info) + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) return -ENOMEM; + data->info = ltc4286_info; + data->rsense = rsense; + info = &data->info; + /* Check MFR1 CONFIG register bit 1 VRANGE_SELECT before driver loading */ vrange_oval = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1); if (vrange_oval < 0) @@ -126,7 +211,10 @@ static int ltc4286_probe(struct i2c_client *client) "Failed to read manufacturer configuration one\n"); vrange_nval = vrange_oval; - if (device_property_read_bool(&client->dev, "adi,vrange-low-enable")) { + data->vrange_low_enable = + device_property_read_bool(&client->dev, "adi,vrange-low-enable"); + + if (data->vrange_low_enable) { vrange_nval &= ~VRANGE_SELECT_BIT; /* VRANGE_SELECT = 0, 25.6 volts */ diff --git a/drivers/hwmon/pmbus/max20826.c b/drivers/hwmon/pmbus/max20826.c new file mode 100644 index 00000000000000..013a97189fde7a --- /dev/null +++ b/drivers/hwmon/pmbus/max20826.c @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hardware monitoring driver for Analog Devices MAX20826 PMBus device + * + * Copyright 2026 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmbus.h" + +#define MAX20826_REG_RAIL_PHASE_CFG 0xB1 +#define MAX20826_REG_CTRL_MISC 0xCB +#define MAX20826_OPL_EN_MSK BIT(7) + +#define MAX20826_REG_VOUT_RES 0xDB +#define MAX20826_VOUT_RES_MSK BIT(7) + +#define MAX20855B_REG_VOUT_VRM 0xD1 +#define MAX20855B_REG_VOUT_VRM_MASK BIT(4) + +#define MAX20826_REG_C_MODEAB 0xDC +/* Byte 1 bits 7 and 6*/ +#define MAX20826_C_MODEAB_MASK GENMASK(15, 14) + +#define MAX20826_REG_ADDR_MODE 0xEC +#define MAX20826_PAGE_MODE_MSK BIT(7) +#define MAX20826_DIRECT_ADDR_MSK GENMASK(6, 0) + +#define MAX20826_REG_OVERRIDE 0xED +#define MAX20826_OVERRIDE_MASK BIT(7) + +#define MAX20826_REG_PHASE_DETECT 0xF3 +#define MAX20826_REG_PHASE_READ 0xF4 + +#define MAX20826_REG_STATUS_MON 0xF9 +#define MAX20826_PHASES_NUM_MASK GENMASK(7, 3) + +#define MAX20826_MAX_PAGES 2 +#define MAX20826_MAX_PHASES 16 +#define MAX20826_PHASES_PER_PAGE 8 +#define MAX20826_INTF_PWMVID 1 +#define MAX20826_INTF_AVSBUS 3 + +#define MAX20855B_PHASES_NUM_MASK GENMASK(7, 4) +#define MAX20855B_MAX_PHASES 8 +#define MAX20908_MAX_PHASES 8 +#define MAX20912_MAX_PHASES 12 +#define MAX20916_MAX_PHASES 16 + +struct max20826_chip_info { + const char *vendor_bus_name; + u8 max_phases; + unsigned int phase_num_mask; + u8 start_index_iin; + u8 start_index_iout; + bool is_reg_addr_mode_block; + bool is_vout_direct; + bool select_vrm; + bool has_avsbus; + bool has_opl; + u8 (*count_phases)(const u8 *config, int page); +}; + +struct max20826 { + const struct max20826_chip_info *chip_info; + struct pmbus_driver_info info; + struct i2c_client *client; + /* RAIL-B direct mode */ + struct i2c_client *client_b; + struct i2c_client *curr_client; + struct gpio_desc *avren; + struct gpio_desc *bvren; + bool vendor_bus; + bool high_speed[MAX20826_MAX_PAGES]; + bool on_off_ctrl[MAX20826_MAX_PAGES]; + bool opl_enabled[MAX20826_MAX_PAGES]; +}; + +struct max20826_debugfs_entry { + struct max20826 *st; + u8 page; +}; + +static u8 __max20826_count_phases(const u8 *config, int page) +{ + if (page) + return hweight8(config[4]); + + return hweight8(config[0]) + hweight8(config[1]) - + hweight8(config[4]); +} + +static u8 __max20855b_count_phases(const u8 *config, int page) +{ + if (page) + return hweight8(config[3] & 0x3F); + + return hweight8(config[0]) + hweight8(config[1] & 0x0F) - + hweight8(config[3] & 0x3F); +} + +static u8 __max20908_count_phases(const u8 *config, int page) +{ + if (page) + return hweight8(config[4]); + + return hweight8(config[0]) + hweight8(config[1] & 0xF0) - + hweight8(config[4]); +} + +static u8 __max20912_count_phases(const u8 *config, int page) +{ + if (page) + return hweight8(config[4]); + + return hweight8(config[0]) + hweight8(config[1] & 0xFC) - + hweight8(config[4]); +} + +#define to_max20826(p) container_of(p, struct max20826, info) + +enum { + RAIL_A, + RAIL_B, +}; + +static const struct regulator_desc __maybe_unused max20826_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), +}; + +static struct i2c_client *max20826_select_rail(struct max20826 *st, + int page, bool probing) +{ + int ret; + + /* + * If in direct mode and we want RAIL_B (page 1) just return client_b. + * Otherwise, set the proper page (if page mode) and return RAIL_A. + */ + if (st->client_b) { + /* if 0xff just return the last client */ + if (page < 0) + return st->curr_client; + if (page) + st->curr_client = st->client_b; + else + st->curr_client = st->client; + + return st->curr_client; + } + + if (!probing) + ret = pmbus_set_page(st->client, page, 0xff); + else + ret = i2c_smbus_write_byte_data(st->client, PMBUS_PAGE, page); + if (ret < 0) + return ERR_PTR(ret); + + return st->client; +} + +static int max20826_rail_read_block_data(struct i2c_client *rail, u8 reg, + u8 *block, u8 min_len) +{ + int ret; + + ret = pmbus_read_smbus_i2c_block_data(rail, reg, block); + if (ret < 0) + return ret; + if (ret < min_len) + return -EIO; + + return ret; +} + +static int __max20826_read_block_data(struct max20826 *st, int page, u8 reg, + u8 *block, u8 min_len) +{ + struct i2c_client *rail; + + rail = max20826_select_rail(st, page, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + return max20826_rail_read_block_data(rail, reg, block, min_len); +} + +static int __max20826_read_byte_data(struct max20826 *st, int page, int reg) +{ + struct i2c_client *rail; + + rail = max20826_select_rail(st, page, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + return i2c_smbus_read_byte_data(rail, reg); +} + +static int __max20826_write_byte_data(struct max20826 *st, int page, int reg, + u8 value) +{ + struct i2c_client *rail; + + rail = max20826_select_rail(st, page, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + return i2c_smbus_write_byte_data(rail, reg, value); +} + +static int __max20826_read_word_data(struct max20826 *st, int page, int reg) +{ + struct i2c_client *rail; + + rail = max20826_select_rail(st, page, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + return i2c_smbus_read_word_data(rail, reg); +} + +/* + * @rail is the already selected rail client and is only given during probe, + * where the PMBus core cannot be used yet. Otherwise, pass NULL and let the + * core dispatch through our own read_byte_data()/write_byte_data() callbacks, + * which take care of selecting the rail. + */ +static int max20826_set_high_speed(struct max20826 *st, struct i2c_client *rail, + u8 page, bool high_speed) +{ + u8 reg, mask, val; + int ret; + + if (st->vendor_bus) { + reg = MAX20826_REG_OVERRIDE; + mask = MAX20826_OVERRIDE_MASK; + val = high_speed ? 0 : FIELD_PREP(MAX20826_OVERRIDE_MASK, 1); + } else { + reg = PMBUS_OPERATION; + mask = PB_OPERATION_CONTROL_V_SRC; + val = high_speed ? FIELD_PREP(PB_OPERATION_CONTROL_V_SRC, MAX20826_INTF_AVSBUS) : 0; + } + + if (!rail) { + ret = pmbus_update_byte_data(st->client, page, reg, mask, val); + } else { + ret = i2c_smbus_read_byte_data(rail, reg); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(rail, reg, (ret & ~mask) | val); + } + + if (ret) + return ret; + + st->high_speed[page] = high_speed; + + return 0; +} + +static int max20826_read_curr(struct max20826 *st, int page, int phase, int reg) +{ + u8 val_out[I2C_SMBUS_BLOCK_MAX], start_byte; + struct i2c_client *rail; + int ret; + + if (phase == 0xff) + return __max20826_read_word_data(st, page, reg); + + /* + * On Rail_A phases are ascending (from 1) while on RAIL_B they + * are descending (from 16). + */ + if (!page) + phase += 1; + else + phase = st->chip_info->max_phases - phase; + + /* phase detect and read are only available on RAIL_A */ + rail = max20826_select_rail(st, RAIL_A, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + ret = i2c_smbus_write_byte_data(rail, MAX20826_REG_PHASE_DETECT, phase); + if (ret < 0) + return ret; + + /* + * For MAX20826: Byte 2:3 is for phase IOUT 4:5 is for phase IIN. + * For other chips: Byte 0:1 is for phase IOUT 2:3 is for phase IIN. + */ + if (reg == PMBUS_READ_IIN) + start_byte = st->chip_info->start_index_iin; + else + start_byte = st->chip_info->start_index_iout; + + ret = max20826_rail_read_block_data(rail, MAX20826_REG_PHASE_READ, + val_out, start_byte + 2); + if (ret < 0) + return ret; + + return get_unaligned_le16(&val_out[start_byte]); +} + +static int max20826_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max20826 *st = to_max20826(info); + + switch (reg) { + case PMBUS_READ_IIN: + case PMBUS_READ_IOUT: + return max20826_read_curr(st, page, phase, reg); + case PMBUS_IOUT_OC_FAULT_LIMIT: + if (!st->chip_info->has_opl || !st->opl_enabled[page]) + return __max20826_read_word_data(st, page, reg); + return -EIO; + case PMBUS_POUT_OP_FAULT_LIMIT: + /* + * If Over Power Limit is enabled, PMBUS_IOUT_OC_FAULT_LIMIT + * shows the power limit and hence we need to report it + * properly in Watts. + */ + if (!st->chip_info->has_opl) + return __max20826_read_word_data(st, page, reg); + + if (st->opl_enabled[page]) + return __max20826_read_word_data(st, page, + PMBUS_IOUT_OC_FAULT_LIMIT); + return -EIO; + default: + if (reg >= PMBUS_VIRT_BASE) + return -EOPNOTSUPP; + return __max20826_read_word_data(st, page, reg); + } +} + +static int max20826_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max20826 *st = to_max20826(info); + struct i2c_client *rail; + + rail = max20826_select_rail(st, page, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + return i2c_smbus_write_word_data(rail, reg, word); +} + +static int max20826_regulator_do_enable(struct max20826 *st, + struct i2c_client *rail, u8 page, + u8 byte) +{ + /* + * If AVSBus is enabled (bits 5 and 4 set) the device refuses to set bit + * 7 of the OPERATION register. Hence, to workaround this, we first + * clear the bits and then set them all together. + */ + if (st->chip_info->has_avsbus && !st->vendor_bus && st->high_speed[page] && + PB_OPERATION_CONTROL_ON & byte) { + u8 __byte = byte & ~(PB_OPERATION_CONTROL_V_SRC | PB_OPERATION_CONTROL_ON); + int ret; + + ret = i2c_smbus_write_byte_data(rail, PMBUS_OPERATION, __byte); + if (ret < 0) + return ret; + } + + return i2c_smbus_write_byte_data(rail, PMBUS_OPERATION, byte); +} + +static int max20826_regulator_enable(struct max20826 *st, int page, u8 byte) +{ + struct i2c_client *rail; + + rail = max20826_select_rail(st, page, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + if (page) + gpiod_set_value_cansleep(st->bvren, !!(PB_OPERATION_CONTROL_ON & byte)); + else + gpiod_set_value_cansleep(st->avren, !!(PB_OPERATION_CONTROL_ON & byte)); + + return max20826_regulator_do_enable(st, rail, page, byte); +} + +static int max20826_write_byte_data(struct i2c_client *client, int page, + int reg, u8 byte) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max20826 *st = to_max20826(info); + + switch (reg) { + case PMBUS_OPERATION: + return max20826_regulator_enable(st, page, byte); + default: + return __max20826_write_byte_data(st, page, reg, byte); + } +} + +static int max20826_write_byte(struct i2c_client *client, int page, u8 byte) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max20826 *st = to_max20826(info); + struct i2c_client *rail; + + rail = max20826_select_rail(st, page, false); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + return i2c_smbus_write_byte(rail, byte); +} + +static int max20826_regulator_enabled(struct max20826 *st, int page) +{ + struct gpio_desc *gpio = page ? st->bvren : st->avren; + int on, ret; + + if (gpio) { + on = gpiod_get_value_cansleep(gpio); + if (on < 0) + return on; + } else { + /* If the gpios are not given, just assume it's on */ + on = 1; + } + + ret = __max20826_read_byte_data(st, page, PMBUS_OPERATION); + if (ret < 0) + return ret; + + if (st->on_off_ctrl[page]) + on = (PB_OPERATION_CONTROL_ON & ret) && on; + + ret &= ~PB_OPERATION_CONTROL_ON; + return ret | FIELD_PREP(PB_OPERATION_CONTROL_ON, on); +} + +static int max20826_iout_status(struct max20826 *st, int page) +{ + int status; + + status = __max20826_read_byte_data(st, page, PMBUS_STATUS_IOUT); + if (status < 0 || !st->chip_info->has_opl) + return status; + + /* + * If Over power limit is on, the fault condition is still set on the OC + * bit + */ + if (st->opl_enabled[page] && (status & PB_IOUT_OC_FAULT)) + return status | PB_POUT_OP_FAULT; + + return status; +} + +static int max20826_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max20826 *st = to_max20826(info); + + switch (reg) { + case PMBUS_OPERATION: + return max20826_regulator_enabled(st, page); + case PMBUS_STATUS_IOUT: + return max20826_iout_status(st, page); + default: + return __max20826_read_byte_data(st, page, reg); + } +} + +static int max20826_read_block_data(struct i2c_client *client, int page, u8 reg, + char *data_buf) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max20826 *st = to_max20826(info); + + switch (reg) { + case PMBUS_MFR_ID: + case PMBUS_MFR_MODEL: + return __max20826_read_block_data(st, page, reg, data_buf, 16); + case PMBUS_MFR_REVISION: + return __max20826_read_block_data(st, page, reg, data_buf, 2); + case PMBUS_MFR_DATE: + return __max20826_read_block_data(st, page, reg, data_buf, 8); + default: + return -EOPNOTSUPP; + } +} + +static struct pmbus_driver_info max20826_default_info = { + .pages = 1, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_PHASE_VIRTUAL, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .m[PSC_VOLTAGE_OUT] = 2, + .R[PSC_VOLTAGE_OUT] = 3, + .write_byte_data = max20826_write_byte_data, + .write_byte = max20826_write_byte, + .read_byte_data = max20826_read_byte_data, + .read_word_data = max20826_read_word_data, + .write_word_data = max20826_write_word_data, + .read_block_data = max20826_read_block_data, +#if IS_ENABLED(CONFIG_SENSORS_MAX20826_REGULATOR) + .num_regulators = 1, + .reg_desc = max20826_reg_desc, +#endif +}; + +static int max20826_detect_addr_mode(struct max20826 *st) +{ + u8 val_buf[I2C_SMBUS_BLOCK_MAX]; + struct device *dev = &st->client->dev; + int ret, val; + + /* + * The idea is that after a POR, we are in page0 in which case we can + * read MAX20826_REG_ADDR_MODE. If we fail to read + * MAX20826_REG_ADDR_MODE, it might be due to a soft reset or unbinding + * the device in which case the device could be left in page 1. Hence, + * let's just try to change to page 0 and error out if we can't. + * + * OTOH, if the addressing mode is direct, we should be able to + * read MAX20826_REG_ADDR_MODE. If not, we'll fail setting the page. + * + * REG_ADDR_MODE, MAX20855B has 6 bytes, other devices have 1. Byte 3 is + * the equivalent of the single byte of the others. + */ + if (st->chip_info->is_reg_addr_mode_block) + val = max20826_rail_read_block_data(st->client, + MAX20826_REG_ADDR_MODE, + val_buf, 3); + else + val = i2c_smbus_read_byte_data(st->client, MAX20826_REG_ADDR_MODE); + + if (val < 0) { + ret = i2c_smbus_write_byte_data(st->client, PMBUS_PAGE, 0); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to change to page 0\n"); + + /* try again! */ + if (st->chip_info->is_reg_addr_mode_block) + val = max20826_rail_read_block_data(st->client, + MAX20826_REG_ADDR_MODE, + val_buf, 3); + else + val = i2c_smbus_read_byte_data(st->client, + MAX20826_REG_ADDR_MODE); + if (val < 0) + return dev_err_probe(dev, val, + "Failed to read MAX20826_REG_ADDR_MODE\n"); + } + + if (st->chip_info->is_reg_addr_mode_block) + val = val_buf[2]; + + if (val & MAX20826_PAGE_MODE_MSK) + return 0; + + /* + * If in direct mode, rail b will be accessible from the addr of RAIL_A + * +1 + */ + st->client_b = devm_i2c_new_dummy_device(dev, st->client->adapter, + st->client->addr + 1); + if (IS_ERR(st->client_b)) + return PTR_ERR(st->client_b); + + return 0; +} + +static int max20826_set_vout_interface(struct max20826 *st, + struct i2c_client *rail, u8 page) +{ + static const char * const props[MAX20826_MAX_PAGES] = { + "adi,rail-a-high-speed", + "adi,rail-b-high-speed", + }; + bool high_speed; + int ret; + + /* + * Rail A 0xDC_1[7:6] sets the operating mode for both rails, so only look + * at it once. + */ + if (page == RAIL_A) { + if (st->chip_info->has_avsbus) { + ret = i2c_smbus_read_word_data(st->client, + MAX20826_REG_C_MODEAB); + if (ret < 0) + return ret; + + if (FIELD_GET(MAX20826_C_MODEAB_MASK, ret) == MAX20826_INTF_PWMVID) + st->vendor_bus = true; + } else { + st->vendor_bus = true; + } + } + + high_speed = device_property_read_bool(&st->client->dev, props[page]); + + return max20826_set_high_speed(st, rail, page, high_speed); +} + +static int max20826_detect_phases(struct max20826 *st, struct i2c_client *rail, + struct pmbus_driver_info *info, u8 page, + u8 expected) +{ + u8 status_mon[I2C_SMBUS_BLOCK_MAX]; + int ret, ret2 = 0, oper_save = -1; + u8 n_phases; + + /* + * In order to detect the number of phases, we need to be regulating. + * It is also assumed we're already in the page we want to detect the + * phases. + * + * If the vendor high speed interface controls the output voltage, the + * enable state is not ours to control (it is given by that interface + * together with the AVREN/BVREN pins) and writing PMBUS_OPERATION is a + * no-op, so just assume the rail is already regulating. + */ + if (!st->vendor_bus || !st->high_speed[page]) { + ret = i2c_smbus_read_byte_data(rail, PMBUS_OPERATION); + if (ret < 0) + return ret; + + if (!(ret & PB_OPERATION_CONTROL_ON)) { + oper_save = ret; + ret = max20826_regulator_do_enable(st, rail, page, + ret | PB_OPERATION_CONTROL_ON); + if (ret < 0) + return ret; + } + } + + ret = max20826_rail_read_block_data(rail, MAX20826_REG_STATUS_MON, + status_mon, 2); + if (ret < 0) + goto out_restore_oper; + + n_phases = field_get(st->chip_info->phase_num_mask, status_mon[1]); + if (n_phases != expected) { + ret = dev_err_probe(&st->client->dev, -EIO, + "Number of phases mismatch: expected=%u, detected=%u\n", + expected, n_phases); + goto out_restore_oper; + } + + info->phases[page] = n_phases; + for (unsigned int phase = 0; phase < n_phases; phase++) + info->pfunc[phase] = PMBUS_HAVE_IOUT | PMBUS_HAVE_IIN; + +out_restore_oper: + if (oper_save >= 0) + ret2 = max20826_regulator_do_enable(st, rail, page, oper_save); + + return ret < 0 ? ret : ret2; +} + +static int max20826_get_rail_config(struct max20826 *st, + struct i2c_client *rail, u8 page) +{ + u8 ctrl_misc[I2C_SMBUS_BLOCK_MAX]; + int ret; + + /* + * Check if we need to control PMBUS_OPERATION in addition to the + * CONTROL pin. + */ + ret = i2c_smbus_read_byte_data(rail, PMBUS_ON_OFF_CONFIG); + if (ret < 0) + return ret; + + st->on_off_ctrl[page] = !!(ret & PB_ON_OFF_CONFIG_OPERATION_REQ); + + /* + * See if Over Power Limit is enabled. This will impact how + * OC_FAULT_LIMIT is handled. + */ + if (st->chip_info->has_opl) { + ret = max20826_rail_read_block_data(rail, MAX20826_REG_CTRL_MISC, + ctrl_misc, 1); + if (ret < 0) + return ret; + + st->opl_enabled[page] = !!(ctrl_misc[0] & MAX20826_OPL_EN_MSK); + } + + return 0; +} + +static int max20826_setup_vout_format(struct max20826 *st) +{ + struct pmbus_driver_info *info = &st->info; + struct i2c_client *rail; + int ret; + + if (st->chip_info->is_vout_direct) { + u8 vout_res[I2C_SMBUS_BLOCK_MAX]; + + rail = max20826_select_rail(st, RAIL_A, true); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + ret = max20826_rail_read_block_data(rail, MAX20826_REG_VOUT_RES, + vout_res, 2); + if (ret < 0) + return ret; + + /* check for 1 mv/LSB */ + if (MAX20826_VOUT_RES_MSK & vout_res[1]) + info->m[PSC_VOLTAGE_OUT] = 1; + + return 0; + } + + st->info.format[PSC_VOLTAGE_OUT] = vid; + for (unsigned int page = 0; page < info->pages; page++) { + if (!st->chip_info->select_vrm) { + /* only vr12 in this case */ + st->info.vrm_version[page] = vr12; + continue; + } + + rail = max20826_select_rail(st, page, true); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + ret = i2c_smbus_read_byte_data(rail, MAX20855B_REG_VOUT_VRM); + if (ret < 0) + return ret; + + if (ret & MAX20855B_REG_VOUT_VRM_MASK) + st->info.vrm_version[page] = vr13; + else + st->info.vrm_version[page] = vr12; + } + + return 0; +} + +static int max20826_setup(struct max20826 *st) +{ + struct pmbus_driver_info *info = &st->info; + u8 config[I2C_SMBUS_BLOCK_MAX], expected_phases; + struct device *dev = &st->client->dev; + struct i2c_client *rail; + int ret; + + ret = max20826_detect_addr_mode(st); + if (ret < 0) + return ret; + + ret = max20826_set_vout_interface(st, st->client, RAIL_A); + if (ret < 0) + return ret; + + ret = max20826_rail_read_block_data(st->client, + MAX20826_REG_RAIL_PHASE_CFG, + config, 5); + if (ret < 0) + return ret; + + /* + * If we have phases in RAIL_B, we need to subtract them on config[1] as + * those bits are also set in case RAIL_B has phases. + */ + expected_phases = st->chip_info->count_phases(config, RAIL_A); + ret = max20826_detect_phases(st, st->client, info, RAIL_A, + expected_phases); + if (ret < 0) + return ret; + + ret = max20826_get_rail_config(st, st->client, RAIL_A); + if (ret < 0) + return ret; + + /* Let's see if RAIL_B is present */ + rail = max20826_select_rail(st, RAIL_B, true); + if (IS_ERR(rail)) + return PTR_ERR(rail); + + /* Let's see if there's something on RAIL_B */ + st->bvren = devm_gpiod_get_optional(dev, "bvren", GPIOD_OUT_HIGH); + if (IS_ERR(st->bvren)) + return PTR_ERR(st->bvren); + + ret = max20826_set_vout_interface(st, rail, RAIL_B); + if (ret < 0) + return ret; + + expected_phases = st->chip_info->count_phases(config, RAIL_B); + ret = max20826_detect_phases(st, rail, info, RAIL_B, expected_phases); + if (ret < 0) + return ret; + + if (info->phases[RAIL_B]) { + info->pages = MAX20826_MAX_PAGES; + info->func[RAIL_B] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_PHASE_VIRTUAL; + + if (IS_ENABLED(CONFIG_SENSORS_MAX20826_REGULATOR)) + info->num_regulators = 2; + + ret = max20826_get_rail_config(st, rail, RAIL_B); + if (ret < 0) + return ret; + } + + ret = max20826_setup_vout_format(st); + if (ret < 0) + return ret; + + if (info->pages == 1) { + /* + * In this case we can be left in RAIL_B and given that we on + * only have one page, pmbus_set_page() will refuse to comeback + * to RAIL_A which is obviously problematic. Hence, let's just + * select RAIL_A here. + */ + rail = max20826_select_rail(st, RAIL_A, true); + if (IS_ERR(rail)) + return PTR_ERR(rail); + } + + return 0; +} + +static int max20826_high_speed_en_get(void *arg, u64 *val) +{ + const struct max20826_debugfs_entry *entry = arg; + + *val = entry->st->high_speed[entry->page]; + + return 0; +} + +static int max20826_high_speed_en_set(void *arg, u64 val) +{ + const struct max20826_debugfs_entry *entry = arg; + struct max20826 *st = entry->st; + + guard(pmbus_lock)(st->client); + + return max20826_set_high_speed(st, NULL, entry->page, !!val); +} +DEFINE_DEBUGFS_ATTRIBUTE(max20826_high_speed_en_fops, + max20826_high_speed_en_get, + max20826_high_speed_en_set, "%llu\n"); + +static int max20826_high_speed_bus_show(struct seq_file *s, void *data) +{ + struct device *dev = s->private; + struct i2c_client *client = to_i2c_client(dev); + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct max20826 *st = to_max20826(info); + + seq_printf(s, "%s\n", st->vendor_bus ? st->chip_info->vendor_bus_name : "AVSBus"); + + return 0; +} + +static void max20826_init_debugfs(struct max20826 *st) +{ + struct device *dev = &st->client->dev; + struct dentry *dir; + + dir = pmbus_get_debugfs_dir(st->client); + if (!dir) + return; + + for (unsigned int page = 0; page < st->info.pages; page++) { + struct max20826_debugfs_entry *entry; + char name[32]; + + entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + entry->st = st; + entry->page = page; + + scnprintf(name, sizeof(name), "in%u_high_speed_en", page + 2); + debugfs_create_file_unsafe(name, 0644, dir, entry, + &max20826_high_speed_en_fops); + } + + debugfs_create_devm_seqfile(dev, "in_high_speed_bus", dir, + max20826_high_speed_bus_show); +} + +static int max20826_probe(struct i2c_client *client) +{ + struct max20826 *st; + int ret; + + st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->client = client; + memcpy(&st->info, &max20826_default_info, sizeof(st->info)); + + st->chip_info = i2c_get_match_data(client); + if (!st->chip_info) + return -EINVAL; + + st->avren = devm_gpiod_get_optional(&client->dev, "avren", GPIOD_OUT_HIGH); + if (IS_ERR(st->avren)) + return PTR_ERR(st->avren); + + ret = max20826_setup(st); + if (ret) + return ret; + + ret = pmbus_do_probe(client, &st->info); + if (ret) + return ret; + + max20826_init_debugfs(st); + + return 0; +} + +static const struct max20826_chip_info chip_info_max20826 = { + .vendor_bus_name = "Nvidia PWMVID", + .max_phases = MAX20826_MAX_PHASES, + .phase_num_mask = MAX20826_PHASES_NUM_MASK, + .start_index_iin = 4, + .start_index_iout = 2, + .is_vout_direct = true, + .has_avsbus = true, + .has_opl = true, + .count_phases = __max20826_count_phases, +}; + +static const struct max20826_chip_info chip_info_max20855b = { + .vendor_bus_name = "Intel SVID", + .max_phases = MAX20855B_MAX_PHASES, + .phase_num_mask = MAX20855B_PHASES_NUM_MASK, + .start_index_iin = 2, + .start_index_iout = 0, + .is_reg_addr_mode_block = true, + .select_vrm = true, + .count_phases = __max20855b_count_phases, +}; + +static const struct max20826_chip_info chip_info_max20908 = { + .vendor_bus_name = "AMD SVI3", + .max_phases = MAX20908_MAX_PHASES, + .phase_num_mask = MAX20826_PHASES_NUM_MASK, + .start_index_iin = 2, + .start_index_iout = 0, + .count_phases = __max20908_count_phases, +}; + +static const struct max20826_chip_info chip_info_max20912 = { + .vendor_bus_name = "AMD SVI3", + .max_phases = MAX20912_MAX_PHASES, + .phase_num_mask = MAX20826_PHASES_NUM_MASK, + .start_index_iin = 2, + .start_index_iout = 0, + .count_phases = __max20912_count_phases, +}; + +static const struct max20826_chip_info chip_info_max20916 = { + .vendor_bus_name = "AMD SVI3", + .max_phases = MAX20916_MAX_PHASES, + .phase_num_mask = MAX20826_PHASES_NUM_MASK, + .start_index_iin = 2, + .start_index_iout = 0, + .count_phases = __max20826_count_phases, +}; + +static const struct i2c_device_id max20826_id[] = { + { "max20826", (kernel_ulong_t)&chip_info_max20826 }, + { "max20855b", (kernel_ulong_t)&chip_info_max20855b }, + { "max20908", (kernel_ulong_t)&chip_info_max20908 }, + { "max20912", (kernel_ulong_t)&chip_info_max20912 }, + { "max20916", (kernel_ulong_t)&chip_info_max20916 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max20826_id); + +static const struct of_device_id max20826_of_match[] = { + { .compatible = "adi,max20826", .data = &chip_info_max20826 }, + { .compatible = "adi,max20855b", .data = &chip_info_max20855b }, + { .compatible = "adi,max20908", .data = &chip_info_max20908 }, + { .compatible = "adi,max20912", .data = &chip_info_max20912 }, + { .compatible = "adi,max20916", .data = &chip_info_max20916 }, + { } +}; +MODULE_DEVICE_TABLE(of, max20826_of_match); + +static struct i2c_driver max20826_driver = { + .driver = { + .name = "max20826", + .of_match_table = max20826_of_match, + }, + .probe = max20826_probe, + .id_table = max20826_id, +}; +module_i2c_driver(max20826_driver); + +MODULE_AUTHOR("Nuno Sá "); +MODULE_DESCRIPTION("PMBus driver for MAX20826"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c index 866d25ae8971c4..c088a57d227f56 100644 --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -19,6 +19,7 @@ enum chips { adpm12160, adpm12200, adpm12250, + adpm12886, max34440, max34441, max34446, @@ -114,7 +115,7 @@ static int max34440_read_word_data(struct i2c_client *client, int page, */ if (data->id == max34451 || data->id == max34452 || data->id == adpm12160 || data->id == adpm12200 || - data->id == adpm12250) + data->id == adpm12250 || data->id == adpm12886) return -ENXIO; ret = -ENODATA; break; @@ -133,7 +134,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, case PMBUS_VIRT_READ_IOUT_AVG: if (data->id != max34446 && data->id != max34451 && data->id != max34452 && data->id != adpm12160 && - data->id != adpm12200 && data->id != adpm12250) + data->id != adpm12200 && data->id != adpm12250 && + data->id != adpm12886) return -ENXIO; ret = pmbus_read_word_data(client, page, phase, MAX34446_MFR_IOUT_AVG); @@ -223,7 +225,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, MAX34440_MFR_IOUT_PEAK, 0); if (!ret && (data->id == max34446 || data->id == max34451 || data->id == max34452 || data->id == adpm12160 || - data->id == adpm12200 || data->id == adpm12250)) + data->id == adpm12200 || data->id == adpm12250 || + data->id == adpm12886)) ret = pmbus_write_word_data(client, page, MAX34446_MFR_IOUT_AVG, 0); @@ -297,7 +300,7 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg) */ if (data->id == max34451 || data->id == max34452 || data->id == adpm12160 || data->id == adpm12200 || - data->id == adpm12250) + data->id == adpm12250 || data->id == adpm12886) return -ENXIO; return -ENODATA; default: @@ -321,7 +324,7 @@ static int max34451_write_byte_data(struct i2c_client *client, int page, */ if (data->id == max34451 || data->id == max34452 || data->id == adpm12160 || data->id == adpm12200 || - data->id == adpm12250) + data->id == adpm12250 || data->id == adpm12886) return -ENXIO; return -ENODATA; default: @@ -539,6 +542,41 @@ static struct pmbus_driver_info max34440_info[] = { .write_byte_data = max34451_write_byte_data, .write_word_data = max34440_write_word_data, }, + [adpm12886] = { + .pages = 19, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 125, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 125, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .m[PSC_CURRENT_IN] = 250, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = -1, + .m[PSC_CURRENT_OUT] = 250, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -1, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + /* absent func below [18] are not for monitoring */ + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[4] = PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[9] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT, + .func[10] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT, + .func[14] = PMBUS_HAVE_IOUT, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34451_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_byte_data = max34451_write_byte_data, + .write_word_data = max34440_write_word_data, + }, [max34440] = { .pages = 14, .format[PSC_VOLTAGE_IN] = direct, @@ -804,7 +842,7 @@ static int max34440_probe(struct i2c_client *client) if (rv) return rv; } else if (data->id == adpm12160 || data->id == adpm12200 || - data->id == adpm12250) { + data->id == adpm12250 || data->id == adpm12886) { data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; } @@ -812,10 +850,13 @@ static int max34440_probe(struct i2c_client *client) return pmbus_do_probe(client, &data->info); } +/* ADPM12300 is just ADPM12250 with higher power capacity. Reuses driver data */ static const struct i2c_device_id max34440_id[] = { { .name = "adpm12160", .driver_data = adpm12160 }, { .name = "adpm12200", .driver_data = adpm12200 }, { .name = "adpm12250", .driver_data = adpm12250 }, + { .name = "adpm12300", .driver_data = adpm12250 }, + { .name = "adpm12886", .driver_data = adpm12886 }, { .name = "max34440", .driver_data = max34440 }, { .name = "max34441", .driver_data = max34441 }, { .name = "max34446", .driver_data = max34446 }, diff --git a/drivers/hwmon/pmbus/mp2888.c b/drivers/hwmon/pmbus/mp2888.c index c5f35daa3fe11e..7a2ecffba960e6 100644 --- a/drivers/hwmon/pmbus/mp2888.c +++ b/drivers/hwmon/pmbus/mp2888.c @@ -272,14 +272,14 @@ static int mp2888_write_word_data(struct i2c_client *client, int page, int reg, word = clamp_val(word, 0, GENMASK(7, 0)); break; case PMBUS_IOUT_OC_WARN_LIMIT: - /* Fix limit according to total curent resolution. */ + /* Fix limit according to total current resolution. */ word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 8) : DIV_ROUND_CLOSEST(word, 4); /* Drop unused bits 15:10. */ word = clamp_val(word, 0, GENMASK(9, 0)); break; case PMBUS_POUT_OP_WARN_LIMIT: - /* Fix limit according to total curent resolution. */ + /* Fix limit according to total current resolution. */ word = data->total_curr_resolution ? DIV_ROUND_CLOSEST(word, 4) : DIV_ROUND_CLOSEST(word, 2); /* Drop unused bits 15:10. */ diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 920c1102ab6df5..5525c048a81058 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -461,6 +461,13 @@ struct pmbus_driver_info { int (*read_byte_data)(struct i2c_client *client, int page, int reg); int (*read_word_data)(struct i2c_client *client, int page, int phase, int reg); + /* + * data_buf is at least I2C_SMBUS_BLOCK_MAX bytes long. The callback + * must never return more than I2C_SMBUS_BLOCK_MAX bytes of data and + * returns the number of bytes written into data_buf. + */ + int (*read_block_data)(struct i2c_client *client, int page, u8 reg, + char *data_buf); int (*write_byte_data)(struct i2c_client *client, int page, int reg, u8 byte); int (*write_word_data)(struct i2c_client *client, int page, int reg, diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 5f69c1420b4e15..d69f1bde17956e 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -616,6 +616,26 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, return pmbus_read_smbus_i2c_block_data(client, reg, data_buf); } +/* + * _pmbus_read_block_data() is similar to pmbus_read_block_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + char *data_buf) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_block_data) { + status = info->read_block_data(client, page, reg, data_buf); + if (status != -ENODATA) + return status; + } + + return pmbus_read_block_data(client, page, reg, data_buf); +} + static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, int reg) { @@ -761,7 +781,7 @@ static bool __maybe_unused pmbus_check_block_register(struct i2c_client *client, struct pmbus_data *data = i2c_get_clientdata(client); char data_buf[I2C_SMBUS_BLOCK_MAX + 2]; - rv = pmbus_read_block_data(client, page, reg, data_buf); + rv = _pmbus_read_block_data(client, page, reg, data_buf); if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) rv = pmbus_check_status_cml(client); if (rv < 0 && (data->flags & PMBUS_READ_STATUS_AFTER_FAILED_CHECK)) @@ -2962,6 +2982,11 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, } for (page = 0; page < info->pages; page++) { + if (info->phases[page] > PMBUS_PHASES) { + dev_err(dev, "Bad number of PMBus phases for page %d: %d\n", + page, info->phases[page]); + return -ENODEV; + } ret = pmbus_identify_common(client, data, page); if (ret < 0) { dev_err(dev, "Failed to identify chip capabilities\n"); @@ -3679,7 +3704,7 @@ static ssize_t pmbus_debugfs_block_read(struct file *file, char __user *buf, char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; scoped_guard(pmbus_lock, client) { - rc = pmbus_read_block_data(client, entry->page, entry->reg, data); + rc = _pmbus_read_block_data(client, entry->page, entry->reg, data); if (rc < 0) return rc; } diff --git a/drivers/hwmon/pmbus/tps25990.c b/drivers/hwmon/pmbus/tps25990.c index 9d318e6509abf6..0d5c99053f23f1 100644 --- a/drivers/hwmon/pmbus/tps25990.c +++ b/drivers/hwmon/pmbus/tps25990.c @@ -47,6 +47,22 @@ PK_MIN_AVG_RST_AVG | \ PK_MIN_AVG_RST_MIN) +#define TPS1689_VIN_OV_RANGE_SEL_MASK GENMASK(7, 6) +#define TPS1689_VIN_VOV_MASK GENMASK(5, 0) +#define TPS1689_VIN_SCALING 251 +#define TPS1689_VIN_VOV_STEP_MV 250 +#define TPS1689_VIN_RANGE_SPAN_MV 16000 + +enum chips { + tps1689, + tps25990, +}; + +struct tps25990_data { + struct pmbus_driver_info info; + enum chips chip_id; +}; + /* * Arbitrary default Rimon value: 1kOhm * This correspond to an overcurrent limit of 55A, close to the specified limit @@ -96,6 +112,8 @@ static int tps25990_mfr_write_protect_get(struct i2c_client *client) static int tps25990_read_word_data(struct i2c_client *client, int page, int phase, int reg) { + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct tps25990_data *data = container_of(info, struct tps25990_data, info); int ret; switch (reg) { @@ -184,9 +202,18 @@ static int tps25990_read_word_data(struct i2c_client *client, ret = pmbus_read_word_data(client, page, phase, reg); if (ret < 0) break; - ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM, - TPS25990_VIN_OVF_DIV); - ret += TPS25990_VIN_OVF_OFF; + if (data->chip_id == tps25990) { + ret = DIV_ROUND_CLOSEST(ret * TPS25990_VIN_OVF_NUM, + TPS25990_VIN_OVF_DIV); + ret += TPS25990_VIN_OVF_OFF; + } else if (data->chip_id == tps1689) { + int rng = (FIELD_GET(TPS1689_VIN_OV_RANGE_SEL_MASK, ret) + 1) * + TPS1689_VIN_RANGE_SPAN_MV; + int vov = FIELD_GET(TPS1689_VIN_VOV_MASK, ret) * TPS1689_VIN_VOV_STEP_MV; + + ret = DIV_ROUND_CLOSEST(rng + vov - TPS1689_VIN_RANGE_SPAN_MV, + TPS1689_VIN_SCALING); + } break; case PMBUS_IIN_OC_FAULT_LIMIT: @@ -229,6 +256,8 @@ static int tps25990_read_word_data(struct i2c_client *client, static int tps25990_write_word_data(struct i2c_client *client, int page, int reg, u16 value) { + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct tps25990_data *data = container_of(info, struct tps25990_data, info); int ret; switch (reg) { @@ -240,26 +269,52 @@ static int tps25990_write_word_data(struct i2c_client *client, case PMBUS_OT_WARN_LIMIT: case PMBUS_OT_FAULT_LIMIT: case PMBUS_PIN_OP_WARN_LIMIT: - value >>= TPS25990_8B_SHIFT; + value = clamp_val((s16)value, 0, S16_MAX) >> TPS25990_8B_SHIFT; value = clamp_val(value, 0, 0xff); ret = pmbus_write_word_data(client, page, reg, value); break; case PMBUS_VIN_OV_FAULT_LIMIT: - value -= TPS25990_VIN_OVF_OFF; - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_VIN_OVF_DIV, - TPS25990_VIN_OVF_NUM); - value = clamp_val(value, 0, 0xf); + if ((s16)value < 0) + return -EINVAL; + + if (data->chip_id == tps25990) { + int tmp = (int)value - TPS25990_VIN_OVF_OFF; + + tmp = clamp_val(tmp, 0, INT_MAX); + value = DIV_ROUND_CLOSEST((unsigned int)tmp * TPS25990_VIN_OVF_DIV, + TPS25990_VIN_OVF_NUM); + value = clamp_val(value, 0, 0xf); + } else if (data->chip_id == tps1689) { + u32 scaled_value = value * TPS1689_VIN_SCALING + TPS1689_VIN_RANGE_SPAN_MV; + u32 rng_idx = scaled_value / TPS1689_VIN_RANGE_SPAN_MV; + u32 ov_set; + + rng_idx = clamp_val(rng_idx, 1, + FIELD_MAX(TPS1689_VIN_OV_RANGE_SEL_MASK) + 1); + ov_set = scaled_value - (TPS1689_VIN_RANGE_SPAN_MV * rng_idx); + ov_set = min_t(u32, ov_set / TPS1689_VIN_VOV_STEP_MV, + FIELD_MAX(TPS1689_VIN_VOV_MASK)); + value = FIELD_PREP(TPS1689_VIN_OV_RANGE_SEL_MASK, rng_idx - 1) | + FIELD_PREP(TPS1689_VIN_VOV_MASK, ov_set); + } ret = pmbus_write_word_data(client, page, reg, value); break; - case PMBUS_IIN_OC_FAULT_LIMIT: - value -= TPS25990_IIN_OCF_OFF; - value = DIV_ROUND_CLOSEST(((unsigned int)value) * TPS25990_IIN_OCF_DIV, + case PMBUS_IIN_OC_FAULT_LIMIT: { + int tmp; + + if ((s16)value < 0) + return -EINVAL; + + tmp = (int)value - TPS25990_IIN_OCF_OFF; + tmp = clamp_val(tmp, 0, INT_MAX); + value = DIV_ROUND_CLOSEST((unsigned int)tmp * TPS25990_IIN_OCF_DIV, TPS25990_IIN_OCF_NUM); value = clamp_val(value, 0, 0x3f); ret = pmbus_write_byte_data(client, page, TPS25990_VIREF, value); break; + } case PMBUS_VIRT_SAMPLES: value = clamp_val(value, 1, 1 << PK_MIN_AVG_AVG_CNT); @@ -337,63 +392,120 @@ static const struct regulator_desc tps25990_reg_desc[] = { }; #endif -static const struct pmbus_driver_info tps25990_base_info = { - .pages = 1, - .format[PSC_VOLTAGE_IN] = direct, - .m[PSC_VOLTAGE_IN] = 5251, - .b[PSC_VOLTAGE_IN] = 0, - .R[PSC_VOLTAGE_IN] = -2, - .format[PSC_VOLTAGE_OUT] = direct, - .m[PSC_VOLTAGE_OUT] = 5251, - .b[PSC_VOLTAGE_OUT] = 0, - .R[PSC_VOLTAGE_OUT] = -2, - .format[PSC_TEMPERATURE] = direct, - .m[PSC_TEMPERATURE] = 140, - .b[PSC_TEMPERATURE] = 32100, - .R[PSC_TEMPERATURE] = -2, - /* - * Current and Power measurement depends on the ohm value - * of Rimon. m is multiplied by 1000 below to have an integer - * and -3 is added to R to compensate. - */ - .format[PSC_CURRENT_IN] = direct, - .m[PSC_CURRENT_IN] = 9538, - .b[PSC_CURRENT_IN] = 0, - .R[PSC_CURRENT_IN] = -6, - .format[PSC_POWER] = direct, - .m[PSC_POWER] = 4901, - .b[PSC_POWER] = 0, - .R[PSC_POWER] = -7, - .func[0] = (PMBUS_HAVE_VIN | - PMBUS_HAVE_VOUT | - PMBUS_HAVE_VMON | - PMBUS_HAVE_IIN | - PMBUS_HAVE_PIN | - PMBUS_HAVE_TEMP | - PMBUS_HAVE_STATUS_VOUT | - PMBUS_HAVE_STATUS_IOUT | - PMBUS_HAVE_STATUS_INPUT | - PMBUS_HAVE_STATUS_TEMP | - PMBUS_HAVE_SAMPLES), - .read_word_data = tps25990_read_word_data, - .write_word_data = tps25990_write_word_data, - .read_byte_data = tps25990_read_byte_data, - .write_byte_data = tps25990_write_byte_data, +static const struct pmbus_driver_info tps25990_base_info[] = { + [tps1689] = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 3984, + .b[PSC_VOLTAGE_IN] = -63750, + .R[PSC_VOLTAGE_IN] = -3, + .format[PSC_VOLTAGE_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1166, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -2, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 140, + .b[PSC_TEMPERATURE] = 32103, + .R[PSC_TEMPERATURE] = -2, + /* + * Current and Power measurement depends on the ohm value + * of Rimon. m is multiplied by 1000 below to have an integer + * and -3 is added to R to compensate. + */ + .format[PSC_CURRENT_IN] = direct, + .m[PSC_CURRENT_IN] = 9548, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = -6, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_CURRENT_OUT] = 24347, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -3, + .format[PSC_POWER] = direct, + .m[PSC_POWER] = 2775, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = -4, + .func[0] = (PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | + PMBUS_HAVE_VMON | + PMBUS_HAVE_IIN | + PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_SAMPLES), + + .read_word_data = tps25990_read_word_data, + .write_word_data = tps25990_write_word_data, + .read_byte_data = tps25990_read_byte_data, + .write_byte_data = tps25990_write_byte_data, #if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR) - .reg_desc = tps25990_reg_desc, - .num_regulators = ARRAY_SIZE(tps25990_reg_desc), + .reg_desc = tps25990_reg_desc, + .num_regulators = ARRAY_SIZE(tps25990_reg_desc), +#endif + }, + [tps25990] = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 5251, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -2, + .format[PSC_VOLTAGE_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 5251, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -2, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 140, + .b[PSC_TEMPERATURE] = 32100, + .R[PSC_TEMPERATURE] = -2, + /* + * Current and Power measurement depends on the ohm value + * of Rimon. m is multiplied by 1000 below to have an integer + * and -3 is added to R to compensate. + */ + .format[PSC_CURRENT_IN] = direct, + .m[PSC_CURRENT_IN] = 9538, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = -6, + .format[PSC_POWER] = direct, + .m[PSC_POWER] = 4901, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = -7, + .func[0] = (PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | + PMBUS_HAVE_VMON | + PMBUS_HAVE_IIN | + PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_SAMPLES), + .read_word_data = tps25990_read_word_data, + .write_word_data = tps25990_write_word_data, + .read_byte_data = tps25990_read_byte_data, + .write_byte_data = tps25990_write_byte_data, +#if IS_ENABLED(CONFIG_SENSORS_TPS25990_REGULATOR) + .reg_desc = tps25990_reg_desc, + .num_regulators = ARRAY_SIZE(tps25990_reg_desc), #endif + }, }; static const struct i2c_device_id tps25990_i2c_id[] = { - { .name = "tps25990" }, - { } + { .name = "tps1689", .driver_data = tps1689 }, + { .name = "tps25990", .driver_data = tps25990 }, + {} }; MODULE_DEVICE_TABLE(i2c, tps25990_i2c_id); static const struct of_device_id tps25990_of_match[] = { - { .compatible = "ti,tps25990" }, + { .compatible = "ti,tps1689", .data = (void *)tps1689 }, + { .compatible = "ti,tps25990", .data = (void *)tps25990 }, {} }; MODULE_DEVICE_TABLE(of, tps25990_of_match); @@ -401,8 +513,9 @@ MODULE_DEVICE_TABLE(of, tps25990_of_match); static int tps25990_probe(struct i2c_client *client) { struct device *dev = &client->dev; - struct pmbus_driver_info *info; + struct tps25990_data *data; const char *propname; + enum chips chip_id; u32 rimon; int ret; @@ -415,15 +528,21 @@ static int tps25990_probe(struct i2c_client *client) rimon = TPS25990_DEFAULT_RIMON; } - info = devm_kmemdup(dev, &tps25990_base_info, sizeof(*info), GFP_KERNEL); - if (!info) + chip_id = (enum chips)(unsigned long)i2c_get_match_data(client); + + data = devm_kzalloc(dev, sizeof(struct tps25990_data), GFP_KERNEL); + if (!data) return -ENOMEM; + data->info = tps25990_base_info[chip_id]; + data->chip_id = chip_id; + /* Adapt the current and power scale for each instance */ - tps25990_set_m(&info->m[PSC_CURRENT_IN], rimon); - tps25990_set_m(&info->m[PSC_POWER], rimon); + tps25990_set_m(&data->info.m[PSC_CURRENT_IN], rimon); + tps25990_set_m(&data->info.m[PSC_CURRENT_OUT], rimon); + tps25990_set_m(&data->info.m[PSC_POWER], rimon); - return pmbus_do_probe(client, info); + return pmbus_do_probe(client, &data->info); } static struct i2c_driver tps25990_driver = { diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c index 6c25701b36fcec..a04a632a509174 100644 --- a/drivers/hwmon/pmbus/tps53679.c +++ b/drivers/hwmon/pmbus/tps53679.c @@ -16,13 +16,21 @@ #include "pmbus.h" enum chips { - tps53647, tps53667, tps53676, tps53679, tps53681, tps53685, tps53688 + tps53622, tps53647, tps53659, tps53667, tps53676, tps53679, tps53681, + tps53685, tps53688, tps536c7 }; #define TPS53647_PAGE_NUM 1 -#define TPS53676_USER_DATA_03 0xb3 +#define TPS536XX_PHASE_CONFIG 0xb3 #define TPS53676_MAX_PHASES 7 +#define TPS536C7_MAX_PHASES 12 + +#define TPS536XX_PHASE_ENABLE BIT(7) +#define TPS536XX_PHASE_PAGE BIT(4) + +#define TPS53676_DEVICE_ID "TI\x53\x67\x60\x00" +#define TPS536C7_DEVICE_ID "TI\x53\x6c\x70\x00" #define TPS53679_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ #define TPS53679_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */ @@ -165,34 +173,72 @@ static int tps53681_identify(struct i2c_client *client, TPS53681_DEVICE_ID); } -static int tps53676_identify(struct i2c_client *client, - struct pmbus_driver_info *info) +static int tps536xx_read_block(struct i2c_client *client, u8 reg, u8 *buf, + bool retry_without_pec) +{ + int ret = i2c_smbus_read_block_data(client, reg, buf); + + /* + * Some TPS536C7 samples return an invalid PEC on the device-ID and + * phase-configuration block reads. Retry once without PEC; ordinary + * telemetry keeps using PEC. + */ + if (ret == -EBADMSG && retry_without_pec && + (client->flags & I2C_CLIENT_PEC)) { + client->flags &= ~I2C_CLIENT_PEC; + ret = i2c_smbus_read_block_data(client, reg, buf); + client->flags |= I2C_CLIENT_PEC; + } + return ret; +} + +static int tps536xx_read_phases(struct i2c_client *client, + const char *device_id, int max_phases, + bool retry_without_pec, int *phases_a, + int *phases_b) { u8 buf[I2C_SMBUS_BLOCK_MAX]; - int phases_a = 0, phases_b = 0; int i, ret; - ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); + ret = tps536xx_read_block(client, PMBUS_IC_DEVICE_ID, buf, + retry_without_pec); if (ret < 0) return ret; - if (ret != 6 || memcmp(buf, "TI\x53\x67\x60\x00", 6)) { + if (ret != 6 || memcmp(buf, device_id, 6)) { dev_err(&client->dev, "Unexpected device ID: %*ph\n", ret, buf); return -ENODEV; } - ret = i2c_smbus_read_block_data(client, TPS53676_USER_DATA_03, buf); + ret = tps536xx_read_block(client, TPS536XX_PHASE_CONFIG, buf, + retry_without_pec); if (ret < 0) return ret; if (ret != 24) return -EIO; - for (i = 0; i < 2 * TPS53676_MAX_PHASES; i += 2) { - if (buf[i + 1] & 0x80) { - if (buf[i] & BIT(4)) - phases_b++; + + *phases_a = 0; + *phases_b = 0; + for (i = 0; i < 2 * max_phases; i += 2) { + if (buf[i + 1] & TPS536XX_PHASE_ENABLE) { + if (buf[i] & TPS536XX_PHASE_PAGE) + (*phases_b)++; else - phases_a++; + (*phases_a)++; } } + return 0; +} + +static int tps53676_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int phases_a, phases_b, ret; + + ret = tps536xx_read_phases(client, TPS53676_DEVICE_ID, + TPS53676_MAX_PHASES, false, + &phases_a, &phases_b); + if (ret) + return ret; info->format[PSC_VOLTAGE_OUT] = linear; info->pages = 1; @@ -213,6 +259,69 @@ static int tps53676_identify(struct i2c_client *client, return 0; } +static int tps536c7_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int phases_a, phases_b, page, ret; + + ret = tps536xx_read_phases(client, TPS536C7_DEVICE_ID, + TPS536C7_MAX_PHASES, true, + &phases_a, &phases_b); + if (ret) + return ret; + if (!phases_a) { + dev_err(&client->dev, + "TPS536C7 without channel A is not supported\n"); + return -EOPNOTSUPP; + } + + info->format[PSC_VOLTAGE_OUT] = linear; + /* + * TPS536C7 can place up to 12 phases on channel A, which exceeds + * PMBUS_PHASES. Report aggregate per-channel telemetry only and do + * not populate info->phases[]. + */ + info->pages = phases_b ? 2 : 1; + + /* + * pmbus_set_page() does not update the PAGE register on single-page + * devices, so select page 0 explicitly in case the boot firmware + * left the device on another page. + */ + if (info->pages == 1) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + } + + /* + * With info->phases[] left unset the PMBus core never programs the + * PHASE selector, so make sure each page reports the aggregate + * current (PHASE = 0xff) rather than whatever a previous boot left. + */ + for (page = 0; page < info->pages; page++) { + ret = pmbus_read_byte_data(client, page, PMBUS_PHASE); + if (ret < 0) + return ret; + if (ret == 0xff) + continue; + ret = pmbus_write_byte_data(client, page, PMBUS_PHASE, 0xff); + if (ret < 0) + return ret; + /* PHASE may be write-protected; confirm it actually changed. */ + ret = pmbus_read_byte_data(client, page, PMBUS_PHASE); + if (ret < 0) + return ret; + if (ret != 0xff) { + dev_err(&client->dev, + "failed to select aggregate PHASE on page %d\n", + page); + return -EIO; + } + } + return 0; +} + static int tps53681_read_word_data(struct i2c_client *client, int page, int phase, int reg) { @@ -277,6 +386,8 @@ static int tps53679_probe(struct i2c_client *client) case tps53676: info->identify = tps53676_identify; break; + case tps53622: + case tps53659: case tps53679: case tps53688: info->pages = TPS53679_PAGE_NUM; @@ -292,6 +403,9 @@ static int tps53679_probe(struct i2c_client *client) info->pages = TPS53679_PAGE_NUM; info->identify = tps53685_identify; break; + case tps536c7: + info->identify = tps536c7_identify; + break; default: return -ENODEV; } @@ -301,26 +415,32 @@ static int tps53679_probe(struct i2c_client *client) static const struct i2c_device_id tps53679_id[] = { { .name = "bmr474", .driver_data = tps53676 }, + { .name = "tps53622", .driver_data = tps53622 }, { .name = "tps53647", .driver_data = tps53647 }, + { .name = "tps53659", .driver_data = tps53659 }, { .name = "tps53667", .driver_data = tps53667 }, { .name = "tps53676", .driver_data = tps53676 }, { .name = "tps53679", .driver_data = tps53679 }, { .name = "tps53681", .driver_data = tps53681 }, { .name = "tps53685", .driver_data = tps53685 }, { .name = "tps53688", .driver_data = tps53688 }, + { .name = "tps536c7", .driver_data = tps536c7 }, { } }; MODULE_DEVICE_TABLE(i2c, tps53679_id); static const struct of_device_id __maybe_unused tps53679_of_match[] = { + {.compatible = "ti,tps53622", .data = (void *)tps53622}, {.compatible = "ti,tps53647", .data = (void *)tps53647}, + {.compatible = "ti,tps53659", .data = (void *)tps53659}, {.compatible = "ti,tps53667", .data = (void *)tps53667}, {.compatible = "ti,tps53676", .data = (void *)tps53676}, {.compatible = "ti,tps53679", .data = (void *)tps53679}, {.compatible = "ti,tps53681", .data = (void *)tps53681}, {.compatible = "ti,tps53685", .data = (void *)tps53685}, {.compatible = "ti,tps53688", .data = (void *)tps53688}, + {.compatible = "ti,tps536c7", .data = (void *)tps536c7}, {} }; MODULE_DEVICE_TABLE(of, tps53679_of_match); diff --git a/drivers/hwmon/pt5161l.c b/drivers/hwmon/pt5161l.c index 2b408a69b085c1..1638f54f8eaebf 100644 --- a/drivers/hwmon/pt5161l.c +++ b/drivers/hwmon/pt5161l.c @@ -611,8 +611,8 @@ static const struct of_device_id __maybe_unused pt5161l_of_match[] = { MODULE_DEVICE_TABLE(of, pt5161l_of_match); static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = { - { "PT5161L", 0 }, - {}, + { .id = "PT5161L" }, + { } }; MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match); diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index a97dda9e92dc5d..fd1f950448f064 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -43,6 +43,7 @@ #define SHT4X_CRC8_LEN 1 #define SHT4X_WORD_LEN 2 #define SHT4X_RESPONSE_LENGTH 6 +#define STS4X_RESPONSE_LENGTH 3 #define SHT4X_CRC8_POLYNOMIAL 0x31 #define SHT4X_CRC8_INIT 0xff #define SHT4X_MIN_TEMPERATURE -45000 @@ -52,9 +53,15 @@ DECLARE_CRC8_TABLE(sht4x_crc8_table); +enum sht4x_chips { + sht4x, + sts4x, +}; + /** * struct sht4x_data - All the data required to operate an SHT4X chip * @client: the i2c client associated with the SHT4X + * @chip_id: the chip type (sht4x or sts4x) * @heating_complete: the time that the last heating finished * @data_pending: true if and only if there are measurements to retrieve after heating * @heater_power: the power at which the heater will be started @@ -67,6 +74,7 @@ DECLARE_CRC8_TABLE(sht4x_crc8_table); */ struct sht4x_data { struct i2c_client *client; + enum sht4x_chips chip_id; unsigned long heating_complete; /* in jiffies */ bool data_pending; u32 heater_power; /* in milli-watts */ @@ -92,11 +100,15 @@ static int sht4x_read_values(struct sht4x_data *data) u8 crc; u8 cmd[SHT4X_CMD_LEN] = {SHT4X_CMD_MEASURE_HPM}; u8 raw_data[SHT4X_RESPONSE_LENGTH]; + size_t response_length = data->chip_id == sts4x ? + STS4X_RESPONSE_LENGTH : SHT4X_RESPONSE_LENGTH; unsigned long curr_jiffies; - curr_jiffies = jiffies; - if (time_before(curr_jiffies, data->heating_complete)) - msleep(jiffies_to_msecs(data->heating_complete - curr_jiffies)); + if (data->chip_id != sts4x) { + curr_jiffies = jiffies; + if (time_before(curr_jiffies, data->heating_complete)) + msleep(jiffies_to_msecs(data->heating_complete - curr_jiffies)); + } if (data->data_pending && time_before(jiffies, data->heating_complete + data->update_interval)) { @@ -115,15 +127,14 @@ static int sht4x_read_values(struct sht4x_data *data) usleep_range(SHT4X_MEAS_DELAY_HPM, SHT4X_MEAS_DELAY_HPM + SHT4X_DELAY_EXTRA); } - ret = i2c_master_recv(client, raw_data, SHT4X_RESPONSE_LENGTH); - if (ret != SHT4X_RESPONSE_LENGTH) { + ret = i2c_master_recv(client, raw_data, response_length); + if (ret != response_length) { if (ret >= 0) ret = -ENODATA; return ret; } t_ticks = raw_data[0] << 8 | raw_data[1]; - rh_ticks = raw_data[3] << 8 | raw_data[4]; crc = crc8(sht4x_crc8_table, &raw_data[0], SHT4X_WORD_LEN, CRC8_INIT_VALUE); if (crc != raw_data[2]) { @@ -131,14 +142,19 @@ static int sht4x_read_values(struct sht4x_data *data) return -EIO; } - crc = crc8(sht4x_crc8_table, &raw_data[3], SHT4X_WORD_LEN, CRC8_INIT_VALUE); - if (crc != raw_data[5]) { - dev_err(&client->dev, "data integrity check failed\n"); - return -EIO; + data->temperature = ((21875 * (int32_t)t_ticks) >> 13) - 45000; + + if (data->chip_id != sts4x) { + rh_ticks = raw_data[3] << 8 | raw_data[4]; + crc = crc8(sht4x_crc8_table, &raw_data[3], SHT4X_WORD_LEN, CRC8_INIT_VALUE); + if (crc != raw_data[5]) { + dev_err(&client->dev, "data integrity check failed\n"); + return -EIO; + } + + data->humidity = ((15625 * (int32_t)rh_ticks) >> 13) - 6000; } - data->temperature = ((21875 * (int32_t)t_ticks) >> 13) - 45000; - data->humidity = ((15625 * (int32_t)rh_ticks) >> 13) - 6000; data->last_updated = jiffies; data->valid = true; return 0; @@ -190,9 +206,14 @@ static umode_t sht4x_hwmon_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct sht4x_data *chip_data = data; + switch (type) { case hwmon_temp: + return 0444; case hwmon_humidity: + if (chip_data->chip_id == sts4x) + return 0; return 0444; case hwmon_chip: return 0644; @@ -388,6 +409,7 @@ static const struct hwmon_chip_info sht4x_chip_info = { static int sht4x_probe(struct i2c_client *client) { + const struct attribute_group **groups = NULL; struct device *device = &client->dev; struct device *hwmon_dev; struct sht4x_data *data; @@ -406,11 +428,15 @@ static int sht4x_probe(struct i2c_client *client) if (!data) return -ENOMEM; + data->chip_id = (uintptr_t)i2c_get_match_data(client); data->update_interval = SHT4X_MIN_POLL_INTERVAL; data->client = client; - data->heater_power = 200; - data->heater_time = 1000; data->heating_complete = jiffies; + if (data->chip_id != sts4x) { + data->heater_power = 200; + data->heater_time = 1000; + groups = sht4x_groups; + } crc8_populate_msb(sht4x_crc8_table, SHT4X_CRC8_POLYNOMIAL); @@ -424,19 +450,21 @@ static int sht4x_probe(struct i2c_client *client) client->name, data, &sht4x_chip_info, - sht4x_groups); + groups); return PTR_ERR_OR_ZERO(hwmon_dev); } static const struct i2c_device_id sht4x_id[] = { - { .name = "sht4x" }, + { .name = "sht4x", .driver_data = sht4x }, + { .name = "sts4x", .driver_data = sts4x }, { } }; MODULE_DEVICE_TABLE(i2c, sht4x_id); static const struct of_device_id sht4x_of_match[] = { - { .compatible = "sensirion,sht4x" }, + { .compatible = "sensirion,sht4x", .data = (void *)sht4x }, + { .compatible = "sensirion,sts40", .data = (void *)sts4x }, { } }; MODULE_DEVICE_TABLE(of, sht4x_of_match); diff --git a/drivers/hwmon/socfpga-hwmon.c b/drivers/hwmon/socfpga-hwmon.c index 5b43274d0aa2d3..5fe5ccab1e4f73 100644 --- a/drivers/hwmon/socfpga-hwmon.c +++ b/drivers/hwmon/socfpga-hwmon.c @@ -433,6 +433,26 @@ static const struct socfpga_hwmon_board_data agilex_hwmon_board = { .num_volt = ARRAY_SIZE(agilex_hwmon_volt_channels), }; +/* + * Agilex 5 exposes the SDM and three corner temperature sensors. Channel 2 + * (top-left corner on Agilex) is not present in hardware, so the SDM channel + * numbering keeps the gap (0, 1, 3, 4) rather than renumbering. + */ +static const struct socfpga_hwmon_channel agilex5_hwmon_temp_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 0), "Main Die SDM" }, + { SOCFPGA_HWMON_CHAN(1, 0), "Main Die corner bottom left max" }, + { SOCFPGA_HWMON_CHAN(3, 0), "Main Die corner bottom right max" }, + { SOCFPGA_HWMON_CHAN(4, 0), "Main Die corner top right max" }, +}; + +/* Agilex 5 reuses the Agilex voltage SDM page/channel encoding and labels. */ +static const struct socfpga_hwmon_board_data agilex5_hwmon_board = { + .temp = agilex5_hwmon_temp_channels, + .num_temp = ARRAY_SIZE(agilex5_hwmon_temp_channels), + .volt = agilex_hwmon_volt_channels, + .num_volt = ARRAY_SIZE(agilex_hwmon_volt_channels), +}; + static const struct socfpga_hwmon_board_data * socfpga_hwmon_get_board(struct device *dev) { @@ -443,6 +463,8 @@ socfpga_hwmon_get_board(struct device *dev) if (of_device_is_compatible(np, "intel,stratix10-svc")) return &s10_hwmon_board; + if (of_device_is_compatible(np, "intel,agilex5-svc")) + return &agilex5_hwmon_board; if (of_device_is_compatible(np, "intel,agilex-svc")) return &agilex_hwmon_board; diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c index 9724cf70b61d4d..52f35448934116 100644 --- a/drivers/hwmon/spd5118.c +++ b/drivers/hwmon/spd5118.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -637,44 +638,54 @@ static int spd5118_i2c_init(struct i2c_client *client) I2C_FUNC_SMBUS_WORD_DATA)) return -ENODEV; + /* Early check to avoid obviously unsupported I2C devices */ regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE); - if (regval < 0 || (regval && regval != 0x5118)) + if (regval < 0) + return regval; + + /* + * Some SPD5118 devices report 0x0 when page 0 is not selected, + * so we only fail here if the register value is not 0x0 or 0x5118. + */ + if (regval && regval != 0x5118) return -ENODEV; /* - * If the device type registers return 0, it is possible that the chip - * has a non-zero page selected and takes the specification literally, + * We must select page 0 to ensure that we can reliably read the volatile + * registers on chips that take the specification literally, * i.e. disables access to volatile registers besides the page register * if the page is not 0. The Renesas/ITD SPD5118 Hub Controller is known - * to show this behavior. Try to identify such chips. + * to show this behavior. + * + * We must also perform an unconditional register write to detect if + * the i2c controller blocks write accesses to the SPD device. Some Intel + * controllers might be configured by the BIOS to do this. */ - if (!regval) { - /* Vendor ID registers must also be 0 */ - regval = i2c_smbus_read_word_data(client, SPD5118_REG_VENDOR); - if (regval) - return -ENODEV; - - /* The selected page in MR11 must not be 0 */ - mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE); - if (mode < 0 || (mode & ~SPD5118_LEGACY_MODE_MASK) || - !(mode & SPD5118_LEGACY_PAGE_MASK)) - return -ENODEV; - - err = i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, - mode & SPD5118_LEGACY_MODE_ADDR); - if (err) - return -ENODEV; - - /* - * If the device type registers are still bad after selecting - * page 0, this is not a SPD5118 device. Restore original - * legacy mode register value and abort. - */ - regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE); - if (regval != 0x5118) { - i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, mode); - return -ENODEV; - } + mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE); + if (mode < 0) + return mode; + + /* 16-bit addressing is not supported */ + if (mode & SPD5118_LEGACY_MODE_ADDR) { + dev_notice(&client->dev, + "Unable to access device due to 16-bit addressing being enabled\n"); + return -ENODEV; + } + + err = i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, + mode & ~SPD5118_LEGACY_PAGE_MASK); + if (err < 0) + return err; + + /* We only need to access SPD5118_REG_TYPE again if regval was 0x0 */ + if (regval == 0x5118) + return 0; + + regval = i2c_smbus_read_word_swapped(client, SPD5118_REG_TYPE); + if (regval != 0x5118) { + /* Restore original register content */ + i2c_smbus_write_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE, mode); + return -ENODEV; } /* We are reasonably sure that this is really a SPD5118 hub controller */ diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index 38b140c23c88eb..eebbb741deac63 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -595,9 +595,9 @@ static void xgene_hwmon_tx_done(struct mbox_client *cl, void *msg, int ret) #ifdef CONFIG_ACPI static const struct acpi_device_id xgene_hwmon_acpi_match[] = { - {"APMC0D29", XGENE_HWMON_V1}, - {"APMC0D8A", XGENE_HWMON_V2}, - {}, + { .id = "APMC0D29", .driver_data = XGENE_HWMON_V1 }, + { .id = "APMC0D8A", .driver_data = XGENE_HWMON_V2 }, + { } }; MODULE_DEVICE_TABLE(acpi, xgene_hwmon_acpi_match); #endif diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c index 278cb089b0fd17..aa328456071563 100644 --- a/drivers/hwmon/yogafan.c +++ b/drivers/hwmon/yogafan.c @@ -53,6 +53,11 @@ struct yoga_fan_data { }; /* Specific configurations mapped via DMI */ +static const struct yogafan_config yoga_740_15iml_cfg = { + .multiplier = 100, + .fan_count = 1, + .paths = { "\\_SB.PCI0.LPCB.EC0.FANS", NULL } +}; static const struct yogafan_config yoga_8bit_fans_cfg = { .multiplier = 100, @@ -90,6 +95,17 @@ static const struct yogafan_config yoga_pro_7_14iah10_cfg = { .paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL } }; +/* + * Lenovo Yoga Pro 9 16IMH9 (83DN) uses the PC00 namespace and has two + * 8-bit fan tachometer fields. + */ +static const struct yogafan_config yoga_pro_83dn_cfg = { + .multiplier = 100, + .fan_count = 2, + .paths = { "\\_SB.PC00.LPCB.EC0.FANS", + "\\_SB.PC00.LPCB.EC0.FA2S" } +}; + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) { ktime_t now = ktime_get_boottime(); @@ -189,6 +205,30 @@ static const struct hwmon_chip_info yoga_fan_chip_info = { }; static const struct dmi_system_id yogafan_quirks[] = { + { + .ident = "Lenovo Yoga 740-15IML", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 740-15IML"), + }, + .driver_data = (void *)&yoga_740_15iml_cfg, + }, + { + .ident = "Lenovo IdeaPad 3 15ALC6 Ub", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6 Ub"), + }, + .driver_data = (void *)&xiaoxin_8bit_dual_cfg, + }, + { + .ident = "Yoga 14cACN 2021", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 14cACN 2021"), + }, + .driver_data = (void *)&yoga_8bit_fans_cfg, + }, { .ident = "Lenovo LOQ 15IAX9", .matches = { @@ -237,6 +277,14 @@ static const struct dmi_system_id yogafan_quirks[] = { }, .driver_data = (void *)&xiaoxin_8bit_dual_cfg, }, + { + .ident = "Lenovo Yoga Pro 9 16IMH9 (83DN)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83DN"), + }, + .driver_data = (void *)&yoga_pro_83dn_cfg, + }, { .ident = "Lenovo Yoga", .matches = { diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index dd713e193d0c3a..a3a7d27f3b5ffd 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -134,6 +134,8 @@ enum hwmon_in_attributes { hwmon_in_max, hwmon_in_lcrit, hwmon_in_crit, + hwmon_in_lemergency, + hwmon_in_emergency, hwmon_in_average, hwmon_in_lowest, hwmon_in_highest, @@ -144,6 +146,8 @@ enum hwmon_in_attributes { hwmon_in_max_alarm, hwmon_in_lcrit_alarm, hwmon_in_crit_alarm, + hwmon_in_lemergency_alarm, + hwmon_in_emergency_alarm, hwmon_in_rated_min, hwmon_in_rated_max, hwmon_in_beep, @@ -156,6 +160,8 @@ enum hwmon_in_attributes { #define HWMON_I_MAX BIT(hwmon_in_max) #define HWMON_I_LCRIT BIT(hwmon_in_lcrit) #define HWMON_I_CRIT BIT(hwmon_in_crit) +#define HWMON_I_LEMERGENCY BIT(hwmon_in_lemergency) +#define HWMON_I_EMERGENCY BIT(hwmon_in_emergency) #define HWMON_I_AVERAGE BIT(hwmon_in_average) #define HWMON_I_LOWEST BIT(hwmon_in_lowest) #define HWMON_I_HIGHEST BIT(hwmon_in_highest) @@ -166,6 +172,8 @@ enum hwmon_in_attributes { #define HWMON_I_MAX_ALARM BIT(hwmon_in_max_alarm) #define HWMON_I_LCRIT_ALARM BIT(hwmon_in_lcrit_alarm) #define HWMON_I_CRIT_ALARM BIT(hwmon_in_crit_alarm) +#define HWMON_I_LEMERGENCY_ALARM BIT(hwmon_in_lemergency_alarm) +#define HWMON_I_EMERGENCY_ALARM BIT(hwmon_in_emergency_alarm) #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) @@ -178,6 +186,7 @@ enum hwmon_curr_attributes { hwmon_curr_max, hwmon_curr_lcrit, hwmon_curr_crit, + hwmon_curr_emergency, hwmon_curr_average, hwmon_curr_lowest, hwmon_curr_highest, @@ -188,6 +197,7 @@ enum hwmon_curr_attributes { hwmon_curr_max_alarm, hwmon_curr_lcrit_alarm, hwmon_curr_crit_alarm, + hwmon_curr_emergency_alarm, hwmon_curr_rated_min, hwmon_curr_rated_max, hwmon_curr_beep, @@ -199,6 +209,7 @@ enum hwmon_curr_attributes { #define HWMON_C_MAX BIT(hwmon_curr_max) #define HWMON_C_LCRIT BIT(hwmon_curr_lcrit) #define HWMON_C_CRIT BIT(hwmon_curr_crit) +#define HWMON_C_EMERGENCY BIT(hwmon_curr_emergency) #define HWMON_C_AVERAGE BIT(hwmon_curr_average) #define HWMON_C_LOWEST BIT(hwmon_curr_lowest) #define HWMON_C_HIGHEST BIT(hwmon_curr_highest) @@ -209,6 +220,7 @@ enum hwmon_curr_attributes { #define HWMON_C_MAX_ALARM BIT(hwmon_curr_max_alarm) #define HWMON_C_LCRIT_ALARM BIT(hwmon_curr_lcrit_alarm) #define HWMON_C_CRIT_ALARM BIT(hwmon_curr_crit_alarm) +#define HWMON_C_EMERGENCY_ALARM BIT(hwmon_curr_emergency_alarm) #define HWMON_C_RATED_MIN BIT(hwmon_curr_rated_min) #define HWMON_C_RATED_MAX BIT(hwmon_curr_rated_max) #define HWMON_C_BEEP BIT(hwmon_curr_beep)