* [PATCH v2 0/6] thermal: Add hardware-binned trip point support
@ 2026-10-31 13:22 Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Gaurav Kohli @ 2026-10-31 13:22 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
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 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 | 57 ++++-
.../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, 751 insertions(+), 114 deletions(-)
---
base-commit: 0d9d0dbf2fddcff5859d623e90ca73c4054276e1
change-id: 20260917-thermal_bin-fe7becdf0e17
Best regards,
--
Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties
2026-10-31 13:22 [PATCH v2 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
@ 2026-10-31 13:22 ` Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 2/6] thermal: add hardware-binning trip-point filtering support Gaurav Kohli
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Gaurav Kohli @ 2026-10-31 13:22 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 | 57 +++++++++++++++++++++-
1 file changed, 56 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
index 999ad40a20d5..62590dfc77bf 100644
--- a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
+++ b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml
@@ -152,6 +152,55 @@ 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
+ 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 provides N
+ 32-bit values describing the hardware it is running on.
+ 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 +228,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 [flat|nested] 7+ messages in thread
* [PATCH v2 2/6] thermal: add hardware-binning trip-point filtering support
2026-10-31 13:22 [PATCH v2 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
@ 2026-10-31 13:22 ` Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens Gaurav Kohli
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Gaurav Kohli @ 2026-10-31 13:22 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 6e50747a621c..d99605b8b54c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27343,6 +27343,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 fe47d74b7219..c5ec7d2a38ca 100644
--- a/drivers/thermal/thermal_of.c
+++ b/drivers/thermal/thermal_of.c
@@ -60,18 +60,165 @@ static int thermal_of_get_trip_type(struct device_node *np,
return -ENODEV;
}
+/*
+ * 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
- *
- * 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.
+ * thermal_of_zone_register_with_bin - Register a thermal zone with device node
+ * sensor and optional hardware binning info
*
- * @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 [flat|nested] 7+ messages in thread
* [PATCH v2 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens
2026-10-31 13:22 [PATCH v2 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 2/6] thermal: add hardware-binning trip-point filtering support Gaurav Kohli
@ 2026-10-31 13:22 ` Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering Gaurav Kohli
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Gaurav Kohli @ 2026-10-31 13:22 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>
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>
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 5a8f7673e730..6899f07ec218 100644
--- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
+++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
@@ -65,6 +65,7 @@ properties:
- qcom,msm8996-tsens
- qcom,msm8998-tsens
- qcom,qcm2290-tsens
+ - qcom,qcm6490-tsens
- qcom,qcs8300-tsens
- qcom,qcs615-tsens
- qcom,sa8255p-tsens
@@ -244,6 +245,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 [flat|nested] 7+ messages in thread
* [PATCH v2 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering
2026-10-31 13:22 [PATCH v2 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
` (2 preceding siblings ...)
2026-10-31 13:22 ` [PATCH v2 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens Gaurav Kohli
@ 2026-10-31 13:22 ` Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support Gaurav Kohli
5 siblings, 0 replies; 7+ messages in thread
From: Gaurav Kohli @ 2026-10-31 13:22 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 2ee117aa91ba..8b2a00956a36 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 b5ec70201e2f..9ba6b3c4c133 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;
@@ -1206,6 +1367,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,
@@ -1218,6 +1385,9 @@ static const struct of_device_id tsens_table[] = {
}, {
.compatible = "qcom,sa8255p-tsens",
.data = &data_automotive_v2,
+ }, {
+ .compatible = "qcom,x1e80100-tsens",
+ .data = &data_x1e80100,
},
{}
};
@@ -1327,12 +1497,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;
@@ -1425,6 +1601,14 @@ static int tsens_probe(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 e8376accdff3..1ebdc6935b79 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 [flat|nested] 7+ messages in thread
* [PATCH v2 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips
2026-10-31 13:22 [PATCH v2 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
` (3 preceding siblings ...)
2026-10-31 13:22 ` [PATCH v2 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering Gaurav Kohli
@ 2026-10-31 13:22 ` Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support Gaurav Kohli
5 siblings, 0 replies; 7+ messages in thread
From: Gaurav Kohli @ 2026-10-31 13:22 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 347815e5983e..b984cf47b70e 100644
--- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
+++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
@@ -1019,6 +1019,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 {
@@ -5782,6 +5794,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>;
@@ -5793,6 +5807,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>;
@@ -7136,7 +7152,8 @@ cpu0_alert0: trip-point0 {
};
cpu0_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7179,7 +7196,8 @@ cpu1_alert0: trip-point0 {
};
cpu1_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7222,7 +7240,8 @@ cpu2_alert0: trip-point0 {
};
cpu2_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7265,7 +7284,8 @@ cpu3_alert0: trip-point0 {
};
cpu3_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7308,7 +7328,8 @@ cpu4_alert0: trip-point0 {
};
cpu4_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7351,7 +7372,8 @@ cpu5_alert0: trip-point0 {
};
cpu5_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7394,7 +7416,8 @@ cpu6_alert0: trip-point0 {
};
cpu6_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7437,7 +7460,8 @@ cpu7_alert0: trip-point0 {
};
cpu7_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7480,7 +7504,8 @@ cpu8_alert0: trip-point0 {
};
cpu8_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7523,7 +7548,8 @@ cpu9_alert0: trip-point0 {
};
cpu9_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7566,7 +7592,8 @@ cpu10_alert0: trip-point0 {
};
cpu10_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7609,7 +7636,8 @@ cpu11_alert0: trip-point0 {
};
cpu11_alert1: trip-point1 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7724,7 +7752,8 @@ gpuss0-thermal {
trips {
gpuss0_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <2000>;
type = "passive";
};
@@ -7751,7 +7780,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 [flat|nested] 7+ messages in thread
* [PATCH v2 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support
2026-10-31 13:22 [PATCH v2 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
` (4 preceding siblings ...)
2026-10-31 13:22 ` [PATCH v2 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips Gaurav Kohli
@ 2026-10-31 13:22 ` Gaurav Kohli
5 siblings, 0 replies; 7+ messages in thread
From: Gaurav Kohli @ 2026-10-31 13:22 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 776bded3a312..03d753a0ab74 100644
--- a/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts
+++ b/arch/arm64/boot/dts/qcom/hamoa-iot-evk.dts
@@ -1553,69 +1553,3 @@ &usb_mp_hsphy0 {
&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>;
- };
- };
- };
-};
diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi
index fcd88e5f5d7e..6cf911e30973 100644
--- a/arch/arm64/boot/dts/qcom/hamoa.dtsi
+++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi
@@ -2766,6 +2766,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>;
@@ -2781,6 +2784,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>;
@@ -2796,6 +2802,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>;
@@ -2811,6 +2820,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>;
@@ -8809,6 +8821,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 {
@@ -9669,7 +9687,8 @@ map0 {
trips {
gpuss0_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9696,7 +9715,8 @@ map0 {
trips {
gpuss1_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9723,7 +9743,8 @@ map0 {
trips {
gpuss2_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9750,7 +9771,8 @@ map0 {
trips {
gpuss3_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9777,7 +9799,8 @@ map0 {
trips {
gpuss4_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9804,7 +9827,8 @@ map0 {
trips {
gpuss5_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9831,7 +9855,8 @@ map0 {
trips {
gpuss6_alert0: trip-point0 {
- temperature = <95000>;
+ temperature-bin = <95000>, <105000>;
+ thermal-hw-bin = <0x1>, <0x2>;
hysteresis = <1000>;
type = "passive";
};
@@ -9858,7 +9883,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 [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-31 13:23 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31 13:22 [PATCH v2 0/6] thermal: Add hardware-binned trip point support Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 1/6] dt-bindings: thermal: thermal-zones: add hardware-binning trip properties Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 2/6] thermal: add hardware-binning trip-point filtering support Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 3/6] dt-bindings: thermal: qcom-tsens: document qcm6490 tsens Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 4/6] thermal: qcom: tsens: add hardware-bin trip-point filtering Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 5/6] arm64: dts: qcom: kodiak: use thermal hw-bin trips Gaurav Kohli
2026-10-31 13:22 ` [PATCH v2 6/6] arm64: dts: qcom: hamoa: add thermal hw-bin support Gaurav Kohli
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®