| Message ID | 20260704171411.1413349-5-iuncuim@gmail.com (mailing list archive) |
|---|---|
| State | New |
| Headers |
Return-Path: <linux-sunxi+bounces-24160-sunxi=pue.re@lists.linux.dev>
X-Original-To: noreply@patchwork.local
Delivered-To: noreply@patchwork.local
Received: from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114])
by mxe881.netcup.net (Postfix) with ESMTPS id 420DE1C00CB
for <noreply@patchwork.local>; Sat, 4 Jul 2026 19:16:04 +0200 (CEST)
Authentication-Results: mxe881;
dkim=pass header.d=gmail.com;
spf=pass (sender IP is 172.105.105.114)
smtp.mailfrom=linux-sunxi+bounces-24160-noreply=patchwork.local@lists.linux.dev
smtp.helo=tor.lore.kernel.org
Received-SPF: pass (mxe881: domain of lists.linux.dev designates
172.105.105.114 as permitted sender) client-ip=172.105.105.114;
envelope-from=linux-sunxi+bounces-24160-noreply=patchwork.local@lists.linux.dev;
helo=tor.lore.kernel.org;
Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org
[100.90.174.1])
by tor.lore.kernel.org (Postfix) with ESMTP id 2F6AB3016003
for <noreply@patchwork.local>; Sat, 4 Jul 2026 17:16:02 +0000 (UTC)
Received: from localhost.localdomain (localhost.localdomain [127.0.0.1])
by smtp.subspace.kernel.org (Postfix) with ESMTP id D1C9D28C009;
Sat, 4 Jul 2026 17:15:58 +0000 (UTC)
Authentication-Results: smtp.subspace.kernel.org;
dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com
header.b="KR3Smi4N"
X-Original-To: linux-sunxi@lists.linux.dev
Received: from mail-pf1-f181.google.com (mail-pf1-f181.google.com
[209.85.210.181])
(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))
(No client certificate requested)
by smtp.subspace.kernel.org (Postfix) with ESMTPS id 37598374E4C
for <linux-sunxi@lists.linux.dev>; Sat, 4 Jul 2026 17:15:56 +0000 (UTC)
Authentication-Results: smtp.subspace.kernel.org;
arc=none smtp.client-ip=209.85.210.181
ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;
t=1783185358; cv=none;
b=eJQDePN2dtcKmVIfKkQ7+DMaDC0P461uS3YDzzp25QGTpnpGQlO3IMbbSFn7ByKV6W3bQyLmPyeeL2g8cYT/TWYfp8boYqk8+RiFc1C1bVXX6ViKbCeRjYc5N1MVIkkEYnwrC+K3Fv0Qgd1j6yxmP91sfHH8M8kh2cTIbp3h/MQ=
ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org;
s=arc-20240116; t=1783185358; c=relaxed/simple;
bh=seTZIEtXtqVVYV7kSKXvd99fs7QNW1ZFwg2DmcgKEAI=;
h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:
MIME-Version;
b=DiL72lJRICBqdSceLSlgfXRMNOlDawaEzXQIDSkQLplaAxepTMS2XEJYocklUS4v8D0A3uPpV+PIK/q5YB+T+W1hvB7soSGQrkW/jl/SdTrfnQxRA+s4DqMqF+YiRasjJPwhzDgCIX8RsgPNy2qK4iLrsUwTreK33aHSGWRl18U=
ARC-Authentication-Results: i=1; smtp.subspace.kernel.org;
dmarc=pass (p=none dis=none) header.from=gmail.com;
spf=pass smtp.mailfrom=gmail.com;
dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com
header.b=KR3Smi4N; arc=none smtp.client-ip=209.85.210.181
Authentication-Results: smtp.subspace.kernel.org;
dmarc=pass (p=none dis=none) header.from=gmail.com
Authentication-Results: smtp.subspace.kernel.org;
spf=pass smtp.mailfrom=gmail.com
Received: by mail-pf1-f181.google.com with SMTP id
d2e1a72fcca58-845c92bc464so1080543b3a.2
for <linux-sunxi@lists.linux.dev>;
Sat, 04 Jul 2026 10:15:56 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=gmail.com; s=20251104; t=1783185356; x=1783790156;
darn=lists.linux.dev;
h=content-transfer-encoding:mime-version:references:in-reply-to
:message-id:date:subject:cc:to:from:from:to:cc:subject:date
:message-id:reply-to:content-type;
bh=+gR5VfBTV21+OrP9WqQJ4BqzawxRB45whAsA2GtJSFg=;
b=KR3Smi4NP6i82wBHQcxJzySC4CkBU2vfVEQUAI7/H7VNHcJXUacA0HppFKa0h0rINm
MEdRUzk5oFWmzXVs1g5Hf9t+fXYdWsKhwyuiq2zPjMAsr+RMUmtOSz7KGP5Sbam0wdd+
V8XpgRZsckRjJDRKvtPpqdv4/4+vv5i68gdzij7vXw8yNua2ZPoz2lz/9QdgQlhNJN9U
1ZXh1lIS5/Bc4tTY4guL0xSXBZtXKa167iLoWyZsq0nDxcatwrYKpU+y2EpvH+omuIpY
3u4TUxiJwL38HYaMksYIn3Q00y27o5L9ztEOVtbqWzxK2Tq2fCOiRLUdmZ0CRq8mZdLJ
oq1A==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20251104; t=1783185356; x=1783790156;
h=content-transfer-encoding:mime-version:references:in-reply-to
:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from
:to:cc:subject:date:message-id:reply-to:content-type;
bh=+gR5VfBTV21+OrP9WqQJ4BqzawxRB45whAsA2GtJSFg=;
b=NTCiygjwb/+3x3RA0fXV1GNv+xZ0f4NmLmXKFL1HHdtQKrNGQjllBBGoyXFh/1RWp0
wyFdO5iF2EEZo8oC1t2fvxwyO7n2SKFDfEThMx06WYmV7AwemZ+sai8fBNZ69aPE8RCZ
IYcR1QhuXaDgr5CRj5PB8SN1pFYReACgaNxfDajx5fKp1p6A6ilEe9JZUh1MaWS0LU1d
w5Yheec0KCWJywwwSOL2zOMIQ1esP5ioDqdMRily43yZaftsx9eYvN/VXGBDqZDJVbDR
QNcDVSvzoytdBYttUcC/6l3aWP1k05d4EHcXkmqnoyvB879wA6LjyRaZwKDZqeUx6UD+
IJ+A==
X-Forwarded-Encrypted: i=1;
AFNElJ+dB1jI2v6JG+ZAybToQANxsu33whHdvM3gvH5CYzjYpbtlygnYLEDsR4f9tztCRTdiVGG2POLDaYMs4w==@lists.linux.dev
X-Gm-Message-State: AOJu0YwFr2pQXElu9yhi8wp9tz1rBwN7oqAYme2z2ZE+93qcP7l6Q5XF
Am5Rntk95L+gpTUt9kCHt8geBYRSahcq/1SbOwIHUJBoZ5yuktGXSjJI
X-Gm-Gg: AfdE7clN/lOMZxpTFyh5E+9wiKmJoonUrsc6A7/SrUsQ+HitEd328aSiF6rSPSN6t6Q
2Qg46lkcZXiSl1WSKiZ/RpvBH41FCduSiCS5cV5solNrQ5pVi7bB2qhL0dsUR4u4bq0exLdkrXX
pv7cMnlIvm8iTxad6a/3n+XT5Sx4wiiEe8kr1tTi5G9sq3yvGhjcOjzMM/WOQvmIf8/LsCZg9Ev
VaVq5/ia6w5e0ilDHH73x0J90ZY6Ijs19Lh/k3FnBkpd468bTDO2eWR38VULsdr8L5YkzjDcpHc
w0lHuSrlbM42QW5Y7C8pZkdWl7DLG87akcu7DfpA5QwZwP/ImtHRxDrYubEI+J3SzNyqD3CB0uB
7+f7J9YNEuzUxsVeSMUXW+TaElgpq6oBk5bowRNwJsxtGOtIGRL6fCUsgHSeJDOktyMqpViPbUQ
wBcjtuebcRsG1GtxQ=
X-Received: by 2002:a05:6a00:2394:b0:845:ec31:26cd with SMTP id
d2e1a72fcca58-847f6f4fe9bmr3324398b3a.46.1783185356439;
Sat, 04 Jul 2026 10:15:56 -0700 (PDT)
Received: from archlinux ([212.192.12.80])
by smtp.gmail.com with ESMTPSA id
d2e1a72fcca58-847f6d498adsm1379101b3a.32.2026.07.04.10.15.36
(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
Sat, 04 Jul 2026 10:15:54 -0700 (PDT)
From: Mikhail Kalashnikov <iuncuim@gmail.com>
To: Vasily Khoruzhick <anarsoul@gmail.com>,
Yangtao Li <tiny.windzz@gmail.com>,
"Rafael J . Wysocki" <rafael@kernel.org>,
Daniel Lezcano <daniel.lezcano@kernel.org>,
Zhang Rui <rui.zhang@intel.com>,
Lukasz Luba <lukasz.luba@arm.com>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Chen-Yu Tsai <wens@kernel.org>,
Jernej Skrabec <jernej.skrabec@gmail.com>,
Samuel Holland <samuel@sholland.org>,
Philipp Zabel <p.zabel@pengutronix.de>
Cc: linux-pm@vger.kernel.org,
devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-sunxi@lists.linux.dev,
linux-kernel@vger.kernel.org
Subject: [PATCH v5 4/5] thermal/drivers/sun8i: Add support for A523 THS0/1
controllers
Date: Sun, 5 Jul 2026 01:14:10 +0800
Message-ID: <20260704171411.1413349-5-iuncuim@gmail.com>
X-Mailer: git-send-email 2.55.0
In-Reply-To: <20260704171411.1413349-1-iuncuim@gmail.com>
References: <20260704171411.1413349-1-iuncuim@gmail.com>
Precedence: bulk
X-Mailing-List: linux-sunxi@lists.linux.dev
List-Id: <linux-sunxi.lists.linux.dev>
List-Subscribe: <mailto:linux-sunxi+subscribe@lists.linux.dev>
List-Unsubscribe: <mailto:linux-sunxi+unsubscribe@lists.linux.dev>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Rspamd-Server: rspamd-worker-8404
X-Spamd-Result: default: False [0.34 / 15.00];
BAYES_HAM(-5.50)[100.00%];
RBL_SENDERSCORE(2.00)[172.105.105.114:from];
SUSPICIOUS_RECIPS(1.50)[];
DMARC_POLICY_SOFTFAIL(1.00)[gmail.com : SPF not aligned (relaxed),
No valid DKIM,none];
MID_CONTAINS_FROM(1.00)[];
R_MISSING_CHARSET(0.50)[];
MAILLIST(-0.15)[generic];
MIME_GOOD(-0.10)[text/plain];
BAD_REP_POLICIES(0.10)[];
HAS_LIST_UNSUB(-0.01)[];
PRECEDENCE_BULK(0.00)[];
TAGGED_RCPT(0.00)[dt];
RCPT_COUNT_TWELVE(0.00)[18];
FUZZY_BLOCKED(0.00)[rspamd.com];
DBL_BLOCKED_OPENRESOLVER(0.00)[tor.lore.kernel.org:rdns,tor.lore.kernel.org:helo];
FORGED_SENDER_MAILLIST(0.00)[];
FROM_HAS_DN(0.00)[];
RCVD_COUNT_FIVE(0.00)[6];
TAGGED_FROM(0.00)[bounces-24160-noreply=patchwork.local];
R_SPF_ALLOW(0.00)[+ip4:172.105.105.114];
FREEMAIL_TO(0.00)[gmail.com,kernel.org,intel.com,arm.com,sholland.org,pengutronix.de];
FREEMAIL_FROM(0.00)[gmail.com];
TO_DN_SOME(0.00)[];
FROM_NEQ_ENVFROM(0.00)[iuncuim@gmail.com,linux-sunxi@lists.linux.dev];
FORGED_RECIPIENTS_MAILLIST(0.00)[];
RCVD_TLS_LAST(0.00)[];
MIME_TRACE(0.00)[0:+];
ASN(0.00)[asn:63949, ipnet:172.105.96.0/20, country:SG];
ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1];
RCVD_VIA_SMTP_AUTH(0.00)[]
X-Rspamd-Queue-Id: 420DE1C00CB
X-MORS-Enabled: yes
X-MORS-DOMAIN: patchwork.local
X-MORS-HOSTING: hosting172546
X-MORS-USER: hosting172546
X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?=
|
| Series |
Allwinner: A523: add support for A523 THS0/1 controllers
|
|
Commit Message
Mikhail Kalashnikov
July 4, 2026, 5:14 p.m. UTC
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 3 sensors: ths1_0 - "big" cores ths1_1 - "little" cores ths1_2 - gpu The datasheet mentions a fourth sensor in the NPU, but lacks any registers for operation other than calibration registers. The vendor code reads the value from ths1_2, but uses separate calibration data, so we get two different values from real one. Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com> Reviewed-by: Chen-Yu Tsai <wens@kernel.org> --- drivers/thermal/sun8i_thermal.c | 133 ++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+)
Comments
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] The calibration check `if (!caldata[0])` will always fail and abort calibration if bits 0-15 of the A523 eFuse are zero. - [High] NULL pointer dereference if the `nvmem-cell-names` property is missing from the Device Tree. - [Medium] Out-of-bounds heap read due to missing `callen` validation in `sun55i_a523_ths_calibrate`. - [Medium] Calibration logic incorrectly hardcodes `SUN55I_A523_SCALE_ABOVE` to convert temperature error into a register offset. -- commit ed04dff3819d46435d0d0c6fa95dfd782738eb1e 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 3 sensors: ths1_0 - "big" cores ths1_1 - "little" cores ths1_2 - gpu The datasheet mentions a fourth sensor in the NPU, but lacks any registers for operation other than calibration registers. The vendor code reads the value from ths1_2, but uses separate calibration data, so we get two different values from real one. > diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c > index 3bdd62aa8bdf1..f48ed9eae6c4f 100644 > --- a/drivers/thermal/sun8i_thermal.c > +++ b/drivers/thermal/sun8i_thermal.c [ ... ] > @@ -299,6 +314,97 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev, [ ... ] > +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[0]) [Severity: High] If the device tree lacks the nvmem-cell-names property, the loop inside sun8i_ths_calibrate() will be skipped entirely. This leaves caldata as NULL when it is passed to the chip-specific calibrate callback: sun8i_ths_calibrate() { ... of_property_for_each_string(...) { ... } tmdev->chip->calibrate(tmdev, (u16 *)caldata, callen); ... } Will checking !caldata[0] here cause a NULL pointer dereference in that scenario? [Severity: High] According to the eFuse layout diagram in the comment block just above this function, bits 0-15 are empty/unused, while the actual factory temperature is stored in bits 24-35 (caldata[1] and caldata[2]). If those unused eFuse bits are typically zero, will this check silently abort the calibration process on the A523? > + 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 there a chance we might read out of bounds here if the provided nvmem data is smaller than expected? If the device tree or nvmem cell provides fewer bytes than the 14 bytes required to access caldata[6], this could result in an out-of-bounds heap read. Should we validate callen before accessing these offsets? > + } else { > + switch (i) { > + case 0: > + sensor_reg = (caldata[2] >> 4) & TEMP_CALIB_MASK; > + 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) > + */ > + cdata = CALIBRATE_DEFAULT - > + ((sensor_temp - ft_temp) / SUN55I_A523_SCALE_ABOVE); [Severity: Medium] The sun55i_a523_calc_temp() function uses a piecewise formula depending on the register value, applying either SUN55I_A523_SCALE_ABOVE or SUN55I_A523_SCALE_BELOW. However, the calibration offset calculation here unconditionally divides the temperature error by SUN55I_A523_SCALE_ABOVE. If the factory temperature corresponds to a register value in the SUN55I_A523_SCALE_BELOW region, will dividing by the SCALE_ABOVE value result in an inaccurate calibration offset? > + > + if (cdata & ~TEMP_CALIB_MASK) { > + /* > + * Calibration value more than 12-bit, but calibration > + * register is 12-bit. In this case, ths hardware can
On Sun, Jul 5, 2026 at 1:15 AM Mikhail Kalashnikov <iuncuim@gmail.com> wrote: > > 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 3 sensors: > ths1_0 - "big" cores > ths1_1 - "little" cores > ths1_2 - gpu > > The datasheet mentions a fourth sensor in the NPU, but lacks any registers > for operation other than calibration registers. The vendor code reads the > value from ths1_2, but uses separate calibration data, so we get two > different values from real one. Are you sure? The calibration data is written to the sensor register. The BSP simply reads the sensor data from the GPU sensor, and passes it off as the value for the NPU sensor. No extra calculation involving the calibration data is done. > > Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com> > Reviewed-by: Chen-Yu Tsai <wens@kernel.org> > --- > drivers/thermal/sun8i_thermal.c | 133 ++++++++++++++++++++++++++++++++ > 1 file changed, 133 insertions(+) > > diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c > index 3bdd62aa8..f48ed9eae 100644 > --- a/drivers/thermal/sun8i_thermal.c > +++ b/drivers/thermal/sun8i_thermal.c > @@ -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_ABOVE 2736 > +#define SUN55I_A523_OFFSET_BELOW 2825 > +#define SUN55I_A523_SCALE_ABOVE 74 > +#define SUN55I_A523_SCALE_BELOW 65 > + > struct tsensor { > struct ths_device *tmdev; > struct thermal_zone_device *tzd; > @@ -114,6 +120,15 @@ static int sun50i_h5_calc_temp(struct ths_device *tmdev, > return -1590 * reg / 10 + 276000; > } > > +static int sun55i_a523_calc_temp(struct ths_device *tmdev, > + int id, int reg) > +{ > + if (reg >= SUN55I_A523_DELIMITER) > + return SUN55I_A523_SCALE_ABOVE * (SUN55I_A523_OFFSET_ABOVE - reg); > + else > + return SUN55I_A523_SCALE_BELOW * (SUN55I_A523_OFFSET_BELOW - reg); > +} > + > static int sun8i_ths_get_temp(struct thermal_zone_device *tz, int *temp) > { > struct tsensor *s = thermal_zone_device_priv(tz); > @@ -299,6 +314,97 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev, > return 0; > } > > +/* > + * The A523 nvmem calibration values. The ths1_3 is not used as it > + * doesn't have its own sensor and doesn't have any internal switch. > + * Instead, the value from the ths1_2 sensor is used, which gives the > + * illusion of an independent sensor for NPU and GPU when using > + * different calibration values. > + * > + * 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[0]) > + 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; > + 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) > + */ > + cdata = CALIBRATE_DEFAULT - > + ((sensor_temp - ft_temp) / SUN55I_A523_SCALE_ABOVE); The BSP version works out to: cdata = CALIBRATE_DEFAULT - (sensor_temp - ft_temp - 5000) / SU55I_A523_SCALE_BELOW Any particular reason why it's different? > + 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; > @@ -717,6 +823,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, In the BSP this is 0. > + .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 = 3, > + .has_bus_clk_reset = true, > + .has_mod_clk = true, > + .ft_deviation = 5000, In the BSP this is 0. ChenYu > + .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 }, > @@ -727,6 +858,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); > -- > 2.55.0 >
Hi Mikhail, Chen-Yu, Andre pointed me at this series. I have a T527 (A523 family) Orange Pi 4A here, so I applied v5 on top of v7.2-rc7 and ran it on the board. It works: thermal_zone0 cpu4-thermal 43.0 C idle 49.1 C under load thermal_zone1 cpu0-thermal 43.2 C idle 49.2 C under load thermal_zone2 gpu-thermal 42.8 C idle 47.2 C under load thermal_zone3 ddr-thermal 43.8 C idle 49.3 C under load (60 s of busy loops on all eight cores.) Boot is clean, no errors from the driver, and the readings track load sensibly, so: Tested-by: Juan Manuel López Carrillo <juanmanuellopezcarrillo@gmail.com> While testing I looked into the open question about the extra sensor, because I had reached a different number in my own out-of-tree work: I use sensor_num = 4 for this controller, and this series uses 3. > Are you sure? The calibration data is written to the sensor register. > The BSP simply reads the sensor data from the GPU sensor, and passes it > off as the value for the NPU sensor. No extra calculation involving the > calibration data is done. What the BSP does in software and what the hardware has look like two different things here. The T527 User Manual v0.92 documents four sensors on this controller: THS_EN (offset 0x04, page 452) has THS0_EN..THS3_EN in bits 3:0 with 31:4 reserved, and page 459 lists four data registers, THS0_DATA 0xC0, THS1_DATA 0xC4, THS2_DATA 0xC8 and THS3_DATA 0xCC, the last described as "Temperature measurement data of Sensor3". THS_DATA_INTC, THS_SHUT_INTC and THS_ALARM_INTC likewise have four per-sensor bits. A documented register is not a wired-up sensor, so I measured it. With this series running, THS_EN reads 0x7, so sensor 3 is off and THS3_DATA is 0x0, as expected. Setting bit 3 and reading the four data registers once per second (raw 12-bit values; lower means hotter on this part): idle THS0 0x875 THS1 0x874 THS2 0x87C THS3 0x85E under load THS0 0x83C THS1 0x83C THS2 0x852 THS3 0x836 delta -57 -56 -42 -40 Sensor 3 starts reading as soon as it is enabled, sits at its own offset of about 0x1C from sensor 2 instead of mirroring it, fluctuates independently between consecutive reads, and follows the load. So there are four live channels on this controller and the series reads three. I restored THS_EN to 0x7 afterwards. What I cannot tell you is which channel belongs to which block, and I want to be honest that my own tree does not settle it either. I followed a vendor recipe that goes the other way round from what you describe: it treats the GPU channel (id 2) as unreliable and reads channel 3 in its place. Your reading is that the vendor takes the GPU channel and reports it as NPU. Both cannot be right, and I have no way to tell which block each channel actually sits next to. All I am claiming is the electrical part: channel 3 is alive and independent. One more thing you may want to check, and I am less confident about this one. Applying your calibration code to my SID gives 0x90D, 0x90D and 0x90E for sensors 0..2, and 0x92B for the ths0/DDR sensor. In the bits your switch does not consume, between caldata[4] and the ths0 field, there is a further 12-bit value of 0x919, in the same numeric range as the other four. That would fit the factory having calibrated four sensors on this controller, but I have not verified the exact packing, so please take it as a hint and not as a claim. If you do end up reading channel 3, there is a trap worth knowing about. Right after the block is enabled that channel can still be at 0 while its first conversion is pending. With the two-slope formula a raw 0 works out to about 183 C, which is above the 110 C critical trip, so the board powers off 2.8 s into boot while it is actually at ~50 C. I hit exactly that and had to guard against a zero reading before using it. I am happy to run whatever test would help on this board, including reading channel 3 with proper calibration if you respin with sensor_num = 4. Finally, a data point for the naming issue Chen-Yu raised on 5/5. On this boot the kernel prints: thermal_sys: No trip points found for thermal-sensor@2009400 id=2 The gpu-thermal zone does define three trips, but inside a container named "gpu-trips", so the thermal core silently ignores all of them. Renaming the containers to "trips" is not only what the binding requires, it is what makes those trips take effect at all. Cheers, Juan
On 7/21/26 23:23, Chen-Yu Tsai wrote: > On Sun, Jul 5, 2026 at 1:15 AM Mikhail Kalashnikov <iuncuim@gmail.com> wrote: >> >> 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 3 sensors: >> ths1_0 - "big" cores >> ths1_1 - "little" cores >> ths1_2 - gpu >> >> The datasheet mentions a fourth sensor in the NPU, but lacks any registers >> for operation other than calibration registers. The vendor code reads the >> value from ths1_2, but uses separate calibration data, so we get two >> different values from real one. > > Are you sure? The calibration data is written to the sensor register. > > The BSP simply reads the sensor data from the GPU sensor, and passes it > off as the value for the NPU sensor. No extra calculation involving the > calibration data is done. > Yes, I think you're right. And as Juan pointed out, my current implementation doesn't match the BSP, since: #define SUN55IW3_GPU_SENSOR_ID (2) #define SUN55IW3_NPU_SENSOR_ID (3) ... /* use NPU sensor data instead of GPU */ if (id == SUN55IW3_GPU_SENSOR_ID) { id = SUN55IW3_NPU_SENSOR_ID; ... } In other words, in the BSP driver, the NPU sensor is used for the GPU. However, based on the A523 user manual—which describes registers for only three sensors, I had argued the opposite. The user manual for the T527 includes a description of a fourth sensor. Thus, it seems that all four sensors are working, but the purpose of this workaround in the BSP remains unclear to me. >> >> Signed-off-by: Mikhail Kalashnikov <iuncuim@gmail.com> >> Reviewed-by: Chen-Yu Tsai <wens@kernel.org> >> --- >> drivers/thermal/sun8i_thermal.c | 133 ++++++++++++++++++++++++++++++++ >> 1 file changed, 133 insertions(+) >> >> diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c >> index 3bdd62aa8..f48ed9eae 100644 >> --- a/drivers/thermal/sun8i_thermal.c >> +++ b/drivers/thermal/sun8i_thermal.c >> @@ -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_ABOVE 2736 >> +#define SUN55I_A523_OFFSET_BELOW 2825 >> +#define SUN55I_A523_SCALE_ABOVE 74 >> +#define SUN55I_A523_SCALE_BELOW 65 >> + >> struct tsensor { >> struct ths_device *tmdev; >> struct thermal_zone_device *tzd; >> @@ -114,6 +120,15 @@ static int sun50i_h5_calc_temp(struct ths_device *tmdev, >> return -1590 * reg / 10 + 276000; >> } >> >> +static int sun55i_a523_calc_temp(struct ths_device *tmdev, >> + int id, int reg) >> +{ >> + if (reg >= SUN55I_A523_DELIMITER) >> + return SUN55I_A523_SCALE_ABOVE * (SUN55I_A523_OFFSET_ABOVE - reg); >> + else >> + return SUN55I_A523_SCALE_BELOW * (SUN55I_A523_OFFSET_BELOW - reg); >> +} >> + >> static int sun8i_ths_get_temp(struct thermal_zone_device *tz, int *temp) >> { >> struct tsensor *s = thermal_zone_device_priv(tz); >> @@ -299,6 +314,97 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev, >> return 0; >> } >> >> +/* >> + * The A523 nvmem calibration values. The ths1_3 is not used as it >> + * doesn't have its own sensor and doesn't have any internal switch. >> + * Instead, the value from the ths1_2 sensor is used, which gives the >> + * illusion of an independent sensor for NPU and GPU when using >> + * different calibration values. >> + * >> + * 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[0]) >> + 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; >> + 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) >> + */ >> + cdata = CALIBRATE_DEFAULT - >> + ((sensor_temp - ft_temp) / SUN55I_A523_SCALE_ABOVE); > > The BSP version works out to: > > cdata = CALIBRATE_DEFAULT - > (sensor_temp - ft_temp - 5000) / SU55I_A523_SCALE_BELOW > > Any particular reason why it's different? In the early versions, I didn't use 'define' for constants, but later added them based on the logic of the value read from the register: a value below the threshold is 'BELOW', and a value above is 'ABOVE'. The BSP driver uses a name for the temperature, but the temperature decreases as the value read from the register increases; therefore, the formula uses SCALE constants with different names but identical values. In the next version, I’ll align them with the BSP values and add a comment explaining that the constants refer to the final temperature value, not the value read from the register. Regarding SUN55IW3_CAL_COM 5000 — when I first looked at the driver code, it seemed to me that this was the value intended for ft_deviation, but now that I’ve double-checked, the version of the calibration formula with SUN55IW3_CAL_COM and without ft_deviation gives the same result as the version without SUN55IW3_CAL_COM but with ft_deviation=5000. But if you'd rather I match BSP literally (5000 in the calibration numerator, ft_deviation = 0), I can do that instead. > >> + 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; >> @@ -717,6 +823,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, > > In the BSP this is 0. > >> + .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 = 3, >> + .has_bus_clk_reset = true, >> + .has_mod_clk = true, >> + .ft_deviation = 5000, > > In the BSP this is 0. > > > ChenYu > >> + .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 }, >> @@ -727,6 +858,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); >> -- >> 2.55.0 >>
diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c index 3bdd62aa8..f48ed9eae 100644 --- a/drivers/thermal/sun8i_thermal.c +++ b/drivers/thermal/sun8i_thermal.c @@ -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_ABOVE 2736 +#define SUN55I_A523_OFFSET_BELOW 2825 +#define SUN55I_A523_SCALE_ABOVE 74 +#define SUN55I_A523_SCALE_BELOW 65 + struct tsensor { struct ths_device *tmdev; struct thermal_zone_device *tzd; @@ -114,6 +120,15 @@ static int sun50i_h5_calc_temp(struct ths_device *tmdev, return -1590 * reg / 10 + 276000; } +static int sun55i_a523_calc_temp(struct ths_device *tmdev, + int id, int reg) +{ + if (reg >= SUN55I_A523_DELIMITER) + return SUN55I_A523_SCALE_ABOVE * (SUN55I_A523_OFFSET_ABOVE - reg); + else + return SUN55I_A523_SCALE_BELOW * (SUN55I_A523_OFFSET_BELOW - reg); +} + static int sun8i_ths_get_temp(struct thermal_zone_device *tz, int *temp) { struct tsensor *s = thermal_zone_device_priv(tz); @@ -299,6 +314,97 @@ static int sun50i_h6_ths_calibrate(struct ths_device *tmdev, return 0; } +/* + * The A523 nvmem calibration values. The ths1_3 is not used as it + * doesn't have its own sensor and doesn't have any internal switch. + * Instead, the value from the ths1_2 sensor is used, which gives the + * illusion of an independent sensor for NPU and GPU when using + * different calibration values. + * + * 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[0]) + 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; + 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) + */ + cdata = CALIBRATE_DEFAULT - + ((sensor_temp - ft_temp) / SUN55I_A523_SCALE_ABOVE); + + 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; @@ -717,6 +823,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 = 3, + .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 }, @@ -727,6 +858,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);