* [PATCH v8 4/4] coresight: cti: expose banked sysfs registers for Qualcomm extended CTI
From: Yingchao Deng @ 2026-04-26 9:44 UTC (permalink / raw)
To: Suzuki K Poulose, Mike Leach, James Clark, Leo Yan,
Alexander Shishkin
Cc: coresight, linux-arm-kernel, linux-kernel, linux-arm-msm,
quic_yingdeng, Jinlong Mao, Tingwei Zhang, Jie Gan, Yingchao Deng
In-Reply-To: <20260426-extended-cti-v8-0-23b900a4902f@oss.qualcomm.com>
Qualcomm extended CTI implements banked trigger status and integration
registers, where each bank covers 32 triggers. Multiple instances of
these registers are required to expose the full trigger space.
Add static sysfs entries for the banked CTI registers and control their
visibility based on the underlying hardware configuration. Numbered
sysfs nodes are hidden on standard ARM CTIs, preserving the existing ABI.
On Qualcomm CTIs, only banked registers backed by hardware are exposed,
with the number of visible banks derived from nr_trig_max.
This ensures that userspace only sees registers that are actually
implemented, while maintaining compatibility with existing CTI tooling.
Signed-off-by: Yingchao Deng <yingchao.deng@oss.qualcomm.com>
---
drivers/hwtracing/coresight/coresight-cti-sysfs.c | 58 +++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/drivers/hwtracing/coresight/coresight-cti-sysfs.c b/drivers/hwtracing/coresight/coresight-cti-sysfs.c
index 8b70e7e38ea3..046757e4e9b6 100644
--- a/drivers/hwtracing/coresight/coresight-cti-sysfs.c
+++ b/drivers/hwtracing/coresight/coresight-cti-sysfs.c
@@ -512,18 +512,36 @@ static struct attribute *coresight_cti_regs_attrs[] = {
&dev_attr_appclear.attr,
&dev_attr_apppulse.attr,
coresight_cti_reg(triginstatus, CTITRIGINSTATUS),
+ coresight_cti_reg(triginstatus1, CTI_REG_SET_NR_CONST(CTITRIGINSTATUS, 1)),
+ coresight_cti_reg(triginstatus2, CTI_REG_SET_NR_CONST(CTITRIGINSTATUS, 2)),
+ coresight_cti_reg(triginstatus3, CTI_REG_SET_NR_CONST(CTITRIGINSTATUS, 3)),
coresight_cti_reg(trigoutstatus, CTITRIGOUTSTATUS),
+ coresight_cti_reg(trigoutstatus1, CTI_REG_SET_NR_CONST(CTITRIGOUTSTATUS, 1)),
+ coresight_cti_reg(trigoutstatus2, CTI_REG_SET_NR_CONST(CTITRIGOUTSTATUS, 2)),
+ coresight_cti_reg(trigoutstatus3, CTI_REG_SET_NR_CONST(CTITRIGOUTSTATUS, 3)),
coresight_cti_reg(chinstatus, CTICHINSTATUS),
coresight_cti_reg(choutstatus, CTICHOUTSTATUS),
#ifdef CONFIG_CORESIGHT_CTI_INTEGRATION_REGS
coresight_cti_reg_rw(itctrl, CORESIGHT_ITCTRL),
coresight_cti_reg(ittrigin, ITTRIGIN),
+ coresight_cti_reg(ittrigin1, CTI_REG_SET_NR_CONST(ITTRIGIN, 1)),
+ coresight_cti_reg(ittrigin2, CTI_REG_SET_NR_CONST(ITTRIGIN, 2)),
+ coresight_cti_reg(ittrigin3, CTI_REG_SET_NR_CONST(ITTRIGIN, 3)),
coresight_cti_reg(itchin, ITCHIN),
coresight_cti_reg_rw(ittrigout, ITTRIGOUT),
+ coresight_cti_reg_rw(ittrigout1, CTI_REG_SET_NR_CONST(ITTRIGOUT, 1)),
+ coresight_cti_reg_rw(ittrigout2, CTI_REG_SET_NR_CONST(ITTRIGOUT, 2)),
+ coresight_cti_reg_rw(ittrigout3, CTI_REG_SET_NR_CONST(ITTRIGOUT, 3)),
coresight_cti_reg_rw(itchout, ITCHOUT),
coresight_cti_reg(itchoutack, ITCHOUTACK),
coresight_cti_reg(ittrigoutack, ITTRIGOUTACK),
+ coresight_cti_reg(ittrigoutack1, CTI_REG_SET_NR_CONST(ITTRIGOUTACK, 1)),
+ coresight_cti_reg(ittrigoutack2, CTI_REG_SET_NR_CONST(ITTRIGOUTACK, 2)),
+ coresight_cti_reg(ittrigoutack3, CTI_REG_SET_NR_CONST(ITTRIGOUTACK, 3)),
coresight_cti_reg_wo(ittriginack, ITTRIGINACK),
+ coresight_cti_reg_wo(ittriginack1, CTI_REG_SET_NR_CONST(ITTRIGINACK, 1)),
+ coresight_cti_reg_wo(ittriginack2, CTI_REG_SET_NR_CONST(ITTRIGINACK, 2)),
+ coresight_cti_reg_wo(ittriginack3, CTI_REG_SET_NR_CONST(ITTRIGINACK, 3)),
coresight_cti_reg_wo(itchinack, ITCHINACK),
#endif
NULL,
@@ -534,10 +552,50 @@ static umode_t coresight_cti_regs_is_visible(struct kobject *kobj,
{
struct device *dev = kobj_to_dev(kobj);
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ static const char * const qcom_suffix_registers[] = {
+ "triginstatus",
+ "trigoutstatus",
+#ifdef CONFIG_CORESIGHT_CTI_INTEGRATION_REGS
+ "ittrigin",
+ "ittrigout",
+ "ittriginack",
+ "ittrigoutack",
+#endif
+ };
+ int i, nr, max_bank;
+ size_t len;
if (attr == &dev_attr_asicctl.attr && !drvdata->config.asicctl_impl)
return 0;
+ /*
+ * Banked regs are exposed as <qcom_suffix_registers><nr> (nr = 1..3).
+ * - Hide them on standard CTIs.
+ * - On QCOM CTIs, hide suffixes beyond the number of banks implied
+ * by nr_trig_max (32 triggers per bank).
+ */
+ for (i = 0; i < ARRAY_SIZE(qcom_suffix_registers); i++) {
+ len = strlen(qcom_suffix_registers[i]);
+
+ if (strncmp(attr->name, qcom_suffix_registers[i], len))
+ continue;
+
+ if (kstrtoint(attr->name + len, 10, &nr))
+ continue;
+
+ if (!drvdata->is_qcom_cti)
+ return 0;
+
+ if (nr < 1 || nr > 3)
+ return 0;
+
+ max_bank = DIV_ROUND_UP(drvdata->config.nr_trig_max, 32) - 1;
+ if (nr > max_bank)
+ return 0;
+
+ break;
+ }
+
return attr->mode;
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v8 3/6] drm/bridge: simple: Add the Lontium LT8711UXD DP-to-HDMI bridge
From: Heiko Stuebner @ 2026-04-26 9:54 UTC (permalink / raw)
To: Dmitry Baryshkov, Laurent Pinchart
Cc: Dennis Gilmore, Andrzej Hajda, Neil Armstrong, Robert Foss,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonas Karlman,
Jernej Skrabec, Maxime Ripard, Alexey Charkov, devicetree,
linux-rockchip, linux-arm-kernel, dri-devel, linux-kernel
In-Reply-To: <20260425234845.GC2964234@killaraus.ideasonboard.com>
Am Sonntag, 26. April 2026, 01:48:45 Mitteleuropäische Sommerzeit schrieb Laurent Pinchart:
> On Sun, Apr 26, 2026 at 12:44:59AM +0300, Dmitry Baryshkov wrote:
> > On Sat, Apr 25, 2026 at 01:10:02PM -0500, Dennis Gilmore wrote:
> > > On Sat, Apr 25, 2026 at 9:24 AM Dmitry Baryshkov wrote:
> > > > On Sat, Apr 25, 2026 at 02:28:44PM +0300, Laurent Pinchart wrote:
> > > > > Hi Dennis,
> > > > >
> > > > > Thank you for the patch.
> > > > >
> > > > > On Fri, Apr 24, 2026 at 10:10:08PM -0500, Dennis Gilmore wrote:
> > > > > > The Lontium LT8711UXD is a high performance two lane Type-C/DP1.4
> > > > > > to HDMI2.0 converter, designed to connect a USB Type-C source or
> > > > > > a DP1.4 source to an HDMI2.0 sink.
> > > > >
> > > > > As far as I can tell, the LT8711UXD has an I2C control interface.
> > > > > Shouldn't it be an I2C device ?
> > > >
> > > > From the datasheet:
> > > >
> > > > The device is capable of automatic operation which is
> > > > enabled by an integrated microprocessor that uses an
> > > > embedded SPI flash for firmware storage. System control
> > > > is also available through the use of a dedicated
> > > > configuration I2C slave interface.
> > > >
> > > > My guess was that it can either be an I2C device or it can function as a
> > > > simple platdev with no I2C controls. Please correct me if my
> > > > understanding was wrong.
> > > >
> > > > But now looking at the schematics, it seems to be connected to I2C6.
> > > > Which means that it should be desribed (and bound) as such.
> > >
> > > Hi Dmitry and Laurent,
> > >
> > > While the schematic shows that it can use I2C and has been wired up,
> > > it also shows that both MODE_SEL and I2C_ADDR have unpopulated 10k
> > > resistors; as a result, MODE_SEL is connected directly to GND,
looking at the schematics linked in the board patch, I somehow see
both R9 (mode_sel -> vcc3v3_io) but also R17 (mode_sel -> gnd) marked
as 10K.nc ?
> > > putting
> > > the bridge in autonomous mode. I confirmed this by running `i2cdetect
> > > -r -y 6`, with the only device on the bus being the HYM8563 RTC at
> > > 0x51. Without reworking the board, the device is not directly
> > > controllable and just runs autonomously.
> >
> > I think it would be nice to mention:
> > - In the commit for the bindings, that the device can be running
> > uncontrolled or it can be attached over I2C, bindings describe the
> > uncontrolled mode.
> > - In this commit message, the same.
> > - In the commit message for the board DT mention your findings about the
> > board, mention soldering R9 or R17 (which one?) and R27.
>
> Additionally, how are we going to handle boards where the device
> operates in I2C mode ? Will we use a different compatible string (maybe
> "lontium,lt8711uxd-i2c") ? If DT maintainers are fine with that, I have
> no objection to this patch.
I would assume it'd be more the dt-maintainers objecting?
I.e. the two different bindings for the same hardware and leaking Linux
implementation-specifics into the binding.
I'm don't have deep insight into the i2c framework, but I guess the i2c
device probe does not need to talk to an i2c device due to resources
needing setup. Does the i2c core need to talk to the device at all?
Because otherwise, you could just do a regular i2c device (the routing
for everything is there afterall), add a lontium,automatic-mode; flag
to the node to denote mode.
And if for whatever reason a variant appears with the lines connected
you can just modifiy the DT via an overlay?
Heiko
^ permalink raw reply
* [PATCH] coresight: cti: Fix DT filter signals silently ignored
From: Yingchao Deng @ 2026-04-26 9:59 UTC (permalink / raw)
To: Suzuki K Poulose, Mike Leach, James Clark, Leo Yan,
Alexander Shishkin, Mathieu Poirier, Greg Kroah-Hartman
Cc: coresight, linux-arm-kernel, linux-kernel, quic_yingdeng,
Jinlong Mao, Tingwei Zhang, Jie Gan, Yingchao Deng
In cti_plat_process_filter_sigs(), after allocating a temporary
cti_trig_grp struct via kzalloc_obj(), the code never assigns tg->nr_sigs
= nr_filter_sigs. Since kzalloc zero-initialises the struct, tg->nr_sigs
remains 0. cti_plat_read_trig_group() guards with:
if (!tgrp->nr_sigs)
return 0;
so it returns immediately without reading any signal indices from DT.
Fix by assigning tg->nr_sigs before calling cti_plat_read_trig_group().
Fixes: a5614770ab97 ("coresight: cti: Add device tree support for custom CTI")
Signed-off-by: Yingchao Deng <yingchao.deng@oss.qualcomm.com>
---
drivers/hwtracing/coresight/coresight-cti-platform.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/hwtracing/coresight/coresight-cti-platform.c b/drivers/hwtracing/coresight/coresight-cti-platform.c
index 4eff96f48594..d6d5388705c3 100644
--- a/drivers/hwtracing/coresight/coresight-cti-platform.c
+++ b/drivers/hwtracing/coresight/coresight-cti-platform.c
@@ -329,6 +329,7 @@ static int cti_plat_process_filter_sigs(struct cti_drvdata *drvdata,
if (!tg)
return -ENOMEM;
+ tg->nr_sigs = nr_filter_sigs;
err = cti_plat_read_trig_group(tg, fwnode, CTI_DT_FILTER_OUT_SIGS);
if (!err)
drvdata->config.trig_out_filter |= tg->used_mask;
---
base-commit: 7080e32d3f09d8688c4a87d81bdcc71f7f606b16
change-id: 20260426-nr_sigs-268add9d09ba
Best regards,
--
Yingchao Deng <yingchao.deng@oss.qualcomm.com>
^ permalink raw reply related
* Re: [PATCH v5 3/6] pwm: Add rockchip PWMv4 driver
From: Damon Ding @ 2026-04-26 10:09 UTC (permalink / raw)
To: Nicolas Frattaroli, Uwe Kleine-König, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Lee Jones,
William Breathitt Gray
Cc: kernel, Jonas Karlman, Alexey Charkov, linux-rockchip, linux-pwm,
devicetree, linux-arm-kernel, linux-kernel, linux-iio
In-Reply-To: <20260420-rk3576-pwm-v5-3-ae7cfbbe5427@collabora.com>
Hi Nicolas,
On 4/20/2026 9:52 PM, Nicolas Frattaroli wrote:
> The Rockchip RK3576 brings with it a new PWM IP, in downstream code
> referred to as "v4". This new IP is different enough from the previous
> Rockchip IP that I felt it necessary to add a new driver for it, instead
> of shoehorning it in the old one.
>
> Add this new driver, based on the PWM core's waveform APIs. Its platform
> device is registered by the parent mfpwm driver, from which it also
> receives a little platform data struct, so that mfpwm can guarantee that
> all the platform device drivers spread across different subsystems for
> this specific hardware IP do not interfere with each other.
>
> Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> ---
> MAINTAINERS | 1 +
> drivers/pwm/Kconfig | 11 ++
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-rockchip-v4.c | 383 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 396 insertions(+)
>
......
> diff --git a/drivers/pwm/pwm-rockchip-v4.c b/drivers/pwm/pwm-rockchip-v4.c
> new file mode 100644
> index 000000000000..b7de72c433c5
> --- /dev/null
> +++ b/drivers/pwm/pwm-rockchip-v4.c
> @@ -0,0 +1,383 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2025 Collabora Ltd.
> + *
> + * A Pulse-Width-Modulation (PWM) generator driver for the generators found in
> + * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". Uses
> + * the MFPWM infrastructure to guarantee exclusive use over the device without
> + * other functions of the device from different drivers interfering with its
> + * operation while it's active.
> + *
> + * Technical Reference Manual: Chapter 31 of the RK3506 TRM Part 1, a SoC which
> + * uses the same PWM hardware and has a publicly available TRM.
> + * https://opensource.rock-chips.com/images/3/36/Rockchip_RK3506_TRM_Part_1_V1.2-20250811.pdf
> + *
> + * Authors:
> + * Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> + *
> + * Limitations:
> + * - The hardware supports both completing the currently running period
> + * on disable (by switching to oneshot mode with a single repetition and
> + * only disable when the complete irq fires), and abrupt disable (freeze).
> + * Only the latter is implemented in the driver.
> + * - When the output is disabled, the pin will remain driven to whatever state
> + * it last had.
This limitation exists because after disabling the PWM output via
registers, the actual shutdown only happens after the current period
completes.
Therefore, the better approach is to add a delay of one full period
before disabling &rockchip_mfpwm_func.core.
> + * - Adjustments to the duty cycle will only take effect during the next period.
> + * - Adjustments to the period length will only take effect during the next
> + * period.
> + * - The hardware only supports offsets in [0, period - duty_cycle]
> + */
> +
......
> +
> + if (wfhw->rate) {
> + if (!was_enabled) {
> + dev_dbg(&chip->dev, "Enabling PWM output\n");
> + ret = clk_enable(pc->pwmf->core);
> + if (ret)
> + goto err_mfpwm_release;
> + ret = clk_set_rate_exclusive(pc->pwmf->core, wfhw->rate);
> + if (ret) {
> + clk_disable(pc->pwmf->core);
> + goto err_mfpwm_release;
> + }
> +
> + /*
> + * Output should be on now, acquire device to guarantee
> + * exclusion with other device functions while it's on.
> + *
> + * It's highly unlikely that this fails, as mfpwm has
> + * already been acquired before, and this is just a
> + * usage counter increase. Not worth the added
> + * complexity of clearing the PWMV4_REG_ENABLE again,
> + * especially considering the CTRL_UPDATE_EN behaviour.
> + */
> + ret = mfpwm_acquire(pc->pwmf);
> + if (ret) {
> + clk_rate_exclusive_put(pc->pwmf->core);
> + clk_disable(pc->pwmf->core);
> + goto err_mfpwm_release;
> + }
> + }
> + } else if (was_enabled) {
> + dev_dbg(&chip->dev, "Disabling PWM output\n");
Delay for one full PWM period before disabling the dclk.
Although this may introduce some latency for disable -> re-enable
operations, it ensures that the state after shutdown aligns with the
actual polarity configuration.
> + clk_rate_exclusive_put(pc->pwmf->core);
> + clk_disable(pc->pwmf->core);
> + /* Output is off now, extra release to balance extra acquire */
> + mfpwm_release(pc->pwmf);
> + }
> +
> +err_mfpwm_release:
> + mfpwm_release(pc->pwmf);
> +
> + return ret;
> +}
> +
>
Best regards,
Damon
^ permalink raw reply
* [PATCH v2 1/4] dt-bindings: timer: allwinner,sun5i-a13-hstimer: add H616 and D1
From: Michal Piekos @ 2026-04-26 10:15 UTC (permalink / raw)
To: Daniel Lezcano, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Maxime Ripard
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-sunxi,
Michal Piekos
In-Reply-To: <20260426-h616-t113s-hstimer-v2-0-e65e9dc0c9da@mmpsystems.pl>
D1 is similar to existing sun5i, but with different register offsets.
H616 uses same offsets as D1.
Add allwinner,sun20i-d1-hstimer
Add allwinner,sun50i-h616-hstimer with fallback to
allwinner,sun20i-d1-hstimer
Extend schema condition for interrupts to cover D1 compatible variant.
Signed-off-by: Michal Piekos <michal.piekos@mmpsystems.pl>
---
.../devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml b/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml
index f1853daec2f9..3e2725c56995 100644
--- a/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml
+++ b/Documentation/devicetree/bindings/timer/allwinner,sun5i-a13-hstimer.yaml
@@ -15,9 +15,13 @@ properties:
oneOf:
- const: allwinner,sun5i-a13-hstimer
- const: allwinner,sun7i-a20-hstimer
+ - const: allwinner,sun20i-d1-hstimer
- items:
- const: allwinner,sun6i-a31-hstimer
- const: allwinner,sun7i-a20-hstimer
+ - items:
+ - const: allwinner,sun50i-h616-hstimer
+ - const: allwinner,sun20i-d1-hstimer
reg:
maxItems: 1
@@ -45,7 +49,10 @@ required:
if:
properties:
compatible:
- const: allwinner,sun5i-a13-hstimer
+ anyOf:
+ - const: allwinner,sun5i-a13-hstimer
+ - contains:
+ const: allwinner,sun20i-d1-hstimer
then:
properties:
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/4] Add hstimer support for H616 and T113-S3
From: Michal Piekos @ 2026-04-26 10:15 UTC (permalink / raw)
To: Daniel Lezcano, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Maxime Ripard
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-sunxi,
Michal Piekos
Add support for Allwinner D1 high speed timer in sun5i hstimer driver
and describe corresponding nodes in dts for H616 and T113-S3 SoC's.
D1 and H616 uses same model as existing driver except register shift
compared to older variants.
Added register layout abstraction in the driver, extended the binding
with new compatibles and wired up dts nodes for T113-S3 and H616 which
uses D1 as fallback compatible.
Signed-off-by: Michal Piekos <michal.piekos@mmpsystems.pl>
---
Changes in v2:
- Change driver handling of different offsets to using quirks
- Change from t113s to d1 as the fallback compatible string
- Fix conditional compatible matching
- Link to v1: https://lore.kernel.org/r/20260419-h616-t113s-hstimer-v1-0-1af74ebef7c5@mmpsystems.pl
---
Michal Piekos (4):
dt-bindings: timer: allwinner,sun5i-a13-hstimer: add H616 and D1
clocksource/drivers/sun5i: add D1 hstimer support
arm: dts: allwinner: t113s: add hstimer node
arm64: dts: allwinner: h616: add hstimer node
.../timer/allwinner,sun5i-a13-hstimer.yaml | 9 ++-
arch/arm/boot/dts/allwinner/sun8i-t113s.dtsi | 11 +++
arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 10 +++
drivers/clocksource/timer-sun5i.c | 88 +++++++++++++++++-----
4 files changed, 98 insertions(+), 20 deletions(-)
---
base-commit: 897d54018cc9aa97fd1529ca08a53b429d05a566
change-id: 20260413-h616-t113s-hstimer-62939948f91c
Best regards,
--
Michal Piekos <michal.piekos@mmpsystems.pl>
^ permalink raw reply
* [PATCH v2 3/4] arm: dts: allwinner: t113s: add hstimer node
From: Michal Piekos @ 2026-04-26 10:15 UTC (permalink / raw)
To: Daniel Lezcano, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Maxime Ripard
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-sunxi,
Michal Piekos
In-Reply-To: <20260426-h616-t113s-hstimer-v2-0-e65e9dc0c9da@mmpsystems.pl>
Describe high speed timer block on Allwinner T113-S3.
Tested on LCPI-PC-T113/F113:
- hstimer is registered as clocksource
- switching clocksource at runtime works
- after rating increase hstimer operates as a broadcast clockevent device
Signed-off-by: Michal Piekos <michal.piekos@mmpsystems.pl>
---
arch/arm/boot/dts/allwinner/sun8i-t113s.dtsi | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/arch/arm/boot/dts/allwinner/sun8i-t113s.dtsi b/arch/arm/boot/dts/allwinner/sun8i-t113s.dtsi
index 424f4a2487e2..40e76cfc8a1d 100644
--- a/arch/arm/boot/dts/allwinner/sun8i-t113s.dtsi
+++ b/arch/arm/boot/dts/allwinner/sun8i-t113s.dtsi
@@ -34,6 +34,17 @@ cpu1: cpu@1 {
};
};
+ soc {
+ hstimer@3008000 {
+ compatible = "allwinner,sun20i-d1-hstimer";
+ reg = <0x03008000 0x1000>;
+ interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_HSTIMER>;
+ resets = <&ccu RST_BUS_HSTIMER>;
+ };
+ };
+
gic: interrupt-controller@1c81000 {
compatible = "arm,gic-400";
reg = <0x03021000 0x1000>,
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/4] clocksource/drivers/sun5i: add D1 hstimer support
From: Michal Piekos @ 2026-04-26 10:15 UTC (permalink / raw)
To: Daniel Lezcano, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Maxime Ripard
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-sunxi,
Michal Piekos
In-Reply-To: <20260426-h616-t113s-hstimer-v2-0-e65e9dc0c9da@mmpsystems.pl>
D1 high speed timer differs from existing timer-sun5i by register base
offset.
Add sunxi quirks to handle D1 specific offset.
Add D1 compatible string to OF match table.
Signed-off-by: Michal Piekos <michal.piekos@mmpsystems.pl>
---
drivers/clocksource/timer-sun5i.c | 88 ++++++++++++++++++++++++++++++---------
1 file changed, 69 insertions(+), 19 deletions(-)
diff --git a/drivers/clocksource/timer-sun5i.c b/drivers/clocksource/timer-sun5i.c
index f827d3f98f60..2b17904debfa 100644
--- a/drivers/clocksource/timer-sun5i.c
+++ b/drivers/clocksource/timer-sun5i.c
@@ -18,21 +18,30 @@
#include <linux/slab.h>
#include <linux/platform_device.h>
-#define TIMER_IRQ_EN_REG 0x00
+#define TIMER_IRQ_EN_REG 0x00
#define TIMER_IRQ_EN(val) BIT(val)
-#define TIMER_IRQ_ST_REG 0x04
-#define TIMER_CTL_REG(val) (0x20 * (val) + 0x10)
+#define TIMER_IRQ_ST_REG 0x04
+#define TIMER_CTL_REG(val, offset) (0x20 * (val) + 0x10 + (offset))
#define TIMER_CTL_ENABLE BIT(0)
#define TIMER_CTL_RELOAD BIT(1)
#define TIMER_CTL_CLK_PRES(val) (((val) & 0x7) << 4)
#define TIMER_CTL_ONESHOT BIT(7)
-#define TIMER_INTVAL_LO_REG(val) (0x20 * (val) + 0x14)
-#define TIMER_INTVAL_HI_REG(val) (0x20 * (val) + 0x18)
-#define TIMER_CNTVAL_LO_REG(val) (0x20 * (val) + 0x1c)
-#define TIMER_CNTVAL_HI_REG(val) (0x20 * (val) + 0x20)
+#define TIMER_INTVAL_LO_REG(val, offset) (0x20 * (val) + 0x14 + (offset))
+#define TIMER_INTVAL_HI_REG(val, offset) (0x20 * (val) + 0x18 + (offset))
+#define TIMER_CNTVAL_LO_REG(val, offset) (0x20 * (val) + 0x1c + (offset))
+#define TIMER_CNTVAL_HI_REG(val, offset) (0x20 * (val) + 0x20 + (offset))
#define TIMER_SYNC_TICKS 3
+/**
+ * struct sunxi_timer_quirks - Differences between SoC variants.
+ *
+ * @from_ctl_base_offset: offset applied from ctl register onwards
+ */
+struct sunxi_timer_quirks {
+ u32 from_ctl_base_offset;
+};
+
struct sun5i_timer {
void __iomem *base;
struct clk *clk;
@@ -40,6 +49,7 @@ struct sun5i_timer {
u32 ticks_per_jiffy;
struct clocksource clksrc;
struct clock_event_device clkevt;
+ const struct sunxi_timer_quirks *quirks;
};
#define nb_to_sun5i_timer(x) \
@@ -57,28 +67,36 @@ struct sun5i_timer {
*/
static void sun5i_clkevt_sync(struct sun5i_timer *ce)
{
- u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1));
+ u32 offset = ce->quirks->from_ctl_base_offset;
+ u32 old = readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset));
- while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1))) < TIMER_SYNC_TICKS)
+ while ((old - readl(ce->base + TIMER_CNTVAL_LO_REG(1, offset))) <
+ TIMER_SYNC_TICKS)
cpu_relax();
}
static void sun5i_clkevt_time_stop(struct sun5i_timer *ce, u8 timer)
{
- u32 val = readl(ce->base + TIMER_CTL_REG(timer));
- writel(val & ~TIMER_CTL_ENABLE, ce->base + TIMER_CTL_REG(timer));
+ u32 offset = ce->quirks->from_ctl_base_offset;
+ u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset));
+
+ writel(val & ~TIMER_CTL_ENABLE,
+ ce->base + TIMER_CTL_REG(timer, offset));
sun5i_clkevt_sync(ce);
}
static void sun5i_clkevt_time_setup(struct sun5i_timer *ce, u8 timer, u32 delay)
{
- writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer));
+ u32 offset = ce->quirks->from_ctl_base_offset;
+
+ writel(delay, ce->base + TIMER_INTVAL_LO_REG(timer, offset));
}
static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool periodic)
{
- u32 val = readl(ce->base + TIMER_CTL_REG(timer));
+ u32 offset = ce->quirks->from_ctl_base_offset;
+ u32 val = readl(ce->base + TIMER_CTL_REG(timer, offset));
if (periodic)
val &= ~TIMER_CTL_ONESHOT;
@@ -86,7 +104,7 @@ static void sun5i_clkevt_time_start(struct sun5i_timer *ce, u8 timer, bool perio
val |= TIMER_CTL_ONESHOT;
writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
- ce->base + TIMER_CTL_REG(timer));
+ ce->base + TIMER_CTL_REG(timer, offset));
}
static int sun5i_clkevt_shutdown(struct clock_event_device *clkevt)
@@ -141,8 +159,9 @@ static irqreturn_t sun5i_timer_interrupt(int irq, void *dev_id)
static u64 sun5i_clksrc_read(struct clocksource *clksrc)
{
struct sun5i_timer *cs = clksrc_to_sun5i_timer(clksrc);
+ u32 offset = cs->quirks->from_ctl_base_offset;
- return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1));
+ return ~readl(cs->base + TIMER_CNTVAL_LO_REG(1, offset));
}
static int sun5i_rate_cb(struct notifier_block *nb,
@@ -173,12 +192,13 @@ static int sun5i_setup_clocksource(struct platform_device *pdev,
unsigned long rate)
{
struct sun5i_timer *cs = platform_get_drvdata(pdev);
+ u32 offset = cs->quirks->from_ctl_base_offset;
void __iomem *base = cs->base;
int ret;
- writel(~0, base + TIMER_INTVAL_LO_REG(1));
+ writel(~0, base + TIMER_INTVAL_LO_REG(1, offset));
writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
- base + TIMER_CTL_REG(1));
+ base + TIMER_CTL_REG(1, offset));
cs->clksrc.name = pdev->dev.of_node->name;
cs->clksrc.rating = 340;
@@ -237,7 +257,9 @@ static int sun5i_setup_clockevent(struct platform_device *pdev,
static int sun5i_timer_probe(struct platform_device *pdev)
{
+ const struct sunxi_timer_quirks *quirks;
struct device *dev = &pdev->dev;
+ struct device_node *node = dev_of_node(&pdev->dev);
struct sun5i_timer *st;
struct reset_control *rstc;
void __iomem *timer_base;
@@ -251,6 +273,9 @@ static int sun5i_timer_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, st);
+ if (!node)
+ return -EINVAL;
+
timer_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(timer_base)) {
dev_err(dev, "Can't map registers\n");
@@ -273,11 +298,18 @@ static int sun5i_timer_probe(struct platform_device *pdev)
return -EINVAL;
}
+ quirks = of_device_get_match_data(&pdev->dev);
+ if (!quirks) {
+ dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
+ return -ENODEV;
+ }
+
st->base = timer_base;
st->ticks_per_jiffy = DIV_ROUND_UP(rate, HZ);
st->clk = clk;
st->clk_rate_cb.notifier_call = sun5i_rate_cb;
st->clk_rate_cb.next = NULL;
+ st->quirks = quirks;
ret = devm_clk_notifier_register(dev, clk, &st->clk_rate_cb);
if (ret) {
@@ -311,9 +343,27 @@ static void sun5i_timer_remove(struct platform_device *pdev)
clocksource_unregister(&st->clksrc);
}
+static const struct sunxi_timer_quirks sun5i_sun7i_hstimer_quirks = {
+ .from_ctl_base_offset = 0x0,
+};
+
+static const struct sunxi_timer_quirks sun20i_d1_hstimer_quirks = {
+ .from_ctl_base_offset = 0x10,
+};
+
static const struct of_device_id sun5i_timer_of_match[] = {
- { .compatible = "allwinner,sun5i-a13-hstimer" },
- { .compatible = "allwinner,sun7i-a20-hstimer" },
+ {
+ .compatible = "allwinner,sun5i-a13-hstimer",
+ .data = &sun5i_sun7i_hstimer_quirks,
+ },
+ {
+ .compatible = "allwinner,sun7i-a20-hstimer",
+ .data = &sun5i_sun7i_hstimer_quirks,
+ },
+ {
+ .compatible = "allwinner,sun20i-d1-hstimer",
+ .data = &sun20i_d1_hstimer_quirks,
+ },
{},
};
MODULE_DEVICE_TABLE(of, sun5i_timer_of_match);
--
2.43.0
^ permalink raw reply related
* [PATCH v2 4/4] arm64: dts: allwinner: h616: add hstimer node
From: Michal Piekos @ 2026-04-26 10:15 UTC (permalink / raw)
To: Daniel Lezcano, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Maxime Ripard
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-sunxi,
Michal Piekos
In-Reply-To: <20260426-h616-t113s-hstimer-v2-0-e65e9dc0c9da@mmpsystems.pl>
Describe high speed timer block on Allwinner H616.
Tested on Orange Pi Zero 3:
- hstimer is registered as clocksource
- switching clocksource at runtime works
- after rating increase hstimer operates as a broadcast clockevent device
Signed-off-by: Michal Piekos <michal.piekos@mmpsystems.pl>
---
arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
index bf054869e78b..1356e5df2562 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
@@ -237,6 +237,16 @@ timer0: timer@3009000 {
clocks = <&osc24M>;
};
+ hstimer@3005000 {
+ compatible = "allwinner,sun50i-h616-hstimer",
+ "allwinner,sun20i-d1-hstimer";
+ reg = <0x03005000 0x1000>;
+ interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_HSTIMER>;
+ resets = <&ccu RST_BUS_HSTIMER>;
+ };
+
watchdog: watchdog@30090a0 {
compatible = "allwinner,sun50i-h616-wdt",
"allwinner,sun6i-a31-wdt";
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] iio: adc: meson-saradc: fix calibration buffer leak on error
From: Jonathan Cameron @ 2026-04-26 10:40 UTC (permalink / raw)
To: Felix Gu
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Neil Armstrong,
Kevin Hilman, Jerome Brunet, Martin Blumenstingl, Rosen Penev,
linux-iio, linux-arm-kernel, linux-amlogic, linux-kernel
In-Reply-To: <CAN4SLj0hp4N_WXPxxQcLjY+espY96LfizsNEqJ3L2UF4_GoCHg@mail.gmail.com>
On Sun, 26 Apr 2026 11:54:47 +0800
Felix Gu <ustc.gu@gmail.com> wrote:
> On Sun, Apr 26, 2026 at 7:16 AM David Lechner <dlechner@baylibre.com> wrote:
> > I just updated iio/testing today and I see return without kfree().
> >
> > priv->tsc_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "amlogic,hhi-sysctrl");
> > if (IS_ERR(priv->tsc_regmap))
> > return dev_err_probe(dev, PTR_ERR(priv->tsc_regmap),
> > "failed to get amlogic,hhi-sysctrl regmap\n");
> > >> Should `#include <linux/cleanup.h>` though rather that relying on it being
> > >> included through another header.
> > >>
> > >> With that fixed...
> > >>
> > >> Reviewed-by: David Lechner <dlechner@baylibre.com>
> > >>
> > >
> >
> Hi Jonathan,
> This code was merged into linux/master yesterday, perhaps you haven't
> updated the code base.
Ah. My bad. I tend to be lazy and use elixir to check stuff like this
and had completely forgotten I merged the patch that broke this :(
J
>
> Best regards,
> Felix
>
^ permalink raw reply
* Re: [PATCH] iio: adc: meson-saradc: fix calibration buffer leak on error
From: Jonathan Cameron @ 2026-04-26 10:41 UTC (permalink / raw)
To: Rosen Penev
Cc: Felix Gu, David Lechner, Nuno Sá, Andy Shevchenko,
Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
linux-iio, linux-arm-kernel, linux-amlogic, linux-kernel
In-Reply-To: <CAKxU2N9_Nmk_apUZLSCGssXpwxYOz9k99ggRcUM3EPGUzaVi7w@mail.gmail.com>
On Sat, 25 Apr 2026 12:20:55 -0700
Rosen Penev <rosenp@gmail.com> wrote:
> On Sat, Apr 25, 2026 at 9:07 AM Felix Gu <ustc.gu@gmail.com> wrote:
> >
> > meson_sar_adc_temp_sensor_init() allocates a buffer with
> > nvmem_cell_read(), but the old code leaked it if
> > syscon_regmap_lookup_by_phandle() failed.
> >
> > Switch buf to __free(kfree) so all return paths release it.
> >
> > Fixes: d6f2eac64403 ("iio: adc: meson: no devm for nvmem_cell_get")
> > Signed-off-by: Felix Gu <ustc.gu@gmail.com>
> > ---
> > drivers/iio/adc/meson_saradc.c | 10 +++-------
> > 1 file changed, 3 insertions(+), 7 deletions(-)
> >
> > diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
> > index 23991a3612bd..9708ddcc4919 100644
> > --- a/drivers/iio/adc/meson_saradc.c
> > +++ b/drivers/iio/adc/meson_saradc.c
> > @@ -786,7 +786,7 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
> > static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev)
> > {
> > struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
> > - u8 *buf, trimming_bits, trimming_mask, upper_adc_val;
> > + u8 trimming_bits, trimming_mask, upper_adc_val;
> > struct device *dev = indio_dev->dev.parent;
> > struct nvmem_cell *temperature_calib;
> > size_t read_len;
> > @@ -807,14 +807,12 @@ static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev)
> > }
> >
> > read_len = MESON_SAR_ADC_EFUSE_BYTES;
> > - buf = nvmem_cell_read(temperature_calib, &read_len);
> > + u8 *buf __free(kfree) = nvmem_cell_read(temperature_calib, &read_len);
> > nvmem_cell_put(temperature_calib);
> > if (IS_ERR(buf))
> > return dev_err_probe(dev, PTR_ERR(buf), "failed to read temperature_calib cell\n");
> > - if (read_len != MESON_SAR_ADC_EFUSE_BYTES) {
> > - kfree(buf);
> I don't like this. Just add the missing kfree.
That is the minimal fix, so we should probably do that first
even if we then circle back to consider if __free() magic is worth using here.
J
> > + if (read_len != MESON_SAR_ADC_EFUSE_BYTES)
> > return dev_err_probe(dev, -EINVAL, "invalid read size of temperature_calib cell\n");
> > - }
> >
> > priv->tsc_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "amlogic,hhi-sysctrl");
> > if (IS_ERR(priv->tsc_regmap))
> > @@ -835,8 +833,6 @@ static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev)
> > priv->temperature_sensor_adc_val |= upper_adc_val << BITS_PER_BYTE;
> > priv->temperature_sensor_adc_val >>= trimming_bits;
> >
> > - kfree(buf);
> > -
> > return 0;
> > }
> >
> >
> > ---
> > base-commit: 7080e32d3f09d8688c4a87d81bdcc71f7f606b16
> > change-id: 20260425-meson_saradc-54abc52d9b31
> >
> > Best regards,
> > --
> > Felix Gu <ustc.gu@gmail.com>
> >
^ permalink raw reply
* Re: [PATCH v5 4/6] counter: Add rockchip-pwm-capture driver
From: Damon Ding @ 2026-04-26 10:55 UTC (permalink / raw)
To: Nicolas Frattaroli, Uwe Kleine-König, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Lee Jones,
William Breathitt Gray
Cc: kernel, Jonas Karlman, Alexey Charkov, linux-rockchip, linux-pwm,
devicetree, linux-arm-kernel, linux-kernel, linux-iio
In-Reply-To: <20260420-rk3576-pwm-v5-4-ae7cfbbe5427@collabora.com>
Hi Nicolas,
On 4/20/2026 9:52 PM, Nicolas Frattaroli wrote:
> Among many other things, Rockchip's new PWMv4 IP in the RK3576 supports
> PWM capture functionality.
>
> Add a basic driver for this that works to expose HPC/LPC counts and
> state change events to userspace through the counter framework. It's
> quite basic, but works well enough to demonstrate the device function
> exclusion stuff that mfpwm does, in order to eventually support all the
> functions of this device in drivers within their appropriate subsystems,
> without them interfering with each other.
>
> Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> ---
> MAINTAINERS | 1 +
> drivers/counter/Kconfig | 11 ++
> drivers/counter/Makefile | 1 +
> drivers/counter/rockchip-pwm-capture.c | 307 +++++++++++++++++++++++++++++++++
> 4 files changed, 320 insertions(+)
>
For functional validation, I connected PWM0/PWM1 (continuous output)
to PWM2 (capture input) pairwise.
I enabled the counter via:
/sys/bus/counter/devices/counter0/count0/enable
Then I verified the functionality by reading the count values from:
/sys/bus/counter/devices/counter0/count0/count
/sys/bus/counter/devices/counter0/count1/count
Tested-by: Damon Ding <damon.ding@rock-chips.com>
BTW: Is there any user-space test tool similar to libpwm for the
counter subsystem?
......
> diff --git a/drivers/counter/rockchip-pwm-capture.c b/drivers/counter/rockchip-pwm-capture.c
> new file mode 100644
> index 000000000000..09a92f2bc409
> --- /dev/null
> +++ b/drivers/counter/rockchip-pwm-capture.c
> @@ -0,0 +1,307 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2025 Collabora Ltd.
> + *
> + * A counter driver for the Pulse-Width-Modulation (PWM) hardware found on
> + * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". It
> + * allows for measuring the high cycles and low cycles of a PWM signal through
> + * the generic counter framework, while guaranteeing exclusive use over the
> + * MFPWM device while the counter is enabled.
> + *
> + * Authors:
> + * Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> + */
> +
> +#include <linux/cleanup.h>
> +#include <linux/counter.h>
> +#include <linux/devm-helpers.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/rockchip-mfpwm.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/spinlock.h>
> +
> +#define RKPWMC_INT_MASK (PWMV4_INT_LPC | PWMV4_INT_HPC)
> +
> +struct rockchip_pwm_capture {
> + struct rockchip_mfpwm_func *pwmf;
> + struct counter_device *counter;
> +};
> +
> +static struct counter_signal rkpwmc_signals[] = {
> + {
> + .id = 0,
> + .name = "PWM Clock"
> + },
> +};
> +
> +static const enum counter_synapse_action rkpwmc_hpc_lpc_actions[] = {
> + COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
> + COUNTER_SYNAPSE_ACTION_NONE,
> +};
For the capture function, it uses the PWM's reference clock (dclk) as
the time base to measure how many reference cycles the high and low
levels of the input waveform last respectively.
I find it a bit strange to set COUNTER_SYNAPSE_ACTION_BOTH_EDGES for
counting. If we treat the input waveform as a sequence of square waves
sampled by dclk cycles, it feels like we should count on a single edge
(rising edge only) rather than both edges.
> +
> +static struct counter_synapse rkpwmc_pwm_synapses[] = {
> + {
> + .actions_list = rkpwmc_hpc_lpc_actions,
> + .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions),
> + .signal = &rkpwmc_signals[0]
> + },
> +};
> +
Best regards,
Damon
^ permalink raw reply
* [PATCH v6 0/6] Add support for ZTE zx297520v3
From: Stefan Dösinger @ 2026-04-26 11:54 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
linux-serial, Stefan Dösinger
Hi,
This is a follow-up on my RFC patches from January [0] for ZTE's
zx297520v3 chipset. This chipset is popular in cheap LTE-to-wifi routers
sold in developing countries. My goal is to run OpenWRT on them. I made
more progress in more work on this SoC and it is time to get serious
about code review and upstreaming.
Since my version in January I managed to get more hardware running: SPI,
I2C, PMIC with real time clock and voltage regulators, Watchdog. LTE is
not working yet, but I am able to start the coprocessor that handles it
and talk to it via mailbox + shared memory. Wifi is working on a few
more devices. Since WiFi, USB and Ethernet are working, the devices can
have actual use with OpenWRT even without LTE.
Another hacker created a free software program to talk to the USB loader
[1] and boot U-Boot and Linux without modifying the on disk files. At
the moment it needs a proprietary blob, so my documentation is
emphasising booting with the on-device U-Boot.
This patchset here is mostly unmodified from the version I sent in
January. It is the bare minimum to get an interactive shell working on
the UART. Future patches can be found on my git repository [2] for those
curious to peek ahead. The first 30 patches are in reasonable shape, but
the further you go the more cleanup is necessary. I expect all of the
patches go require a few rounds of feedback though.
My plan for upstreaming is largly this:
1) This bare minimum boot patchset
2) Add clock and pinctrl drivers
3) Add standard hardware to the device tree
4) Add zx29 specific drivers one by one: Watchdog, spi, i2c, DMA, PMIC,
battery
5) SDIO backend for rtl8xxxu
6) rproc, mailbox and rpmsg
I am willing to maintain support for the SoC within reason. My patches
add myself as maintainer. This is a hobby project for me though, keep
that in mind if you want to ship a commercial product with these SoCs
and upstreaming Linux.
Cheers,
Stefan
0: https://lists.infradead.org/pipermail/linux-arm-kernel/2026-January/1099306.html
1: https://github.com/zx297520v3-mainline/zx297520v3-loader
2: https://gitlab.com/stefandoesinger/zx297520-kernel/
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Patch changelog:
Changes in v6:
Squashed DT commits into one
Removed for-now unused board DT bindings
Add "zte" to DT patch subject
Regenerate the defconfig with make savedefconfig
- Link to v5: https://lore.kernel.org/r/20260421-send-v5-0-ace038e63515@gmail.com
v5:
Spelling fixes
Renamed dlink-dwr-932m.dts to zx297520v3-dlink-dwr932m.dts
DT binding indentation fixes
Use a manufacturer 0x8b for the UART, fix patch prefix
Declare all UARTs, remove uart aliases for now
Consistent license declarations. I made every new file except the DT
binding GPL-2.0-only but I don't particularly mind GPL-2.0-or-later
either.
- Link to v4: https://lore.kernel.org/r/20260416-send-v4-0-e19d02b944ec@gmail.com
v4: rename zx29.yaml to zte.yaml and add board enums
v3: Remove [RFC] tag, add defconfig
v2: checkpatch.pl fixes
---
Stefan Dösinger (6):
dt-bindings: arm: zte: Add D-Link DWR932M board based on zx297520v3 SoC
ARM: zte: Add zx297520v3 platform support
ARM: zte: Add support for zx29 low level debug
amba/serial: amba-pl011: Bring back zx29 UART support
ARM: dts: zte: Add D-Link DWR-932M support
ARM: zte: defconfig: Add a zx29 defconfig file
Documentation/arch/arm/zte/zx297520v3.rst | 158 +++++++++++++++++++++
Documentation/devicetree/bindings/arm/zte.yaml | 26 ++++
MAINTAINERS | 7 +
arch/arm/Kconfig | 2 +
arch/arm/Kconfig.debug | 12 ++
arch/arm/Makefile | 1 +
arch/arm/boot/dts/Makefile | 1 +
arch/arm/boot/dts/zte/Makefile | 3 +
arch/arm/boot/dts/zte/zx297520v3-dlink-dwr932m.dts | 22 +++
arch/arm/boot/dts/zte/zx297520v3.dtsi | 103 ++++++++++++++
arch/arm/configs/zx29_defconfig | 54 +++++++
arch/arm/include/debug/pl01x.S | 7 +
arch/arm/mach-zte/Kconfig | 26 ++++
arch/arm/mach-zte/Makefile | 2 +
arch/arm/mach-zte/zx297520v3.c | 19 +++
drivers/tty/serial/amba-pl011.c | 42 ++++++
16 files changed, 485 insertions(+)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260416-send-5c08e095e5c9
Best regards,
--
Stefan Dösinger <stefandoesinger@gmail.com>
^ permalink raw reply
* [PATCH v6 1/6] dt-bindings: arm: zte: Add D-Link DWR932M board based on zx297520v3 SoC
From: Stefan Dösinger @ 2026-04-26 11:54 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
linux-serial, Stefan Dösinger
In-Reply-To: <20260426-send-v6-0-d49efa72bb09@gmail.com>
This adds a new binding file for ZTE, containing their zx297520v3 SoC
and one board (D-Link DWR-932M) based on it.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changelog:
v6:
Removed extra boards, I'll add them when submitting their individual
DTS files. Rephrase the subject to add "zte" and remove the redundant
use of "binding".
Moved the devicetree bindings patch ahead of the implementation patches.
Moved the MAINTAINERS section from "ZX29" to "ARM/ZTE".
---
Documentation/devicetree/bindings/arm/zte.yaml | 26 ++++++++++++++++++++++++++
MAINTAINERS | 4 ++++
2 files changed, 30 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/zte.yaml b/Documentation/devicetree/bindings/arm/zte.yaml
new file mode 100644
index 000000000000..f028d2cec7ab
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/zte.yaml
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/zte.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE zx platforms
+
+maintainers:
+ - Stefan Dösinger <stefandoesinger@gmail.com>
+
+description: |
+ ARM platforms using SoCs designed by ZTE. Currently this supports devices
+ based on the zx297520v3 SoC which is found in LTE routers.
+
+properties:
+ $nodename:
+ const: "/"
+ compatible:
+ oneOf:
+ - items:
+ - enum:
+ - dlink,dwr932m
+ - const: zte,zx297520v3
+
+additionalProperties: true
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f..b768b9da37a4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3772,6 +3772,10 @@ F: drivers/video/fbdev/vt8500lcdfb.*
F: drivers/video/fbdev/wm8505fb*
F: drivers/video/fbdev/wmt_ge_rops.*
+ARM/ZTE ZX29 SOC SUPPORT
+M: Stefan Dösinger <stefandoesinger@gmail.com>
+F: Documentation/devicetree/bindings/arm/zte.yaml
+
ARM/ZYNQ ARCHITECTURE
M: Michal Simek <michal.simek@amd.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
--
2.53.0
^ permalink raw reply related
* [PATCH v6 2/6] ARM: zte: Add zx297520v3 platform support
From: Stefan Dösinger @ 2026-04-26 11:54 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
linux-serial, Stefan Dösinger
In-Reply-To: <20260426-send-v6-0-d49efa72bb09@gmail.com>
This SoC is used in low end LTE-to-WiFi routers, for example some D-Link
DWR 932 revisions, ZTE K10, ZLT S10 4G, but also models that are branded
and sold by ISPs themselves. They are widespread in Africa, China,
Russia and Eastern Europe.
This SoC is a relative of the zx296702 and zx296718 that had some
upstream support until commit 89d4f98ae90d ("ARM: remove zte zx
platform"). My eventual goal is to enable OpenWRT to run on these
devices.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Documentation/arch/arm/zte/zx297520v3.rst | 158 ++++++++++++++++++++++++++++++
MAINTAINERS | 1 +
arch/arm/Kconfig | 2 +
arch/arm/Makefile | 1 +
arch/arm/mach-zte/Kconfig | 26 +++++
arch/arm/mach-zte/Makefile | 2 +
arch/arm/mach-zte/zx297520v3.c | 19 ++++
7 files changed, 209 insertions(+)
diff --git a/Documentation/arch/arm/zte/zx297520v3.rst b/Documentation/arch/arm/zte/zx297520v3.rst
new file mode 100644
index 000000000000..6621ea72769f
--- /dev/null
+++ b/Documentation/arch/arm/zte/zx297520v3.rst
@@ -0,0 +1,158 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+====================================
+Booting Linux on ZTE zx297520v3 SoCs
+====================================
+
+...............................................................................
+
+Author: Stefan Dösinger
+
+Date : 27 Jan 2026
+
+1. Hardware description
+---------------------------
+Zx297520v3 SoCs use a 64 bit capable Cortex-A53 CPU and GICv3, although they
+run in arm32 mode only. The CPU has support EL3, but no hypervisor (EL2) and
+it seems to lack VFP and NEON.
+
+The SoC is used in a number of cheap LTE to WiFi routers, both battery powered
+MiFis and stationary CPEs. In addition to the CPU these devices usually have
+64 MB Ram (although some is shared with the LTE chip), 128 MB NAND flash, an
+SDIO connected RTL8192-type Wifi chip limited to 2.4 ghz operation, USB 2,
+and buttons. Devices with as low as 32 MB or as high as 128 MB ram exist, as
+do devices with 8 or 16 MB of NOR flash.
+
+Some devices, especially the stationary ones, have 100 mbit Ethernet and an
+Ethernet switch.
+
+Usually the devices have LEDs for status indication, although some have SPI or
+I2C connected displays
+
+Some have an SD card slot. If it exists, it is a better choice for the root
+file system because it easily outperforms the built-in NAND.
+
+The LTE interface runs on a separate DSP called ZSP880. It is probably derived
+from LSI ZSPs and has an undocumented instruction set. The ZSP communicates
+with the main CPU via SRAM and DRAM and a mailbox hardware that can generate
+IRQs on either ends.
+
+There is also a Cortex M0 CPU, which is responsible for early HW initialization
+and starting the Cortex A53 CPU. It does not have any essential purpose once
+U-Boot is started. A SRAM-Based handover protocol exists to run custom code on
+this CPU.
+
+2. Booting via USB
+---------------------------
+
+The Boot ROM has support for booting custom code via USB. This mode can be
+entered by connecting a Boot PIN to GND or by modifying the third byte on NAND
+(set it to anything other than 0x5A aka 'Z'). A free software tool to start
+custom U-Boot and kernels can be found here:
+
+https://github.com/zx297520v3-mainline/zx297520v3-loader
+
+If USB download mode is entered but no boot commands are sent through USB, the
+device will proceed to boot normally after a few seconds. It is therefore
+possible to enable USB boot permanently and still leave the default boot files
+in place.
+
+3. Building for built-in U-Boot
+---------------------------
+The devices come with an ancient U-Boot that loads legacy uImages from NAND and
+boots them without a chance for the user to interrupt. The images are stored in
+files ap_cpuap.bin and ap_recovery.bin on a jffs2 partition named imagefs,
+usually mtd4. A file named "fotaflag" switches between the two modes.
+
+In addition to the uImage header, those files have a 384 byte signature header,
+which is used for authenticating the images on some devices. Most devices have
+this authentication disabled and it is enough to pad the uImage files with 384
+zero bytes.
+
+Builtin U-Boot also poorly sets up the CPU. Read the next section for details
+on this. It has no support for loading DTBs, so CONFIG_ARM_APPENDED_DTB is
+needed.
+
+So to build an image that boots from NAND the following steps are necessary:
+
+1) Patch the assembly code from section 3 into arch/arm/kernel/head.S.
+2) make zx29_defconfig
+3) make [-j x]
+4) cat arch/arm/boot/zImage arch/arm/boot/dts/zte/[device].dtb > kernel+dtb
+5) mkimage -A arm -O linux -T kernel -C none -a 0x20008000 -d kernel+dtb uimg
+6) dd if=/dev/zero bs=1 count=384 of=ap_recovery.bin
+7) cat uimg >> ap_recovery.bin
+8) Place this file onto imagefs on the device. Delete ap_cpuap.bin if the
+free space is not enough.
+9) Create the file fotaflag: echo -n FOTA-RECOVERY > fotaflag
+
+For development, booting ap_recovery.bin is recommended because the normal boot
+mode arms the watchdog before starting the kernel.
+
+4. CPU and GIC Setup
+---------------------------
+
+Generally CPU and GICv3 need to be set up according to the requirements spelled
+out in Documentation/arch/arm64/booting.rst. For zx297520v3 this means:
+
+1. GICD_CTLR.DS=1 to disable GIC security
+2. Enable access to ICC_SRE
+3. Disable trapping IRQs into monitor mode
+4. Configure EL2 and below to run in insecure mode.
+5. Configure timer PPIs to active-low.
+
+The kernel sources provided by ZTE do not boot either (interrupts do not work
+at all). They are incomplete in other aspects too, so it is assumed that there
+is some workaround similar to the one described in this document somewhere in
+the binary blobs.
+
+The assembly code below is given as an example of how to achieve this:
+
+```
+#include <linux/irqchip/arm-gic-v3.h>
+#include <asm/assembler.h>
+#include <asm/cp15.h>
+
+@ This allows EL1 to handle ints hat are normally handled by EL2/3.
+ldr r3, =0xf2000000
+ldr r4, =(GICD_CTLR_ARE_NS | GICD_CTLR_DS)
+str r4, [r3]
+
+cps #MON_MODE
+
+@ Work in non-secure physical address space: SCR_EL3.NS = 1. At least the UART
+@ seems to respond only to non-secure addresses. I have taken insipiration from
+@ Raspberry pi's armstub7.S here.
+@
+@ ARM docs say modify this bit in monitor mode only...
+mov r3, #0x131 @ non-secure, Make F, A bits in CPSR writeable
+ @ Allow hypervisor call.
+mcr p15, 0, r3, c1, c1, 0
+
+@ AP_PPI_MODE_REG: Configure timer PPIs (10, 11, 13, 14) to active-low.
+ldr r3, =0xF22020a8
+ldr r4, =0x50
+str r4, [r3]
+ldr r3, =0xF22020ac
+ldr r4, =0x14
+str r4, [r3]
+
+@ Enable EL2 access to ICC_SRE (bit 3, ICC_SRE_EL3.Enable). Enable system reg
+@ access to GICv3 registers (bit 0, ICC_SRE_EL3.SRE) for EL1 and EL3.
+mrc p15, 6, r3, c12, c12, 5 @ ICC_SRE_EL3
+orr r3, #0x9 @ FIXME: No defines for SRE_EL3 values?
+mcr p15, 6, r3, c12, c12, 5
+mrc p15, 0, r3, c12, c12, 5 @ ICC_SRE_EL1
+orr r3, #(ICC_SRE_EL1_SRE)
+mcr p15, 0, r3, c12, c12, 5
+
+@ Like ICC_SRE_EL3, enable EL1 access to ICC_SRE and system register access
+@ for EL2.
+mrc p15, 4, r3, c12, c9, 5 @ ICC_SRE_EL2 aka ICC_HSRE
+orr r3, r3, #(ICC_SRE_EL2_ENABLE | ICC_SRE_EL2_SRE)
+mcr p15, 4, r3, c12, c9, 5
+isb
+
+@ Back to SVC mode. TODO: Doesn't safe_svcmode_maskall do this for us anyway?
+cps #SVC_MODE
+```
diff --git a/MAINTAINERS b/MAINTAINERS
index b768b9da37a4..e707176c2114 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3775,6 +3775,7 @@ F: drivers/video/fbdev/wmt_ge_rops.*
ARM/ZTE ZX29 SOC SUPPORT
M: Stefan Dösinger <stefandoesinger@gmail.com>
F: Documentation/devicetree/bindings/arm/zte.yaml
+F: arch/arm/mach-zte/
ARM/ZYNQ ARCHITECTURE
M: Michal Simek <michal.simek@amd.com>
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index ec33376f8e2b..4217ed704e48 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -464,6 +464,8 @@ source "arch/arm/mach-versatile/Kconfig"
source "arch/arm/mach-vt8500/Kconfig"
+source "arch/arm/mach-zte/Kconfig"
+
source "arch/arm/mach-zynq/Kconfig"
# ARMv7-M architecture
diff --git a/arch/arm/Makefile b/arch/arm/Makefile
index b7de4b6b284c..573813ef5e77 100644
--- a/arch/arm/Makefile
+++ b/arch/arm/Makefile
@@ -223,6 +223,7 @@ machine-$(CONFIG_ARCH_SUNXI) += sunxi
machine-$(CONFIG_ARCH_TEGRA) += tegra
machine-$(CONFIG_ARCH_U8500) += ux500
machine-$(CONFIG_ARCH_VT8500) += vt8500
+machine-$(CONFIG_ARCH_ZTE) += zte
machine-$(CONFIG_ARCH_ZYNQ) += zynq
machine-$(CONFIG_PLAT_VERSATILE) += versatile
machine-$(CONFIG_PLAT_SPEAR) += spear
diff --git a/arch/arm/mach-zte/Kconfig b/arch/arm/mach-zte/Kconfig
new file mode 100644
index 000000000000..2e3abee94994
--- /dev/null
+++ b/arch/arm/mach-zte/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig ARCH_ZTE
+ bool "ZTE zx family"
+ depends on ARCH_MULTI_V7
+ help
+ Support for ZTE zx-based family of processors.
+
+if ARCH_ZTE
+
+config SOC_ZX297520V3
+ default y if ARCH_ZTE
+ bool "zx297520v3"
+ select ARM_GIC_V3
+ select ARM_AMBA
+ select HAVE_ARM_ARCH_TIMER
+ select PM_GENERIC_DOMAINS if PM
+ help
+ Support for ZTE zx297520v3 SoC. It is a single core SoC used in cheap
+ LTE to WiFi routers. These devices can be identified by the occurrence
+ of the string "zx297520v3" in the boot output and /proc/cpuinfo of
+ their stock firmware.
+
+ Please read Documentation/arch/arm/zte/zx297520v3.rst on how to boot
+ the kernel.
+
+endif
diff --git a/arch/arm/mach-zte/Makefile b/arch/arm/mach-zte/Makefile
new file mode 100644
index 000000000000..1bfe4fddd6af
--- /dev/null
+++ b/arch/arm/mach-zte/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SOC_ZX297520V3) += zx297520v3.o
diff --git a/arch/arm/mach-zte/zx297520v3.c b/arch/arm/mach-zte/zx297520v3.c
new file mode 100644
index 000000000000..c11c7e836f91
--- /dev/null
+++ b/arch/arm/mach-zte/zx297520v3.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2026 Stefan Dösinger
+ */
+
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+static const char *const zx297520v3_dt_compat[] __initconst = {
+ "zte,zx297520v3",
+ NULL,
+};
+
+DT_MACHINE_START(ZX, "ZTE zx297520v3 (Device Tree)")
+ .dt_compat = zx297520v3_dt_compat,
+MACHINE_END
--
2.53.0
^ permalink raw reply related
* [PATCH v6 3/6] ARM: zte: Add support for zx29 low level debug
From: Stefan Dösinger @ 2026-04-26 11:54 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
linux-serial, Stefan Dösinger
In-Reply-To: <20260426-send-v6-0-d49efa72bb09@gmail.com>
This is based on the removed zx29 code. A separate (more complicated)
patch will re-add the register map to the pl011 serial driver.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
I am unsure about the virtual address. It doesn't seem to matter, as
long as it is a valid address. This address is based on the old removed
code. Is there a rule-of-thumb physical to virtual mapping I can use to
give a sensible default value?
---
arch/arm/Kconfig.debug | 12 ++++++++++++
arch/arm/include/debug/pl01x.S | 7 +++++++
2 files changed, 19 insertions(+)
diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug
index 366f162e147d..98d8a5a60048 100644
--- a/arch/arm/Kconfig.debug
+++ b/arch/arm/Kconfig.debug
@@ -1331,6 +1331,16 @@ choice
This option selects UART0 on VIA/Wondermedia System-on-a-chip
devices, including VT8500, WM8505, WM8650 and WM8850.
+ config DEBUG_ZTE_ZX
+ bool "Kernel low-level debugging via zx29 UART"
+ select DEBUG_UART_PL01X
+ depends on ARCH_ZTE
+ help
+ Say Y here if you are enabling ZTE zx297520v3 SOC and need
+ debug UART support. This UART is a PL011 with different
+ register addresses. The UART for boot messages on zx29 boards
+ is usually UART1 and is operating at 921600 8N1.
+
config DEBUG_ZYNQ_UART0
bool "Kernel low-level debugging on Xilinx Zynq using UART0"
depends on ARCH_ZYNQ
@@ -1545,6 +1555,7 @@ config DEBUG_UART_8250
config DEBUG_UART_PHYS
hex "Physical base address of debug UART"
+ default 0x01408000 if DEBUG_ZTE_ZX
default 0x01c28000 if DEBUG_SUNXI_UART0
default 0x01c28400 if DEBUG_SUNXI_UART1
default 0x01d0c000 if DEBUG_DAVINCI_DA8XX_UART1
@@ -1701,6 +1712,7 @@ config DEBUG_UART_VIRT
default 0xf31004c0 if DEBUG_MESON_UARTAO
default 0xf4090000 if DEBUG_LPC32XX
default 0xf4200000 if DEBUG_GEMINI
+ default 0xf4708000 if DEBUG_ZTE_ZX
default 0xf6200000 if DEBUG_PXA_UART1
default 0xf7000000 if DEBUG_SUN9I_UART0
default 0xf7000000 if DEBUG_S3C64XX_UART && DEBUG_S3C_UART0
diff --git a/arch/arm/include/debug/pl01x.S b/arch/arm/include/debug/pl01x.S
index c7e02d0628bf..0c7bfa4c10db 100644
--- a/arch/arm/include/debug/pl01x.S
+++ b/arch/arm/include/debug/pl01x.S
@@ -8,6 +8,13 @@
*/
#include <linux/amba/serial.h>
+#ifdef CONFIG_DEBUG_ZTE_ZX
+#undef UART01x_DR
+#undef UART01x_FR
+#define UART01x_DR 0x04
+#define UART01x_FR 0x14
+#endif
+
#ifdef CONFIG_DEBUG_UART_PHYS
.macro addruart, rp, rv, tmp
ldr \rp, =CONFIG_DEBUG_UART_PHYS
--
2.53.0
^ permalink raw reply related
* [PATCH v6 4/6] amba/serial: amba-pl011: Bring back zx29 UART support
From: Stefan Dösinger @ 2026-04-26 11:54 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
linux-serial, Stefan Dösinger
In-Reply-To: <20260426-send-v6-0-d49efa72bb09@gmail.com>
This is based on code removed in commit 89d4f98ae90d ("ARM: remove zte
zx platform"). I did not bring back the zx29-uart .compatible as the
arm,primecell-periphid does the job.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changes since v4:
Use ZTE's JEDEC ID instead of 0xfe for the DT-Provided AMBA ID.
---
drivers/tty/serial/amba-pl011.c | 42 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
index 7f17d288c807..f24cc403d9e0 100644
--- a/drivers/tty/serial/amba-pl011.c
+++ b/drivers/tty/serial/amba-pl011.c
@@ -216,6 +216,38 @@ static struct vendor_data vendor_st = {
.get_fifosize = get_fifosize_st,
};
+static const u16 pl011_zte_offsets[REG_ARRAY_SIZE] = {
+ [REG_DR] = ZX_UART011_DR,
+ [REG_FR] = ZX_UART011_FR,
+ [REG_LCRH_RX] = ZX_UART011_LCRH,
+ [REG_LCRH_TX] = ZX_UART011_LCRH,
+ [REG_IBRD] = ZX_UART011_IBRD,
+ [REG_FBRD] = ZX_UART011_FBRD,
+ [REG_CR] = ZX_UART011_CR,
+ [REG_IFLS] = ZX_UART011_IFLS,
+ [REG_IMSC] = ZX_UART011_IMSC,
+ [REG_RIS] = ZX_UART011_RIS,
+ [REG_MIS] = ZX_UART011_MIS,
+ [REG_ICR] = ZX_UART011_ICR,
+ [REG_DMACR] = ZX_UART011_DMACR,
+};
+
+static unsigned int get_fifosize_zte(struct amba_device *dev)
+{
+ return 16;
+}
+
+static struct vendor_data vendor_zte = {
+ .reg_offset = pl011_zte_offsets,
+ .access_32b = true,
+ .ifls = UART011_IFLS_RX4_8 | UART011_IFLS_TX4_8,
+ .fr_busy = ZX_UART01x_FR_BUSY,
+ .fr_dsr = ZX_UART01x_FR_DSR,
+ .fr_cts = ZX_UART01x_FR_CTS,
+ .fr_ri = ZX_UART011_FR_RI,
+ .get_fifosize = get_fifosize_zte,
+};
+
/* Deals with DMA transactions */
struct pl011_dmabuf {
@@ -3081,6 +3113,16 @@ static const struct amba_id pl011_ids[] = {
.mask = 0x00ffffff,
.data = &vendor_st,
},
+ {
+ /* This is an invented ID. The actual hardware that contains
+ * these ZTE UARTs (zx29 boards) has no AMBA PIDs stored. ZTE
+ * JEDEC ID (ignoring banks) and the "011" part number as used
+ * by ARM.
+ */
+ .id = 0x0008c011,
+ .mask = 0x000fffff,
+ .data = &vendor_zte,
+ },
{ 0, 0 },
};
--
2.53.0
^ permalink raw reply related
* [PATCH v6 5/6] ARM: dts: zte: Add D-Link DWR-932M support
From: Stefan Dösinger @ 2026-04-26 11:54 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
linux-serial, Stefan Dösinger
In-Reply-To: <20260426-send-v6-0-d49efa72bb09@gmail.com>
This adds base DT definition for zx297520v3 and one board that consumes it.
The stock kernel does not use the armv7 timer, but it seems to work
fine. The board has other board-specific timers that would need a driver
and I see no reason to bother with them since the arm standard timer
works.
The caveat is the non-standard GIC setup needed to handle the timer's
level-low PPI. This is the responsibility of the boot loader and
documented in Documentation/arch/arm/zte/zx297520v3.rst.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changes in
v6: Squash board + timer + uart patches into one
v5: Prepend the SoC name in the device specific DTS filename.
v4:
Declare all uarts
Remove the UART aliases for now. I can revisit this when I get my
hands on a board that exposes two UARTs.
---
MAINTAINERS | 1 +
arch/arm/boot/dts/Makefile | 1 +
arch/arm/boot/dts/zte/Makefile | 3 +
arch/arm/boot/dts/zte/zx297520v3-dlink-dwr932m.dts | 22 +++++
arch/arm/boot/dts/zte/zx297520v3.dtsi | 103 +++++++++++++++++++++
5 files changed, 130 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e707176c2114..6f51ba1c5ada 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3775,6 +3775,7 @@ F: drivers/video/fbdev/wmt_ge_rops.*
ARM/ZTE ZX29 SOC SUPPORT
M: Stefan Dösinger <stefandoesinger@gmail.com>
F: Documentation/devicetree/bindings/arm/zte.yaml
+F: arch/arm/boot/dts/zte/
F: arch/arm/mach-zte/
ARM/ZYNQ ARCHITECTURE
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index efe38eb25301..28fba538d552 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -39,3 +39,4 @@ subdir-y += unisoc
subdir-y += vt8500
subdir-y += xen
subdir-y += xilinx
+subdir-y += zte
diff --git a/arch/arm/boot/dts/zte/Makefile b/arch/arm/boot/dts/zte/Makefile
new file mode 100644
index 000000000000..f052cfbd636c
--- /dev/null
+++ b/arch/arm/boot/dts/zte/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+dtb-$(CONFIG_SOC_ZX297520V3) += \
+ zx297520v3-dlink-dwr932m.dtb
diff --git a/arch/arm/boot/dts/zte/zx297520v3-dlink-dwr932m.dts b/arch/arm/boot/dts/zte/zx297520v3-dlink-dwr932m.dts
new file mode 100644
index 000000000000..1700f46aba86
--- /dev/null
+++ b/arch/arm/boot/dts/zte/zx297520v3-dlink-dwr932m.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 Stefan Dösinger <stefandoesinger@gmail.com>
+ */
+
+/dts-v1/;
+
+#include "zx297520v3.dtsi"
+
+/ {
+ model = "D-Link DWR-932M";
+ compatible = "dlink,dwr932m", "zte,zx297520v3";
+
+ memory@20000000 {
+ device_type = "memory";
+ reg = <0x20000000 0x04000000>;
+ };
+};
+
+&uart1 {
+ status = "okay";
+};
diff --git a/arch/arm/boot/dts/zte/zx297520v3.dtsi b/arch/arm/boot/dts/zte/zx297520v3.dtsi
new file mode 100644
index 000000000000..ca65797ed926
--- /dev/null
+++ b/arch/arm/boot/dts/zte/zx297520v3.dtsi
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 Stefan Dösinger <stefandoesinger@gmail.com>
+ */
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a53";
+ reg = <0>;
+ };
+ };
+
+ /* Base bus clock and default for the UART. It will be replaced once a clock driver has
+ * been added.
+ */
+ uartclk: uartclk: uartclk-26000000 {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <26000000>;
+ };
+
+ timer {
+ compatible = "arm,armv7-timer";
+ interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
+ <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+ clock-frequency = <26000000>;
+ interrupt-parent = <&gic>;
+ /* I don't think uboot sets CNTVOFF and the stock kernel doesn't use the
+ * arm timer at all. Since this is a single CPU system I don't think it
+ * really matters that the offset is random though.
+ */
+ arm,cpu-registers-not-fw-configured;
+ };
+
+ soc {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "simple-bus";
+ interrupt-parent = <&gic>;
+ ranges;
+
+ /* The GIC has a non-standard way of configuring ints between level-low/level
+ * high or rising edge/falling edge at 0xf2202070 and onwards. See AP_INT_MODE_BASE
+ * and AP_PPI_MODE_REG in the ZTE kernel, although the offsets in the kernel source
+ * seem wrong.
+ *
+ * Everything defaults to active-high/rising edge, but the timer is active-low. We
+ * currently rely on the boot loader to change timer IRQs to active-low for us for
+ * now.
+ */
+ gic: interrupt-controller@f2000000 {
+ compatible = "arm,gic-v3";
+ interrupt-controller;
+ #interrupt-cells = <3>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xf2000000 0x10000>,
+ <0xf2040000 0x20000>;
+ };
+
+ uart0: serial@131000 {
+ compatible = "arm,primecell";
+ arm,primecell-periphid = <0x0018c011>;
+ reg = <0x00131000 0x1000>;
+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uartclk>, <&uartclk>;
+ clock-names = "uartclk", "apb_pclk";
+ status = "disabled";
+ };
+
+ uart1: serial@1408000 {
+ compatible = "arm,pl011", "arm,primecell";
+ arm,primecell-periphid = <0x0018c011>;
+ reg = <0x01408000 0x1000>;
+ interrupts = <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uartclk>, <&uartclk>;
+ clock-names = "uartclk", "apb_pclk";
+ status = "disabled";
+ };
+
+ uart2: serial@140d000 {
+ compatible = "arm,primecell";
+ arm,primecell-periphid = <0x0018c011>;
+ reg = <0x0140d000 0x1000>;
+ interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uartclk>, <&uartclk>;
+ clock-names = "uartclk", "apb_pclk";
+ status = "disabled";
+ };
+ };
+};
--
2.53.0
^ permalink raw reply related
* [PATCH v6 6/6] ARM: zte: defconfig: Add a zx29 defconfig file
From: Stefan Dösinger @ 2026-04-26 11:54 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Russell King, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
linux-serial, Stefan Dösinger
In-Reply-To: <20260426-send-v6-0-d49efa72bb09@gmail.com>
This enables existing drivers for hardware that is present on this board
even if it is not present in the DT yet.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Changes: v5 to v6: Regenerate the file with make savedefconfig.
An open question: What's the appropriate name? zx29_defconfig?
zte_defconfig? zte_zx29_defconfig? There's e.g. stm32_defconfig without
an extra mention of STMicro in the name.
---
MAINTAINERS | 1 +
arch/arm/configs/zx29_defconfig | 54 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f51ba1c5ada..5dc52b84cc09 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3776,6 +3776,7 @@ ARM/ZTE ZX29 SOC SUPPORT
M: Stefan Dösinger <stefandoesinger@gmail.com>
F: Documentation/devicetree/bindings/arm/zte.yaml
F: arch/arm/boot/dts/zte/
+F: arch/arm/configs/zx29_defconfig
F: arch/arm/mach-zte/
ARM/ZYNQ ARCHITECTURE
diff --git a/arch/arm/configs/zx29_defconfig b/arch/arm/configs/zx29_defconfig
new file mode 100644
index 000000000000..54fa62ed56e7
--- /dev/null
+++ b/arch/arm/configs/zx29_defconfig
@@ -0,0 +1,54 @@
+CONFIG_SYSVIPC=y
+CONFIG_BLK_DEV_INITRD=y
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_RD_XZ is not set
+# CONFIG_RD_LZ4 is not set
+CONFIG_EXPERT=y
+CONFIG_KALLSYMS_ALL=y
+CONFIG_ARCH_ZTE=y
+CONFIG_ARM_PSCI=y
+CONFIG_ARM_APPENDED_DTB=y
+CONFIG_CMDLINE="console=ttyAMA0 earlyprintk root=/dev/ram rw"
+CONFIG_CPU_FREQ=y
+CONFIG_CPUFREQ_DT_PLATDEV=y
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_BINFMT_FLAT=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+# CONFIG_STANDALONE is not set
+# CONFIG_PREVENT_FIRMWARE_BUILD is not set
+# CONFIG_ALLOW_DEV_COREDUMP is not set
+CONFIG_MTD=y
+CONFIG_MTD_BLOCK=y
+CONFIG_BLK_DEV_RAM=y
+CONFIG_BLK_DEV_RAM_COUNT=4
+CONFIG_SRAM=y
+CONFIG_KEYBOARD_GPIO_POLLED=y
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_VT_HW_CONSOLE_BINDING=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_DEV_BUS=y
+# CONFIG_HW_RANDOM is not set
+CONFIG_PINCTRL=y
+CONFIG_GPIOLIB=y
+CONFIG_GPIO_GENERIC_PLATFORM=y
+CONFIG_POWER_RESET=y
+CONFIG_MFD_SYSCON=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+# CONFIG_HID is not set
+CONFIG_USB_DWC2=y
+CONFIG_USB_GADGET=y
+CONFIG_MMC=y
+CONFIG_MMC_DW=y
+CONFIG_RESET_CONTROLLER=y
+CONFIG_RESET_SIMPLE=y
+CONFIG_JFFS2_FS=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DEBUG_LL=y
+CONFIG_EARLY_PRINTK=y
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v13 3/4] gpio: rpmsg: add generic rpmsg GPIO driver
From: Padhi, Beleswar @ 2026-04-26 12:43 UTC (permalink / raw)
To: Shenwei Wang, Linus Walleij, Bartosz Golaszewski, Jonathan Corbet,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Mathieu Poirier, Frank Li, Sascha Hauer
Cc: Shuah Khan, linux-gpio, linux-doc, linux-kernel,
Pengutronix Kernel Team, Fabio Estevam, Peng Fan, devicetree,
linux-remoteproc, imx, linux-arm-kernel, linux-imx,
Bartosz Golaszewski, Andrew Lunn
In-Reply-To: <20260422212849.1240591-4-shenwei.wang@nxp.com>
Hello Shenwei, Greetings,
On 4/23/2026 2:58 AM, Shenwei Wang wrote:
> On an AMP platform, the system may include two processors:
s/two/multiple
> - An MCU running an RTOS
s/An MCU/MCUs
> - An MPU running Linux
>
> These processors communicate via the RPMSG protocol.
> The driver implements the standard GPIO interface, allowing
> the Linux side to control GPIO controllers which reside in
> the remote processor via RPMSG protocol.
>
> Cc: Bartosz Golaszewski <brgl@bgdev.pl>
> Cc: Andrew Lunn <andrew@lunn.ch>
> Signed-off-by: Shenwei Wang <shenwei.wang@nxp.com>
> ---
> drivers/gpio/Kconfig | 17 ++
> drivers/gpio/Makefile | 1 +
> drivers/gpio/gpio-rpmsg.c | 573 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 591 insertions(+)
> create mode 100644 drivers/gpio/gpio-rpmsg.c
>
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index 020e51e30317..4ad299fe3c6f 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -1917,6 +1917,23 @@ config GPIO_SODAVILLE
>
> endmenu
>
> +menu "RPMSG GPIO drivers"
> + depends on RPMSG
> +
> +config GPIO_RPMSG
> + tristate "Generic RPMSG GPIO support"
> + depends on OF && REMOTEPROC
> + select GPIOLIB_IRQCHIP
> + default REMOTEPROC
> + help
> + Say yes here to support the generic GPIO functions over the RPMSG
> + bus. Currently supported devices: i.MX7ULP, i.MX8ULP, i.MX8x, and
> + i.MX9x.
> +
> + If unsure, say N.
> +
> +endmenu
> +
> menu "SPI GPIO expanders"
> depends on SPI_MASTER
>
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index b267598b517d..ee75c0e65b8b 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -157,6 +157,7 @@ obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
> obj-$(CONFIG_GPIO_REALTEK_OTTO) += gpio-realtek-otto.o
> obj-$(CONFIG_GPIO_REG) += gpio-reg.o
> obj-$(CONFIG_GPIO_ROCKCHIP) += gpio-rockchip.o
> +obj-$(CONFIG_GPIO_RPMSG) += gpio-rpmsg.o
> obj-$(CONFIG_GPIO_RTD) += gpio-rtd.o
> obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
> obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
> diff --git a/drivers/gpio/gpio-rpmsg.c b/drivers/gpio/gpio-rpmsg.c
> new file mode 100644
> index 000000000000..993cde7af2fa
> --- /dev/null
> +++ b/drivers/gpio/gpio-rpmsg.c
> @@ -0,0 +1,573 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright 2026 NXP
> + *
> + * The driver exports a standard gpiochip interface to control
> + * the GPIO controllers via RPMSG on a remote processor.
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/init.h>
> +#include <linux/irqdomain.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/remoteproc.h>
> +#include <linux/rpmsg.h>
> +#include <linux/virtio_gpio.h>
> +
> +#define MAX_PORT_PER_CHANNEL 10
> +#define GPIOS_PER_PORT_DEFAULT 32
> +#define RPMSG_TIMEOUT 1000
> +
> +/* GPIO RPMSG Type */
> +#define GPIO_RPMSG_SEND 0
> +#define GPIO_RPMSG_REPLY 1
> +#define GPIO_RPMSG_NOTIFY 2
> +
> +struct rpmsg_gpio_packet {
> + u8 type; /* Message type */
> + u8 cmd; /* Command code */
> + u8 port_idx;
> + u8 line;
> + u8 val1;
> + u8 val2;
> +};
Could you please document the fields in these structs (and the below
ones too)? From the code, it looks like while sending a message from
Linux to Firmware; val1 and val2 are used to describe the values to
set. Whereas while receiving a response, val1 represents a possible
error code, and val2 represents the actual message of get type
queries. If that is so, you might want to change the variable names to
be more descriptive and also use a union.
> +
> +struct rpmsg_gpio_line {
> + u8 irq_shutdown;
> + u8 irq_unmask;
> + u8 irq_mask;
> + u32 irq_wake_enable;
> + u32 irq_type;
> + struct rpmsg_gpio_packet msg;
We don't need to have this field. More info at [0].
> +};
> +
> +struct rpmsg_gpio_info {
> + struct rpmsg_device *rpdev;
> + struct rpmsg_gpio_packet *reply_msg;
> + struct completion cmd_complete;
> + struct mutex lock;
> + void **port_store;
> +};
> +
> +struct rpmsg_gpio_port {
> + struct gpio_chip gc;
> + struct rpmsg_gpio_line lines[GPIOS_PER_PORT_DEFAULT];
> + struct rpmsg_gpio_info info;
> + u32 ngpios;
> + u32 idx;
> +};
> +
> +/**
> + * struct rpmsg_drvdata - driver data per channel.
> + * @rproc_name: the name of the remote proc.
> + * @recv_pkt: a pointer to the received packet for protocol fix up.
> + * @channel_devices: an array of the devices related to the rpdev.
> + */
> +struct rpdev_drvdata {
> + const char *rproc_name;
> + void *recv_pkt;
I don't see any use of this field in the code?
> + void *channel_devices[MAX_PORT_PER_CHANNEL];
So this is technically a rpmsg endpoint (struct rpmsg_endpoint) without
naming it "endpoint". Every rpmsg endpoint has a reference to its
parent rpmsg channel (struct rpmsg_device) which represents the same
information here. So we should use the framework standard here.
This also allows for dynamic creation and deletion of ports too! (if/when
the firmware supports it)
Which means at port init time, we should make a call to
rpmsg_create_ept() for each port tying the same callback
rpmsg_gpio_channel_callback(). And based on the 'u32 src', we could
identify the appropriate gpio port in the callback.
> +};
> +
> +static int rpmsg_gpio_send_message(struct rpmsg_gpio_port *port,
> + struct rpmsg_gpio_packet *msg)
> +{
> + struct rpmsg_gpio_info *info = &port->info;
> + int ret;
> +
> + reinit_completion(&info->cmd_complete);
> +
> + ret = rpmsg_send(info->rpdev->ept, msg, sizeof(*msg));
> + if (ret) {
> + dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", ret);
> + return ret;
> + }
> +
> + ret = wait_for_completion_timeout(&info->cmd_complete,
> + msecs_to_jiffies(RPMSG_TIMEOUT));
> + if (ret == 0) {
> + dev_err(&info->rpdev->dev, "rpmsg_send timeout!\n");
> + return -ETIMEDOUT;
> + }
> +
> + if (info->reply_msg->val1) {
> + dev_err(&info->rpdev->dev, "remote core replies an error: %d!\n",
> + info->reply_msg->val1);
> + return -EINVAL;
> + }
> +
> + /* copy the reply message */
> + memcpy(&port->lines[info->reply_msg->line].msg,
> + info->reply_msg, sizeof(*info->reply_msg));
The rpmsg_gpio_port structure already holds a pointer to the reply
message via ->info. We do not need this extra copy. More info at [0].
> +
> + return 0;
> +}
> +
> +static struct rpmsg_gpio_packet *
> +rpmsg_gpio_msg_init_common(struct rpmsg_gpio_port *port, unsigned int line, u8 cmd)
> +{
> + struct rpmsg_gpio_packet *msg = &port->lines[line].msg;
[0]: We really don't need to carry the stale message in the line
structure everytime. While sending requests, we should just request a
buffer from kzalloc and use it everytime. As far as response is
concerned, we are holding a lock everytime we are sending any message,
so we are sure the pointer in info->reply_msg would have the response to
only one request that was sent.
> +
> + memset(msg, 0, sizeof(*msg));
This also means we can get rid of this memset.
> + msg->type = GPIO_RPMSG_SEND;
> + msg->cmd = cmd;
> + msg->port_idx = port->idx;
> + msg->line = line;
> +
> + return msg;
> +}
> +
> +static int rpmsg_gpio_get(struct gpio_chip *gc, unsigned int line)
> +{
> + struct rpmsg_gpio_port *port = gpiochip_get_data(gc);
> + struct rpmsg_gpio_packet *msg;
> + int ret;
> +
> + guard(mutex)(&port->info.lock);
> +
> + msg = rpmsg_gpio_msg_init_common(port, line, VIRTIO_GPIO_MSG_GET_VALUE);
> +
> + ret = rpmsg_gpio_send_message(port, msg);
> + if (!ret)
> + ret = !!port->lines[line].msg.val2;
Which means here & everywhere else, we could just read the reply message
from !!port->info->reply_msg->val2
> +
> + return ret;
> +}
> +
> +static int rpmsg_gpio_get_direction(struct gpio_chip *gc, unsigned int line)
> +{
> + struct rpmsg_gpio_port *port = gpiochip_get_data(gc);
> + struct rpmsg_gpio_packet *msg;
> + int ret;
> +
> + guard(mutex)(&port->info.lock);
> +
> + msg = rpmsg_gpio_msg_init_common(port, line, VIRTIO_GPIO_MSG_GET_DIRECTION);
> +
> + ret = rpmsg_gpio_send_message(port, msg);
> + if (ret)
> + return ret;
> +
> + switch (port->lines[line].msg.val2) {
> + case VIRTIO_GPIO_DIRECTION_IN:
> + return GPIO_LINE_DIRECTION_IN;
> + case VIRTIO_GPIO_DIRECTION_OUT:
> + return GPIO_LINE_DIRECTION_OUT;
> + default:
> + break;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int rpmsg_gpio_direction_input(struct gpio_chip *gc, unsigned int line)
> +{
> + struct rpmsg_gpio_port *port = gpiochip_get_data(gc);
> + struct rpmsg_gpio_packet *msg;
> +
> + guard(mutex)(&port->info.lock);
> +
> + msg = rpmsg_gpio_msg_init_common(port, line, VIRTIO_GPIO_MSG_SET_DIRECTION);
> + msg->val1 = VIRTIO_GPIO_DIRECTION_IN;
> +
> + return rpmsg_gpio_send_message(port, msg);
> +}
> +
> +static int rpmsg_gpio_set(struct gpio_chip *gc, unsigned int line, int val)
> +{
> + struct rpmsg_gpio_port *port = gpiochip_get_data(gc);
> + struct rpmsg_gpio_packet *msg;
> +
> + guard(mutex)(&port->info.lock);
> +
> + msg = rpmsg_gpio_msg_init_common(port, line, VIRTIO_GPIO_MSG_SET_VALUE);
> + msg->val1 = val;
> +
> + return rpmsg_gpio_send_message(port, msg);
> +}
> +
> +static int rpmsg_gpio_direction_output(struct gpio_chip *gc, unsigned int line, int val)
> +{
> + struct rpmsg_gpio_port *port = gpiochip_get_data(gc);
> + struct rpmsg_gpio_packet *msg;
> + int ret;
> +
> + guard(mutex)(&port->info.lock);
> +
> + msg = rpmsg_gpio_msg_init_common(port, line, VIRTIO_GPIO_MSG_SET_DIRECTION);
> + msg->val1 = VIRTIO_GPIO_DIRECTION_OUT;
> +
> + ret = rpmsg_gpio_send_message(port, msg);
> + if (ret)
> + return ret;
> +
> + msg = rpmsg_gpio_msg_init_common(port, line, VIRTIO_GPIO_MSG_SET_VALUE);
> + msg->val1 = val;
> +
> + return rpmsg_gpio_send_message(port, msg);
> +}
> +
> +static int gpio_rpmsg_irq_set_type(struct irq_data *d, u32 type)
> +{
> + struct rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
> + u32 line = d->hwirq;
> +
> + switch (type) {
> + case IRQ_TYPE_EDGE_RISING:
> + type = VIRTIO_GPIO_IRQ_TYPE_EDGE_RISING;
> + irq_set_handler_locked(d, handle_simple_irq);
> + break;
> + case IRQ_TYPE_EDGE_FALLING:
> + type = VIRTIO_GPIO_IRQ_TYPE_EDGE_FALLING;
> + irq_set_handler_locked(d, handle_simple_irq);
> + break;
> + case IRQ_TYPE_EDGE_BOTH:
> + type = VIRTIO_GPIO_IRQ_TYPE_EDGE_BOTH;
> + irq_set_handler_locked(d, handle_simple_irq);
> + break;
> + case IRQ_TYPE_LEVEL_LOW:
> + type = VIRTIO_GPIO_IRQ_TYPE_LEVEL_LOW;
> + irq_set_handler_locked(d, handle_level_irq);
> + break;
> + case IRQ_TYPE_LEVEL_HIGH:
> + type = VIRTIO_GPIO_IRQ_TYPE_LEVEL_HIGH;
> + irq_set_handler_locked(d, handle_level_irq);
> + break;
> + default:
> + dev_err(&port->info.rpdev->dev, "unsupported irq type: %u\n", type);
> + return -EINVAL;
> + }
> +
> + port->lines[line].irq_type = type;
> +
> + return 0;
> +}
> +
> +static int gpio_rpmsg_irq_set_wake(struct irq_data *d, u32 enable)
> +{
> + struct rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
> + u32 line = d->hwirq;
> +
> + port->lines[line].irq_wake_enable = enable;
> +
> + return 0;
> +}
> +
> +/*
> + * This unmask/mask function is invoked in two situations:
> + * - when an interrupt is being set up, and
> + * - after an interrupt has occurred.
> + *
> + * The GPIO driver does not access hardware registers directly.
> + * Instead, it caches all relevant information locally, and then sends
> + * the accumulated state to the remote system at this stage.
It is actually sent at the .bus_sync_unlock() stage.
> + */
> +static void gpio_rpmsg_unmask_irq(struct irq_data *d)
> +{
> + struct rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
> + u32 line = d->hwirq;
> +
> + port->lines[line].irq_unmask = 1;
> +}
> +
> +static void gpio_rpmsg_mask_irq(struct irq_data *d)
> +{
> + struct rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
> + u32 line = d->hwirq;
> +
> + /*
> + * When an interrupt occurs, the remote system masks the interrupt
> + * and then sends a notification to Linux. After Linux processes
> + * that notification, it sends an RPMsg command back to the remote
> + * system to unmask the interrupt again.
> + */
> + port->lines[line].irq_mask = 1;
> +}
> +
> +static void gpio_rpmsg_irq_shutdown(struct irq_data *d)
> +{
> + struct rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
> + u32 line = d->hwirq;
> +
> + port->lines[line].irq_shutdown = 1;
> +}
> +
> +static void gpio_rpmsg_irq_bus_lock(struct irq_data *d)
> +{
> + struct rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
> +
> + mutex_lock(&port->info.lock);
> +}
> +
> +static void gpio_rpmsg_irq_bus_sync_unlock(struct irq_data *d)
> +{
> + struct rpmsg_gpio_port *port = irq_data_get_irq_chip_data(d);
> + struct rpmsg_gpio_packet *msg;
> + u32 line = d->hwirq;
> +
> + /*
> + * For mask irq, do nothing here.
> + * The remote system will mask interrupt after an interrupt occurs,
> + * and then send a notification to Linux system. After Linux system
> + * handles the notification, it sends an rpmsg back to the remote
> + * system to unmask this interrupt again.
> + */
> + if (port->lines[line].irq_mask && !port->lines[line].irq_unmask) {
> + port->lines[line].irq_mask = 0;
> + mutex_unlock(&port->info.lock);
> + return;
> + }
> +
> + msg = rpmsg_gpio_msg_init_common(port, line, VIRTIO_GPIO_MSG_IRQ_TYPE);
> +
> + if (port->lines[line].irq_shutdown) {
> + port->lines[line].irq_shutdown = 0;
> + msg->val1 = VIRTIO_GPIO_IRQ_TYPE_NONE;
> + msg->val2 = 0;
> + } else {
> + msg->val1 = port->lines[line].irq_type;
> +
> + if (port->lines[line].irq_unmask) {
> + msg->val2 = 0;
> + port->lines[line].irq_unmask = 0;
> + } else /* irq set wake */
> + msg->val2 = port->lines[line].irq_wake_enable;
> + }
> +
> + rpmsg_gpio_send_message(port, msg);
> + mutex_unlock(&port->info.lock);
> +}
> +
> +static const struct irq_chip gpio_rpmsg_irq_chip = {
> + .irq_mask = gpio_rpmsg_mask_irq,
> + .irq_unmask = gpio_rpmsg_unmask_irq,
> + .irq_set_wake = gpio_rpmsg_irq_set_wake,
> + .irq_set_type = gpio_rpmsg_irq_set_type,
> + .irq_shutdown = gpio_rpmsg_irq_shutdown,
> + .irq_bus_lock = gpio_rpmsg_irq_bus_lock,
> + .irq_bus_sync_unlock = gpio_rpmsg_irq_bus_sync_unlock,
> + .flags = IRQCHIP_IMMUTABLE,
> +};
> +
> +static void rpmsg_gpio_remove_action(void *data)
> +{
> + struct rpmsg_gpio_port *port = data;
> +
> + port->info.port_store[port->idx] = NULL;
> +}
> +
> +static int rpmsg_gpiochip_register(struct rpmsg_device *rpdev, struct device_node *np)
> +{
> + struct rpdev_drvdata *drvdata = dev_get_drvdata(&rpdev->dev);
> + struct rpmsg_gpio_port *port;
> + struct gpio_irq_chip *girq;
> + struct gpio_chip *gc;
> + int ret;
> +
> + port = devm_kzalloc(&rpdev->dev, sizeof(*port), GFP_KERNEL);
> + if (!port)
> + return -ENOMEM;
> +
> + ret = of_property_read_u32(np, "reg", &port->idx);
> + if (ret)
> + return ret;
> +
> + if (port->idx >= MAX_PORT_PER_CHANNEL)
> + return -EINVAL;
> +
> + ret = devm_mutex_init(&rpdev->dev, &port->info.lock);
> + if (ret)
> + return ret;
> +
> + ret = of_property_read_u32(np, "ngpios", &port->ngpios);
> + if (ret || port->ngpios > GPIOS_PER_PORT_DEFAULT)
> + port->ngpios = GPIOS_PER_PORT_DEFAULT;
> +
> + port->info.reply_msg = devm_kzalloc(&rpdev->dev,
> + sizeof(*port->info.reply_msg),
> + GFP_KERNEL);
> + if (!port->info.reply_msg)
> + return -ENOMEM;
> +
> + init_completion(&port->info.cmd_complete);
> + port->info.port_store = drvdata->channel_devices;
> + port->info.port_store[port->idx] = port;
> + port->info.rpdev = rpdev;
> +
> + gc = &port->gc;
> + gc->owner = THIS_MODULE;
> + gc->parent = &rpdev->dev;
> + gc->fwnode = of_fwnode_handle(np);
> + gc->ngpio = port->ngpios;
> + gc->base = -1;
> + gc->label = devm_kasprintf(&rpdev->dev, GFP_KERNEL, "%s-gpio%d",
> + drvdata->rproc_name, port->idx);
> +
> + gc->direction_input = rpmsg_gpio_direction_input;
> + gc->direction_output = rpmsg_gpio_direction_output;
> + gc->get_direction = rpmsg_gpio_get_direction;
> + gc->get = rpmsg_gpio_get;
> + gc->set = rpmsg_gpio_set;
> +
> + girq = &gc->irq;
> + gpio_irq_chip_set_chip(girq, &gpio_rpmsg_irq_chip);
> + girq->parent_handler = NULL;
> + girq->num_parents = 0;
> + girq->parents = NULL;
> + girq->chip->name = devm_kasprintf(&rpdev->dev, GFP_KERNEL, "%s-gpio%d",
> + drvdata->rproc_name, port->idx);
We could just re-use gc->label here...
> +
> + ret = devm_add_action_or_reset(&rpdev->dev, rpmsg_gpio_remove_action, port);
> + if (ret)
> + return ret;
> +
> + return devm_gpiochip_add_data(&rpdev->dev, gc, port);
> +}
> +
> +static const char *rpmsg_get_rproc_node_name(struct rpmsg_device *rpdev)
> +{
> + const char *name = NULL;
> + struct device_node *np;
> + struct rproc *rproc;
> +
> + rproc = rproc_get_by_child(&rpdev->dev);
> + if (!rproc)
> + return NULL;
> +
> + np = of_node_get(rproc->dev.of_node);
> + if (!np && rproc->dev.parent)
> + np = of_node_get(rproc->dev.parent->of_node);
> +
> + if (np) {
> + name = devm_kstrdup(&rpdev->dev, np->name, GFP_KERNEL);
> + of_node_put(np);
> + }
> +
> + return name;
> +}
> +
> +static struct device_node *
> +rpmsg_get_channel_ofnode(struct rpmsg_device *rpdev, char *chan_name)
> +{
> + struct device_node *np_chan = NULL, *np;
> + struct rproc *rproc;
> +
> + rproc = rproc_get_by_child(&rpdev->dev);
> + if (!rproc)
> + return NULL;
> +
> + np = of_node_get(rproc->dev.of_node);
> + if (!np && rproc->dev.parent)
> + np = of_node_get(rproc->dev.parent->of_node);
> +
> + /* The of_node_put() is performed by of_find_node_by_name(). */
> + if (np)
> + np_chan = of_find_node_by_name(np, chan_name);
> +
> + return np_chan;
> +}
> +
> +static int rpmsg_gpio_channel_callback(struct rpmsg_device *rpdev, void *data,
> + int len, void *priv, u32 src)
> +{
> + struct rpmsg_gpio_packet *msg = data;
> + struct rpmsg_gpio_port *port = NULL;
> + struct rpdev_drvdata *drvdata;
> +
> + drvdata = dev_get_drvdata(&rpdev->dev);
> + if (!msg || !drvdata)
> + return -EINVAL;
> +
> + if (msg->port_idx < MAX_PORT_PER_CHANNEL)
> + port = drvdata->channel_devices[msg->port_idx];
> +
> + if (!port || msg->line >= port->ngpios) {
> + dev_err(&rpdev->dev, "wrong port index or line number. port:%d line:%d\n",
> + msg->port_idx, msg->line);
> + return -EINVAL;
> + }
> +
> + if (msg->type == GPIO_RPMSG_REPLY) {
> + *port->info.reply_msg = *msg;
> + complete(&port->info.cmd_complete);
> + } else if (msg->type == GPIO_RPMSG_NOTIFY) {
> + generic_handle_domain_irq_safe(port->gc.irq.domain, msg->line);
> + } else {
> + dev_err(&rpdev->dev, "wrong command type (0x%x)\n", msg->type);
> + }
> +
> + return 0;
> +}
> +
> +static int rpmsg_gpio_channel_probe(struct rpmsg_device *rpdev)
> +{
> + struct device *dev = &rpdev->dev;
> + struct rpdev_drvdata *drvdata;
> + struct device_node *np;
> + int ret = -ENODEV;
> +
> + if (!dev->of_node) {
> + np = rpmsg_get_channel_ofnode(rpdev, rpdev->id.name);
> + if (np) {
> + dev->of_node = np;
> + set_primary_fwnode(dev, of_fwnode_handle(np));
> + }
> + return -EPROBE_DEFER;
I know this was asked in the v10 version also. But I don't think the
answer is sufficient. Should we not continue the intialization of
drvdata etc if np != 0? Why return a deferred probe, and let the kernel
come back to it again to do the same stuff with extra latency?
We could just do:
if (!np) return -EPROBE_DEFER;
else {everything_else};
> + }
> +
> + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
> + if (!drvdata)
> + return -ENOMEM;
> +
> + drvdata->rproc_name = rpmsg_get_rproc_node_name(rpdev);
> + dev_set_drvdata(dev, drvdata);
> +
> + for_each_child_of_node_scoped(dev->of_node, child) {
> + if (!of_device_is_available(child))
> + continue;
> +
> + if (!of_match_node(dev->driver->of_match_table, child))
> + continue;
> +
> + ret = rpmsg_gpiochip_register(rpdev, child);
> + if (ret < 0)
> + break;
Why break here? If one port initialization fails, it shouldn't impact
the other port right? We should just log this and store the
appropriate value in ret.
Thanks for your time,
Beleswar
> + }
> +
> + return ret;
> +}
> +
> +static const struct of_device_id rpmsg_gpio_dt_ids[] = {
> + { .compatible = "rpmsg-gpio" },
> + { /* sentinel */ }
> +};
> +
> +static struct rpmsg_device_id rpmsg_gpio_channel_id_table[] = {
> + { .name = "rpmsg-io" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(rpmsg, rpmsg_gpio_channel_id_table);
> +
> +static struct rpmsg_driver rpmsg_gpio_channel_client = {
> + .callback = rpmsg_gpio_channel_callback,
> + .id_table = rpmsg_gpio_channel_id_table,
> + .probe = rpmsg_gpio_channel_probe,
> + .drv = {
> + .name = KBUILD_MODNAME,
> + .of_match_table = rpmsg_gpio_dt_ids,
> + },
> +};
> +module_rpmsg_driver(rpmsg_gpio_channel_client);
> +
> +MODULE_AUTHOR("Shenwei Wang <shenwei.wang@nxp.com>");
> +MODULE_DESCRIPTION("generic rpmsg gpio driver");
> +MODULE_LICENSE("GPL");
^ permalink raw reply
* [PATCH] media: stm32: dcmi: unregister notifier on probe failure
From: 박명훈 @ 2026-04-26 12:43 UTC (permalink / raw)
To: Hugues Fruchet, Alain Volmat, Mauro Carvalho Chehab,
Maxime Coquelin, Alexandre Torgue
Cc: linux-media, linux-stm32, linux-arm-kernel, linux-kernel,
Myeonghun Pak
From: Myeonghun Pak <mhun512@gmail.com>
dcmi_graph_init() registers the async notifier before dcmi_probe() toggles
the reset line. If reset_control_assert() or reset_control_deassert()
fails afterwards, probe returns through err_cleanup and the driver core
will not call dcmi_remove().
Unregister the notifier before cleaning it up on that error path,
matching the successful remove path and the V4L2 async notifier lifetime
rules.
The local history only contains a Linux 7.0 snapshot, so the introducing
commit could not be identified from this worktree and no Fixes tag is
included in this draft.
Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
---
drivers/media/platform/st/stm32/stm32-dcmi.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c
index 13762861b7..200b498127 100644
--- a/drivers/media/platform/st/stm32/stm32-dcmi.c
+++ b/drivers/media/platform/st/stm32/stm32-dcmi.c
@@ -2063,6 +2063,7 @@ static int dcmi_probe(struct platform_device *pdev)
return 0;
err_cleanup:
+ v4l2_async_nf_unregister(&dcmi->notifier);
v4l2_async_nf_cleanup(&dcmi->notifier);
err_media_entity_cleanup:
media_entity_cleanup(&dcmi->vdev->entity);
--
2.47.1
^ permalink raw reply related
* [PATCH] media: sun4i-csi: Clean up media device on probe errors
From: Myeonghun Pak @ 2026-04-26 12:59 UTC (permalink / raw)
To: Maxime Ripard, Mauro Carvalho Chehab
Cc: Myeonghun Pak, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
linux-media, linux-arm-kernel, linux-sunxi, linux-kernel,
Ijae Kim
sun4i_csi_probe() initializes the media device before acquiring the
MMIO resource, IRQ, clocks, reset control and media entity pads.
Several of those failure paths return directly.
media_device_cleanup() is still required after media_device_init(),
and a failed probe does not run the driver remove callback. Route
those errors through the existing media-device cleanup path before
returning.
Co-developed-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Ijae Kim <ae878000@gmail.com>
Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
---
drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c | 30 ++++++++++++++--------
1 file changed, 19 insertions(+), 11 deletions(-)
diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
index e53a07b770..f6798cf0a2 100644
--- a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
+++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
@@ -187,37 +187,45 @@ static int sun4i_csi_probe(struct platform_device *pdev)
csi->v4l.mdev = &csi->mdev;
csi->regs = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(csi->regs))
- return PTR_ERR(csi->regs);
+ if (IS_ERR(csi->regs)) {
+ ret = PTR_ERR(csi->regs);
+ goto err_clean_mdev;
+ }
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
- return irq;
+ if (irq < 0) {
+ ret = irq;
+ goto err_clean_mdev;
+ }
csi->bus_clk = devm_clk_get(&pdev->dev, "bus");
if (IS_ERR(csi->bus_clk)) {
dev_err(&pdev->dev, "Couldn't get our bus clock\n");
- return PTR_ERR(csi->bus_clk);
+ ret = PTR_ERR(csi->bus_clk);
+ goto err_clean_mdev;
}
if (csi->traits->has_isp) {
csi->isp_clk = devm_clk_get(&pdev->dev, "isp");
if (IS_ERR(csi->isp_clk)) {
dev_err(&pdev->dev, "Couldn't get our ISP clock\n");
- return PTR_ERR(csi->isp_clk);
+ ret = PTR_ERR(csi->isp_clk);
+ goto err_clean_mdev;
}
}
csi->ram_clk = devm_clk_get(&pdev->dev, "ram");
if (IS_ERR(csi->ram_clk)) {
dev_err(&pdev->dev, "Couldn't get our ram clock\n");
- return PTR_ERR(csi->ram_clk);
+ ret = PTR_ERR(csi->ram_clk);
+ goto err_clean_mdev;
}
csi->rst = devm_reset_control_get(&pdev->dev, NULL);
if (IS_ERR(csi->rst)) {
dev_err(&pdev->dev, "Couldn't get our reset line\n");
- return PTR_ERR(csi->rst);
+ ret = PTR_ERR(csi->rst);
+ goto err_clean_mdev;
}
/* Initialize subdev */
@@ -236,13 +244,13 @@ static int sun4i_csi_probe(struct platform_device *pdev)
ret = media_entity_pads_init(&subdev->entity, CSI_SUBDEV_PADS,
csi->subdev_pads);
if (ret < 0)
- return ret;
+ goto err_clean_mdev;
csi->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
vdev->entity.ops = &sun4i_csi_video_entity_ops;
ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad);
if (ret < 0)
- return ret;
+ goto err_clean_mdev;
ret = sun4i_csi_dma_register(csi, irq);
if (ret)
@@ -266,7 +274,7 @@ static int sun4i_csi_probe(struct platform_device *pdev)
media_device_unregister(&csi->mdev);
sun4i_csi_dma_unregister(csi);
-err_clean_pad:
+err_clean_mdev:
media_device_cleanup(&csi->mdev);
return ret;
^ permalink raw reply related
* Re: [PATCH v5 3/6] pwm: Add rockchip PWMv4 driver
From: Uwe Kleine-König @ 2026-04-26 13:06 UTC (permalink / raw)
To: Damon Ding
Cc: Nicolas Frattaroli, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heiko Stuebner, Lee Jones, William Breathitt Gray,
kernel, Jonas Karlman, Alexey Charkov, linux-rockchip, linux-pwm,
devicetree, linux-arm-kernel, linux-kernel, linux-iio
In-Reply-To: <4592b323-bebd-4242-ae31-892a51b5b3be@rock-chips.com>
[-- Attachment #1: Type: text/plain, Size: 2010 bytes --]
Hello,
first of all thanks for your extensive testing, very appreciated.
On Sun, Apr 26, 2026 at 05:44:46PM +0800, Damon Ding wrote:
> On 4/20/2026 9:52 PM, Nicolas Frattaroli wrote:
> > The Rockchip RK3576 brings with it a new PWM IP, in downstream code
> > referred to as "v4". This new IP is different enough from the previous
> > Rockchip IP that I felt it necessary to add a new driver for it, instead
> > of shoehorning it in the old one.
> >
> > Add this new driver, based on the PWM core's waveform APIs. Its platform
> > device is registered by the parent mfpwm driver, from which it also
> > receives a little platform data struct, so that mfpwm can guarantee that
> > all the platform device drivers spread across different subsystems for
> > this specific hardware IP do not interfere with each other.
> >
> > Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> Tested-by: Damon Ding <damon.ding@rock-chips.com>
>
> The continuous mode of all PWM channels has been preliminarily tested
> and verified working on the RK3576 IoT board.
>
> I have tested with several typical period and duty cycle configurations.
>
> Following Uwe's suggestion [0], I also tested with libpwm using commands
> similar to the following:
>
> ./pwmset -c 0 -p 0 -P 1000000 -D 500000 -s 5000
This one is good if you have an oscilloscope (or something similar) to
verify the output. Without that (or additionally) pwmtestperf creates a
series of requests that in combination with PWM_DEBUG should uncover
rounding errors in the .tohw and .fromhw callbacks.
A good set of calls then is:
pwmtestperf -p ... -c ... -P 50000 -S1
pwmtestperf -p ... -c ... -P 50000 -S1 -I
pwmtestperf -p ... -c ... -P 50000 -S-1
pwmtestperf -p ... -c ... -P 50000 -S-1 -I
(Assuming that 50000 is a sensible period for the device under test.)
And yes, I know, I need to document that using something more permanent
than a mailing list post.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH v8 3/6] drm/bridge: simple: Add the Lontium LT8711UXD DP-to-HDMI bridge
From: Laurent Pinchart @ 2026-04-26 13:27 UTC (permalink / raw)
To: Heiko Stuebner
Cc: Dmitry Baryshkov, Dennis Gilmore, Andrzej Hajda, Neil Armstrong,
Robert Foss, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonas Karlman, Jernej Skrabec, Maxime Ripard, Alexey Charkov,
devicetree, linux-rockchip, linux-arm-kernel, dri-devel,
linux-kernel
In-Reply-To: <6519122.LvFx2qVVIh@phil>
On Sun, Apr 26, 2026 at 11:54:57AM +0200, Heiko Stuebner wrote:
> Am Sonntag, 26. April 2026, 01:48:45 Mitteleuropäische Sommerzeit schrieb Laurent Pinchart:
> > On Sun, Apr 26, 2026 at 12:44:59AM +0300, Dmitry Baryshkov wrote:
> > > On Sat, Apr 25, 2026 at 01:10:02PM -0500, Dennis Gilmore wrote:
> > > > On Sat, Apr 25, 2026 at 9:24 AM Dmitry Baryshkov wrote:
> > > > > On Sat, Apr 25, 2026 at 02:28:44PM +0300, Laurent Pinchart wrote:
> > > > > > Hi Dennis,
> > > > > >
> > > > > > Thank you for the patch.
> > > > > >
> > > > > > On Fri, Apr 24, 2026 at 10:10:08PM -0500, Dennis Gilmore wrote:
> > > > > > > The Lontium LT8711UXD is a high performance two lane Type-C/DP1.4
> > > > > > > to HDMI2.0 converter, designed to connect a USB Type-C source or
> > > > > > > a DP1.4 source to an HDMI2.0 sink.
> > > > > >
> > > > > > As far as I can tell, the LT8711UXD has an I2C control interface.
> > > > > > Shouldn't it be an I2C device ?
> > > > >
> > > > > From the datasheet:
> > > > >
> > > > > The device is capable of automatic operation which is
> > > > > enabled by an integrated microprocessor that uses an
> > > > > embedded SPI flash for firmware storage. System control
> > > > > is also available through the use of a dedicated
> > > > > configuration I2C slave interface.
> > > > >
> > > > > My guess was that it can either be an I2C device or it can function as a
> > > > > simple platdev with no I2C controls. Please correct me if my
> > > > > understanding was wrong.
> > > > >
> > > > > But now looking at the schematics, it seems to be connected to I2C6.
> > > > > Which means that it should be desribed (and bound) as such.
> > > >
> > > > Hi Dmitry and Laurent,
> > > >
> > > > While the schematic shows that it can use I2C and has been wired up,
> > > > it also shows that both MODE_SEL and I2C_ADDR have unpopulated 10k
> > > > resistors; as a result, MODE_SEL is connected directly to GND,
>
> looking at the schematics linked in the board patch, I somehow see
> both R9 (mode_sel -> vcc3v3_io) but also R17 (mode_sel -> gnd) marked
> as 10K.nc ?
>
> > > > putting
> > > > the bridge in autonomous mode. I confirmed this by running `i2cdetect
> > > > -r -y 6`, with the only device on the bus being the HYM8563 RTC at
> > > > 0x51. Without reworking the board, the device is not directly
> > > > controllable and just runs autonomously.
> > >
> > > I think it would be nice to mention:
> > > - In the commit for the bindings, that the device can be running
> > > uncontrolled or it can be attached over I2C, bindings describe the
> > > uncontrolled mode.
> > > - In this commit message, the same.
> > > - In the commit message for the board DT mention your findings about the
> > > board, mention soldering R9 or R17 (which one?) and R27.
> >
> > Additionally, how are we going to handle boards where the device
> > operates in I2C mode ? Will we use a different compatible string (maybe
> > "lontium,lt8711uxd-i2c") ? If DT maintainers are fine with that, I have
> > no objection to this patch.
>
> I would assume it'd be more the dt-maintainers objecting?
Yes, probably :-) My main concern here is making sure we're not
cornering ourselves.
> I.e. the two different bindings for the same hardware and leaking Linux
> implementation-specifics into the binding.
>
> I'm don't have deep insight into the i2c framework, but I guess the i2c
> device probe does not need to talk to an i2c device due to resources
> needing setup. Does the i2c core need to talk to the device at all?
>
> Because otherwise, you could just do a regular i2c device (the routing
> for everything is there afterall), add a lontium,automatic-mode; flag
> to the node to denote mode.
That would work in this case, but if we have a board where the I2C lines
are really not routed, we would have to invent a fake connection to an
I2C controller. That's not very nice.
There seem to be precedents for devices that can be controlled through
either I2C or SPI. See for instance
Documentation/devicetree/bindings/net/nfc/st,st-nci.yaml that defines
two compatible strings for the same device, "st,st21nfcb-i2c" and
"st,st21nfcb-spi". I don't know if that's the best practice recommended
by the DT maintainers, or a hack that slipped through.
> And if for whatever reason a variant appears with the lines connected
> you can just modifiy the DT via an overlay?
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH v3 1/3] crypto: atmel-sha204a - fix memory leak at non-blocking RNG work_data
From: Thorsten Blum @ 2026-04-26 13:33 UTC (permalink / raw)
To: Lothar Rubusch
Cc: herbert, davem, nicolas.ferre, alexandre.belloni, claudiu.beznea,
ardb, linusw, linux-crypto, linux-arm-kernel, linux-kernel
In-Reply-To: <20260422210936.20095-2-l.rubusch@gmail.com>
On Wed, Apr 22, 2026 at 09:09:34PM +0000, Lothar Rubusch wrote:
> The driver allocated memory for work_data in the non-blocking read
> path but never free'd it again.
Yes, 'work_data' is allocated once on the first nonblocking RNG request
and reused for subsequent requests, but the memory is eventually freed
on device removal in atmel_sha204a_remove().
The memory might be retained unnecessarily after use when the device is
idle (probably negligible), but it's not a memory leak.
Best,
Thorsten
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox