[v6,4/5] thermal/drivers/sun8i: Add support for A523 THS0/1 controllers
Commit Message
From: Mikhail Kalashnikov <iuncuim@gmail.com>
The A523 processor has two temperature controllers, THS0 and THS1.
THS0 has only one temperature sensor, which is located in the DRAM
controller. THS1 does have 4 sensors:
ths1_0 - "big" cores
ths1_1 - "little" cores
ths1_2 - gpu
ths1_3 - npu
The user manual for the T527 processor describes 4 sensors, whereas
the one for the A523 describes only 3. As additional information, the BSP
driver was used, in which the value SUN55IW3_CAL_COM(5000) is used as
the calibration value in the formula; in the mainline driver it is
treated as the ft_deviation value. Also, in the BSP driver the NPU sensor
is used for both the GPU and the NPU — its actual purpose is unclear;
both sensors are functional and show temperature changes depending on
load.
Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
---
drivers/thermal/sun8i_thermal.c | 147 ++++++++++++++++++++++++++++++++
1 file changed, 147 insertions(+)
Comments
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Incorrect check for unprogrammed eFuse can reject valid calibration data.
- [Medium] Missing bounds check on `callen` in `sun55i_a523_ths_calibrate` allows out-of-bounds memory reads on the dynamically allocated `caldata` array.
--
commit 1a5244bb0d5a03f3ec7f2a8c21b6249015b66057
Author: Mikhail Kalashnikov <iuncuim@gmail.com>
thermal/drivers/sun8i: Add support for A523 THS0/1 controllers
The A523 processor has two temperature controllers, THS0 and THS1.
THS0 has only one temperature sensor, which is located in the DRAM
controller. THS1 does have 4 sensors:
ths1_0 - "big" cores
ths1_1 - "little" cores
ths1_2 - gpu
ths1_3 - npu
> diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c
> index 3606548801f02..4a81486f8faec 100644
> --- a/drivers/thermal/sun8i_thermal.c
> +++ b/drivers/thermal/sun8i_thermal.c
[ ... ]
> @@ -298,6 +319,105 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev,
> return 0;
> }
>
> +/*
> + * The A523 calibration data is stored in the eFuse, split across two
> + * nvmem cells. THS1 has four sensors, including ths1_3 which is used
> + * for the NPU, each with its own calibration value. The fields are
> + * packed as shown in the layouts below.
> + *
> + * efuse layout 0x38-0x3F (caldata[0..3]):
> + * caldata[0] caldata[1] caldata[2] caldata[3]
> + * 0 16 24 32 36 48 60 64
> + * +---------------+---------------+---------------+---------------+
> + * | | | temp | ths1_0 | ths1_1 | +
> + * +---------------+---------------+---------------+---------------+
> + *
> + * efuse layout 0x44-0x4B (caldata[4..7]):
> + * caldata[4] caldata[5] caldata[6] caldata[7]
> + * 0 12 16 24 32 36 48 64
> + * +---------------+---------------+---------------+---------------+
> + * | ths1_2 | ths1_3 | ths0 | | +
> + * +---------------+---------------+---------------+---------------+
> + */
> +static int sun55i_a523_ths_calibrate(struct ths_device *tmdev,
> + u16 *caldata, int callen)
> +{
> + struct device *dev = tmdev->dev;
> + int i, ft_temp;
> +
> + if (!caldata[1])
[Severity: High]
Does this check inadvertently reject valid calibration data?
If the 12-bit factory calibration temperature happens to be a multiple of
25.6 C (meaning its lowest 8 bits are 0, like 0x100), caldata[1] will be
legitimately 0 since it only holds those lower 8 bits. This would cause the
driver to incorrectly abort calibration on perfectly valid hardware.
[Severity: Medium]
Can this read out-of-bounds memory?
The function receives a dynamically allocated caldata array of size callen
populated from the NVMEM cells, but callen is never verified here. If the
device tree provides a malformed or incomplete NVMEM cell, this access and
subsequent reads will happen past the end of the allocation.
> + return -EINVAL;
> +
> + ft_temp = (((caldata[2] << 8) | (caldata[1] >> 8)) & FT_TEMP_MASK) * 100;
> +
> + for (i = 0; i < tmdev->chip->sensor_num; i++) {
> + int sensor_reg, sensor_temp, cdata, offset;
> + /*
> + * Chips ths0 and ths1 have common parameters for value
> + * calibration. To separate them we can use the number of
> + * temperature sensors on each chip.
> + * For ths0 this value is 1.
> + */
> + if (tmdev->chip->sensor_num == 1) {
> + sensor_reg = ((caldata[5] >> 8) |
> + (caldata[6] << 8)) & TEMP_CALIB_MASK;
[Severity: Medium]
Is it possible to read out-of-bounds memory here as well?
This is the highest index accessed in sun55i_a523_ths_calibrate. It strictly
requires a minimum buffer size of 14 bytes. If the device tree provides an
8-byte NVMEM cell, accessing caldata[5] and caldata[6] will pull
uninitialized heap data into the calibration calculations or cause a crash.
> + } else {
> + switch (i) {
> + case 0:
On Sun, Sep 13, 2026 at 10:56 PM Mikhail Kalashnikov <iuncuim@gmail.com> wrote:
>
> From: Mikhail Kalashnikov <iuncuim@gmail.com>
>
> The A523 processor has two temperature controllers, THS0 and THS1.
> THS0 has only one temperature sensor, which is located in the DRAM
> controller. THS1 does have 4 sensors:
> ths1_0 - "big" cores
> ths1_1 - "little" cores
> ths1_2 - gpu
> ths1_3 - npu
>
> The user manual for the T527 processor describes 4 sensors, whereas
> the one for the A523 describes only 3. As additional information, the BSP
> driver was used, in which the value SUN55IW3_CAL_COM(5000) is used as
> the calibration value in the formula; in the mainline driver it is
> treated as the ft_deviation value. Also, in the BSP driver the NPU sensor
> is used for both the GPU and the NPU — its actual purpose is unclear;
> both sensors are functional and show temperature changes depending on
> load.
>
> Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com>
Reviewed-by: Chen-Yu Tsai <wens@kernel.org>
I think we can ignore Sashiko's concerns for this patch.
1. calibration likely happens at room temperature
2. it is not the drivers responsibility to guard against incorrect
device trees
@@ -59,6 +59,12 @@
#define SUN50I_H6_THS_PC_TEMP_PERIOD(x) ((GENMASK(19, 0) & (x)) << 12)
#define SUN50I_H6_THS_DATA_IRQ_STS(x) BIT(x)
+#define SUN55I_A523_DELIMITER 0x7c8
+#define SUN55I_A523_OFFSET_BELOW 2736
+#define SUN55I_A523_OFFSET_ABOVE 2825
+#define SUN55I_A523_SCALE_BELOW 74
+#define SUN55I_A523_SCALE_ABOVE 65
+
struct tsensor {
struct ths_device *tmdev;
struct thermal_zone_device *tzd;
@@ -114,6 +120,21 @@ static int sun50i_h5_calc_temp(struct ths_device *tmdev,
return -1590 * reg / 10 + 276000;
}
+/*
+ * The constant names were chosen in accordance with the BSP driver;
+ * they indicate whether the value corresponds to a high or low
+ * temperature (relative to a value of ~55 °C), rather than the reg value
+ * that was read.
+ */
+static int sun55i_a523_calc_temp(struct ths_device *tmdev,
+ int id, int reg)
+{
+ if (reg >= SUN55I_A523_DELIMITER)
+ return SUN55I_A523_SCALE_BELOW * (SUN55I_A523_OFFSET_BELOW - reg);
+ else
+ return SUN55I_A523_SCALE_ABOVE * (SUN55I_A523_OFFSET_ABOVE - reg);
+}
+
static int sun8i_ths_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct tsensor *s = thermal_zone_device_priv(tz);
@@ -298,6 +319,105 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev,
return 0;
}
+/*
+ * The A523 calibration data is stored in the eFuse, split across two
+ * nvmem cells. THS1 has four sensors, including ths1_3 which is used
+ * for the NPU, each with its own calibration value. The fields are
+ * packed as shown in the layouts below.
+ *
+ * efuse layout 0x38-0x3F (caldata[0..3]):
+ * caldata[0] caldata[1] caldata[2] caldata[3]
+ * 0 16 24 32 36 48 60 64
+ * +---------------+---------------+---------------+---------------+
+ * | | | temp | ths1_0 | ths1_1 | +
+ * +---------------+---------------+---------------+---------------+
+ *
+ * efuse layout 0x44-0x4B (caldata[4..7]):
+ * caldata[4] caldata[5] caldata[6] caldata[7]
+ * 0 12 16 24 32 36 48 64
+ * +---------------+---------------+---------------+---------------+
+ * | ths1_2 | ths1_3 | ths0 | | +
+ * +---------------+---------------+---------------+---------------+
+ */
+static int sun55i_a523_ths_calibrate(struct ths_device *tmdev,
+ u16 *caldata, int callen)
+{
+ struct device *dev = tmdev->dev;
+ int i, ft_temp;
+
+ if (!caldata[1])
+ return -EINVAL;
+
+ ft_temp = (((caldata[2] << 8) | (caldata[1] >> 8)) & FT_TEMP_MASK) * 100;
+
+ for (i = 0; i < tmdev->chip->sensor_num; i++) {
+ int sensor_reg, sensor_temp, cdata, offset;
+ /*
+ * Chips ths0 and ths1 have common parameters for value
+ * calibration. To separate them we can use the number of
+ * temperature sensors on each chip.
+ * For ths0 this value is 1.
+ */
+ if (tmdev->chip->sensor_num == 1) {
+ sensor_reg = ((caldata[5] >> 8) |
+ (caldata[6] << 8)) & TEMP_CALIB_MASK;
+ } else {
+ switch (i) {
+ case 0:
+ sensor_reg = (caldata[2] >> 4) & TEMP_CALIB_MASK;
+ break;
+ case 1:
+ sensor_reg = caldata[3] & TEMP_CALIB_MASK;
+ break;
+ case 2:
+ sensor_reg = caldata[4] & TEMP_CALIB_MASK;
+ break;
+ case 3:
+ sensor_reg = ((caldata[4] >> 12) |
+ (caldata[5] << 4)) & TEMP_CALIB_MASK;
+ break;
+ default:
+ sensor_reg = 0;
+ break;
+ }
+ }
+
+ sensor_temp = tmdev->chip->calc_temp(tmdev, i, sensor_reg);
+
+ /*
+ * Calibration data is CALIBRATE_DEFAULT - (calculated
+ * temperature from sensor reading at factory temperature
+ * minus actual factory temperature) * X (scale from
+ * temperature to register values).
+ *
+ * The BSP's SUN55IW3_CAL_COM(5000) is applied as ft_deviation
+ * in sun8i_ths_get_temp(), after calc_temp(), and therefore
+ * intentionally not repeated here.
+ */
+ cdata = CALIBRATE_DEFAULT -
+ ((sensor_temp - ft_temp) / SUN55I_A523_SCALE_BELOW);
+
+ if (cdata & ~TEMP_CALIB_MASK) {
+ /*
+ * Calibration value more than 12-bit, but calibration
+ * register is 12-bit. In this case, ths hardware can
+ * still work without calibration, although the data
+ * won't be so accurate.
+ */
+ dev_warn(dev, "sensor%d is not calibrated.\n", i);
+ continue;
+ }
+
+ offset = (i % 2) * 16;
+ regmap_update_bits(tmdev->regmap,
+ SUN50I_H6_THS_TEMP_CALIB + (i / 2 * 4),
+ TEMP_CALIB_MASK << offset,
+ cdata << offset);
+ }
+
+ return 0;
+}
+
static int sun8i_ths_calibrate(struct ths_device *tmdev)
{
struct nvmem_cell *calcell = NULL;
@@ -719,6 +839,31 @@ static const struct ths_thermal_chip sun50i_h616_ths = {
.calc_temp = sun8i_ths_calc_temp,
};
+/* The A523 has a shared reset line for both chips */
+static const struct ths_thermal_chip sun55i_a523_ths0 = {
+ .sensor_num = 1,
+ .has_bus_clk_reset = true,
+ .has_mod_clk = true,
+ .ft_deviation = 5000,
+ .temp_data_base = SUN50I_H6_THS_TEMP_DATA,
+ .calibrate = sun55i_a523_ths_calibrate,
+ .init = sun50i_h6_thermal_init,
+ .irq_ack = sun50i_h6_irq_ack,
+ .calc_temp = sun55i_a523_calc_temp,
+};
+
+static const struct ths_thermal_chip sun55i_a523_ths1 = {
+ .sensor_num = 4,
+ .has_bus_clk_reset = true,
+ .has_mod_clk = true,
+ .ft_deviation = 5000,
+ .temp_data_base = SUN50I_H6_THS_TEMP_DATA,
+ .calibrate = sun55i_a523_ths_calibrate,
+ .init = sun50i_h6_thermal_init,
+ .irq_ack = sun50i_h6_irq_ack,
+ .calc_temp = sun55i_a523_calc_temp,
+};
+
static const struct of_device_id of_ths_match[] = {
{ .compatible = "allwinner,sun8i-a83t-ths", .data = &sun8i_a83t_ths },
{ .compatible = "allwinner,sun8i-h3-ths", .data = &sun8i_h3_ths },
@@ -729,6 +874,8 @@ static const struct of_device_id of_ths_match[] = {
{ .compatible = "allwinner,sun50i-h6-ths", .data = &sun50i_h6_ths },
{ .compatible = "allwinner,sun20i-d1-ths", .data = &sun20i_d1_ths },
{ .compatible = "allwinner,sun50i-h616-ths", .data = &sun50i_h616_ths },
+ { .compatible = "allwinner,sun55i-a523-ths0", .data = &sun55i_a523_ths0 },
+ { .compatible = "allwinner,sun55i-a523-ths1", .data = &sun55i_a523_ths1 },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, of_ths_match);