* [PATCH v3 0/6] thermal: Add hardware-binned trip point support
@ 2026-11-13 8:43 Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Gaurav Kohli @ 2026-11-13 8:43 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Amit Kucheria, Thara Gopinath,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: linux-pm, devicetree, linux-kernel, linux-doc, linux-arm-msm,
Gaurav Kohli, Amit Kucheria, Krzysztof Kozlowski
Some SoCs are available in multiple thermal/package bins. The silicon is
otherwise the same, but the package/bin determines which junction
temperature limits are valid for a given part. This information is
programmed in manufacturing fuses and needs to be reflected in the trip
points used by the kernel.
At the moment this is difficult to describe cleanly. Board-specific trip
overrides can handle individual cases, but they do not describe the real
hardware property: the limit is selected by SoC fuse state, not by board
identity. Duplicating thermal zones or adding vendor-specific trip
properties would also make the binding harder to share across platforms.
This series adds generic hardware-binned trip point support to the
thermal OF parser, following the same general model as the OPP
framework's opp-supported-hw property. A thermal sensor driver provides
the decoded hardware bin when registering a thermal zone, and the OF
parser uses that information to select the applicable trip data.
Two generic trip-point properties are added:
- thermal-hw-bin:
one or more hardware-bin mask groups for which the trip is valid
- temperature-bin:
per-bin trip temperatures, selected by the matching
thermal-hw-bin group
This topic was discussed at the Linux Plumbers Conference Power and
Thermal Management microconference as "Thermal binning support". The
discussion covered how to represent SoCs whose valid junction temperature
limits depend on package/bin characteristics. The approach taken here is
to keep the DT description generic and let the platform thermal driver
provide the hardware-specific fuse decoding.
LPC discussion:
https://lpc.events/event/19/contributions/2245/
Slides:
https://lpc.events/event/19/contributions/2245/attachments/1885/4034/LPC2025-Thermal-Microconf-Amit-Tj-Thermal-binning.pdf
Qualcomm TSENS is added as the first user of the generic support. On
SC7280/QCM6490-class platforms, TSENS reads the jtag-id and feat-id
QFPROM cells and maps them to the thermal hardware bin. On
X1E80100-class platforms, TSENS reads the i-temp QFPROM bit. The decoded
bin is passed to the thermal OF registration path before trip points are
parsed.
The DTS patches use the new properties for the affected Kodiak and Hamoa
thermal zones. For the standard bin the passive trip remains at 95C; for
the elevated bin it is selected as 105C.
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
Changes in v3:
- Add minItems/maxItems constraints for thermal-hw-bin. (Krzysztof)
- Return -EINVAL for invalid trip types so -ENODEV remains reserved for
skipped hw-bin trips. (Sashiko)
- Add Reviewed-by from Krzysztof for the qcom-tsens binding patch.
- Link to v2: https://lore.kernel.org/r/20261031-thermal_bin-v2-0-d76fd073ae98@oss.qualcomm.com
Changes in v2:
- Format temperature-bin as separate DT cells. (Konrad)
- Add min/max constraints for temperature-bin entries. (Sashiko)
- Drop the post-registration hardware-bin API. (Sashiko)
- Remove CONFIG_THERMAL_BIN and make hardware-bin trip parsing part of
the thermal OF registration path. (Sashiko)
- Keep hardware-bin selection registration-time only through
devm_thermal_of_zone_register_with_bin(), because OF trips are parsed
synchronously during zone registration. (Sashiko)
- Propagate -EPROBE_DEFER when TSENS cannot read the hardware-bin nvmem
cells yet. (Sashiko)
- Link to v1: https://lore.kernel.org/r/20261002-thermal_bin-v1-0-5a29be7e8ec5@oss.qualcomm.com
---
Amit Kucheria (6):
dt-bindings: thermal: thermal-zones: add hardware-binning trip properties
thermal: add hardware-binning trip-point filtering support
dt-bindings: thermal: qcom-tsens: document qcm6490 tsens
thermal: qcom: tsens: add hardware-bin trip-point filtering
arm64: dts: qcom: kodiak: use thermal hw-bin trips
arm64: dts: qcom: hamoa: add thermal hw-bin support
.../devicetree/bindings/thermal/qcom-tsens.yaml | 8 +
.../devicetree/bindings/thermal/thermal-zones.yaml | 60 ++++-
.../driver-api/thermal/hardware-binning.rst | 112 +++++++++
Documentation/driver-api/thermal/index.rst | 1 +
MAINTAINERS | 1 +
arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts | 66 ------
arch/arm64/boot/dts/qcom/hamoa.dtsi | 42 +++-
arch/arm64/boot/dts/qcom/kodiak.dtsi | 58 +++--
drivers/thermal/qcom/tsens-v2.c | 16 ++
drivers/thermal/qcom/tsens.c | 190 +++++++++++++++-
drivers/thermal/qcom/tsens.h | 29 +++
drivers/thermal/thermal_of.c | 253 +++++++++++++++++++--
include/linux/thermal.h | 32 +++
13 files changed, 754 insertions(+), 114 deletions(-)
---
base-commit: 6d582d78fad9d828e575ca0cdcff8ea47a77220b
change-id: 20260917-thermal_bin-fe7becdf0e17
Best regards,
--
Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties
2026-11-13 8:43 [PATCH v3 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
@ 2026-11-13 8:43 ` Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 2/6] thermal: add hardware-binning trip-point filtering support Gaurav Kohli
` (4 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Gaurav Kohli @ 2026-11-13 8:43 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Amit Kucheria, Thara Gopinath,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: linux-pm, devicetree, linux-kernel, linux-doc, linux-arm-msm,
Gaurav Kohli, Amit Kucheria
From: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Differences in SoC packaging can be used to provide multiple SoC SKUs at
different thermal and performance characteristics. The underlying SoC
remains the same except for how much heat the packaging can dissipate,
thereby influencing the critical trip point at which an SoC SKU needs to
be throttled. This SKU-specific data is typically burned into fuses
during manufacturing and can be read back at runtime.
Add two optional trip-point properties to describe this:
- 'temperature-bin': an array of per-bin trip temperatures, used
instead of 'temperature' when the trip depends on the hardware
revision encoded in fuses.
- 'thermal-hw-bin': one or more bitmask groups describing the hardware
revisions for which a trip point is valid, modeled on the
'opp-supported-hw' property of the OPP framework.
Signed-off-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
.../devicetree/bindings/thermal/thermal-zones.yaml | 60 +++++++++++++++++++++-
1 file changed, 59 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
index 999ad40a20d5..cb9a8c96d053 100644
--- a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
+++ b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
@@ -152,6 +152,58 @@ patternProperties:
situation where the trip gets constantly triggered soon after
cooling action is removed.
+ temperature-bin:
+ $ref: /schemas/types.yaml#/definitions/int32-array
+ items:
+ minimum: -273000
+ maximum: 200000
+ description: |
+ An array of integers expressing trip temperatures in
+ millicelsius, one entry per hardware bin sub-group defined
+ in the thermal-hw-bin property. Each value is the trip
+ temperature for the corresponding hardware bin.
+
+ Use this property instead of 'temperature' when the trip
+ temperature depends on the SoC hardware revision as encoded
+ in fuse values. The platform driver reads the fuse and
+ registers the hardware-version bitfields with the thermal
+ zone; the framework then selects the matching entry.
+
+ Exactly one of 'temperature' or 'temperature-bin' must be
+ present in each trip node.
+
+ thermal-hw-bin:
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ minItems: 1
+ maxItems: 32
+ description: |
+ Optional bitmask(s) describing the hardware revisions for
+ which this trip point is valid.
+
+ Uses the same format and semantics as the opp-supported-hw
+ property in the OPP framework. The platform thermal driver
+ provides N 32-bit values describing the hardware it is
+ running on, in a platform-specific format.
+ Each trip node may provide one or more groups of N 32-bit
+ mask values.
+
+ The total number of 32-bit cells must be a multiple of N.
+ Each contiguous group of N cells forms a sub-group. A
+ sub-group matches if, for all i in [0 .. N-1], the bitwise
+ AND of the i-th mask and the i-th platform hardware value
+ is non-zero. The trip point is enabled if any sub-group
+ matches. If multiple sub-groups match, the first matching
+ sub-group selects the corresponding temperature-bin entry.
+
+ When used together with 'temperature-bin', the number of
+ sub-groups must equal the number of entries in
+ 'temperature-bin'.
+
+ If absent, the trip point is valid for all hardware.
+ items:
+ minItems: 1
+ maxItems: 4
+
type:
$ref: /schemas/types.yaml#/definitions/string
enum:
@@ -179,8 +231,14 @@ patternProperties:
The active trip type can be used to control other HW to
help in cooling e.g. fans can be sped up or slowed down
+ oneOf:
+ - required:
+ - temperature
+ - required:
+ - temperature-bin
+ dependentRequired:
+ temperature-bin: [thermal-hw-bin]
required:
- - temperature
- hysteresis
- type
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 2/6] thermal: add hardware-binning trip-point filtering support
2026-11-13 8:43 [PATCH v3 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
@ 2026-11-13 8:43 ` Gaurav Kohli
2026-11-13 9:01 ` sashiko-bot
2026-11-13 8:43 ` [PATCH v3 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens Gaurav Kohli
` (3 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Gaurav Kohli @ 2026-11-13 8:43 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Amit Kucheria, Thara Gopinath,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: linux-pm, devicetree, linux-kernel, linux-doc, linux-arm-msm,
Gaurav Kohli, Amit Kucheria
From: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Differences in SoC packaging can be used to provide multiple SoC SKUs at
different thermal and performance characteristics. The underlying SoC
remains the same except for how much heat the packaging can dissipate,
thereby influencing what is the critical trip point at which the SoC SKU
needs to be throttled. This is the basis for thermal-binning.
This SKU-specific data is typically burned into EFUSEs during
manufacturing. We can read back this information at runtime to populate
the appropriate trip points applicable to an SKU.
Linux support for this has been modeled along the lines of the
opp-supported-hw property in the OPP framework that already allows
similar mechanisms to allow/disallow certain operating frequencies for
the processor. The thermal-hw-bin property allows multiple SKUs of an
SoC to be configured for different critical trip points based on a fuse
mask.
Add thermal OF support to parse the thermal-hw-bin and temperature-bin
DT properties. Platform sensor drivers pass decoded hardware-version
information to devm_thermal_of_zone_register_with_bin() before trip
points are parsed, allowing the OF parser to filter trips and select
per-bin temperatures during zone registration.
Signed-off-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Co-developed-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
.../driver-api/thermal/hardware-binning.rst | 112 +++++++++
Documentation/driver-api/thermal/index.rst | 1 +
MAINTAINERS | 1 +
drivers/thermal/thermal_of.c | 253 +++++++++++++++++++--
include/linux/thermal.h | 32 +++
5 files changed, 377 insertions(+), 22 deletions(-)
diff --git a/Documentation/driver-api/thermal/hardware-binning.rst b/Documentation/driver-api/thermal/hardware-binning.rst
new file mode 100644
index 000000000000..c385a2e224e3
--- /dev/null
+++ b/Documentation/driver-api/thermal/hardware-binning.rst
@@ -0,0 +1,112 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=====================
+Thermal hardware bins
+=====================
+
+:Author: Amit Kucheria
+
+Overview
+========
+
+Differences in SoC packaging let a vendor ship several SKUs built from the
+same silicon but with different thermal characteristics. Better packaging
+dissipates more heat, so the temperature at which a SKU must be throttled (or
+shut down) differs between SKUs. This SKU-specific information is typically
+burned into eFUSEs during manufacturing and can be read back at runtime.
+
+Thermal "hardware binning" lets a single device tree describe the trip points
+for every SKU of an SoC and have the kernel pick the values that match the
+silicon it is actually running on. The mechanism is modelled on the
+``opp-supported-hw`` property used by the OPP framework: the platform supplies
+one or more 32-bit hardware-version words decoded from fuses, and each trip
+point declares the hardware bitmasks for which it is valid.
+
+Hardware-bin trip parsing is part of the thermal OF helpers.
+
+Device tree properties
+=======================
+
+Two properties are added to a trip-point node (see
+``Documentation/devicetree/bindings/thermal/thermal-zones.yaml`` for the
+authoritative schema):
+
+``temperature-bin``
+ An array of trip temperatures in millicelsius, one entry per
+ ``thermal-hw-bin`` sub-group. It is used *instead of* the plain
+ ``temperature`` property when the trip temperature depends on the hardware
+ bin. Exactly one of ``temperature`` or ``temperature-bin`` must be present
+ in a trip node.
+
+``thermal-hw-bin``
+ One or more groups of N 32-bit masks describing the hardware revisions for
+ which the trip point is valid, where N is the number of hardware-version
+ words supplied by the platform (``supported_hw_bin_count``, see below). The
+ format and semantics match ``opp-supported-hw``: a sub-group matches when,
+ for every word ``i`` in ``[0 .. N-1]``, the bitwise AND of the ``i``-th mask
+ and the ``i``-th platform hardware value is non-zero. The trip is enabled if
+ any sub-group matches. When paired with ``temperature-bin`` the number of
+ sub-groups must equal the number of ``temperature-bin`` entries, and the
+ matching sub-group selects which ``temperature-bin`` entry is used. When
+ ``temperature-bin`` is present, ``thermal-hw-bin`` is required.
+
+Example::
+
+ trips {
+ cpu-critical {
+ /* bin 0 throttles at 95C, bin 1 at 105C */
+ temperature-bin = <95000 105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
+ hysteresis = <1000>;
+ type = "critical";
+ };
+ };
+
+Here the platform registers a single hardware-version word (N = 1). If that
+word ANDed with ``0x1`` is non-zero, the first sub-group matches and the trip
+uses 95000 mC; if it ANDed with ``0x2`` is non-zero, the second sub-group
+matches and the trip uses 105000 mC. If neither matches the trip is skipped.
+
+Platform driver API
+===================
+
+A platform driver decodes the SoC fuses into hardware-version words and hands
+them to the thermal core, which makes them available to the OF trip parser.
+The words are carried in ``struct thermal_hw_bin_info``::
+
+ struct thermal_hw_bin_info {
+ const u32 *supported_hw_bin;
+ unsigned int supported_hw_bin_count;
+ };
+
+``supported_hw_bin``
+ Array of N bitfields identifying the running hardware version.
+
+``supported_hw_bin_count``
+ Number of valid entries in ``supported_hw_bin`` (the N above). Must be in
+ the range ``1 .. THERMAL_HW_BIN_MAX_LEVELS``.
+
+Pass a filled ``struct thermal_hw_bin_info`` to
+``devm_thermal_of_zone_register_with_bin()`` when registering the thermal zone.
+The hardware-version information must be available at registration time because
+the OF trip points are parsed synchronously during zone registration::
+
+ struct thermal_zone_device *
+ devm_thermal_of_zone_register_with_bin(struct device *dev, int id,
+ void *data,
+ const struct thermal_zone_device_ops *ops,
+ const struct thermal_hw_bin_info *hw_bin_info);
+
+The caller owns the storage referenced by ``hw_bin_info``. It only needs to
+remain valid for the duration of ``devm_thermal_of_zone_register_with_bin()``,
+because the OF parser consumes the values before the function returns.
+
+If a trip uses ``temperature-bin`` but no bin info has been provided for the
+zone, the core cannot select an entry; it warns once and falls back to the
+first ``temperature-bin`` value. Platform drivers should therefore provide
+the bin info before the zone's trips are parsed.
+
+The Qualcomm tsens driver is the reference user: it reads the named nvmem
+cells described by a per-SoC ``struct tsens_hw_bin_desc``, converts the raw
+fuse values into hardware-version words, and passes the result to
+``devm_thermal_of_zone_register_with_bin()``.
diff --git a/Documentation/driver-api/thermal/index.rst b/Documentation/driver-api/thermal/index.rst
index a886028014ab..5f858fabc865 100644
--- a/Documentation/driver-api/thermal/index.rst
+++ b/Documentation/driver-api/thermal/index.rst
@@ -11,6 +11,7 @@ Thermal
cpu-idle-cooling
sysfs-api
power_allocator
+ hardware-binning
exynos_thermal
exynos_thermal_emulation
diff --git a/MAINTAINERS b/MAINTAINERS
index 7070cf5dbfe9..8bb1ddd2ec3c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28003,6 +28003,7 @@ F: Documentation/ABI/testing/sysfs-class-thermal
F: Documentation/admin-guide/thermal/
F: Documentation/devicetree/bindings/thermal/
F: Documentation/driver-api/thermal/
+F: Documentation/driver-api/thermal/hardware-binning.rst
F: drivers/thermal/
F: include/dt-bindings/thermal/
F: include/linux/cpu_cooling.h
diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c
index 9e96fb1b033e..57500e565560 100644
--- a/drivers/thermal/thermal_of.c
+++ b/drivers/thermal/thermal_of.c
@@ -57,21 +57,168 @@ static int thermal_of_get_trip_type(struct device_node *np,
return 0;
}
+ return -EINVAL;
+}
+
+/*
+ * thermal_of_match_hw_bin_group - test one thermal-hw-bin sub-group
+ * @np: device node containing the property
+ * @hw: hardware binning info (must be non-NULL)
+ * @levels: number of words per sub-group, equal to hw->supported_hw_bin_count
+ * @group: sub-group index to test
+ *
+ * Returns true if every word in sub-group @group has a non-zero bitwise
+ * AND with the corresponding hw->supported_hw_bin[] entry. The caller is
+ * responsible for ensuring @group < total-cells / @levels.
+ */
+static bool thermal_of_match_hw_bin_group(struct device_node *np,
+ const struct thermal_hw_bin_info *hw,
+ unsigned int levels, unsigned int group)
+{
+ unsigned int j;
+ u32 val;
+
+ for (j = 0; j < levels; j++) {
+ if (of_property_read_u32_index(np, "thermal-hw-bin",
+ group * levels + j, &val))
+ return false;
+ if (!(val & hw->supported_hw_bin[j]))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * thermal_of_trip_is_supported - check if a trip node is valid for this hardware
+ * @np: device node for the trip point
+ * @hw: hardware binning info (may be NULL)
+ *
+ * If @hw is NULL or the trip node has no thermal-hw-bin property, the trip is
+ * considered valid for all hardware. Otherwise, the property is read as groups
+ * of hw->supported_hw_bin_count 32-bit masks; the trip is valid if any group
+ * matches (bitwise AND non-zero for every word in the group).
+ *
+ * Return: true if the trip is valid for the running hardware, false otherwise.
+ */
+static bool thermal_of_trip_is_supported(struct device_node *np,
+ const struct thermal_hw_bin_info *hw)
+{
+ unsigned int levels, ngroups, i;
+ int count;
+
+ if (!hw)
+ return true;
+
+ levels = hw->supported_hw_bin_count;
+
+ count = of_property_count_u32_elems(np, "thermal-hw-bin");
+ if (count == -EINVAL)
+ return true;
+ if (count < 0) {
+ pr_warn_once("%pOF: error reading thermal-hw-bin: %d\n", np, count);
+ return false;
+ }
+ if (count == 0)
+ return true;
+
+ if (count % levels) {
+ pr_warn("%pOF: thermal-hw-bin length %d not a multiple of supported_hw_bin_count %u; disabling trip\n",
+ np, count, levels);
+ return false;
+ }
+
+ ngroups = count / levels;
+ for (i = 0; i < ngroups; i++)
+ if (thermal_of_match_hw_bin_group(np, hw, levels, i))
+ return true;
+
+ return false;
+}
+
+/**
+ * thermal_of_trip_read_temperature - read trip temperature, with bin support
+ * @np: device node for the trip point
+ * @hw: hardware binning info (may be NULL)
+ * @temp: output temperature in millicelsius
+ *
+ * Tries the scalar 'temperature' property first. If absent, falls back to
+ * 'temperature-bin', selecting the entry whose corresponding thermal-hw-bin
+ * sub-group matches the running hardware. Returns -ENODEV if temperature-bin
+ * is present but no sub-group matches (caller should skip this trip).
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int thermal_of_trip_read_temperature(struct device_node *np,
+ const struct thermal_hw_bin_info *hw,
+ s32 *temp)
+{
+ unsigned int levels, ngroups, i;
+ int count_hw, count_temp;
+ s32 t;
+
+ if (!of_property_read_s32(np, "temperature", &t)) {
+ *temp = t;
+ return 0;
+ }
+
+ count_temp = of_property_count_elems_of_size(np, "temperature-bin",
+ sizeof(u32));
+ if (count_temp < 0)
+ return -EINVAL;
+
+ if (!hw) {
+ u32 utmp;
+
+ pr_warn("%pOF: temperature-bin requires platform hw binning info; using first entry\n",
+ np);
+ if (of_property_read_u32_index(np, "temperature-bin", 0, &utmp))
+ return -EINVAL;
+ *temp = (s32)utmp;
+ return 0;
+ }
+
+ levels = hw->supported_hw_bin_count;
+ count_hw = of_property_count_u32_elems(np, "thermal-hw-bin");
+ if (count_hw <= 0 || count_hw % levels)
+ return -EINVAL;
+
+ ngroups = count_hw / levels;
+ if (count_temp != (int)ngroups)
+ return -EINVAL;
+
+ for (i = 0; i < ngroups; i++) {
+ if (thermal_of_match_hw_bin_group(np, hw, levels, i)) {
+ u32 utmp;
+
+ if (of_property_read_u32_index(np, "temperature-bin",
+ i, &utmp))
+ return -EINVAL;
+ *temp = (s32)utmp;
+ return 0;
+ }
+ }
+
+ pr_warn("%pOF: no thermal-hw-bin sub-group matched for temperature-bin\n", np);
return -ENODEV;
}
static int thermal_of_populate_trip(struct device_node *np,
- struct thermal_trip *trip)
+ struct thermal_trip *trip,
+ const struct thermal_hw_bin_info *hw)
{
u32 hysteresis;
s32 temperature;
int ret;
- ret = of_property_read_s32(np, "temperature", &temperature);
- if (ret < 0) {
- pr_err("missing temperature property\n");
- return ret;
+ if (!thermal_of_trip_is_supported(np, hw)) {
+ pr_debug("%pOF: trip skipped: no thermal-hw-bin match\n", np);
+ return -ENODEV;
}
+
+ ret = thermal_of_trip_read_temperature(np, hw, &temperature);
+ if (ret)
+ return ret;
trip->temperature = temperature;
ret = of_property_read_u32(np, "hysteresis", &hysteresis);
@@ -94,7 +241,9 @@ static int thermal_of_populate_trip(struct device_node *np,
return 0;
}
-static struct thermal_trip *thermal_of_trips_init(struct device_node *np, int *ntrips)
+static struct thermal_trip *thermal_of_trips_init(struct device_node *np,
+ int *ntrips,
+ const struct thermal_hw_bin_info *hw)
{
int ret, count;
@@ -114,9 +263,12 @@ static struct thermal_trip *thermal_of_trips_init(struct device_node *np, int *n
count = 0;
for_each_child_of_node_scoped(trips, trip) {
- ret = thermal_of_populate_trip(trip, &tt[count++]);
+ ret = thermal_of_populate_trip(trip, &tt[count], hw);
+ if (ret == -ENODEV)
+ continue;
if (ret)
return ERR_PTR(ret);
+ count++;
}
*ntrips = count;
@@ -364,27 +516,24 @@ static void thermal_of_zone_unregister(struct thermal_zone_device *tz)
}
/**
- * thermal_of_zone_register - Register a thermal zone with device node
- * sensor
+ * thermal_of_zone_register_with_bin - Register a thermal zone with device node
+ * sensor and optional hardware binning info
*
- * The thermal_of_zone_register() parses a device tree given a device
- * node sensor and identifier. It searches for the thermal zone
- * associated to the couple sensor/id and retrieves all the thermal
- * zone properties and registers new thermal zone with those
- * properties.
- *
- * @sensor: A device node pointer corresponding to the sensor in the device tree
- * @id: An integer as sensor identifier
- * @data: A private data to be stored in the thermal zone dedicated private area
- * @ops: A set of thermal sensor ops
+ * @sensor: A device node pointer corresponding to the sensor in the device tree
+ * @id: An integer as sensor identifier
+ * @data: A private data to be stored in the thermal zone dedicated private area
+ * @ops: A set of thermal sensor ops
+ * @hw_bin_info: Optional hardware binning info for trip filtering (may be NULL)
*
* Return: a valid thermal zone structure pointer on success.
* - EINVAL: if the device tree thermal description is malformed
* - ENOMEM: if one structure can not be allocated
* - Other negative errors are returned by the underlying called functions
*/
-static struct thermal_zone_device *thermal_of_zone_register(struct device_node *sensor, int id, void *data,
- const struct thermal_zone_device_ops *ops)
+static struct thermal_zone_device *
+thermal_of_zone_register_with_bin(struct device_node *sensor, int id, void *data,
+ const struct thermal_zone_device_ops *ops,
+ const struct thermal_hw_bin_info *hw_bin_info)
{
struct thermal_zone_device_ops of_ops = *ops;
struct thermal_zone_device *tz;
@@ -403,7 +552,7 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node *
return ERR_CAST(np);
}
- trips = thermal_of_trips_init(np, &ntrips);
+ trips = thermal_of_trips_init(np, &ntrips, hw_bin_info);
if (IS_ERR(trips)) {
pr_err("Failed to parse trip points for %pOFP id=%d\n", sensor, id);
ret = PTR_ERR(trips);
@@ -461,6 +610,26 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node *
return ERR_PTR(ret);
}
+/**
+ * thermal_of_zone_register - Register a thermal zone with device node sensor
+ *
+ * @sensor: A device node pointer corresponding to the sensor in the device tree
+ * @id: An integer as sensor identifier
+ * @data: A private data to be stored in the thermal zone dedicated private area
+ * @ops: A set of thermal sensor ops
+ *
+ * Return: a valid thermal zone structure pointer on success.
+ * - EINVAL: if the device tree thermal description is malformed
+ * - ENOMEM: if one structure can not be allocated
+ * - Other negative errors are returned by the underlying called functions
+ */
+static struct thermal_zone_device *
+thermal_of_zone_register(struct device_node *sensor, int id, void *data,
+ const struct thermal_zone_device_ops *ops)
+{
+ return thermal_of_zone_register_with_bin(sensor, id, data, ops, NULL);
+}
+
static void devm_thermal_of_zone_release(struct device *dev, void *res)
{
thermal_of_zone_unregister(*(struct thermal_zone_device **)res);
@@ -510,6 +679,46 @@ struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, in
}
EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register);
+/**
+ * devm_thermal_of_zone_register_with_bin - register a thermal zone with hardware binning info
+ *
+ * Like devm_thermal_of_zone_register(), but passes @hw_bin_info to the trip-point
+ * parser so that trips can be filtered and per-bin temperatures selected at
+ * zone registration time. Use this when fuse values are read before zone
+ * registration (e.g. in a platform driver's probe function).
+ *
+ * @dev: device structure pointer to sensor
+ * @id: the sensor identifier
+ * @data: private data stored in the thermal zone 'devdata' field
+ * @ops: ops structure associated with the sensor
+ * @hw_bin_info: hardware binning info for trip filtering (may be NULL)
+ */
+struct thermal_zone_device *
+devm_thermal_of_zone_register_with_bin(struct device *dev, int id, void *data,
+ const struct thermal_zone_device_ops *ops,
+ const struct thermal_hw_bin_info *hw_bin_info)
+{
+ struct thermal_zone_device **ptr, *tzd;
+
+ ptr = devres_alloc(devm_thermal_of_zone_release, sizeof(*ptr),
+ GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ tzd = thermal_of_zone_register_with_bin(dev->of_node, id, data,
+ ops, hw_bin_info);
+ if (IS_ERR(tzd)) {
+ devres_free(ptr);
+ return tzd;
+ }
+
+ *ptr = tzd;
+ devres_add(dev, ptr);
+
+ return tzd;
+}
+EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register_with_bin);
+
/**
* devm_thermal_of_zone_unregister - Resource managed version of
* thermal_of_zone_unregister().
diff --git a/include/linux/thermal.h b/include/linux/thermal.h
index 306ad17aed89..e9e4b2dc2824 100644
--- a/include/linux/thermal.h
+++ b/include/linux/thermal.h
@@ -86,6 +86,23 @@ struct thermal_trip {
#define THERMAL_TRIP_PRIV_TO_INT(_val_) (uintptr_t)(_val_)
#define THERMAL_INT_TO_TRIP_PRIV(_val_) (void *)(uintptr_t)(_val_)
+/* Matches the maxItems: 4 constraint on the thermal-hw-bin binding. */
+#define THERMAL_HW_BIN_MAX_LEVELS 4
+
+/**
+ * struct thermal_hw_bin_info - platform-specific hardware binning description
+ * @supported_hw_bin: array of bitfields identifying the running hardware version
+ * @supported_hw_bin_count: number of valid entries in @supported_hw_bin
+ *
+ * Filled by platform code that decodes SoC revision / fuse information.
+ * Shared with the thermal OF parser to filter trip points and select
+ * per-bin temperatures via the thermal-hw-bin / temperature-bin DT properties.
+ */
+struct thermal_hw_bin_info {
+ const u32 *supported_hw_bin;
+ unsigned int supported_hw_bin_count;
+};
+
struct cooling_spec {
unsigned long upper; /* Highest cooling state */
unsigned long lower; /* Lowest cooling state */
@@ -216,6 +233,12 @@ devm_thermal_of_child_cooling_device_register(struct device *dev,
struct device_node *np,
const char *type, void *devdata,
const struct thermal_cooling_device_ops *ops);
+
+struct thermal_zone_device *
+devm_thermal_of_zone_register_with_bin(struct device *dev, int id, void *data,
+ const struct thermal_zone_device_ops *ops,
+ const struct thermal_hw_bin_info *hw_bin_info);
+
#else
static inline
@@ -254,6 +277,15 @@ devm_thermal_of_child_cooling_device_register(struct device *dev,
{
return ERR_PTR(-ENODEV);
}
+
+static inline struct thermal_zone_device *
+devm_thermal_of_zone_register_with_bin(struct device *dev, int id, void *data,
+ const struct thermal_zone_device_ops *ops,
+ const struct thermal_hw_bin_info *hw_bin_info)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
#endif
int for_each_thermal_trip(struct thermal_zone_device *tz,
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens
2026-11-13 8:43 [PATCH v3 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 2/6] thermal: add hardware-binning trip-point filtering support Gaurav Kohli
@ 2026-11-13 8:43 ` Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering Gaurav Kohli
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Gaurav Kohli @ 2026-11-13 8:43 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Amit Kucheria, Thara Gopinath,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: linux-pm, devicetree, linux-kernel, linux-doc, linux-arm-msm,
Gaurav Kohli, Amit Kucheria, Krzysztof Kozlowski
From: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
QCM6490 uses a tsens-v2 IP block and supports hardware-binned critical
trip points. Add its compatible to the tsens-v2 enum.
SoCs that support hardware-bin critical trips read their manufacturing
bin from qfprom fuse cells rather than the usual calibration cells: a
single "i-temp" bit on hamoa/purwa-class parts, and a "jtag-id"/
"feat-id" pair on sc7280/qcm6490. Extend nvmem-cell-names with two
oneOf branches so these fuse-cell names validate.
Signed-off-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
index c78074fbcecf..e72d6b8b657b 100644
--- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
+++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
@@ -66,6 +66,7 @@ properties:
- qcom,msm8996-tsens
- qcom,msm8998-tsens
- qcom,qcm2290-tsens
+ - qcom,qcm6490-tsens
- qcom,qcs8300-tsens
- qcom,qcs615-tsens
- qcom,sa8255p-tsens
@@ -245,6 +246,13 @@ properties:
- pattern: '^tsens_sens[0-9]+_off$'
- pattern: '^tsens_sens[0-9]+_off$'
- pattern: '^tsens_sens[0-9]+_off$'
+ # hardware-binning fuse cells: a single i-temp bit
+ - items:
+ - const: i-temp
+ # hardware-binning fuse cells: jtag-id and feature-id
+ - items:
+ - const: jtag-id
+ - const: feat-id
"#qcom,sensors":
description:
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering
2026-11-13 8:43 [PATCH v3 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
` (2 preceding siblings ...)
2026-11-13 8:43 ` [PATCH v3 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens Gaurav Kohli
@ 2026-11-13 8:43 ` Gaurav Kohli
2026-11-13 8:55 ` sashiko-bot
2026-11-13 8:43 ` [PATCH v3 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support Gaurav Kohli
5 siblings, 1 reply; 11+ messages in thread
From: Gaurav Kohli @ 2026-11-13 8:43 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Amit Kucheria, Thara Gopinath,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: linux-pm, devicetree, linux-kernel, linux-doc, linux-arm-msm,
Gaurav Kohli, Amit Kucheria
From: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Some Qualcomm SoCs are binned at manufacturing time and expose the bin
in qfprom fuses. The thermal OF parser can filter trip points by
hardware version; wire tsens up to feed it.
Add the per-SoC plumbing:
- struct tsens_hw_bin_desc names the nvmem cells to read and supplies a
compute_hw_version() callback that turns the raw fuse values into the
hw-version bitfields consumed by the thermal OF parser. A SoC opts in
by pointing tsens_plat_data.hw_bin_desc at one of these descriptors;
leaving it NULL disables the feature for that SoC.
- tsens_populate_hw_bin_info() reads the named cells at probe time,
invokes the callback, and stashes the result in priv->hw_bin_info.
When hw_bin_desc is NULL it is a no-op, so SoCs that do not bin are
unaffected.
- tsens_register() now uses devm_thermal_of_zone_register_with_bin(),
passing &priv->hw_bin_info only when a bin was actually read.
Two descriptors and their SoC wiring are provided:
- tsens_hw_bin_desc_i_temp reads a single i-temp bit and reports it
directly as the hw-bin value; used by data_x1e80100.
- tsens_hw_bin_desc_qcm6490 reads the jtag-id and feat-id fuses and maps
the known qcm6490/sc7280 ID pairs to a synthesized hw-bin value (0x1
for the standard profile, 0x2 for the elevated profile); used by
data_sc7280, which serves both the sc7280 and qcm6490 compatibles.
Register the qcm6490, sc7280 and x1e80100 compatibles in tsens_table.
Signed-off-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Co-developed-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
drivers/thermal/qcom/tsens-v2.c | 16 ++++
drivers/thermal/qcom/tsens.c | 190 +++++++++++++++++++++++++++++++++++++++-
drivers/thermal/qcom/tsens.h | 29 ++++++
3 files changed, 232 insertions(+), 3 deletions(-)
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
index 843dd29fad2e..3a56c1496b84 100644
--- a/drivers/thermal/qcom/tsens-v2.c
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -271,6 +271,22 @@ struct tsens_plat_data data_tsens_v2 = {
.fields = tsens_v2_regfields,
};
+/* x1e80100: tsens-v2 with i-temp hardware-bin trip filtering */
+const struct tsens_plat_data data_x1e80100 = {
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+ .hw_bin_desc = &tsens_hw_bin_desc_i_temp,
+};
+
+/* sc7280/qcm6490: tsens-v2 with jtag-id/feat-id hardware-bin trip filtering */
+const struct tsens_plat_data data_sc7280 = {
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+ .hw_bin_desc = &tsens_hw_bin_desc_qcm6490,
+};
+
struct tsens_plat_data data_ipq8074 = {
.ops = &ops_generic_v2,
.feat = &ipq8074_feat,
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index bca3fb1d2bf9..db4e02529c91 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -71,6 +71,167 @@ char *qfprom_read(struct device *dev, const char *cname)
return ret;
}
+/*
+ * Hardware-bin fuse support
+ *
+ * Each SoC that needs trip-point filtering based on fuse values provides a
+ * struct tsens_hw_bin_desc in its tsens_plat_data. The descriptor names the
+ * nvmem cells to read and supplies a compute_hw_version() callback that
+ * converts the raw fuse values into the hw-version bitfields consumed by the
+ * thermal OF parser.
+ */
+
+static int tsens_compute_hw_version_qcm6490(struct device *dev,
+ const u32 *fuse, unsigned int nfuse,
+ u32 *hw, unsigned int *count)
+{
+ u32 tsens_jtag;
+ u8 tsens_feat_id;
+ bool elevate_trip;
+
+ /* Hardware-bin values for thermal-hw-bin matching in DT. */
+ const u32 hw_bin_std = 0x1;
+ const u32 hw_bin_elevated = 0x2;
+
+ /* Higher thermal profile identification for qcm6490/sc7280 bins. */
+ const u32 tsens_chip_id0 = 0x197;
+ const u32 tsens_chip_id1 = 0x198;
+ const u32 tsens_chip_id2 = 0x20e;
+ const u32 tsens_chip_id3 = 0x20f;
+ const u8 tsens_feat_id2 = 0x2;
+ const u8 tsens_feat_id3 = 0x3;
+ const u8 tsens_feat_id4 = 0x4;
+
+ if (nfuse < 2)
+ return -EINVAL;
+
+ tsens_jtag = fuse[0] & GENMASK(19, 0);
+ tsens_feat_id = fuse[1] & GENMASK(7, 0);
+
+ elevate_trip =
+ (tsens_jtag == tsens_chip_id0 && tsens_feat_id == tsens_feat_id3) ||
+ (tsens_jtag == tsens_chip_id1 && tsens_feat_id == tsens_feat_id4) ||
+ (tsens_jtag == tsens_chip_id2 && tsens_feat_id == tsens_feat_id3) ||
+ (tsens_jtag == tsens_chip_id3 && tsens_feat_id == tsens_feat_id2);
+
+ hw[0] = elevate_trip ? hw_bin_elevated : hw_bin_std;
+ *count = 1;
+
+ dev_dbg(dev,
+ "qcm6490 hw-bin from jtag-id=0x%x feat-id=0x%x => 0x%x\n",
+ tsens_jtag, tsens_feat_id, hw[0]);
+
+ return 0;
+}
+
+static int tsens_compute_hw_version_i_temp(struct device *dev,
+ const u32 *fuse, unsigned int nfuse,
+ u32 *hw, unsigned int *count)
+{
+ if (nfuse < 1)
+ return -EINVAL;
+
+ /* Single bit: set bit 1 for high-temp bin, bit 0 for standard bin */
+ hw[0] = (fuse[0] & BIT(0)) ? 0x2u : 0x1u;
+ *count = 1;
+
+ dev_dbg(dev, "i-temp hw-bin from i-temp=0x%x => 0x%x\n",
+ fuse[0], hw[0]);
+
+ return 0;
+}
+
+static const char * const tsens_cells_qcm6490[] = { "jtag-id", "feat-id" };
+static const char * const tsens_cells_i_temp[] = { "i-temp" };
+
+const struct tsens_hw_bin_desc tsens_hw_bin_desc_qcm6490 = {
+ .cell_names = tsens_cells_qcm6490,
+ .ncells = ARRAY_SIZE(tsens_cells_qcm6490),
+ .compute_hw_version = tsens_compute_hw_version_qcm6490,
+};
+
+const struct tsens_hw_bin_desc tsens_hw_bin_desc_i_temp = {
+ .cell_names = tsens_cells_i_temp,
+ .ncells = ARRAY_SIZE(tsens_cells_i_temp),
+ .compute_hw_version = tsens_compute_hw_version_i_temp,
+};
+
+/**
+ * tsens_populate_hw_bin_info - read fuse cells and populate priv->hw_bin_info
+ * @priv: tsens private data
+ * @data: platform data containing the optional hw_bin_desc pointer
+ *
+ * If data->hw_bin_desc is NULL, this is a no-op and returns 0. Otherwise,
+ * reads the nvmem cells named in hw_bin_desc->cell_names, calls the
+ * compute_hw_version() callback, and stores the result in priv->hw_bin_info
+ * for later use by tsens_register().
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int tsens_populate_hw_bin_info(struct tsens_priv *priv,
+ const struct tsens_plat_data *data)
+{
+ const struct tsens_hw_bin_desc *desc;
+ u32 fuse_vals[TSENS_HW_BIN_MAX_CELLS];
+ u32 hw_versions[THERMAL_HW_BIN_MAX_LEVELS] = { 0 };
+ unsigned int hw_count = 0;
+ int i, ret;
+
+ if (!data->hw_bin_desc)
+ return 0;
+
+ desc = data->hw_bin_desc;
+
+ if (!desc->ncells || desc->ncells > TSENS_HW_BIN_MAX_CELLS) {
+ dev_err(priv->dev, "hw_bin_desc ncells %u out of range\n",
+ desc->ncells);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < (int)desc->ncells; i++) {
+ ret = nvmem_cell_read_variable_le_u32(priv->dev,
+ desc->cell_names[i],
+ &fuse_vals[i]);
+ if (ret < 0) {
+ dev_err(priv->dev,
+ "failed to read nvmem cell '%s': %d\n",
+ desc->cell_names[i], ret);
+ return ret;
+ }
+ }
+
+ ret = desc->compute_hw_version(priv->dev, fuse_vals, desc->ncells,
+ hw_versions, &hw_count);
+ if (ret) {
+ dev_err(priv->dev, "compute_hw_version failed: %d\n", ret);
+ return ret;
+ }
+
+ if (!hw_count || hw_count > THERMAL_HW_BIN_MAX_LEVELS) {
+ dev_err(priv->dev,
+ "compute_hw_version returned invalid hw_count %u\n",
+ hw_count);
+ return -EINVAL;
+ }
+
+ /*
+ * Store a copy of the hw-version array in priv->hw_bin_info so
+ * tsens_register() can pass it to the thermal OF parser while trips are
+ * parsed.
+ *
+ * Use devm_kmemdup so the copy is freed when the device is unbound.
+ */
+ priv->hw_bin_info.supported_hw_bin = devm_kmemdup(priv->dev, hw_versions,
+ hw_count * sizeof(u32),
+ GFP_KERNEL);
+ if (!priv->hw_bin_info.supported_hw_bin)
+ return -ENOMEM;
+
+ priv->hw_bin_info.supported_hw_bin_count = hw_count;
+
+ return 0;
+}
+
int tsens_read_calibration(struct tsens_priv *priv, int shift, u32 *p1, u32 *p2, bool backup)
{
u32 mode;
@@ -1209,6 +1370,12 @@ static const struct of_device_id tsens_table[] = {
}, {
.compatible = "qcom,msm8996-tsens",
.data = &data_8996,
+ }, {
+ .compatible = "qcom,qcm6490-tsens",
+ .data = &data_sc7280,
+ }, {
+ .compatible = "qcom,sc7280-tsens",
+ .data = &data_sc7280,
}, {
.compatible = "qcom,tsens-v1",
.data = &data_tsens_v1,
@@ -1221,6 +1388,9 @@ static const struct of_device_id tsens_table[] = {
}, {
.compatible = "qcom,sa8255p-tsens",
.data = &data_automotive_v2,
+ }, {
+ .compatible = "qcom,x1e80100-tsens",
+ .data = &data_x1e80100,
},
{}
};
@@ -1330,12 +1500,18 @@ static int tsens_register(struct tsens_priv *priv)
{
int i, ret;
struct thermal_zone_device *tzd;
+ const struct thermal_hw_bin_info *bin_info;
+
+ bin_info = priv->hw_bin_info.supported_hw_bin_count ?
+ &priv->hw_bin_info : NULL;
for (i = 0; i < priv->num_sensors; i++) {
priv->sensor[i].priv = priv;
- tzd = devm_thermal_of_zone_register(priv->dev, priv->sensor[i].hw_id,
- &priv->sensor[i],
- &tsens_of_ops);
+ tzd = devm_thermal_of_zone_register_with_bin(priv->dev,
+ priv->sensor[i].hw_id,
+ &priv->sensor[i],
+ &tsens_of_ops,
+ bin_info);
if (IS_ERR(tzd))
continue;
priv->sensor[i].tzd = tzd;
@@ -1443,6 +1619,14 @@ static int tsens_probe_common(struct platform_device *pdev)
priv->feat = data->feat;
priv->fields = data->fields;
+ ret = tsens_populate_hw_bin_info(priv, data);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ if (ret)
+ dev_warn(&pdev->dev,
+ "thermal-bin failed to read FUSE data (%d): trips with temperature-bin will use first entry\n",
+ ret);
+
platform_set_drvdata(pdev, priv);
device_init_wakeup(dev, !data->no_irq_wake);
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
index a35b15312961..24489542b8a2 100644
--- a/drivers/thermal/qcom/tsens.h
+++ b/drivers/thermal/qcom/tsens.h
@@ -40,6 +40,29 @@ enum tsens_ver {
VER_2_X_NO_RPM,
};
+/**
+ * struct tsens_hw_bin_desc - per-SoC descriptor for hardware-bin fuse reading
+ * @cell_names: NULL-terminated array of nvmem cell names to read
+ * @ncells: number of entries in @cell_names
+ * @compute_hw_version: callback that converts raw fuse values to hw-version
+ * bitfields for thermal OF trip filtering
+ *
+ * Embed a pointer to this struct in tsens_plat_data.hw_bin_desc to enable
+ * hardware-bin trip filtering for a given SoC. Set to NULL to disable.
+ */
+struct tsens_hw_bin_desc {
+ const char * const *cell_names;
+ unsigned int ncells;
+ int (*compute_hw_version)(struct device *dev,
+ const u32 *fuse, unsigned int nfuse,
+ u32 *hw, unsigned int *count);
+};
+
+#define TSENS_HW_BIN_MAX_CELLS 8
+
+extern const struct tsens_hw_bin_desc tsens_hw_bin_desc_qcm6490;
+extern const struct tsens_hw_bin_desc tsens_hw_bin_desc_i_temp;
+
enum tsens_irq_type {
LOWER,
UPPER,
@@ -533,6 +556,7 @@ struct tsens_features {
* @feat: features of the IP
* @fields: bitfield locations
* @no_irq_wake: if set, TSENS interrupts will not be configured as wakeup sources
+ * @hw_bin_desc: optional hardware-bin fuse descriptor for trip filtering
*/
struct tsens_plat_data {
const u32 num_sensors;
@@ -541,6 +565,7 @@ struct tsens_plat_data {
struct tsens_features *feat;
const struct reg_field *fields;
bool no_irq_wake;
+ const struct tsens_hw_bin_desc *hw_bin_desc;
};
/**
@@ -568,6 +593,7 @@ struct tsens_context {
* @feat: features of the IP
* @fields: bitfield locations
* @ops: pointer to list of callbacks supported by this device
+ * @hw_bin_info: hardware binning info read from SoC fuse (populated at probe)
* @debug_root: pointer to debugfs dentry for all tsens
* @debug: pointer to debugfs dentry for tsens controller
* @uplow_irq: IRQ number for uplow (upper/lower) threshold interrupts
@@ -591,6 +617,8 @@ struct tsens_priv {
const struct reg_field *fields;
const struct tsens_ops *ops;
+ struct thermal_hw_bin_info hw_bin_info;
+
struct dentry *debug_root;
struct dentry *debug;
@@ -677,6 +705,7 @@ extern const struct tsens_plat_data data_ipq5018;
/* TSENS v2 targets */
extern struct tsens_plat_data data_8996, data_ipq8074, data_tsens_v2;
extern const struct tsens_plat_data data_ipq5332, data_ipq5424;
+extern const struct tsens_plat_data data_x1e80100, data_sc7280;
/* TSENS automotive targets */
extern struct tsens_plat_data data_automotive_v2;
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips
2026-11-13 8:43 [PATCH v3 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
` (3 preceding siblings ...)
2026-11-13 8:43 ` [PATCH v3 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering Gaurav Kohli
@ 2026-11-13 8:43 ` Gaurav Kohli
2026-11-13 8:59 ` sashiko-bot
2026-11-13 8:43 ` [PATCH v3 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support Gaurav Kohli
5 siblings, 1 reply; 11+ messages in thread
From: Gaurav Kohli @ 2026-11-13 8:43 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Amit Kucheria, Thara Gopinath,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: linux-pm, devicetree, linux-kernel, linux-doc, linux-arm-msm,
Gaurav Kohli, Amit Kucheria
From: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
kodiak-based SoCs are binned at manufacturing time, with the bin
computed from the jtag-id and feat-id qfprom fuses. The tsens driver
now reads these and feeds the resulting bin to the thermal core, which
selects per-bin trip temperatures.
Add the jtag-id and feat-id nvmem cells to the tsens nodes and convert
the passive mitigation trip points from a fixed 95C to per-bin values
(95C for bin 0x1, 105C for bin 0x2) using the new
temperature-bin/thermal-hw-bin properties.
Signed-off-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Co-developed-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/kodiak.dtsi | 58 +++++++++++++++++++++++++++---------
1 file changed, 44 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
index 10e9a5279ed6..79553bf43a7a 100644
--- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
+++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
@@ -1028,6 +1028,18 @@ gpu_speed_bin: gpu-speed-bin@1e9 {
reg = <0x1e9 0x2>;
bits = <5 8>;
};
+
+ jtag_id: jtag-id@180 {
+ reg = <0x180 0x4>;
+ /* bits = <offset width>: 20 bits at offset 0 */
+ bits = <0 20>;
+ };
+
+ feat_id: feat-id@180 {
+ reg = <0x180 0x4>;
+ /* bits = <offset width>: 8 bits at offset 20 */
+ bits = <20 8>;
+ };
};
sdhc_1: mmc@7c4000 {
@@ -5810,6 +5822,8 @@ tsens0: thermal-sensor@c263000 {
compatible = "qcom,sc7280-tsens","qcom,tsens-v2";
reg = <0 0x0c263000 0 0x1ff>, /* TM */
<0 0x0c222000 0 0x1ff>; /* SROT */
+ nvmem-cells = <&jtag_id>, <&feat_id>;
+ nvmem-cell-names = "jtag-id", "feat-id";
#qcom,sensors = <15>;
interrupts = <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>;
@@ -5821,6 +5835,8 @@ tsens1: thermal-sensor@c265000 {
compatible = "qcom,sc7280-tsens","qcom,tsens-v2";
reg = <0 0x0c265000 0 0x1ff>, /* TM */
<0 0x0c223000 0 0x1ff>; /* SROT */
+ nvmem-cells = <&jtag_id>, <&feat_id>;
+ nvmem-cell-names = "jtag-id", "feat-id";
#qcom,sensors = <12>;
interrupts = <GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH>;
@@ -7164,7 +7180,8 @@ cpu0_alert0: trip-point0 {
};
cpu0_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7207,7 +7224,8 @@ cpu1_alert0: trip-point0 {
};
cpu1_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7250,7 +7268,8 @@ cpu2_alert0: trip-point0 {
};
cpu2_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7293,7 +7312,8 @@ cpu3_alert0: trip-point0 {
};
cpu3_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7336,7 +7356,8 @@ cpu4_alert0: trip-point0 {
};
cpu4_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7379,7 +7400,8 @@ cpu5_alert0: trip-point0 {
};
cpu5_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7422,7 +7444,8 @@ cpu6_alert0: trip-point0 {
};
cpu6_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7465,7 +7488,8 @@ cpu7_alert0: trip-point0 {
};
cpu7_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7508,7 +7532,8 @@ cpu8_alert0: trip-point0 {
};
cpu8_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7551,7 +7576,8 @@ cpu9_alert0: trip-point0 {
};
cpu9_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7594,7 +7620,8 @@ cpu10_alert0: trip-point0 {
};
cpu10_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7637,7 +7664,8 @@ cpu11_alert0: trip-point0 {
};
cpu11_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7752,7 +7780,8 @@ gpuss0-thermal {
trips {
gpuss0_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7779,7 +7808,8 @@ gpuss1-thermal {
trips {
gpuss1_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support
2026-11-13 8:43 [PATCH v3 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
` (4 preceding siblings ...)
2026-11-13 8:43 ` [PATCH v3 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips Gaurav Kohli
@ 2026-11-13 8:43 ` Gaurav Kohli
2026-11-13 9:00 ` sashiko-bot
5 siblings, 1 reply; 11+ messages in thread
From: Gaurav Kohli @ 2026-11-13 8:43 UTC (permalink / raw)
To: Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Amit Kucheria, Thara Gopinath,
Bjorn Andersson, Konrad Dybcio, Abel Vesa
Cc: linux-pm, devicetree, linux-kernel, linux-doc, linux-arm-msm,
Gaurav Kohli, Amit Kucheria
From: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
hamoa-based SoCs expose their manufacturing bin in a single i-temp bit
of a qfprom fuse. Add the i-temp nvmem cell to the qfprom node
and wire the tsens controllers to it so the tsens driver can feed
the bin to the thermal core.
Use that bin for the GPU passive trip points so industrial-temp
parts keep the higher 105C threshold without a board-specific
override.
Signed-off-by: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
Co-developed-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts | 66 ------------------------------
arch/arm64/boot/dts/qcom/hamoa.dtsi | 42 +++++++++++++++----
2 files changed, 34 insertions(+), 74 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts
index 484cb9ee18a2..1124da9817f4 100644
--- a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts
+++ b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts
@@ -1478,72 +1478,6 @@ &usb_mp_hsphy1 {
phys = <&eusb6_repeater>;
};
-&thermal_zones {
- gpuss-0-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-
- gpuss-1-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-
- gpuss-2-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-
- gpuss-3-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-
- gpuss-4-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-
- gpuss-5-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-
- gpuss-6-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-
- gpuss-7-thermal {
- trips {
- trip-point0 {
- temperature = <105000>;
- };
- };
- };
-};
-
&nsp0_alert0 {
temperature = <105000>;
};
diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi
index 443c1a5c212d..e25729ff23c8 100644
--- a/arch/arm64/boot/dts/qcom/hamoa.dtsi
+++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi
@@ -2767,6 +2767,9 @@ tsens0: thermal-sensor@c271000 {
interrupt-names = "uplow",
"critical";
+ nvmem-cells = <&i_temp>;
+ nvmem-cell-names = "i-temp";
+
#qcom,sensors = <16>;
#thermal-sensor-cells = <1>;
@@ -2782,6 +2785,9 @@ tsens1: thermal-sensor@c272000 {
interrupt-names = "uplow",
"critical";
+ nvmem-cells = <&i_temp>;
+ nvmem-cell-names = "i-temp";
+
#qcom,sensors = <16>;
#thermal-sensor-cells = <1>;
@@ -2797,6 +2803,9 @@ tsens2: thermal-sensor@c273000 {
interrupt-names = "uplow",
"critical";
+ nvmem-cells = <&i_temp>;
+ nvmem-cell-names = "i-temp";
+
#qcom,sensors = <16>;
#thermal-sensor-cells = <1>;
@@ -2812,6 +2821,9 @@ tsens3: thermal-sensor@c274000 {
interrupt-names = "uplow",
"critical";
+ nvmem-cells = <&i_temp>;
+ nvmem-cell-names = "i-temp";
+
#qcom,sensors = <16>;
#thermal-sensor-cells = <1>;
@@ -8829,6 +8841,12 @@ gpu_speed_bin: gpu-speed-bin@119 {
reg = <0x119 0x2>;
bits = <7 8>;
};
+
+ i_temp: i-temp@180 {
+ reg = <0x180 0x4>;
+ /* bits = <offset width>: 1 bit at offset 0 */
+ bits = <0 1>;
+ };
};
pmu@24091000 {
@@ -9751,7 +9769,8 @@ map0 {
trips {
gpuss0_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9778,7 +9797,8 @@ map0 {
trips {
gpuss1_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9805,7 +9825,8 @@ map0 {
trips {
gpuss2_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9832,7 +9853,8 @@ map0 {
trips {
gpuss3_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9859,7 +9881,8 @@ map0 {
trips {
gpuss4_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9886,7 +9909,8 @@ map0 {
trips {
gpuss5_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9913,7 +9937,8 @@ map0 {
trips {
gpuss6_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9940,7 +9965,8 @@ map0 {
trips {
gpuss7_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering
2026-11-13 8:43 ` [PATCH v3 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering Gaurav Kohli
@ 2026-11-13 8:55 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-13 8:55 UTC (permalink / raw)
To: Gaurav Kohli; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] thermal: qcom: tsens: broken error handling in
tsens_populate_hw_bin_info()
- [Low] thermal: qcom: tsens: inaccurate cell_names kernel-doc in
tsens_hw_bin_desc
- [Low] thermal: qcom: tsens: inaccurate i-temp description in commit
message
--
commit 65356a4ab856b03d856c9e9331810962e03057df
Author: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
thermal: qcom: tsens: add hardware-bin trip-point filtering
This commit reads hardware-binning qfprom fuse cells at probe time and passes
the computed hardware version bitfields to the thermal OF parser for
qcm6490, sc7280, and x1e80100 TSENS controllers.
> - tsens_hw_bin_desc_i_temp reads a single i-temp bit and reports it
> directly as the hw-bin value; used by data_x1e80100.
[Severity: Low]
Is this description of tsens_hw_bin_desc_i_temp accurate?
tsens_compute_hw_version_i_temp() maps bit 0 of the fuse to the bitmask
values 0x1u (standard bin) or 0x2u (high-temp bin) rather than reporting
the raw bit value directly.
> diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
> index bca3fb1d2bf99..db4e02529c91d 100644
> --- a/drivers/thermal/qcom/tsens.c
> +++ b/drivers/thermal/qcom/tsens.c
[ ... ]
> +static const char * const tsens_cells_qcm6490[] = { "jtag-id", "feat-id" };
> +static const char * const tsens_cells_i_temp[] = { "i-temp" };
> +
> +const struct tsens_hw_bin_desc tsens_hw_bin_desc_qcm6490 = {
> + .cell_names = tsens_cells_qcm6490,
> + .ncells = ARRAY_SIZE(tsens_cells_qcm6490),
> + .compute_hw_version = tsens_compute_hw_version_qcm6490,
> +};
> +
> +const struct tsens_hw_bin_desc tsens_hw_bin_desc_i_temp = {
> + .cell_names = tsens_cells_i_temp,
> + .ncells = ARRAY_SIZE(tsens_cells_i_temp),
> + .compute_hw_version = tsens_compute_hw_version_i_temp,
> +};
> +
> +/**
> + * tsens_populate_hw_bin_info - read fuse cells and populate priv->hw_bin_info
> + * @priv: tsens private data
> + * @data: platform data containing the optional hw_bin_desc pointer
> + *
> + * If data->hw_bin_desc is NULL, this is a no-op and returns 0. Otherwise,
> + * reads the nvmem cells named in hw_bin_desc->cell_names, calls the
> + * compute_hw_version() callback, and stores the result in priv->hw_bin_info
> + * for later use by tsens_register().
> + *
> + * Return: 0 on success, negative errno on failure.
> + */
> +static int tsens_populate_hw_bin_info(struct tsens_priv *priv,
> + const struct tsens_plat_data *data)
> +{
> + const struct tsens_hw_bin_desc *desc;
> + u32 fuse_vals[TSENS_HW_BIN_MAX_CELLS];
> + u32 hw_versions[THERMAL_HW_BIN_MAX_LEVELS] = { 0 };
> + unsigned int hw_count = 0;
> + int i, ret;
> +
> + if (!data->hw_bin_desc)
> + return 0;
> +
> + desc = data->hw_bin_desc;
> +
> + if (!desc->ncells || desc->ncells > TSENS_HW_BIN_MAX_CELLS) {
> + dev_err(priv->dev, "hw_bin_desc ncells %u out of range\n",
> + desc->ncells);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < (int)desc->ncells; i++) {
> + ret = nvmem_cell_read_variable_le_u32(priv->dev,
> + desc->cell_names[i],
> + &fuse_vals[i]);
> + if (ret < 0) {
> + dev_err(priv->dev,
> + "failed to read nvmem cell '%s': %d\n",
> + desc->cell_names[i], ret);
> + return ret;
> + }
> + }
> +
> + ret = desc->compute_hw_version(priv->dev, fuse_vals, desc->ncells,
> + hw_versions, &hw_count);
> + if (ret) {
> + dev_err(priv->dev, "compute_hw_version failed: %d\n", ret);
> + return ret;
> + }
> +
> + if (!hw_count || hw_count > THERMAL_HW_BIN_MAX_LEVELS) {
> + dev_err(priv->dev,
> + "compute_hw_version returned invalid hw_count %u\n",
> + hw_count);
> + return -EINVAL;
> + }
> +
> + /*
> + * Store a copy of the hw-version array in priv->hw_bin_info so
> + * tsens_register() can pass it to the thermal OF parser while trips are
> + * parsed.
> + *
> + * Use devm_kmemdup so the copy is freed when the device is unbound.
> + */
> + priv->hw_bin_info.supported_hw_bin = devm_kmemdup(priv->dev, hw_versions,
> + hw_count * sizeof(u32),
> + GFP_KERNEL);
> + if (!priv->hw_bin_info.supported_hw_bin)
> + return -ENOMEM;
> +
> + priv->hw_bin_info.supported_hw_bin_count = hw_count;
> +
> + return 0;
> +}
[ ... ]
> @@ -1443,6 +1619,14 @@ static int tsens_probe_common(struct platform_device *pdev)
> priv->feat = data->feat;
> priv->fields = data->fields;
>
> + ret = tsens_populate_hw_bin_info(priv, data);
> + if (ret == -EPROBE_DEFER)
> + return ret;
> + if (ret)
> + dev_warn(&pdev->dev,
> + "thermal-bin failed to read FUSE data (%d): trips with temperature-bin will use first entry\n",
> + ret);
> +
> platform_set_drvdata(pdev, priv);
>
> device_init_wakeup(dev, !data->no_irq_wake);
[Severity: Medium]
Does tsens_populate_hw_bin_info() log a dev_err() on -EPROBE_DEFER before
tsens_probe_common() checks for probe deferral?
When TSENS probes before the qfprom nvmem provider,
nvmem_cell_read_variable_le_u32() returns -EPROBE_DEFER and
tsens_populate_hw_bin_info() emits an error message on every deferred
probe attempt. When booting with existing sc7280 or x1e80100 DTBs that do
not define the optional nvmem-cells for hardware binning,
nvmem_cell_read_variable_le_u32() returns -ENOENT, causing both the
dev_err() in tsens_populate_hw_bin_info() and the dev_warn() in
tsens_probe_common() (which also passes &pdev->dev instead of dev) to be
printed. In addition, if devm_kmemdup() returns -ENOMEM,
tsens_probe_common() treats it as a non-fatal fuse read warning instead of
failing probe.
> diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
> index a35b15312961f..24489542b8a21 100644
> --- a/drivers/thermal/qcom/tsens.h
> +++ b/drivers/thermal/qcom/tsens.h
> @@ -40,6 +40,29 @@ enum tsens_ver {
> VER_2_X_NO_RPM,
> };
>
> +/**
> + * struct tsens_hw_bin_desc - per-SoC descriptor for hardware-bin fuse reading
> + * @cell_names: NULL-terminated array of nvmem cell names to read
> + * @ncells: number of entries in @cell_names
> + * @compute_hw_version: callback that converts raw fuse values to hw-version
> + * bitfields for thermal OF trip filtering
> + *
> + * Embed a pointer to this struct in tsens_plat_data.hw_bin_desc to enable
> + * hardware-bin trip filtering for a given SoC. Set to NULL to disable.
> + */
> +struct tsens_hw_bin_desc {
> + const char * const *cell_names;
> + unsigned int ncells;
> + int (*compute_hw_version)(struct device *dev,
> + const u32 *fuse, unsigned int nfuse,
> + u32 *hw, unsigned int *count);
> +};
[Severity: Low]
Should the kernel-doc for @cell_names drop "NULL-terminated"?
tsens_cells_qcm6490 and tsens_cells_i_temp are not NULL-terminated and
size @ncells with ARRAY_SIZE(), while tsens_populate_hw_bin_info()
iterates from 0 to desc->ncells - 1. If a future descriptor follows the
comment and NULL-terminates cell_names while using ARRAY_SIZE() for
ncells, the NULL sentinel will be passed as cell_id to
nvmem_cell_read_variable_le_u32().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-thermal_bin-v3-0-878df6eef8bc@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips
2026-11-13 8:43 ` [PATCH v3 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips Gaurav Kohli
@ 2026-11-13 8:59 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-13 8:59 UTC (permalink / raw)
To: Gaurav Kohli; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] arm64: dts: qcom: kodiak: duplicate @180 unit-address in qfprom
--
commit e6d4eae865bd91c0bd0f76066337d957b0861f51
Author: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
arm64: dts: qcom: kodiak: use thermal hw-bin trips
This commit adds jtag-id and feat-id nvmem cells to the tsens nodes in
kodiak.dtsi and converts passive mitigation trip points to per-bin
temperature values.
> diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
> index 10e9a5279ed60..79553bf43a7a2 100644
> --- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
> +++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
> @@ -1028,6 +1028,18 @@ gpu_speed_bin: gpu-speed-bin@1e9 {
> reg = <0x1e9 0x2>;
> bits = <5 8>;
> };
> +
> + jtag_id: jtag-id@180 {
> + reg = <0x180 0x4>;
> + /* bits = <offset width>: 20 bits at offset 0 */
> + bits = <0 20>;
> + };
> +
> + feat_id: feat-id@180 {
> + reg = <0x180 0x4>;
> + /* bits = <offset width>: 8 bits at offset 20 */
> + bits = <20 8>;
> + };
> };
[Severity: Low]
Does giving both jtag-id@180 and feat-id@180 the same @180 unit-address
trigger a dtc unique_unit_address warning when building DTBs with W=1?
qfprom_fixup_dt_cell_info() aligns byte-addressed QFPROM cells to 4-byte
words at runtime, so feat-id can be described at its starting byte offset
(feat-id@182 with reg = <0x182 0x2> and bits = <4 8>, and jtag-id@180 with
reg = <0x180 0x3>), placed before gpu-speed-bin@1e9 in ascending
unit-address order.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-thermal_bin-v3-0-878df6eef8bc@oss.qualcomm.com?part=5
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support
2026-11-13 8:43 ` [PATCH v3 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support Gaurav Kohli
@ 2026-11-13 9:00 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-13 9:00 UTC (permalink / raw)
To: Gaurav Kohli; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] arm64: dts: qcom: hamoa: leftover temperature overrides
shadow temperature-bin
--
commit 35a11d22a7dfa97cb85bfddd513a803ab8607fca
Author: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
arm64: dts: qcom: hamoa: add thermal hw-bin support
This commit adds the i-temp nvmem cell to hamoa.dtsi, wires the tsens
controllers to it, and switches the GPU passive trip points to use
temperature-bin and thermal-hw-bin instead of board-specific overrides.
> diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi
> index 443c1a5c212d9..e25729ff23c87 100644
> --- a/arch/arm64/boot/dts/qcom/hamoa.dtsi
> +++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi
[ ... ]
> @@ -9751,7 +9769,8 @@ map0 {
>
> trips {
> gpuss0_alert0: trip-point0 {
> - temperature = <95000>;
> + temperature-bin = <95000>, <105000>;
> + thermal-hw-bin = <0x1>, <0x2>;
> hysteresis = <1000>;
> type = "passive";
> };
[Severity: Medium]
Should the GPU trip-point0 temperature overrides also be removed from
hamoa-advantech-som-db5830.dts, hamoa-som-db5830.dts, and
purwa-iot-evk.dts?
Those boards include hamoa.dtsi and still set temperature = <105000> on
their GPU trip-point0 nodes. Because temperature and temperature-bin are
distinct properties, the compiled DTBs retain both on the same trip node,
which violates the oneOf schema constraint in thermal-zones.yaml and causes
thermal_of_trip_read_temperature() to read the scalar temperature property
first, bypassing temperature-bin selection on standard-bin parts.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-thermal_bin-v3-0-878df6eef8bc@oss.qualcomm.com?part=6
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 2/6] thermal: add hardware-binning trip-point filtering support
2026-11-13 8:43 ` [PATCH v3 2/6] thermal: add hardware-binning trip-point filtering support Gaurav Kohli
@ 2026-11-13 9:01 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-11-13 9:01 UTC (permalink / raw)
To: Gaurav Kohli; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Low] thermal: dead code and masked errors in
thermal_of_populate_trip() bin parsing
- [Low] thermal: duplicated devres logic in
devm_thermal_of_zone_register_with_bin()
--
commit 587b57fa5fb124fbca43f4241cacd250c0901807
Author: Amit Kucheria <amit.kucheria@oss.qualcomm.com>
thermal: add hardware-binning trip-point filtering support
This commit adds Device Tree trip-point filtering and per-bin temperature
selection via the thermal-hw-bin and temperature-bin properties, along with
devm_thermal_of_zone_register_with_bin() for platform drivers to supply
decoded fuse masks at zone registration time.
> diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c
> index 9e96fb1b033e9..57500e565560c 100644
> --- a/drivers/thermal/thermal_of.c
> +++ b/drivers/thermal/thermal_of.c
> @@ -57,21 +57,168 @@ static int thermal_of_get_trip_type(struct device_node *np,
[ ... ]
> +static bool thermal_of_trip_is_supported(struct device_node *np,
> + const struct thermal_hw_bin_info *hw)
> +{
> + unsigned int levels, ngroups, i;
> + int count;
> +
> + if (!hw)
> + return true;
> +
> + levels = hw->supported_hw_bin_count;
> +
> + count = of_property_count_u32_elems(np, "thermal-hw-bin");
> + if (count == -EINVAL)
> + return true;
> + if (count < 0) {
> + pr_warn_once("%pOF: error reading thermal-hw-bin: %d\n", np, count);
> + return false;
> + }
> + if (count == 0)
> + return true;
> +
> + if (count % levels) {
> + pr_warn("%pOF: thermal-hw-bin length %d not a multiple of supported_hw_bin_count %u; disabling trip\n",
> + np, count, levels);
> + return false;
> + }
> +
> + ngroups = count / levels;
> + for (i = 0; i < ngroups; i++)
> + if (thermal_of_match_hw_bin_group(np, hw, levels, i))
> + return true;
> +
> + return false;
> +}
[ ... ]
> +static int thermal_of_trip_read_temperature(struct device_node *np,
> + const struct thermal_hw_bin_info *hw,
> + s32 *temp)
> +{
[ ... ]
> + levels = hw->supported_hw_bin_count;
> + count_hw = of_property_count_u32_elems(np, "thermal-hw-bin");
> + if (count_hw <= 0 || count_hw % levels)
> + return -EINVAL;
> +
> + ngroups = count_hw / levels;
> + if (count_temp != (int)ngroups)
> + return -EINVAL;
> +
> + for (i = 0; i < ngroups; i++) {
> + if (thermal_of_match_hw_bin_group(np, hw, levels, i)) {
> + u32 utmp;
> +
> + if (of_property_read_u32_index(np, "temperature-bin",
> + i, &utmp))
> + return -EINVAL;
> + *temp = (s32)utmp;
> + return 0;
> + }
> + }
> +
> + pr_warn("%pOF: no thermal-hw-bin sub-group matched for temperature-bin\n", np);
> + return -ENODEV;
> }
>
> static int thermal_of_populate_trip(struct device_node *np,
> - struct thermal_trip *trip)
> + struct thermal_trip *trip,
> + const struct thermal_hw_bin_info *hw)
> {
> u32 hysteresis;
> s32 temperature;
> int ret;
>
> - ret = of_property_read_s32(np, "temperature", &temperature);
> - if (ret < 0) {
> - pr_err("missing temperature property\n");
> - return ret;
> + if (!thermal_of_trip_is_supported(np, hw)) {
> + pr_debug("%pOF: trip skipped: no thermal-hw-bin match\n", np);
> + return -ENODEV;
> }
> +
> + ret = thermal_of_trip_read_temperature(np, hw, &temperature);
[Severity: Low]
Does calling thermal_of_trip_is_supported() before
thermal_of_trip_read_temperature() leave the no-match path in
thermal_of_trip_read_temperature() unreachable and mask malformed
Device Tree properties?
Whenever thermal_of_trip_read_temperature() reaches its sub-group loop
with a non-NULL hw, thermal_of_trip_is_supported() has already verified
that count_hw % levels == 0 and that a sub-group matches, so the second
loop repeats the same property reads and the trailing warning and -ENODEV
return in thermal_of_trip_read_temperature() cannot be reached.
Because thermal_of_trip_is_supported() returns false on count < 0 or
count % levels != 0, a temperature-bin trip with a malformed
thermal-hw-bin property returns -ENODEV and is silently skipped by
thermal_of_trips_init() instead of failing registration with -EINVAL, and
a count_temp != ngroups mismatch is likewise hidden when no sub-group
matches. In addition, of_property_count_u32_elems() returns -EINVAL both
when thermal-hw-bin is absent and when its byte length is not a multiple
of 4, so returning true on count == -EINVAL (without checking
of_property_present()) or count == 0 enables a scalar-temperature trip
with a truncated or empty thermal-hw-bin property on all hardware.
[ ... ]
> @@ -510,6 +679,46 @@ struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, in
> }
> EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register);
>
> +/**
> + * devm_thermal_of_zone_register_with_bin - register a thermal zone with hardware binning info
> + *
> + * Like devm_thermal_of_zone_register(), but passes @hw_bin_info to the trip-point
> + * parser so that trips can be filtered and per-bin temperatures selected at
> + * zone registration time. Use this when fuse values are read before zone
> + * registration (e.g. in a platform driver's probe function).
> + *
> + * @dev: device structure pointer to sensor
> + * @id: the sensor identifier
> + * @data: private data stored in the thermal zone 'devdata' field
> + * @ops: ops structure associated with the sensor
> + * @hw_bin_info: hardware binning info for trip filtering (may be NULL)
> + */
> +struct thermal_zone_device *
> +devm_thermal_of_zone_register_with_bin(struct device *dev, int id, void *data,
> + const struct thermal_zone_device_ops *ops,
> + const struct thermal_hw_bin_info *hw_bin_info)
> +{
> + struct thermal_zone_device **ptr, *tzd;
> +
> + ptr = devres_alloc(devm_thermal_of_zone_release, sizeof(*ptr),
> + GFP_KERNEL);
> + if (!ptr)
> + return ERR_PTR(-ENOMEM);
> +
> + tzd = thermal_of_zone_register_with_bin(dev->of_node, id, data,
> + ops, hw_bin_info);
> + if (IS_ERR(tzd)) {
> + devres_free(ptr);
> + return tzd;
> + }
> +
> + *ptr = tzd;
> + devres_add(dev, ptr);
> +
> + return tzd;
> +}
> +EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register_with_bin);
[Severity: Low]
This isn't a bug, but devm_thermal_of_zone_register_with_bin() duplicates
the devres handling of devm_thermal_of_zone_register(); having
devm_thermal_of_zone_register() call
devm_thermal_of_zone_register_with_bin(dev, sensor_id, data, ops, NULL)
directly would avoid the duplication and the extra static
thermal_of_zone_register() wrapper.
Also, the kernel-doc comment above places the function description before
the @argument list and omits a Return: section, which causes W=2
kernel-doc warnings. In thermal_of_trip_read_temperature(), the missing
hw warning uses pr_warn() on every trip node whereas hardware-binning.rst
states that it warns once. Finally, the new
F: Documentation/driver-api/thermal/hardware-binning.rst line in
MAINTAINERS is already covered by F: Documentation/driver-api/thermal/.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-thermal_bin-v3-0-878df6eef8bc@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-11-13 9:01 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 8:43 [PATCH v3 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 2/6] thermal: add hardware-binning trip-point filtering support Gaurav Kohli
2026-11-13 9:01 ` sashiko-bot
2026-11-13 8:43 ` [PATCH v3 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens Gaurav Kohli
2026-11-13 8:43 ` [PATCH v3 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering Gaurav Kohli
2026-11-13 8:55 ` sashiko-bot
2026-11-13 8:43 ` [PATCH v3 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips Gaurav Kohli
2026-11-13 8:59 ` sashiko-bot
2026-11-13 8:43 ` [PATCH v3 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support Gaurav Kohli
2026-11-13 9:00 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.