* Re: [PATCH] arm64/hwcap: Include kernel-hwcap.h in list of generated files
From: Geert Uytterhoeven @ 2026-04-16 16:14 UTC (permalink / raw)
To: Mark Brown
Cc: Catalin Marinas, Will Deacon, Marek Vasut, linux-arm-kernel,
linux-kernel
In-Reply-To: <20260413-arm64-hwcap-gen-fix-v1-1-26c56aed6908@kernel.org>
On Mon, 13 Apr 2026 at 17:46, Mark Brown <broonie@kernel.org> wrote:
> When adding generation for the kernel internal constants for hwcaps the
> generated file was not explicitly flagged as such in the build system,
> causing it to be regenerated on each build. This wasn't obvious when the
> series the change was included in was developed since it was all about
> changes that trigger rebuilds anyway.
>
> Fixes: abed23c3c44f5 (arm64/hwcap: Generate the KERNEL_HWCAP_ definitions for the hwcaps)
> Reported-by: Marek Vasut <marex@nabladev.com>
> Signed-off-by: Mark Brown <broonie@kernel.org>
Just noticed the issue, bisected it, and found a link to the fix...
Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH net-next 5/6] net: stmmac: move PHY handling out of __stmmac_open()/release()
From: Jakub Kicinski @ 2026-04-16 16:08 UTC (permalink / raw)
To: Russell King (Oracle)
Cc: Alexander Stein, Andrew Lunn, Heiner Kallweit, Alexandre Torgue,
Andrew Lunn, David S. Miller, Eric Dumazet, linux-arm-kernel,
linux-stm32, Maxime Coquelin, netdev, Paolo Abeni
In-Reply-To: <aeDojTdDTELfpT0X@shell.armlinux.org.uk>
On Thu, 16 Apr 2026 14:47:57 +0100 Russell King (Oracle) wrote:
> The next problem will be netdev's policy over reviews vs patches
> balance which I'm already in deficit, and I have *NO* *TIME*
> what so ever to review patches - let alone propose patches to
> fix people's problems.
>
> So I'm going to say this plainly: if netdev wants to enforce that
> rule, then I won't be fixing people's problems.
Do you have a better proposal?
I'm under the same pressure of million stupid projects from my employer
as you are. Do y'all think that upstream maintainers have time given by
their employers to do the reviews? SMH.
^ permalink raw reply
* Re: [PATCH v7 1/3] dt-bindings: pinctrl: Add aspeed,ast2700-soc0-pinctrl
From: Conor Dooley @ 2026-04-16 15:54 UTC (permalink / raw)
To: Billy Tsai
Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Joel Stanley, Andrew Jeffery, Linus Walleij, Bartosz Golaszewski,
Ryan Chen, Andrew Jeffery, devicetree, linux-arm-kernel,
linux-aspeed, linux-kernel, openbmc, linux-gpio, linux-clk
In-Reply-To: <20260416-upstream_pinctrl-v7-1-d72762253163@aspeedtech.com>
[-- Attachment #1: Type: text/plain, Size: 5099 bytes --]
On Thu, Apr 16, 2026 at 03:29:43PM +0800, Billy Tsai wrote:
> Add a device tree binding for the pin controller found in the
> ASPEED AST2700 SoC0.
>
> The controller manages various peripheral functions such as eMMC, USB,
> VGA DDC, JTAG, and PCIe root complex signals.
>
> Describe the AST2700 SoC0 pin controller using standard pin multiplexing
> and configuration properties.
>
> Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
> ---
> .../pinctrl/aspeed,ast2700-soc0-pinctrl.yaml | 162 +++++++++++++++++++++
> 1 file changed, 162 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/pinctrl/aspeed,ast2700-soc0-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/aspeed,ast2700-soc0-pinctrl.yaml
> new file mode 100644
> index 000000000000..947f3cd09fcc
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pinctrl/aspeed,ast2700-soc0-pinctrl.yaml
> @@ -0,0 +1,162 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pinctrl/aspeed,ast2700-soc0-pinctrl.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: ASPEED AST2700 SoC0 Pin Controller
> +
> +maintainers:
> + - Billy Tsai <billy_tsai@aspeedtech.com>
> +
> +description:
> + The AST2700 features a dual-SoC architecture with two interconnected SoCs,
> + each having its own System Control Unit (SCU) for independent pin control.
> + This pin controller manages the pin multiplexing for SoC0.
> +
> + The SoC0 pin controller manages pin functions including eMMC, VGA DDC,
> + dual USB3/USB2 ports (A and B), JTAG, and PCIe root complex interfaces.
> +
> +properties:
> + compatible:
> + const: aspeed,ast2700-soc0-pinctrl
> + reg:
> + maxItems: 1
> +
> +patternProperties:
> + '-state$':
> + type: object
> + allOf:
> + - $ref: pinmux-node.yaml#
> + - $ref: pincfg-node.yaml#
> +
> + additionalProperties: false
> +
> + properties:
> + function:
> + enum:
> + - EMMC
> + - JTAGDDR
> + - JTAGM0
> + - JTAGPCIEA
> + - JTAGPCIEB
> + - JTAGPSP
> + - JTAGSSP
> + - JTAGTSP
> + - JTAGUSB3A
> + - JTAGUSB3B
> + - PCIERC0PERST
> + - PCIERC1PERST
> + - TSPRSTN
> + - UFSCLKI
> + - USB2AD0
> + - USB2AD1
> + - USB2AH
> + - USB2AHP
> + - USB2AHPD0
> + - USB2AXH
> + - USB2AXH2B
> + - USB2AXHD1
> + - USB2AXHP
> + - USB2AXHP2B
> + - USB2AXHPD1
> + - USB2BD0
> + - USB2BD1
> + - USB2BH
> + - USB2BHP
> + - USB2BHPD0
> + - USB2BXH
> + - USB2BXH2A
> + - USB2BXHD1
> + - USB2BXHP
> + - USB2BXHP2A
> + - USB2BXHPD1
> + - USB3AXH
> + - USB3AXH2B
> + - USB3AXHD
> + - USB3AXHP
> + - USB3AXHP2B
> + - USB3AXHPD
> + - USB3BXH
> + - USB3BXH2A
> + - USB3BXHD
> + - USB3BXHP
> + - USB3BXHP2A
> + - USB3BXHPD
> + - VB
> + - VGADDC
> +
> + groups:
> + enum:
> + - EMMCCDN
> + - EMMCG1
> + - EMMCG4
> + - EMMCG8
> + - EMMCWPN
> + - JTAG0
> + - PCIERC0PERST
> + - PCIERC1PERST
> + - TSPRSTN
> + - UFSCLKI
> + - USB2A
> + - USB2AAP
> + - USB2ABP
> + - USB2ADAP
> + - USB2AH
> + - USB2AHAP
> + - USB2B
> + - USB2BAP
> + - USB2BBP
> + - USB2BDBP
> + - USB2BH
> + - USB2BHBP
> + - USB3A
> + - USB3AAP
> + - USB3ABP
> + - USB3B
> + - USB3BAP
> + - USB3BBP
> + - VB0
> + - VB1
> + - VGADDC
> + pins:
> + enum:
> + - AB13
> + - AB14
> + - AC13
> + - AC14
> + - AD13
> + - AD14
> + - AE13
> + - AE14
> + - AE15
> + - AF13
> + - AF14
> + - AF15
Why do you have groups and pins?
Is it valid in your device to have groups and pins in the same node?
> +
> + drive-strength:
> + enum: [3, 6, 8, 11, 16, 18, 20, 23, 30, 32, 33, 35, 37, 38, 39, 41]
> +
> + bias-disable: true
> + bias-pull-up: true
> + bias-pull-down: true
> +
> +required:
> + - compatible
> + - reg
> +
> +allOf:
> + - $ref: pinctrl.yaml#
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + pinctrl@400 {
> + compatible = "aspeed,ast2700-soc0-pinctrl";
> + reg = <0x400 0x318>;
> + emmc-state {
> + function = "EMMC";
> + groups = "EMMCG1";
> + };
> + };
>
> --
> 2.34.1
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v5 04/12] coresight: etm4x: exclude ss_status from drvdata->config
From: Leo Yan @ 2026-04-16 15:51 UTC (permalink / raw)
To: Yeoreum Yun
Cc: coresight, linux-arm-kernel, linux-kernel, suzuki.poulose,
mike.leach, james.clark, alexander.shishkin, jie.gan
In-Reply-To: <20260415165528.3369607-5-yeoreum.yun@arm.com>
On Wed, Apr 15, 2026 at 05:55:20PM +0100, Yeoreum Yun wrote:
[...]
> @@ -573,11 +573,9 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata)
> etm4x_relaxed_write32(csa, config->res_ctrl[i], TRCRSCTLRn(i));
>
> for (i = 0; i < caps->nr_ss_cmp; i++) {
> - /* always clear status bit on restart if using single-shot */
> - if (config->ss_ctrl[i] || config->ss_pe_cmp[i])
> - config->ss_status[i] &= ~TRCSSCSRn_STATUS;
> etm4x_relaxed_write32(csa, config->ss_ctrl[i], TRCSSCCRn(i));
> - etm4x_relaxed_write32(csa, config->ss_status[i], TRCSSCSRn(i));
> + /* always clear status and pending bits on restart if using single-shot */
> + etm4x_relaxed_write32(csa, 0x0, TRCSSCSRn(i));
In Arm ARM, D24.4.60 TRCSSCSR<n>, bits[0..3] are RO. I think it is
fine for directly clear the regiser with zero (means it will only
clear status / pending bits).
[...]
> @@ -1841,10 +1839,11 @@ static ssize_t sshot_status_show(struct device *dev,
> {
> unsigned long val;
> struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
> + const struct etmv4_caps *caps = &drvdata->caps;
> struct etmv4_config *config = &drvdata->config;
>
> raw_spin_lock(&drvdata->spinlock);
> - val = config->ss_status[config->ss_idx];
> + val = caps->ss_cmp[config->ss_idx];
> raw_spin_unlock(&drvdata->spinlock);
> return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
> }
This sysfs knob never can print out a realtime status for sshot, I am
fine for only printing caps->ss_cmp, this can avoid any misleading.
@Suzuki, @Mike, do you agree with the change above?
If maintainers agree with this, as Jie suggested, it is good to add a
comment in the code and update the document:
Documentation/trace/coresight/coresight-etm4x-reference.rst
Thanks,
Leo
^ permalink raw reply
* [PATCH] gpio: rockchip: Fix GPIO after convert to dynamic base allocation
From: Jonas Karlman @ 2026-04-16 15:49 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Heiko Stuebner, Shawn Lin
Cc: Jonas Karlman, Bartosz Golaszewski, linux-gpio, linux-arm-kernel,
linux-rockchip, linux-kernel
The commit c8079f83e0bf ("gpio: rockchip: convert to dynamic GPIO base
allocation") broke GPIO on devices using device trees which don't set
the gpio-ranges property, something only Rockchip RK35xx SoC DTs do.
On a Rockchip RK3399 device something like following is now observed:
[ 0.082771] rockchip-gpio ff720000.gpio: probed /pinctrl/gpio@ff720000
[ 0.083531] rockchip-gpio ff730000.gpio: probed /pinctrl/gpio@ff730000
[ 0.084110] rockchip-gpio ff780000.gpio: probed /pinctrl/gpio@ff780000
[ 0.084746] rockchip-gpio ff788000.gpio: probed /pinctrl/gpio@ff788000
[ 0.085389] rockchip-gpio ff790000.gpio: probed /pinctrl/gpio@ff790000
--
[ 0.212208] rockchip-pinctrl pinctrl: pin 637 is not registered so it cannot be requested
[ 0.212271] rockchip-pinctrl pinctrl: error -EINVAL: pin-637 (gpio3:637)
[ 0.212344] leds-gpio leds: error -EINVAL: Failed to get GPIO '/leds/led-0'
[ 0.212389] leds-gpio leds: probe with driver leds-gpio failed with error -22
--
[ 0.607545] rockchip-pinctrl pinctrl: pin 519 is not registered so it cannot be requested
[ 0.608775] rockchip-pinctrl pinctrl: error -EINVAL: pin-519 (gpio0:519)
[ 0.610003] dwmmc_rockchip fe320000.mmc: probe with driver dwmmc_rockchip failed with error -22
--
[ 0.805882] rockchip-pinctrl pinctrl: pin 547 is not registered so it cannot be requested
[ 0.806672] rockchip-pinctrl pinctrl: error -EINVAL: pin-547 (gpio1:547)
[ 0.807301] reg-fixed-voltage regulator-vbus-typec: error -EINVAL: can't get GPIO
[ 0.807307] rockchip-pinctrl pinctrl: pin 602 is not registered so it cannot be requested
[ 0.807970] reg-fixed-voltage regulator-vbus-typec: probe with driver reg-fixed-voltage failed with error -22
[ 0.808692] rockchip-pinctrl pinctrl: error -EINVAL: pin-602 (gpio2:602)
[ 0.810279] reg-fixed-voltage regulator-vcc3v3-pcie: error -EINVAL: can't get GPIO
[ 0.810284] rockchip-pinctrl pinctrl: pin 665 is not registered so it cannot be requested
[ 0.810299] rockchip-pinctrl pinctrl: error -EINVAL: pin-665 (gpio4:665)
[ 0.810960] reg-fixed-voltage regulator-vcc3v3-pcie: probe with driver reg-fixed-voltage failed with error -22
[ 0.811679] reg-fixed-voltage regulator-vcc5v0-host: error -EINVAL: can't get GPIO
[ 0.813943] reg-fixed-voltage regulator-vcc5v0-host: probe with driver reg-fixed-voltage failed with error -22
--
[ 0.867788] rockchip-pinctrl pinctrl: pin 522 is not registered so it cannot be requested
[ 0.868537] rockchip-pinctrl pinctrl: error -EINVAL: pin-522 (gpio0:522)
[ 0.869166] pwrseq_simple sdio-pwrseq: error -EINVAL: reset GPIOs not ready
[ 0.869798] pwrseq_simple sdio-pwrseq: probe with driver pwrseq_simple failed with error -22
--
[ 0.940365] rockchip-pinctrl pinctrl: pin 623 is not registered so it cannot be requested
[ 0.941084] rockchip-pinctrl pinctrl: error -EINVAL: pin-623 (gpio3:623)
[ 0.941823] rk_gmac-dwmac fe300000.ethernet: error -EINVAL: Cannot register the MDIO bus
[ 0.942542] rk_gmac-dwmac fe300000.ethernet: error -EINVAL: MDIO bus (id: 0) registration failed
[ 0.943772] rk_gmac-dwmac fe300000.ethernet: probe with driver rk_gmac-dwmac failed with error -22
Restore GPIO to a working state on devices using older Rockchip SoCs
and/or DTs not having the gpio-ranges property set by restoring prior
use of bank->pin_base as the pin_offset value.
Also change to use bank->nr_pins as the npins value to align and prevent
a possible future breakage if gc->ngpio is ever changed to match the 32
GPIOs each controller theoretically can handle.
Fixes: c8079f83e0bf ("gpio: rockchip: convert to dynamic GPIO base allocation")
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
drivers/gpio/gpio-rockchip.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpio/gpio-rockchip.c b/drivers/gpio/gpio-rockchip.c
index 08ea64434f8f..44d7ebd12724 100644
--- a/drivers/gpio/gpio-rockchip.c
+++ b/drivers/gpio/gpio-rockchip.c
@@ -617,7 +617,7 @@ static int rockchip_gpiolib_register(struct rockchip_pin_bank *bank)
return -ENODEV;
ret = gpiochip_add_pin_range(gc, dev_name(pctldev->dev), 0,
- gc->base, gc->ngpio);
+ bank->pin_base, bank->nr_pins);
if (ret) {
dev_err(bank->dev, "Failed to add pin range\n");
goto fail;
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2] spi: mtk-snfi: fix memory leak in probe
From: Mark Brown @ 2026-04-16 14:54 UTC (permalink / raw)
To: Matthias Brugger, AngeloGioacchino Del Regno, Xiangsheng Hou,
Felix Gu
Cc: linux-spi, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260416-mtk-snfi-v2-1-3f487689dacb@gmail.com>
On Thu, 16 Apr 2026 21:00:08 +0800, Felix Gu wrote:
> spi: mtk-snfi: fix memory leak in probe
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-7.1
Thanks!
[1/1] spi: mtk-snfi: fix memory leak in probe
https://git.kernel.org/broonie/spi/c/51942b77f443
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark
^ permalink raw reply
* Re: [PATCH/RFT] arm64: dts: renesas: r8a78000: Fix SCIF brg_int clocks
From: Geert Uytterhoeven @ 2026-04-16 15:36 UTC (permalink / raw)
To: Khanh Le, Vinh Nguyen, Phong Hoang, Kuninori Morimoto,
Magnus Damm
Cc: linux-renesas-soc, Linux ARM
In-Reply-To: <459d360a8332f92b3766b30814e7e1c76169aaf7.1767719254.git.geert+renesas@glider.be>
On Tue, 6 Jan 2026 at 18:09, Geert Uytterhoeven <geert+renesas@glider.be> wrote:
> According to the documentation, the internal clock input for the BRG is
> SGASYNCD4_PERW_BUSφ.
>
> Fixes: c13a643e2c491f5b ("arm64: dts: renesas: Add R8A78000 SoC support")
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> ---
> Untested due to lack of access of local hardware.
>
> This is consistent with SCIF on other SOCs, where fck typically runs at
> 1/4 of the speed of brg_int.
>
> The correctness of the clock rates of the various clock sources can be
> tested by forcing the use of "fck", "brg_int" or "scif_clk", by
> commenting out all but one of the four blocks below the "There can be
> multiple sources for the sampling clock" in drivers/tty/serial/sh-sci.c.
Confirmed on actual hardware, will queue in renesas-devel for v7.2.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH v5 4/4] MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver
From: Uwe Kleine-König @ 2026-04-16 15:28 UTC (permalink / raw)
To: Krzysztof Kozlowski, Richard Genoud
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Philipp Zabel, Paul Kocialkowski,
Thomas Petazzoni, John Stultz, Joao Schim, bigunclemax, linux-pwm,
devicetree, linux-arm-kernel, linux-sunxi, linux-kernel
In-Reply-To: <e9904440-b08d-4f9d-8d66-121633289695@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 1119 bytes --]
Hello,
On Thu, Apr 16, 2026 at 03:23:49PM +0200, Krzysztof Kozlowski wrote:
> On 16/04/2026 15:14, Richard Genoud wrote:
> > Add myself as maintainer of Allwinner SUN8I PWM driver.
> >
> > Tested-by: John Stultz <jstultz@google.com>
>
> Please drop or help me understand how one can test maintainers change?
> Build process tools are not testing.
For me that is fine. This is the only way we have to record that John
tested the series. Also if I applied the original series I would have
let b4 add it to all patches in reply to a "tested-by" send in reply to
the cover letter.
My feedback here would be more: Don't send a new revision for such
comments within 30 min, also not for
> And you have commit msg trailing junk.
. Maintainers differ, but if this is the only concern and the series is
fine otherwise, I'd just fix that when applying. (But I think the "don't
send a new iteration on the same day" is more universal, also for more
fundamental feedback.) There is no need to bother all subscribers of 5
mailing lists with a new thread in such quick sequence.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH v5 02/12] coresight: etm4x: fix underflow for nrseqstate
From: Leo Yan @ 2026-04-16 15:11 UTC (permalink / raw)
To: Yeoreum Yun
Cc: coresight, linux-arm-kernel, linux-kernel, suzuki.poulose,
mike.leach, james.clark, alexander.shishkin, jie.gan
In-Reply-To: <20260415165528.3369607-3-yeoreum.yun@arm.com>
On Wed, Apr 15, 2026 at 05:55:18PM +0100, Yeoreum Yun wrote:
> TCRSEQEVR<n> is implemented only when TCRIDR5.NUMSEQSTATE is 0b100,
> in which case n ranges from 0 to 2; otherwise, TCRIDR5.NUMSEQSTATE is 0b000.
My suggestion in previous version is not quite right, thanks for
making this correct.
[...]
> @@ -1395,6 +1395,8 @@ static ssize_t seq_idx_store(struct device *dev,
> struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
> struct etmv4_config *config = &drvdata->config;
>
> + if (!drvdata->nrseqstate)
> + return -EINVAL;
For "nrseqstate = 0" case, would it return -EOPNOTSUPP instead?
Otherwise, LGTM:
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* Re: [PATCH v3 4/8] sframe: Provide PC lookup for vmlinux .sframe section.
From: Jens Remus @ 2026-04-16 15:10 UTC (permalink / raw)
To: Dylan Hatch, Roman Gushchin, Weinan Liu, Will Deacon,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Steven Rostedt,
Catalin Marinas, Jiri Kosina
Cc: Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan, Song Liu,
joe.lawrence, linux-toolchains, linux-kernel, live-patching,
linux-arm-kernel, Heiko Carstens
In-Reply-To: <20260406185000.1378082-5-dylanbhatch@google.com>
On 4/6/2026 8:49 PM, Dylan Hatch wrote:
> With SFRAME_UNWINDER, read in the .sframe section at boot. This provides
> unwind data as an alternative/supplement to frame pointer-based
> unwinding.
>
> Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
Reviewed-by: Jens Remus <jremus@linux.ibm.com>
> ---
> arch/arm64/kernel/setup.c | 2 ++
> include/linux/sframe.h | 14 ++++++++++++++
> kernel/unwind/sframe.c | 39 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 55 insertions(+)
Regards,
Jens
--
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com
IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
^ permalink raw reply
* Re: [PATCH v3 7/8] sframe: Introduce in-kernel SFRAME_VALIDATION.
From: Jens Remus @ 2026-04-16 15:04 UTC (permalink / raw)
To: Dylan Hatch, Roman Gushchin, Weinan Liu, Will Deacon,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Steven Rostedt,
Catalin Marinas, Jiri Kosina
Cc: Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan, Song Liu,
joe.lawrence, linux-toolchains, linux-kernel, live-patching,
linux-arm-kernel, Heiko Carstens
In-Reply-To: <20260406185000.1378082-8-dylanbhatch@google.com>
Hello Dylan!
On 4/6/2026 8:49 PM, Dylan Hatch wrote:
> Generalize the __safe* helpers to support a non-user-access code path.
> Allow for kernel FDE read failures due to the presence of .rodata.text.
> This section contains code that can't be executed by the kernel
> direclty, and thus lies ouside the normal kernel-text bounds.
Nits: s/direclty/directly/ s/ouside/outside/
Could you please explain the issue? How/why does .sframe for
.rodata.text pose an issue for .sframe verification?
> Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
> diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
> @@ -690,6 +699,13 @@ static int sframe_validate_section(struct sframe_section *sec)
> int ret;
>
> ret = safe_read_fde(sec, i, &fde);
> + /*
> + * Code in .rodata.text is not considered part of normal kernel
> + * text, but there is no easy way to prevent sframe data from
> + * being generated for it.
> + */
> + if (ret && sec->sec_type == SFRAME_KERNEL)
> + continue;
> if (ret)
> return ret;
>
Thanks and regards,
Jens
--
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com
IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
^ permalink raw reply
* Re: [PATCH v5 01/12] coresight: etm4x: fix wrong check of etm4x_sspcicrn_present()
From: Leo Yan @ 2026-04-16 15:02 UTC (permalink / raw)
To: Yeoreum Yun
Cc: coresight, linux-arm-kernel, linux-kernel, suzuki.poulose,
mike.leach, james.clark, alexander.shishkin, jie.gan
In-Reply-To: <20260415165528.3369607-2-yeoreum.yun@arm.com>
On Wed, Apr 15, 2026 at 05:55:17PM +0100, Yeoreum Yun wrote:
> According to Embedded Trace Macrocell Architecture Specification
> ETMv4.0 to ETM4.6 [0], TRCSSPCICR<n> is present only if all of
> the following are true:
>
> - TRCIDR4.NUMSSCC > n.
> - TRCIDR4.NUMPC > 0b0000.
> - TRCSSCSR<n>.PC == 0b1.
>
> Comment for etm4x_sspcicrn_present() is align with the specification.
> However, the check should use drvdata->nr_pe_cmp to check TRCIDR4.NUMPC
> not nr_pe.
>
> Link: https://developer.arm.com/documentation/ihi0064/latest/ [0]
> Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Leo Yan <leo.yan@arm.com>
^ permalink raw reply
* Re: [PATCH v2] raid6: arm64: add SVE optimized implementation for syndrome generation
From: Demian Shulhan @ 2026-04-16 14:59 UTC (permalink / raw)
To: Ard Biesheuvel
Cc: Christoph Hellwig, Mark Rutland, Song Liu, Yu Kuai, Will Deacon,
Catalin Marinas, Mark Brown, linux-arm-kernel, Robin Murphy,
Li Nan, linux-raid, linux-kernel
In-Reply-To: <5158e4e0-3275-4c29-a8fc-2dfabc13a69d@app.fastmail.com>
Hi Ard!
> So what exactly did you fix in your test case?
I just added the missing memset. You're right, "aliasing" was the
wrong term for PIP.
> This is the result where all data buffer pointers point to the same
> memory, right? I.e., the zero page? So this is an unrealistic use
> case that we can disregard.
Yes, that's right. It was a flaw in my previous test setup.
> Sorry but your result that SVE is 2x faster does not remain fully intact,
> right? Given that the speedup is now 5.5%?
> Should we just disregard the above results (and explanations) and focus
> on the stuff below?
Yes, it's better to focus on the data from SnapRAID. It was made on
larger blocks and a wider range of disks, providing more realistic
metrics.
> OK, so the takeaway here is that SVE is only worth the hassle if the vector
> length is at least 256 bits. This is not entirely surprising, but given that
> Graviton4 went back to 128 bit vectors from 256, I wonder what the future
> expectation is here.
I agree. The results from the SnapRAID tests are not as impressive as
I hoped, and the fact that Neoverse-V2 went back to 128-bit is a red
flag. It suggests that wide SVE registers might not be a priority in
future architecture versions.
> These results seem very relevant - perhaps Christoph can give some guidance
> on how we might use these to improve the built-in benchmarks to be more
> accurate.
This is the most important part of this report, I think. SVE looks
good only like my first idea on paper but in the real scenario it
brings more problems than benefits.
I’m happy to drop the SVE implementation for now and instead focus on
modernizing the built-in benchmarks to ensure the kernel chooses the
best available NEON path for actual storage workloads.
If you give me the green flag for it, I can start working on improving
these built-in tests.
Best regards,
Demian
чт, 16 квіт. 2026 р. о 16:40 Ard Biesheuvel <ardb@kernel.org> пише:
>
> Hi Demian,
>
> On Thu, 16 Apr 2026, at 14:40, Demian Shulhan wrote:
> > Hi all,
> >
> > Sorry for the delay. The tests became more complex than I initially
> > thought, so I needed to gather more data and properly validate the
> > results across different hardware configurations.
> >
> > Firstly, I want to clarify the results from my March 29 tests. I found
> > a flaw in my initial custom benchmark. The massive 2x throughput gap on
> > 24 disks wasn't solely due to SVE's superiority, but rather a severe L1
> > D-Cache thrashing issue that disproportionately penalized NEON.
> >
> > My custom test lacked memset() initialization, causing all data buffers
> > to map to the Linux Zero Page (Virtually Indexed, Physically Tagged
> > cache aliasing).
>
> D-caches always behave as PIPT on arm64. This is complex stuff, so please
> don't present conjecture as fact.
>
> > Furthermore, even with memset(), allocating contiguous
> > page-aligned buffers can causes severe Cache Address Sharing (a known
> > issue that Andrea Mazzoleni solved in SnapRAID 13 years ago using
> > RAID_MALLOC_DISPLACEMENT).
> >
> > Because SVE (svex4) uses 256-bit registers on Neoverse-V1, it performs
> > exactly half the number of memory load instructions compared to 128-bit
> > NEON. This dramatically reduced the L1 cache alias thrashing, allowing
> > SVE to survive the memory bottleneck while NEON choked:
> >
>
> You are drawing some conclusions here without disclosing the actual
> information that you based this on. D-caches are non-aliasing on arm64.
>
> So what exactly did you fix in your test case?
>
> > Custom test without memset (4kb block):
> > | algo=neonx4 ndisks=24 iterations=1M time=11.014s MB/s=7802.57
> > | algo=svex4 ndisks=24 iterations=1M time=5.719s MB/s=15026.92
> >
>
> This is the result where all data buffer pointers point to the same
> memory, right? I.e., the zero page? So this is an unrealistic use
> case that we can disregard.
>
> > Custom test with memset (4kb block):
> > | algo=neonx4 ndisks=24 iterations=1M time=6.165s MB/s=13939.08
> > | algo=svex4 ndisks=24 iterations=1M time=5.839s MB/s=14718.23
> >
> > Even with the corrected memory setup, the throughput gap narrowed, but
> > the fundamental CPU-efficiency result remained fully intact.
> >
>
> Sorry but your result that SVE is 2x faster does not remain fully intact,
> right? Given that the speedup is now 5.5%?
>
> Should we just disregard the above results (and explanations) and focus
> on the stuff below?
>
> > To completely isolate these variables and provide accurate real-world
> > data, the following test campaigns were done based on the SnapRAID
> > project (https://github.com/amadvance/snapraid) using its
> > perf_bench.c tool with proper memory displacement and a 256 KiB block
> > size.
> >
> > Test configurations:
> > - c7g.medium (AWS Graviton3, 1 vCPU): Neoverse-V1, 256-bit SVE
> > - c7g.xlarge (AWS Graviton3, 4 vCPUs): Neoverse-V1, 256-bit SVE
> > - c8g.xlarge (AWS Graviton4, 4 vCPUs): Neoverse-V2, 128-bit SVE
> >
> >
> > =========================================================
> > Section 1: SnapRAID Validation on Graviton3 / Neoverse-V1
> > =========================================================
> >
> ...
> >
> > 1.3 Main Graviton3 Conclusions
> > - On 256-bit SVE hardware, svex4 consistently retires about ~34% fewer
> > instructions and ~10-15% fewer CPU cycles than neonx4.
> >
> > =========================================================
> > Section 2: SnapRAID Validation on Graviton4 / Neoverse-V2
> > =========================================================
> >
> ...
> >
> > 2.3 Main Graviton4 Conclusions
> > - On Neoverse-V2, SVE vector length is 128-bit (same as NEON).
> > - Without the 256-bit width, NEON outperforms SVE.
> > - svex4 retires ~32% MORE instructions here and is consistently slower.
> >
> > =========================================================
> > Section 3: Validation on c7g.medium (1 vCPU)
> > =========================================================
> >
> ...
> > 3.3 Main c7g.medium Conclusions
> > - The instruction count reduction (~34%) perfectly matches the 4-vCPU
> > instance.
> > - The single vCPU is heavily memory-bandwidth constrained (cycle counts
> > are much higher waiting for RAM).
> >
>
> OK, so the takeaway here is that SVE is only worth the hassle if the vector
> length is at least 256 bits. This is not entirely surprising, but given that
> Graviton4 went back to 128 bit vectors from 256, I wonder what the future
> expectation is here.
>
> But having these numbers is definitely a good first step. Now we need to
> quantify the overhead associated with having kernel mode SVE state that
> needs to be preserved/restored.
>
> However, 10%-15% speedup that can only be achieved on SVE implementations
> with 256 bit vectors or more may not be that enticing in the end. (The
> fact that you are retiring 34% instructions less does not really matter
> here unless there is some meaningful SMT-like sharing of functional units
> going on in the meantime, which seems unlikely on a CPU that is maxed out
> on the data side)
>
>
> > =========================================================
> > Section 4: The Pitfalls of the Current Kernel Benchmark
> > =========================================================
> >
>
> These results seem very relevant - perhaps Christoph can give some guidance
> on how we might use these to improve the built-in benchmarks to be more
> accurate.
>
>
> Thanks,
>
^ permalink raw reply
* Re: [PATCH v3 5/8] sframe: Allow unsorted FDEs.
From: Jens Remus @ 2026-04-16 14:57 UTC (permalink / raw)
To: Dylan Hatch, Roman Gushchin, Weinan Liu, Will Deacon,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Steven Rostedt,
Catalin Marinas, Jiri Kosina
Cc: Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan, Song Liu,
joe.lawrence, linux-toolchains, linux-kernel, live-patching,
linux-arm-kernel, Heiko Carstens
In-Reply-To: <20260406185000.1378082-6-dylanbhatch@google.com>
On 4/6/2026 8:49 PM, Dylan Hatch wrote:
> The .sframe in kernel modules is built without SFRAME_F_FDE_SORTED set.
> In order to allow sframe PC lookup in modules, add a code path to handle
> unsorted FDE tables by doing a simple linear search.
>
> Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
With my below two minor comments considered:
Reviewed-by: Jens Remus <jremus@linux.ibm.com>
> diff --git a/include/linux/sframe.h b/include/linux/sframe.h
> @@ -64,6 +64,7 @@ struct sframe_section {
> unsigned long text_start;
> unsigned long text_end;
>
> + bool fdes_sorted;
> unsigned long fdes_start;
> unsigned long fres_start;
> unsigned long fres_end;
The struct would be smaller if the bool fdes_sorted flag would be
inserted after the unsigned int num_fdes field:
$ pahole -C sframe_section kernel/unwind/sframe.o
Yours:
size: 96
With bool fdes_sorted moved after unsigned int num_fdes:
size: 88
> diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
> @@ -179,9 +179,34 @@ static __always_inline int __read_fde(struct sframe_section *sec,
> return -EFAULT;
> }
>
> -static __always_inline int __find_fde(struct sframe_section *sec,
> - unsigned long ip,
> - struct sframe_fde_internal *fde)
> +static __always_inline int __find_fde_unsorted(struct sframe_section *sec,
> + unsigned long ip,
> + struct sframe_fde_internal *fde)
> +{
> + struct sframe_fde_v3 *cur, *start, *end;
> +
> + start = (struct sframe_fde_v3 *)sec->fdes_start;
> + end = start + sec->num_fdes;
> +
> + for (cur = start; cur < end; cur++) {
> + s64 func_off;
> + u32 func_size;
> + unsigned long func_addr;
> +
> + DATA_GET(sec, func_off, &cur->func_start_off, s64, Efault);
> + DATA_GET(sec, func_size, &cur->func_size, u32, Efault);
> + func_addr = (unsigned long)cur + func_off;
> +
> + if (ip >= func_addr && ip < func_addr + func_size)
> + return __read_fde(sec, cur - start, fde);
> + }
__find_fde() (now __find_fde_sorted()) returns -EINVAL, if no FDE is found:
return -EINVAL;
> +Efault:
> + return -EFAULT;
> +}
Regards,
Jens
--
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com
IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
^ permalink raw reply
* Re: [PATCH 5/9] ASoC: mediatek: mt2701: add HDMI audio memif, FE and BE DAIs
From: Mark Brown @ 2026-04-16 14:23 UTC (permalink / raw)
To: Daniel Golle
Cc: Liam Girdwood, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Jaroslav Kysela,
Takashi Iwai, Cyril Chao, Arnd Bergmann, Kuninori Morimoto,
Nícolas F. R. A. Prado, Eugen Hristev, linux-sound,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <975f39291cffc5d3f201d2ec7fdc2cfdd1fed6aa.1776265610.git.daniel@makrotopia.org>
[-- Attachment #1: Type: text/plain, Size: 1066 bytes --]
On Wed, Apr 15, 2026 at 04:23:59PM +0100, Daniel Golle wrote:
> Extend the MT2701/MT7623N AFE driver with the HDMI playback path:
> +static int mt2701_afe_hdmi_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)
> +{
> + /* Channel count into the HDMI output memif (bits [7:4]). */
> + regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0,
> + 0x000000f0, channels << 4);
Are the other fields guaranteed to be already set up properly?
> + /*
> + * 8-channel I2S framing: standard I2S, 32-bit slots,
> + * LRCK/BCK inverted. The wire protocol is fixed.
> + */
> + regmap_update_bits(afe->regmap, AFE_8CH_I2S_OUT_CON,
> + AFE_8CH_I2S_OUT_CON_WLEN_MASK |
> + AFE_8CH_I2S_OUT_CON_I2S_DELAY |
> + AFE_8CH_I2S_OUT_CON_LRCK_INV |
> + AFE_8CH_I2S_OUT_CON_BCK_INV,
> + AFE_8CH_I2S_OUT_CON_WLEN_32BIT |
> + AFE_8CH_I2S_OUT_CON_I2S_DELAY |
> + AFE_8CH_I2S_OUT_CON_LRCK_INV |
> + AFE_8CH_I2S_OUT_CON_BCK_INV);
Does this need suspend/resume handling?
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH] arm64: KVM: Initialize vGIC before preempt-disabled section in kvm_reset_vcpu()
From: Marc Zyngier @ 2026-04-16 14:20 UTC (permalink / raw)
To: Deepanshu Kartikey
Cc: oupton, joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas,
will, drjones, christoffer.dall, linux-arm-kernel, kvmarm,
linux-kernel, syzbot+12b178b7c756664d2518
In-Reply-To: <20260412080437.38782-1-kartikey406@gmail.com>
On Sun, 12 Apr 2026 09:04:37 +0100,
Deepanshu Kartikey <kartikey406@gmail.com> wrote:
>
> kvm_reset_vcpu() calls kvm_timer_vcpu_reset() inside a preempt-disabled
> section to avoid races with preempt notifiers that also call vcpu put/load.
>
> However, kvm_timer_vcpu_reset() eventually calls kvm_vgic_inject_irq()
> which triggers vgic_lazy_init() if the vGIC has not been initialized yet.
> vgic_lazy_init() acquires a mutex and calls vgic_init() which invokes
> synchronize_srcu_expedited() -- both of which may sleep. Sleeping inside
> a preempt-disabled section is illegal and causes:
>
> BUG: scheduling while atomic: syz.1.49/3699/0x00000002
>
> Fix this by calling vgic_lazy_init() before preempt_disable(). On the
> second call inside kvm_vgic_inject_irq(), vgic_initialized() will return
> true and vgic_lazy_init() will return immediately without sleeping.
>
I think this really goes in the wrong direction. Forcing the vgic (a
global resource) to initialise when the vcpu's timer (a local
resource) is reset feels at best bizarre. Now you are promoting it to
be forced at vcpu reset. This makes things worse.
You probably want to take a step back and look at *why* we end-up
here. The core reason seems to be that the timer emulation caches the
level in a per-timer structure, and tries hard not call into the vgic
unless the level changes. Which means that unless the vgic is
initialised and is able to latch that state, the initial pending state
will not be propagated to the guest.
But do we need this optimisation? I don't think so. Other emulated
devices don't require it. We can let the vgic know the state of the
timer at every vcpu entry, just like we do for other virtual
interrupts that the kernel injects (PMU, vgic MI).
Once you remove the this cache and the need for the vgic to buffer
things outside of normal execution, you can also drop the magic init
from the interrupt injection path, because the injection will happen
on the run path, just like any other PPI.
That'd be a much better approach IMO.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply
* Re: [PATCH bpf v15 0/5] emit ENDBR/BTI instructions for indirect
From: patchwork-bot+netdevbpf @ 2026-04-16 14:10 UTC (permalink / raw)
To: Xu Kuohai
Cc: bpf, linux-kernel, linux-arm-kernel, ast, daniel, andrii,
martin.lau, eddyz87, memxor, yonghong.song, puranjay,
a.s.protopopov, alexis.lothore, list+bpf, linux, yangtiezhu,
hengqi.chen, johan.almbladh, paulburton, hbathini, chleroy,
naveen, luke.r.nels, xi.wang, bjorn, pulehui, iii, hca, gor,
davem, udknight
In-Reply-To: <20260416064341.151802-1-xukuohai@huaweicloud.com>
Hello:
This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <ast@kernel.org>:
On Thu, 16 Apr 2026 06:43:36 +0000 you wrote:
> On architectures with CFI protection enabled that require landing pad
> instructions at indirect jump targets, such as x86 with CET/IBT enabled
> and arm64 with BTI enabled, kernel panics when an indirect jump lands on
> a target without landing pad. Therefore, the JIT must emit landing pad
> instructions for indirect jump targets.
>
> The verifier already recognizes which instructions are indirect jump
> targets during the verification phase. So we can store this information
> in env->insn_aux_data and pass it to the JIT as new parameter, allowing
> the JIT to consult env->insn_aux_data to determine which instructions are
> indirect jump targets.
>
> [...]
Here is the summary with links:
- [bpf,v15,1/5] bpf: Move constants blinding out of arch-specific JITs
https://git.kernel.org/bpf/bpf/c/d3e945223e01
- [bpf,v15,2/5] bpf: Pass bpf_verifier_env to JIT
https://git.kernel.org/bpf/bpf/c/d9ef13f72711
- [bpf,v15,3/5] bpf: Add helper to detect indirect jump targets
https://git.kernel.org/bpf/bpf/c/07ae6c130b46
- [bpf,v15,4/5] bpf, x86: Emit ENDBR for indirect jump targets
https://git.kernel.org/bpf/bpf/c/9a0e89dcc9be
- [bpf,v15,5/5] bpf, arm64: Emit BTI for indirect jump target
https://git.kernel.org/bpf/bpf/c/f6606a44bc43
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH v3 3/8] arm64: entry: add unwind info for various kernel entries
From: Jens Remus @ 2026-04-16 14:09 UTC (permalink / raw)
To: Dylan Hatch, Roman Gushchin, Weinan Liu, Will Deacon,
Josh Poimboeuf, Indu Bhagat, Peter Zijlstra, Steven Rostedt,
Catalin Marinas, Jiri Kosina
Cc: Mark Rutland, Prasanna Kumar T S M, Puranjay Mohan, Song Liu,
joe.lawrence, linux-toolchains, linux-kernel, live-patching,
linux-arm-kernel, Heiko Carstens, Sven Schnelle
In-Reply-To: <20260406185000.1378082-4-dylanbhatch@google.com>
Hello Dylan and Weinan!
On 4/6/2026 8:49 PM, Dylan Hatch wrote:
> From: Weinan Liu <wnliu@google.com>
>
> DWARF CFI (Call Frame Information) specifies how to recover the return
> address and callee-saved registers at each PC in a given function.
> Compilers are able to generate the CFI annotations when they compile
> the code to assembly language. For handcrafted assembly, we need to
> annotate them by hand.
>
> Annotate CFI unwind info for assembly for interrupt and exception
> handlers.
It took me a while to figure, why CFI annotations are uncommonly only
added to selected instruction (ranges) and not the whole functions. I
guess you only want to enable stacktracing using SFrame through
el1*_64_*() (from el1*_64_*_handler()) and call_on_irq_stack(), that is
why the added CFI annotations start after the bl/blr instructions, so
that whenever an unwound return address points after those bl/blr
SFrame can recover the stack pointer, frame pointer, and return address.
Wouldn't that be worth to be documented in the commit message?
> Signed-off-by: Weinan Liu <wnliu@google.com>
> Signed-off-by: Dylan Hatch <dylanbhatch@google.com>
> diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
> @@ -575,7 +575,12 @@ SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
> .if \el == 0
> b ret_to_user
> .else
/* Minimal DWARF CFI for unwinding across call above. */
> + .cfi_startproc
> + .cfi_def_cfa_offset PT_REGS_SIZE
> + .cfi_offset 29, S_FP - PT_REGS_SIZE
> + .cfi_offset 30, S_LR - PT_REGS_SIZE
> b ret_to_kernel
> + .cfi_endproc
> .endif
> SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
> .endm
> @@ -889,6 +894,10 @@ SYM_FUNC_START(call_on_irq_stack)
> add sp, x16, #IRQ_STACK_SIZE
> restore_irq x9
> blr x1
/* Minimal DWARF CFI for unwinding across indirect call above. */
> + .cfi_startproc
> + .cfi_def_cfa 29, 16
> + .cfi_offset 29, -16
> + .cfi_offset 30, -8
>
> save_and_disable_daif x9
> /*
> @@ -900,6 +909,7 @@ SYM_FUNC_START(call_on_irq_stack)
> scs_load_current
> restore_irq x9
> ret
> + .cfi_endproc
> SYM_FUNC_END(call_on_irq_stack)
> NOKPROBE(call_on_irq_stack)
While above minimal DWARF CFI works for your use case, the following
minor issue should probably be better corrected (excerpt from
call_on_irq_stack with your patch applied):
blr x1
.cfi_startproc
.cfi_def_cfa 29, 16 <-- CFA is defined as FP + 16
.cfi_offset 29, -16
.cfi_offset 30, -8
save_and_disable_daif x9
/*
* Restore the SP from the FP, and restore the FP and LR from the frame
* record.
*/
mov sp, x29
ldp x29, x30, [sp], #16 <-- FP is restored, so that the CFA definition is no longer valid
[CORRECTION]
.cfi_restore 29
.cfi_restore 30
.cfi_def_cfa 31, 0
[/CORRECTION]
scs_load_current
restore_irq x9
ret
.cfi_endproc
SYM_FUNC_END(call_on_irq_stack)
Would it alternatively make sense to add complete DWARF CFI annotations
to call_on_irq_stack()? I think the following would do:
SYM_FUNC_START(call_on_irq_stack)
.cfi_startproc
...
/* Create a frame record to save our LR and SP (implicit in FP) */
stp x29, x30, [sp, #-16]!
mov x29, sp
.cfi_def_cfa 29, 16
.cfi_offset 29, -16
.cfi_offset 30, -8
...
/*
* Restore the SP from the FP, and restore the FP and LR from the frame
* record.
*/
mov sp, x29
ldp x29, x30, [sp], #16
.cfi_restore 29
.cfi_restore 30
.cfi_def_cfa 31, 0
...
ret
.cfi_endproc
SYM_FUNC_END(call_on_irq_stack)
Thanks and regards,
Jens
--
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com
IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/
^ permalink raw reply
* Re: [PATCH 1/5] dt-bindings: interrupt-controller: Add support for secure donated SGIs
From: Rob Herring @ 2026-04-16 14:02 UTC (permalink / raw)
To: Sudeep Holla
Cc: Krzysztof Kozlowski, Conor Dooley, Marc Zyngier, devicetree,
linux-kernel, linux-arm-kernel
In-Reply-To: <20260416-true-prophetic-centipede-3ad8e5@sudeepholla>
On Thu, Apr 16, 2026 at 8:09 AM Sudeep Holla <sudeep.holla@kernel.org> wrote:
>
> On Thu, Apr 16, 2026 at 07:11:46AM -0500, Rob Herring wrote:
> > On Sun, Apr 12, 2026 at 06:04:37PM +0100, Sudeep Holla wrote:
> > > In GICv3, SGI security is defined by interrupt grouping and configuration
> > > rather than by SGI number alone. Linux conventionally reserves SGIs 0-7
> > > for non-secure internal kernel IPIs, while higher SGIs is assumed to be
> > > owned/stolen by the Secure world unless explicitly made available.
> > >
> > > Document secure donated SGI interrupt specifiers for the GICv3 binding.
> > > It describes "arm,secure-donated-ns-sgi-ranges" for SGIs donated by the
> > > secure world to non-secure software. It excludes SGIs 0-7, which are
> > > already used by the kernel for internal IPI purposes.
> > >
> > > Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
> > > ---
> > > .../bindings/interrupt-controller/arm,gic-v3.yaml | 27 +++++++++++++++++++++-
> > > include/dt-bindings/interrupt-controller/arm-gic.h | 1 +
> > > 2 files changed, 27 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml
> > > index bfd30aae682b..664727d071c9 100644
> > > --- a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml
> > > +++ b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml
> > > @@ -45,17 +45,24 @@ description: |
> > >
> > > The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
> > > interrupts, 2 for interrupts in the Extended SPI range, 3 for the
> > > - Extended PPI range. Other values are reserved for future use.
> > > + Extended PPI range, and 4 for SGI interrupts. Other values are
> > > + reserved for future use.
> > >
> > > The 2nd cell contains the interrupt number for the interrupt type.
> > > SPI interrupts are in the range [0-987]. PPI interrupts are in the
> > > range [0-15]. Extended SPI interrupts are in the range [0-1023].
> > > Extended PPI interrupts are in the range [0-127].
> > >
> > > + SGI interrupts are in the range [8-15] which overlaps with the SGIs
> > > + assigned to/reserved for the secure world but donated to the non
> > > + secure world to use. Refer "arm,secure-donated-ns-sgi-ranges" for
> > > + more details.
> > > +
> > > The 3rd cell is the flags, encoded as follows:
> > > bits[3:0] trigger type and level flags.
> > > 1 = edge triggered
> > > 4 = level triggered
> > > + SGIs are edge triggered and must be described as such.
> > >
> > > The 4th cell is a phandle to a node describing a set of CPUs this
> > > interrupt is affine to. The interrupt must be a PPI, and the node
> > > @@ -136,6 +143,24 @@ description: |
> > > - $ref: /schemas/types.yaml#/definitions/uint32
> > > - $ref: /schemas/types.yaml#/definitions/uint64
> > >
> > > + arm,secure-donated-ns-sgi-ranges:
> > > + description:
> > > + A list of pairs <sgi span>, where "sgi" is the first SGI INTID of a
> > > + range donated by the secure side to non-secure software, and "span" is
> > > + the size of that range. Multiple ranges can be provided.
> > > +
> > > + SGIs described by interrupt specifiers with type 4 (SGI) must fall
> > > + within one of these ranges. SGIs(0-7) reserved by non-secure world
> > > + for internal IPIs must not be listed here. "sgi" must be in the
> > > + range [8-15], "span" must be in the range [1-8], and the range must
> > > + not extend past SGI 15.
> > > + $ref: /schemas/types.yaml#/definitions/uint32-matrix
> > > + items:
> >
> > As a matrix, you need:
> >
> > items:
> > items:
> > - ...
> > - ...
> >
> > However, given this is at most 8 entries, I would just do an array:
> >
> > minItems: 1
> > maxItems: 8
> > uniqueItems: true
> > items:
> > minimum: 8
> > maximum: 15
> >
>
> Makes sense.
>
> > Unless we need more flexibility in GICv5?
> >
>
> IIUC, there are not SGIs in GICv5 and we may need to use one software PPI
> as a replacement for SGIs. LPIs are used for IPIs.
>
> I am assuming Marc will soon post his opinion/rejection on this series 😉
> based on some offline discussion we had with respect to how it fits with
> GICv5.
>
> > Is there an example we can stick this property into so it gets tested?
> >
>
> Not sure if [1] serves as an example or you are looking for something else.
In the binding example, but don't add a whole new example for it.
Did [1] pass validation? If it did, it shouldn't have and I need to
investigate. If you didn't run it, then that would be the reason I
want it in the binding example.
Rob
^ permalink raw reply
* Re: [PATCH v2 2/3] pwm: rp1: Add RP1 PWM controller driver
From: Uwe Kleine-König @ 2026-04-16 13:48 UTC (permalink / raw)
To: Andrea della Porta
Cc: linux-pwm, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Florian Fainelli, Broadcom internal kernel review list,
devicetree, linux-rpi-kernel, linux-arm-kernel, linux-kernel,
Naushir Patuck, Stanimir Varbanov, mbrugger
In-Reply-To: <aeC6U7D6TfWm8JPx@apocalypse>
[-- Attachment #1: Type: text/plain, Size: 3091 bytes --]
Hello Andrea,
one thing I forgot to ask: Is there a public reference manual covering
the hardware. If yes, please add a link at the top of the driver.
On Thu, Apr 16, 2026 at 12:30:43PM +0200, Andrea della Porta wrote:
> On 19:31 Fri 10 Apr , Uwe Kleine-König wrote:
> > I assume there is a glitch if I update two channels and the old
> > configuration of the first channel ends while I'm in the middle of
> > configuring the second?
>
> The configuration registers are per-channel but the update flag is global.
> I don't have details of the hw insights, my best guess is that anything that
> you set in the registers before updating the flag will take effect, so there
> should be no glitches.
Would be great if you could test that. (Something along the lines of:
configure a very short period and wait a bit to be sure the short
configuration is active. Configure something with a long period and wait
shortly to be sure that the long period started, then change the duty,
toggle the update bit and modify a 2nd channel without toggling update
again. Then check the output of the 2nd channel after the first
channel's period ended.
> > > + if (ticks > U32_MAX)
> > > + ticks = U32_MAX;
> > > + wfhw->period_ticks = ticks;
> >
> > What happens if wf->period_length_ns > 0 but ticks == 0?
>
> I've added a check, returning 1 to signal teh round-up, and a minimum tick of 1
> in this case.
Sounds good. Are you able to verify that there is no +1 missing in the
calculation, e.g. using 1 as register value really gives you a period of
1 tick and not 2?
> > > + if (wf->duty_offset_ns + wf->duty_length_ns >= wf->period_length_ns) {
> >
> > The maybe surprising effect here is that in the two cases
> >
> > wf->duty_offset_ns == wf->period_length_ns and wf->duty_length_ns == 0
> >
> > and
> >
> > wf->duty_length_ns == wf->period_length_ns and wf->duty_offset_ns == 0
> >
> > you're configuring inverted polarity. I doesn't matter technically
> > because the result is the same, but for consumers still using pwm_state
> > this is irritating. That's why pwm-stm32 uses inverted polarity only if
> > also wf->duty_length_ns and wf->duty_offset_ns are non-zero.
Please align to the pwm-stm32 algorithm (as of
https://patch.msgid.link/c5e7767cee821b5f6e00f95bd14a5e13015646fb.1776264104.git.u.kleine-koenig@baylibre.com)
here to decide when to select inverted polarity.
> > > + }
> > > +
> > > + return 0;
> > > +
> > > +err_disable_clk:
> > > + clk_disable_unprepare(rp1->clk);
> > > +
> > > + return ret;
> > > +}
> >
> > On remove you miss to balance the call to clk_prepare_enable() (if no
> > failed call to clk_prepare_enable() in rp1_pwm_resume() happend).
>
> Since this driver now exports a syscon, it's only builtin (=Y) so
> it cannot be unloaded.
> I've also avoided the .remove callback via .suppress_bind_attrs.
Oh no, please work cleanly here and make the driver unbindable. This
yields better code quality and also helps during development and
debugging.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH net-next 5/6] net: stmmac: move PHY handling out of __stmmac_open()/release()
From: Russell King (Oracle) @ 2026-04-16 13:47 UTC (permalink / raw)
To: Alexander Stein
Cc: Andrew Lunn, Heiner Kallweit, Alexandre Torgue, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, linux-arm-kernel,
linux-stm32, Maxime Coquelin, netdev, Paolo Abeni
In-Reply-To: <aeDSTIS9-TDSihbX@shell.armlinux.org.uk>
On Thu, Apr 16, 2026 at 01:13:00PM +0100, Russell King (Oracle) wrote:
> On Thu, Apr 16, 2026 at 02:02:53PM +0200, Alexander Stein wrote:
> > Hi Russel,
> >
> > Am Donnerstag, 16. April 2026, 12:49:25 CEST schrieb Russell King (Oracle):
> > > On Thu, Apr 16, 2026 at 08:20:13AM +0200, Alexander Stein wrote:
> > > > Am Mittwoch, 15. April 2026, 14:59:32 CEST schrieb Russell King (Oracle):
> > > > > On Wed, Apr 15, 2026 at 08:08:40AM +0200, Alexander Stein wrote:
> > > > > > Hi,
> > > > > >
> > > > > > Am Dienstag, 23. September 2025, 13:26:19 CEST schrieb Russell King (Oracle):
> > > > > > > Move the PHY attachment/detachment from the network driver out of
> > > > > > > __stmmac_open() and __stmmac_release() into stmmac_open() and
> > > > > > > stmmac_release() where these actions will only happen when the
> > > > > > > interface is administratively brought up or down. It does not make
> > > > > > > sense to detach and re-attach the PHY during a change of MTU.
> > > > > >
> > > > > > Sorry for coming up now. But I recently noticed this commit breaks changing
> > > > > > the MTU on i.MX8MP. Once I simply change the MTU I run into some DMA error:
> > > > > > $ ip link set dev end1 mtu 1400
> > > > > > imx-dwmac 30bf0000.ethernet end1: Register MEM_TYPE_PAGE_POOL RxQ-0
> > > > > > imx-dwmac 30bf0000.ethernet end1: Register MEM_TYPE_PAGE_POOL RxQ-1
> > > > > > imx-dwmac 30bf0000.ethernet end1: Register MEM_TYPE_PAGE_POOL RxQ-2
> > > > > > imx-dwmac 30bf0000.ethernet end1: Register MEM_TYPE_PAGE_POOL RxQ-3
> > > > > > imx-dwmac 30bf0000.ethernet end1: Register MEM_TYPE_PAGE_POOL RxQ-4
> > > > > > imx-dwmac 30bf0000.ethernet end1: Link is Down
> > > > > > imx-dwmac 30bf0000.ethernet end1: Failed to reset the dma
> > > > > > imx-dwmac 30bf0000.ethernet end1: stmmac_hw_setup: DMA engine initialization failed
> > > > >
> > > > > This basically means that a clock is missing. Please provide more
> > > > > information:
> > > > >
> > > > > - what kernel version are you using?
> > > >
> > > > Currently I am using v6.18.22.
> > > > $ ethtool -i end1
> > > > driver: st_gmac
> > > > version: 6.18.22
> > > > firmware-version:
> > > > expansion-rom-version:
> > > > bus-info: 30bf0000.ethernet
> > > > supports-statistics: yes
> > > > supports-test: no
> > > > supports-eeprom-access: no
> > > > supports-register-dump: yes
> > > > supports-priv-flags: no
> > > >
> > > > > - has EEE been negotiated?
> > > >
> > > > No. It is marked as not supported
> > > >
> > > > $ ethtool --show-eee end1
> > > > EEE settings for end1:
> > > > EEE status: not supported
> > > >
> > > > > - does the problem persist when EEE is disabled?
> > > >
> > > > As EEE is not supported the problem occurs even with EEE disabled.
> > > >
> > > > > - which PHY is attached to stmmac?
> > > >
> > > > It is a TI DP83867.
> > > >
> > > > imx-dwmac 30bf0000.ethernet eth1: PHY [stmmac-1:03] driver [TI DP83867] (irq=136)
> > > >
> > > > > - which PHY interface mode is being used to connect the PHY to stmmac?
> > > >
> > > > For this interface
> > > > > phy-mode = "rgmii-id";
> > > > is set.
> > > >
> > > > In case it is helpful. My platform is arch/arm64/boot/dts/freescale/imx8mp-tqma8mpql-mba8mpxl.dts
> > > > Thanks for assisting. If there a further questions, don't hesitate to ask.
> > >
> > > Thanks.
> > >
> > > So, as best I can determine at the moment, we end up with the following
> > > sequence:
> > >
> > > stmmac_change_mtu()
> > > __stmmac_release()
> > > phylink_stop()
> > > phy_stop()
> > > phy->state = PHY_HALTED
> > > _phy_state_machine() returns PHY_STATE_WORK_SUSPEND
> > > _phy_state_machine_post_work()
> > > phy_suspend()
> > > genphy_suspend()
> > > phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN)
> > >
> > > With the DP83867, this causes most of the PHY to be powered down, thus
> > > stopping the clocks, and this causes the stmmac reset to time out.
> > >
> > > Prior to this commit, we would have called phylink_disconnect_phy()
> > > immediately after phylink_stop(), but I can see nothing that would
> > > be affected by this change there (since that also calls
> > > phy_suspend(), but as the PHY is already suspended, this becomes a
> > > no-op.)
> > >
> > > However, __stmmac_open() would have called stmmac_init_phy(), which
> > > would reattach the PHY. This would have called phy_init_hw(),
> > > resetting the PHY, and phy_resume() which would ensure that the
> > > PDOWN bit is clear - thus clocks would be running.
> > >
> > > As a hack, please can you try calling phylink_prepare_resume()
> > > between the __stmmac_release() and __stmmac_open() in
> > > stmmac_change_mtu(). This should resume the PHY, thus restoring the
> > > clocks necessary for stmmac to reset.
> >
> > I tried the following patch. This works as you suspected.
>
> Brilliant, thanks for proving the theory why it broke.
>
> I'll have a think about the best way to solve this, because
> phylink_prepare_resume() is supposed to be paired with phylink_resume()
> and that isn't the case here.
>
> Please bear with me as my availability for looking at the kernel is
> very unpredictable at present (family health issues.)
I have some patches which passed build testing, but more chaos means
I can't post them nor test them. I'll do something when I'm next able
to, whenever that will be.
The next problem will be netdev's policy over reviews vs patches
balance which I'm already in deficit, and I have *NO* *TIME*
what so ever to review patches - let alone propose patches to
fix people's problems.
So I'm going to say this plainly: if netdev wants to enforce that
rule, then I won't be fixing people's problems.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply
* Re: [patch 35/38] s390: Select ARCH_HAS_RANDOM_ENTROPY
From: Heiko Carstens @ 2026-04-16 13:42 UTC (permalink / raw)
To: Thomas Gleixner
Cc: LKML, linux-s390, Arnd Bergmann, x86, Lu Baolu, iommu,
Michael Grzeschik, netdev, linux-wireless, Herbert Xu,
linux-crypto, Vlastimil Babka, linux-mm, David Woodhouse,
Bernie Thompson, linux-fbdev, Theodore Tso, linux-ext4,
Andrew Morton, Uladzislau Rezki, Marco Elver, Dmitry Vyukov,
kasan-dev, Andrey Ryabinin, Thomas Sailer, linux-hams,
Jason A. Donenfeld, Richard Henderson, linux-alpha, Russell King,
linux-arm-kernel, Catalin Marinas, Huacai Chen, loongarch,
Geert Uytterhoeven, linux-m68k, Dinh Nguyen, Jonas Bonn,
linux-openrisc, Helge Deller, linux-parisc, Michael Ellerman,
linuxppc-dev, Paul Walmsley, linux-riscv, David S. Miller,
sparclinux
In-Reply-To: <20260410120319.924028412@kernel.org>
On Fri, Apr 10, 2026 at 02:21:19PM +0200, Thomas Gleixner wrote:
> The only remaining non-architecture usage of get_cycles() is to provide
> random_get_entropy().
>
> Switch s390 over to the new scheme of selecting ARCH_HAS_RANDOM_ENTROPY and
> providing random_get_entropy() in asm/random.h.
>
> Add 'asm/timex.h' includes to the relevant files, so the global include can
> be removed once all architectures are converted over.
>
> Signed-off-by: Thomas Gleixner <tglx@kernel.org>
> Cc: Heiko Carstens <hca@linux.ibm.com>
> Cc: linux-s390@vger.kernel.org
> ---
> arch/s390/Kconfig | 1 +
> arch/s390/include/asm/random.h | 12 ++++++++++++
> arch/s390/include/asm/timex.h | 6 ------
> arch/s390/kernel/time.c | 1 +
> arch/s390/kernel/vtime.c | 1 +
> 5 files changed, 15 insertions(+), 6 deletions(-)
Acked-by: Heiko Carstens <hca@linux.ibm.com>
Thomas, would you mind adding the below as minor improvement to this
series?
From 7072e5d66b99a7fa666d17c6ad8cb254f2d8f473 Mon Sep 17 00:00:00 2001
From: Heiko Carstens <hca@linux.ibm.com>
Date: Thu, 16 Apr 2026 15:08:15 +0200
Subject: [PATCH] s390: Use get_tod_clock_fast() for random_get_entropy()
Use get_tod_clock_fast() instead of get_tod_clock_monotonic() to implement
random_get_entropy().
There is no need for random_get_entropy() to provide monotonic increasing
values, nor is there any need to provide (close to) nanosecond granularity
timestamps by shifting the result.
This slightly reduces the execution time of random_get_entropy() and adds
two bits of randomness.
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
---
arch/s390/include/asm/random.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/s390/include/asm/random.h b/arch/s390/include/asm/random.h
index 7daf42dbed32..f6d9312efdbf 100644
--- a/arch/s390/include/asm/random.h
+++ b/arch/s390/include/asm/random.h
@@ -6,7 +6,7 @@
static inline unsigned long random_get_entropy(void)
{
- return (unsigned long)get_tod_clock_monotonic() >> 2;
+ return get_tod_clock_fast();
}
#endif
--
2.51.0
^ permalink raw reply related
* [PATCH v6 4/4] MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver
From: Richard Genoud @ 2026-04-16 13:40 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Philipp Zabel
Cc: Paul Kocialkowski, Thomas Petazzoni, John Stultz, Joao Schim,
bigunclemax, linux-pwm, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, Richard Genoud
In-Reply-To: <20260416134037.3160537-1-richard.genoud@bootlin.com>
Add myself as maintainer of Allwinner SUN8I PWM driver.
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
MAINTAINERS | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f..256ab8be19f1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -912,6 +912,11 @@ S: Maintained
F: Documentation/devicetree/bindings/hwlock/allwinner,sun6i-a31-hwspinlock.yaml
F: drivers/hwspinlock/sun6i_hwspinlock.c
+ALLWINNER SUN8I PWM DRIVER
+M: Richard Genoud <richard.genoud@bootlin.com>
+S: Maintained
+F: drivers/pwm/pwm-sun8i.c
+
ALLWINNER THERMAL DRIVER
M: Vasily Khoruzhick <anarsoul@gmail.com>
M: Yangtao Li <tiny.windzz@gmail.com>
^ permalink raw reply related
* [PATCH v6 3/4] arm64: dts: allwinner: h616: add PWM controller
From: Richard Genoud @ 2026-04-16 13:40 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Philipp Zabel
Cc: Paul Kocialkowski, Thomas Petazzoni, John Stultz, Joao Schim,
bigunclemax, linux-pwm, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, Richard Genoud
In-Reply-To: <20260416134037.3160537-1-richard.genoud@bootlin.com>
The H616 has a PWM controller that can provide PWM signals, but also
plain clocks.
Add the PWM controller node and pins in the device tree.
Tested-by: John Stultz <jstultz@google.com>
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
.../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 47 +++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
index 8d1110c14bad..1c7628a6e4bb 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
@@ -236,6 +236,17 @@ watchdog: watchdog@30090a0 {
clocks = <&osc24M>;
};
+ pwm: pwm@300a000 {
+ compatible = "allwinner,sun50i-h616-pwm";
+ reg = <0x0300a000 0x400>;
+ clocks = <&osc24M>, <&ccu CLK_BUS_PWM>;
+ clock-names = "mod", "bus";
+ resets = <&ccu RST_BUS_PWM>;
+ #pwm-cells = <3>;
+ #clock-cells = <1>;
+ status = "disabled";
+ };
+
pio: pinctrl@300b000 {
compatible = "allwinner,sun50i-h616-pinctrl";
reg = <0x0300b000 0x400>;
@@ -340,6 +351,42 @@ nand_rb1_pin: nand-rb1-pin {
bias-pull-up;
};
+ /omit-if-no-ref/
+ pwm0_pin: pwm0-pin {
+ pins = "PD28";
+ function = "pwm0";
+ };
+
+ /omit-if-no-ref/
+ pwm1_pin: pwm1-pin {
+ pins = "PG19";
+ function = "pwm1";
+ };
+
+ /omit-if-no-ref/
+ pwm2_pin: pwm2-pin {
+ pins = "PH2";
+ function = "pwm2";
+ };
+
+ /omit-if-no-ref/
+ pwm3_pin: pwm3-pin {
+ pins = "PH0";
+ function = "pwm3";
+ };
+
+ /omit-if-no-ref/
+ pwm4_pin: pwm4-pin {
+ pins = "PI14";
+ function = "pwm4";
+ };
+
+ /omit-if-no-ref/
+ pwm5_pin: pwm5-pin {
+ pins = "PA12";
+ function = "pwm5";
+ };
+
/omit-if-no-ref/
spi0_pins: spi0-pins {
pins = "PC0", "PC2", "PC4";
^ permalink raw reply related
* [PATCH v6 2/4] pwm: sun8i: Add H616 PWM support
From: Richard Genoud @ 2026-04-16 13:40 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Philipp Zabel
Cc: Paul Kocialkowski, Thomas Petazzoni, John Stultz, Joao Schim,
bigunclemax, linux-pwm, devicetree, linux-arm-kernel, linux-sunxi,
linux-kernel, Richard Genoud
In-Reply-To: <20260416134037.3160537-1-richard.genoud@bootlin.com>
Add driver for Allwinner H616 PWM controller, supporting up to 6
channels.
Those channels output can be either a PWM signal output or a clock
output, thanks to the bypass.
The channels are paired (0/1, 2/3 and 4/5) and each pair has a
prescaler/mux/gate.
Moreover, each channel has its own prescaler and bypass.
The clock provider part of this driver is needed not only because the
H616 PWM controller provides also clocks when bypass is enabled, but
really because pwm-clock isn't fit to handle all cases here.
pwm-clock would work if the 100MHz clock is requested, but if a lower
clock is requested (like 24MHz), it will request a 42ns period to the
PWM driver which will happily serve, with the 100MHz clock as input a
25MHz frequency and a duty cycle adjustable in the range [0-4]/4,
because that is a sane thing to do for a PWM.
The information missing is that a real clock is resquested, not a PWM.
Tested-by: John Stultz <jstultz@google.com>
Tested-by: Joao Schim <joao@schimsalabim.eu>
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
drivers/pwm/Kconfig | 12 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-sun8i.c | 938 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 951 insertions(+)
create mode 100644 drivers/pwm/pwm-sun8i.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 6f3147518376..c4fd682860d6 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -736,6 +736,18 @@ config PWM_SUN4I
To compile this driver as a module, choose M here: the module
will be called pwm-sun4i.
+config PWM_SUN8I
+ tristate "Allwinner sun8i/sun50i PWM support"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ depends on HAS_IOMEM && COMMON_CLK
+ help
+ Generic PWM framework driver for Allwinner H616 SoCs.
+ It supports generic PWM, but can also provides a plain clock.
+ The AC300 PHY integrated in H616 SoC needs such a clock.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-sun8i.
+
config PWM_SUNPLUS
tristate "Sunplus PWM support"
depends on ARCH_SUNPLUS || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 0dc0d2b69025..ba2e0ec7fc17 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_PWM_STM32) += pwm-stm32.o
obj-$(CONFIG_PWM_STM32_LP) += pwm-stm32-lp.o
obj-$(CONFIG_PWM_STMPE) += pwm-stmpe.o
obj-$(CONFIG_PWM_SUN4I) += pwm-sun4i.o
+obj-$(CONFIG_PWM_SUN8I) += pwm-sun8i.o
obj-$(CONFIG_PWM_SUNPLUS) += pwm-sunplus.o
obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o
obj-$(CONFIG_PWM_TH1520) += pwm_th1520.o
diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c
new file mode 100644
index 000000000000..8f1023e3a2e5
--- /dev/null
+++ b/drivers/pwm/pwm-sun8i.c
@@ -0,0 +1,938 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Allwinner sun8i Pulse Width Modulation Controller
+ *
+ * (C) Copyright 2025 Richard Genoud, Bootlin <richard.genoud@bootlin.com>
+ *
+ * Based on drivers/pwm/pwm-sun4i.c with Copyright:
+ *
+ * Copyright (C) 2014 Alexandre Belloni <alexandre.belloni@bootlin.com>
+ *
+ * Limitations:
+ * - As the channels are paired (0/1, 2/3, 4/5), they share the same clock
+ * source and prescaler(div_m), but they also have their own prescaler(div_k)
+ * and bypass.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+#include <linux/time.h>
+
+/* PWM IRQ Enable Register */
+#define SUN8I_PWM_PIER 0x0
+
+/* PWM IRQ Status Register */
+#define SUN8I_PWM_PISR 0x4
+
+/* PWM Capture IRQ Enable Register */
+#define SUN8I_PWM_CIER 0x10
+
+/* PWM Capture IRQ Status Register */
+#define SUN8I_PWM_CISR 0x14
+
+/* PWMCC Pairs Clock Configuration Registers */
+#define SUN8I_PWM_PCCR(pair) (0x20 + ((pair) * 0x4))
+#define SUN8I_PWM_PCCR_SRC_SHIFT 7
+#define SUN8I_PWM_PCCR_SRC_MASK 1
+#define SUN8I_PWM_PCCR_GATE_BIT 4
+#define SUN8I_PWM_PCCR_BYPASS_BIT(chan) ((chan) % 2 + 5)
+#define SUN8I_PWM_PCCR_DIV_M_SHIFT 0
+
+/* PWMCC Pairs Dead Zone Control Registers */
+#define SUN8I_PWM_PDZCR(pair) (0x30 + ((pair) * 0x4))
+
+/* PWM Enable Register */
+#define SUN8I_PWM_PER 0x40
+#define SUN8I_PWM_ENABLE(chan) BIT(chan)
+
+/* PWM Capture Enable Register */
+#define SUN8I_PWM_CER 0x44
+
+/* PWM Control Register */
+#define SUN8I_PWM_PCR(chan) (0x60 + (chan) * 0x20)
+#define SUN8I_PWM_PCR_PRESCAL_K_SHIFT 0
+#define SUN8I_PWM_PCR_PRESCAL_K_WIDTH 8
+#define SUN8I_PWM_PCR_ACTIVE_STATE BIT(8)
+
+/* PWM Period Register */
+#define SUN8I_PWM_PPR(chan) (0x64 + (chan) * 0x20)
+#define SUN8I_PWM_PPR_PERIOD_MASK GENMASK(31, 16)
+#define SUN8I_PWM_PPR_DUTY_MASK GENMASK(15, 0)
+#define SUN8I_PWM_PPR_PERIOD_VALUE(reg) (FIELD_GET(SUN8I_PWM_PPR_PERIOD_MASK, reg) + 1)
+#define SUN8I_PWM_PPR_DUTY_VALUE(reg) FIELD_GET(SUN8I_PWM_PPR_DUTY_MASK, reg)
+#define SUN8I_PWM_PPR_PERIOD(prd) FIELD_PREP(SUN8I_PWM_PPR_PERIOD_MASK, (prd) - 1)
+#define SUN8I_PWM_DUTY(dty) FIELD_PREP(SUN8I_PWM_PPR_DUTY_MASK, dty)
+#define SUN8I_PWM_PPR_PERIOD_MAX (FIELD_MAX(SUN8I_PWM_PPR_PERIOD_MASK) + 1)
+
+/* PWM Count Register */
+#define SUN8I_PWM_PCNTR(chan) (0x68 + (chan) * 0x20)
+
+/* PWM Capture Control Register */
+#define SUN8I_PWM_CCR(chan) (0x6c + (chan) * 0x20)
+
+/* PWM Capture Rise Lock Register */
+#define SUN8I_PWM_CRLR(chan) (0x70 + (chan) * 0x20)
+
+/* PWM Capture Fall Lock Register */
+#define SUN8I_PWM_CFLR(chan) (0x74 + (chan) * 0x20)
+
+#define SUN8I_PWM_PAIR_IDX(chan) ((chan) >> 1)
+
+/*
+ * Block diagram of the PWM clock controller:
+ *
+ * _____ ______ ________
+ * OSC24M --->| | | | | |
+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> SUN8I_PWM_clock_src_xy
+ * |_____| |______| |________|
+ * ________
+ * | |
+ * +->| /div_k |---> SUN8I_PWM_clock_x
+ * | |________|
+ * | ______
+ * | | |
+ * +-->| Gate |----> SUN8I_PWM_bypass_clock_x
+ * | |______|
+ * SUN8I_PWM_clock_src_xy ---+ ________
+ * | | |
+ * +->| /div_k |---> SUN8I_PWM_clock_y
+ * | |________|
+ * | ______
+ * | | |
+ * +-->| Gate |----> SUN8I_PWM_bypass_clock_y
+ * |______|
+ *
+ * NB: when the bypass is set, all the PWM logic is bypassed.
+ * So, the duty cycle and polarity can't be modified (we just have a clock).
+ * The bypass in PWM mode is used to achieve a 1/2 relative duty cycle with the
+ * fastest clock.
+ *
+ * SUN8I_PWM_clock_x/y serve for the PWM purpose.
+ * SUN8I_PWM_bypass_clock_x/y serve for the clock-provider purpose.
+ *
+ */
+
+/*
+ * Table used for /div_m (diviser before obtaining SUN8I_PWM_clock_src_xy)
+ * It's actually CLK_DIVIDER_POWER_OF_TWO, but limited to /256
+ */
+#define CLK_TABLE_DIV_M_ENTRY(i) { \
+ .val = (i), .div = 1 << (i) \
+}
+
+static const struct clk_div_table clk_table_div_m[] = {
+ CLK_TABLE_DIV_M_ENTRY(0),
+ CLK_TABLE_DIV_M_ENTRY(1),
+ CLK_TABLE_DIV_M_ENTRY(2),
+ CLK_TABLE_DIV_M_ENTRY(3),
+ CLK_TABLE_DIV_M_ENTRY(4),
+ CLK_TABLE_DIV_M_ENTRY(5),
+ CLK_TABLE_DIV_M_ENTRY(6),
+ CLK_TABLE_DIV_M_ENTRY(7),
+ CLK_TABLE_DIV_M_ENTRY(8),
+ { /* sentinel */ }
+};
+
+#define SUN8I_PWM_XY_SRC_GATE(_pair, _reg) \
+struct clk_gate gate_xy_##_pair = { \
+ .reg = (void *)(_reg), \
+ .bit_idx = SUN8I_PWM_PCCR_GATE_BIT, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_gate_ops, \
+ } \
+}
+
+#define SUN8I_PWM_XY_SRC_MUX(_pair, _reg) \
+struct clk_mux mux_xy_##_pair = { \
+ .reg = (void *)(_reg), \
+ .shift = SUN8I_PWM_PCCR_SRC_SHIFT, \
+ .mask = SUN8I_PWM_PCCR_SRC_MASK, \
+ .flags = CLK_MUX_ROUND_CLOSEST, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_mux_ops, \
+ } \
+}
+
+#define SUN8I_PWM_XY_SRC_DIV(_pair, _reg) \
+struct clk_divider rate_xy_##_pair = { \
+ .reg = (void *)(_reg), \
+ .shift = SUN8I_PWM_PCCR_DIV_M_SHIFT, \
+ .table = clk_table_div_m, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_divider_ops, \
+ } \
+}
+
+#define SUN8I_PWM_X_DIV(_idx, _reg) \
+struct clk_divider rate_x_##_idx = { \
+ .reg = (void *)(_reg), \
+ .shift = SUN8I_PWM_PCR_PRESCAL_K_SHIFT, \
+ .width = SUN8I_PWM_PCR_PRESCAL_K_WIDTH, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_divider_ops, \
+ } \
+}
+
+#define SUN8I_PWM_X_BYPASS_GATE(_idx) \
+struct clk_gate gate_x_bypass_##_idx = { \
+ .reg = (void *)SUN8I_PWM_PER, \
+ .bit_idx = _idx, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_gate_ops, \
+ } \
+}
+
+#define SUN8I_PWM_XY_CLK_SRC(_pair, _reg) \
+ static SUN8I_PWM_XY_SRC_MUX(_pair, _reg); \
+ static SUN8I_PWM_XY_SRC_GATE(_pair, _reg); \
+ static SUN8I_PWM_XY_SRC_DIV(_pair, _reg)
+
+#define SUN8I_PWM_X_CLK(_idx) \
+ static SUN8I_PWM_X_DIV(_idx, SUN8I_PWM_PCR(_idx))
+
+#define SUN8I_PWM_X_BYPASS_CLK(_idx) \
+ SUN8I_PWM_X_BYPASS_GATE(_idx)
+
+#define REF_CLK_XY_SRC(_pair) \
+ { \
+ .name = "pwm-clk-src" #_pair, \
+ .mux_hw = &mux_xy_##_pair.hw, \
+ .gate_hw = &gate_xy_##_pair.hw, \
+ .rate_hw = &rate_xy_##_pair.hw, \
+ }
+
+#define REF_CLK_X(_idx, _pair) \
+ { \
+ .name = "pwm-clk" #_idx, \
+ .parent_names = (const char *[]){ "pwm-clk-src" #_pair }, \
+ .num_parents = 1, \
+ .rate_hw = &rate_x_##_idx.hw, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }
+
+#define REF_CLK_BYPASS(_idx, _pair) \
+ { \
+ .name = "pwm-clk-bypass" #_idx, \
+ .parent_names = (const char *[]){ "pwm-clk-src" #_pair }, \
+ .num_parents = 1, \
+ .gate_hw = &gate_x_bypass_##_idx.hw, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }
+
+/*
+ * SUN8I_PWM_clock_src_xy generation:
+ * _____ ______ ________
+ * OSC24M --->| | | | | |
+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> SUN8I_PWM_clock_src_xy
+ * |_____| |______| |________|
+ */
+SUN8I_PWM_XY_CLK_SRC(01, SUN8I_PWM_PCCR(0));
+SUN8I_PWM_XY_CLK_SRC(23, SUN8I_PWM_PCCR(1));
+SUN8I_PWM_XY_CLK_SRC(45, SUN8I_PWM_PCCR(2));
+
+/*
+ * SUN8I_PWM_clock_x_div generation:
+ * ________
+ * | | SUN8I_PWM_clock_x/y
+ * SUN8I_PWM_clock_src_xy -->| /div_k |--------------->
+ * |________|
+ */
+SUN8I_PWM_X_CLK(0);
+SUN8I_PWM_X_CLK(1);
+SUN8I_PWM_X_CLK(2);
+SUN8I_PWM_X_CLK(3);
+SUN8I_PWM_X_CLK(4);
+SUN8I_PWM_X_CLK(5);
+
+/*
+ * SUN8I_PWM_bypass_clock_xy generation:
+ * ______
+ * | |
+ * SUN8I_PWM_clock_src_xy --->| Gate |-------> SUN8I_PWM_bypass_clock_x
+ * |______|
+ *
+ * The gate is actually SUN8I_PWM_PER register.
+ */
+SUN8I_PWM_X_BYPASS_CLK(0);
+SUN8I_PWM_X_BYPASS_CLK(1);
+SUN8I_PWM_X_BYPASS_CLK(2);
+SUN8I_PWM_X_BYPASS_CLK(3);
+SUN8I_PWM_X_BYPASS_CLK(4);
+SUN8I_PWM_X_BYPASS_CLK(5);
+
+struct clk_pwm_data {
+ const char *name;
+ const char **parent_names;
+ unsigned int num_parents;
+ struct clk_hw *mux_hw;
+ struct clk_hw *rate_hw;
+ struct clk_hw *gate_hw;
+ unsigned long flags;
+};
+
+/* Indexes of REF_CLK_BYPASS and REF_CLK_XY_SRC in the array */
+#define CLK_BYPASS_IDX(sun8i_chip, chan) ((sun8i_chip)->data->npwm + (chan))
+#define CLK_XY_SRC_IDX(sun8i_chip, chan) \
+ ((sun8i_chip)->data->npwm * 2 + SUN8I_PWM_PAIR_IDX(chan))
+static struct clk_pwm_data pwmcc_data[] = {
+ REF_CLK_X(0, 01),
+ REF_CLK_X(1, 01),
+ REF_CLK_X(2, 23),
+ REF_CLK_X(3, 23),
+ REF_CLK_X(4, 45),
+ REF_CLK_X(5, 45),
+ REF_CLK_BYPASS(0, 01),
+ REF_CLK_BYPASS(1, 01),
+ REF_CLK_BYPASS(2, 23),
+ REF_CLK_BYPASS(3, 23),
+ REF_CLK_BYPASS(4, 45),
+ REF_CLK_BYPASS(5, 45),
+ REF_CLK_XY_SRC(01),
+ REF_CLK_XY_SRC(23),
+ REF_CLK_XY_SRC(45),
+ { /* sentinel */ }
+};
+
+enum sun8i_pwm_mode {
+ SUN8I_PWM_MODE_NONE,
+ SUN8I_PWM_MODE_PWM,
+ SUN8I_PWM_MODE_CLK,
+};
+
+struct sun8i_pwm_data {
+ unsigned int npwm;
+};
+
+struct sun8i_pwm_channel {
+ struct clk *pwm_clk;
+ enum sun8i_pwm_mode mode;
+};
+
+struct clk_pwm_pdata {
+ struct clk_hw_onecell_data *hw_data;
+ spinlock_t lock;
+ void __iomem *reg;
+};
+
+struct sun8i_pwm_chip {
+ struct clk_pwm_pdata *clk_pdata;
+ struct sun8i_pwm_channel *channels;
+ struct clk *bus_clk;
+ struct reset_control *rst;
+ void __iomem *base;
+ const struct sun8i_pwm_data *data;
+};
+
+struct sun8i_pwm_waveform {
+ u8 enabled:1;
+ u8 active_state:1;
+ u8 bypass_en:1;
+ u16 duty_ticks;
+ u32 period_ticks;
+ unsigned long clk_rate;
+};
+
+static inline struct sun8i_pwm_chip *sun8i_pwm_from_chip(const struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static inline u32 sun8i_pwm_readl(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned long offset)
+{
+ return readl(sun8i_chip->base + offset);
+}
+
+static inline void sun8i_pwm_writel(struct sun8i_pwm_chip *sun8i_chip,
+ u32 val, unsigned long offset)
+{
+ writel(val, sun8i_chip->base + offset);
+}
+
+static void sun8i_pwm_set_bypass(struct sun8i_pwm_chip *sun8i_chip,
+ unsigned int idx, bool en_bypass)
+{
+ unsigned long flags, reg_offset;
+ u32 val;
+
+ spin_lock_irqsave(&sun8i_chip->clk_pdata->lock, flags);
+
+ reg_offset = SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(idx));
+ val = sun8i_pwm_readl(sun8i_chip, reg_offset);
+ if (en_bypass)
+ val |= BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx));
+ else
+ val &= ~BIT(SUN8I_PWM_PCCR_BYPASS_BIT(idx));
+
+ sun8i_pwm_writel(sun8i_chip, val, reg_offset);
+
+ spin_unlock_irqrestore(&sun8i_chip->clk_pdata->lock, flags);
+}
+
+static int sun8i_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+
+ scoped_guard(spinlock_irqsave, &sun8i_chip->clk_pdata->lock) {
+ if (chan->mode == SUN8I_PWM_MODE_CLK)
+ return -EBUSY;
+ chan->mode = SUN8I_PWM_MODE_PWM;
+ }
+
+ return clk_prepare_enable(chan->pwm_clk);
+}
+
+static void sun8i_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+
+ clk_disable_unprepare(chan->pwm_clk);
+ chan->mode = SUN8I_PWM_MODE_NONE;
+}
+
+static int sun8i_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct sun8i_pwm_waveform *wfhw = _wfhw;
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ u32 val;
+
+ wfhw->clk_rate = clk_get_rate(chan->pwm_clk);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ wfhw->enabled = !!(SUN8I_PWM_ENABLE(pwm->hwpwm) & val);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCCR(SUN8I_PWM_PAIR_IDX(pwm->hwpwm)));
+ wfhw->bypass_en = !!(val & BIT(SUN8I_PWM_PCCR_BYPASS_BIT(pwm->hwpwm)));
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm));
+ wfhw->active_state = !!(val & SUN8I_PWM_PCR_ACTIVE_STATE);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PPR(pwm->hwpwm));
+ wfhw->duty_ticks = SUN8I_PWM_PPR_DUTY_VALUE(val);
+ wfhw->period_ticks = SUN8I_PWM_PPR_PERIOD_VALUE(val);
+
+ dev_dbg(pwmchip_parent(chip),
+ "pwm%u: %s, bypass: %s, polarity: %s, clk_rate=%lu period_ticks=%u duty_ticks=%u\n",
+ pwm->hwpwm,
+ wfhw->enabled ? "enabled" : "disabled",
+ wfhw->bypass_en ? "enabled" : "disabled",
+ wfhw->active_state ? "normal" : "inversed",
+ wfhw->clk_rate, wfhw->period_ticks, wfhw->duty_ticks);
+
+ return 0;
+}
+
+static int sun8i_pwm_round_waveform_fromhw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw,
+ struct pwm_waveform *wf)
+{
+ const struct sun8i_pwm_waveform *wfhw = _wfhw;
+ u64 tmp, resolution;
+
+ dev_dbg(pwmchip_parent(chip),
+ "pwm%u: %s, bypass: %s, polarity: %s, clk_rate=%lu period_ticks=%u duty_ticks=%u\n",
+ pwm->hwpwm,
+ wfhw->enabled ? "enabled" : "disabled",
+ wfhw->bypass_en ? "enabled" : "disabled",
+ wfhw->active_state ? "normal" : "inversed",
+ wfhw->clk_rate, wfhw->period_ticks, wfhw->duty_ticks);
+
+ wf->duty_offset_ns = 0;
+
+ if (!wfhw->enabled || !wfhw->clk_rate) {
+ wf->period_length_ns = 0;
+ wf->duty_length_ns = 0;
+ return 0;
+ }
+
+ if (wfhw->bypass_en) {
+ wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC,
+ wfhw->clk_rate);
+ wf->duty_length_ns = DIV_ROUND_UP_ULL(wf->period_length_ns, 2);
+ return 0;
+ }
+
+ tmp = NSEC_PER_SEC * (u64)wfhw->period_ticks;
+ wf->period_length_ns = DIV_ROUND_UP_ULL(tmp, wfhw->clk_rate);
+
+ tmp = NSEC_PER_SEC * (u64)wfhw->duty_ticks;
+ wf->duty_length_ns = DIV_ROUND_UP_ULL(tmp, wfhw->clk_rate);
+ if (!wfhw->active_state) {
+ /*
+ * For inverted polarity, we have to fix cases where
+ * computed duty_length_ns > requested duty_length_ns
+ * For that, we subtract the actual resolution of the PWM
+ * registers
+ */
+ wf->duty_offset_ns = wf->duty_length_ns;
+ wf->duty_length_ns = wf->period_length_ns - wf->duty_length_ns;
+
+ resolution = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wfhw->clk_rate);
+
+ if (wf->duty_offset_ns >= resolution)
+ wf->duty_offset_ns -= resolution;
+ }
+
+ dev_dbg(pwmchip_parent(chip),
+ "pwm%u period_length_ns=%llu duty_length_ns=%llu duty_offset_ns=%llu\n",
+ pwm->hwpwm, wf->period_length_ns, wf->duty_length_ns,
+ wf->duty_offset_ns);
+
+ return 0;
+}
+
+static int sun8i_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm, const void *_wfhw)
+{
+ const struct sun8i_pwm_waveform *wfhw = _wfhw;
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ unsigned long flags;
+ u32 val;
+ int ret;
+
+ ret = clk_set_rate(chan->pwm_clk, wfhw->clk_rate);
+ if (ret)
+ return ret;
+
+ sun8i_pwm_set_bypass(sun8i_chip, pwm->hwpwm, wfhw->bypass_en);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PCR(pwm->hwpwm));
+ if (wfhw->active_state)
+ val |= SUN8I_PWM_PCR_ACTIVE_STATE;
+ else
+ val &= ~SUN8I_PWM_PCR_ACTIVE_STATE;
+ sun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PCR(pwm->hwpwm));
+
+ val = SUN8I_PWM_DUTY(wfhw->duty_ticks);
+ val |= SUN8I_PWM_PPR_PERIOD(wfhw->period_ticks);
+ sun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PPR(pwm->hwpwm));
+
+ spin_lock_irqsave(&sun8i_chip->clk_pdata->lock, flags);
+
+ val = sun8i_pwm_readl(sun8i_chip, SUN8I_PWM_PER);
+ if (wfhw->enabled)
+ val |= SUN8I_PWM_ENABLE(pwm->hwpwm);
+ else
+ val &= ~SUN8I_PWM_ENABLE(pwm->hwpwm);
+ sun8i_pwm_writel(sun8i_chip, val, SUN8I_PWM_PER);
+
+ spin_unlock_irqrestore(&sun8i_chip->clk_pdata->lock, flags);
+
+ return 0;
+}
+
+static int sun8i_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct sun8i_pwm_chip *sun8i_chip = sun8i_pwm_from_chip(chip);
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[pwm->hwpwm];
+ struct sun8i_pwm_waveform *wfhw = _wfhw;
+ unsigned long max_rate;
+ long calc_rate;
+ u64 period_ratio, double_duty_ratio, freq, duty_cycle;
+
+ dev_dbg(pwmchip_parent(chip),
+ "pwm%u period_length_ns=%llu duty_length_ns=%llu duty_offset_ns=%llu\n",
+ pwm->hwpwm, wf->period_length_ns, wf->duty_length_ns,
+ wf->duty_offset_ns);
+
+ if (wf->period_length_ns == 0) {
+ wfhw->enabled = 0;
+ return 0;
+ }
+
+ wfhw->enabled = 1;
+
+ duty_cycle = wf->duty_length_ns;
+ if (wf->duty_length_ns + wf->duty_offset_ns < wf->period_length_ns)
+ wfhw->active_state = 1;
+ else
+ wfhw->active_state = 0;
+
+ dev_dbg(pwmchip_parent(chip), "polarity: %s\n",
+ wfhw->active_state ? "normal" : "inversed");
+
+ /*
+ * Lowest possible period case:
+ * Without bypass, the lowest possible period is when:
+ * duty cycle = 1 and period cycle = 2 (0x10001 in period register)
+ * E.g. if the input clock is 100MHz, we have a lowest period of 20ns.
+ * Now, with the bypass, the period register is ignored and we directly
+ * have the 100MHz clock as PWM output, that can act as a 10ns period
+ * with 5ns duty.
+ * So, to detect this lowest period case, just get the maximum possible
+ * rate from chan->pwm_clk and compare it with requested period and
+ * duty_cycle.
+ *
+ * But, to get the maximum possible rate, we have to use U32_MAX instead
+ * of (unsigned long)-1.
+ * This is because clk_round_rate() uses ultimately DIV_ROUND_UP_ULL()
+ * that in turn do_div(n,base). And base is uint32_t divisor.
+ */
+ max_rate = clk_round_rate(chan->pwm_clk, U32_MAX);
+
+ dev_dbg(pwmchip_parent(chip), "max_rate: %ld Hz\n", max_rate);
+
+ period_ratio = mul_u64_u64_div_u64(wf->period_length_ns,
+ max_rate, NSEC_PER_SEC);
+ double_duty_ratio = mul_u64_u64_div_u64(duty_cycle, (u64)max_rate * 2,
+ NSEC_PER_SEC);
+ if (period_ratio == 1) {
+ if (double_duty_ratio == 0)
+ /* requested period and duty are too small */
+ return -EINVAL;
+ /*
+ * If the requested period is to small to be generated by the
+ * PWM, but matches the highest clock with a
+ * duty_cycle >= period*2, just bypass the PWM logic
+ */
+ freq = div64_u64(NSEC_PER_SEC, wf->period_length_ns);
+ wfhw->bypass_en = true;
+ } else {
+ wfhw->bypass_en = false;
+ freq = div64_u64(NSEC_PER_SEC * (u64)SUN8I_PWM_PPR_PERIOD_MAX,
+ wf->period_length_ns);
+ /*
+ * Same remark as above, this is to prevent a value to big for
+ * clk_round_rate() to handle
+ */
+ if (freq > U32_MAX)
+ freq = U32_MAX;
+ }
+
+ dev_dbg(pwmchip_parent(chip), "bypass: %s\n",
+ wfhw->bypass_en ? "enabled" : "disabled");
+
+ calc_rate = clk_round_rate(chan->pwm_clk, freq);
+ if (calc_rate <= 0)
+ return calc_rate ? calc_rate : -EINVAL;
+
+ dev_dbg(pwmchip_parent(chip), "calc_rate: %ld Hz\n", calc_rate);
+
+ wfhw->period_ticks = mul_u64_u64_div_u64(calc_rate,
+ wf->period_length_ns,
+ NSEC_PER_SEC);
+ if (wfhw->period_ticks > SUN8I_PWM_PPR_PERIOD_MAX)
+ wfhw->period_ticks = SUN8I_PWM_PPR_PERIOD_MAX;
+
+ /* min value in period register is 1 */
+ if (wfhw->period_ticks == 0)
+ return -EINVAL;
+
+ wfhw->duty_ticks = mul_u64_u64_div_u64(calc_rate, duty_cycle,
+ NSEC_PER_SEC);
+
+ if (wfhw->duty_ticks > wfhw->period_ticks)
+ wfhw->duty_ticks = wfhw->period_ticks;
+
+ if (!wfhw->active_state)
+ wfhw->duty_ticks = wfhw->period_ticks - wfhw->duty_ticks;
+
+ dev_dbg(pwmchip_parent(chip),
+ "pwm%u period_ticks=%u duty_cycle=%llu duty_ticks=%u\n",
+ pwm->hwpwm, wfhw->period_ticks, duty_cycle, wfhw->duty_ticks);
+
+ wfhw->clk_rate = calc_rate;
+
+ return 0;
+}
+
+static const struct pwm_ops sun8i_pwm_ops = {
+ .request = sun8i_pwm_request,
+ .free = sun8i_pwm_free,
+ .sizeof_wfhw = sizeof(struct sun8i_pwm_waveform),
+ .round_waveform_tohw = sun8i_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = sun8i_pwm_round_waveform_fromhw,
+ .read_waveform = sun8i_pwm_read_waveform,
+ .write_waveform = sun8i_pwm_write_waveform,
+};
+
+static struct clk_hw *sun8i_pwm_of_clk_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct sun8i_pwm_chip *sun8i_chip = data;
+ struct clk_hw_onecell_data *hw_data = sun8i_chip->clk_pdata->hw_data;
+ unsigned int idx = clkspec->args[0];
+ struct sun8i_pwm_channel *chan;
+ struct clk_hw *ret_clk = NULL;
+ unsigned long flags;
+
+ if (idx >= sun8i_chip->data->npwm)
+ return ERR_PTR(-EINVAL);
+
+ chan = &sun8i_chip->channels[idx];
+
+ spin_lock_irqsave(&sun8i_chip->clk_pdata->lock, flags);
+
+ if (chan->mode == SUN8I_PWM_MODE_PWM) {
+ ret_clk = ERR_PTR(-EBUSY);
+ } else {
+ chan->mode = SUN8I_PWM_MODE_CLK;
+ ret_clk = hw_data->hws[CLK_BYPASS_IDX(sun8i_chip, idx)];
+ }
+ spin_unlock_irqrestore(&sun8i_chip->clk_pdata->lock, flags);
+
+ if (IS_ERR(ret_clk))
+ goto out;
+
+ sun8i_pwm_set_bypass(sun8i_chip, idx, true);
+out:
+ return ret_clk;
+}
+
+static int sun8i_add_composite_clk(struct clk_pwm_data *data,
+ void __iomem *reg, spinlock_t *lock,
+ struct device *dev, struct clk_hw **hw)
+{
+ const struct clk_ops *mux_ops = NULL, *gate_ops = NULL, *rate_ops = NULL;
+ struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *rate_hw = NULL;
+ struct device_node *node = dev->of_node;
+
+ if (data->mux_hw) {
+ struct clk_mux *mux;
+
+ mux_hw = data->mux_hw;
+ mux = to_clk_mux(mux_hw);
+ mux->lock = lock;
+ mux_ops = mux_hw->init->ops;
+ mux->reg = (uintptr_t)mux->reg + reg;
+ }
+
+ if (data->gate_hw) {
+ struct clk_gate *gate;
+
+ gate_hw = data->gate_hw;
+ gate = to_clk_gate(gate_hw);
+ gate->lock = lock;
+ gate_ops = gate_hw->init->ops;
+ gate->reg = (uintptr_t)gate->reg + reg;
+ }
+
+ if (data->rate_hw) {
+ struct clk_divider *rate;
+
+ rate_hw = data->rate_hw;
+ rate = to_clk_divider(rate_hw);
+ rate_ops = rate_hw->init->ops;
+ rate->lock = lock;
+ rate->reg = (uintptr_t)rate->reg + reg;
+
+ if (rate->table) {
+ const struct clk_div_table *clkt;
+ int table_size = 0;
+
+ for (clkt = rate->table; clkt->div; clkt++)
+ table_size++;
+ rate->width = order_base_2(table_size);
+ }
+ }
+
+ /*
+ * Retrieve the parent clock names from DTS for pwm-clk-srcxy
+ */
+ if (!data->parent_names) {
+ data->num_parents = of_clk_get_parent_count(node);
+ if (data->num_parents == 0)
+ return -ENOENT;
+
+ data->parent_names = devm_kzalloc(dev,
+ sizeof(*data->parent_names),
+ GFP_KERNEL);
+ for (unsigned int i = 0; i < data->num_parents; i++)
+ data->parent_names[i] = of_clk_get_parent_name(node, i);
+ }
+
+ *hw = clk_hw_register_composite(dev, data->name, data->parent_names,
+ data->num_parents, mux_hw,
+ mux_ops, rate_hw, rate_ops,
+ gate_hw, gate_ops, data->flags);
+
+ return PTR_ERR_OR_ZERO(*hw);
+}
+
+static int sun8i_pwm_init_clocks(struct platform_device *pdev,
+ struct sun8i_pwm_chip *sun8i_chip)
+{
+ struct clk_pwm_pdata *pdata;
+ struct device *dev = &pdev->dev;
+ int num_clocks = 0;
+ int ret;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate clk_pwm_pdata\n");
+
+ while (pwmcc_data[num_clocks].name)
+ num_clocks++;
+
+ pdata->hw_data = devm_kzalloc(dev, struct_size(pdata->hw_data, hws, num_clocks),
+ GFP_KERNEL);
+ if (!pdata->hw_data)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate hw clocks\n");
+
+ pdata->hw_data->num = num_clocks;
+ pdata->reg = sun8i_chip->base;
+
+ spin_lock_init(&pdata->lock);
+
+ for (int i = 0; i < num_clocks; i++) {
+ struct clk_hw **hw = &pdata->hw_data->hws[i];
+
+ ret = sun8i_add_composite_clk(&pwmcc_data[i], pdata->reg,
+ &pdata->lock, dev, hw);
+ if (ret) {
+ dev_err_probe(dev, ret,
+ "Failed to register hw clock %s\n",
+ pwmcc_data[i].name);
+ for (i--; i >= 0; i--)
+ clk_hw_unregister_composite(pdata->hw_data->hws[i]);
+ return ret;
+ }
+ }
+
+ sun8i_chip->clk_pdata = pdata;
+
+ return 0;
+}
+
+static void sun8i_pwm_unregister_clk(void *data)
+{
+ struct clk_hw_onecell_data *hw_data = data;
+
+ for (unsigned int i = 0; i < hw_data->num; i++)
+ clk_hw_unregister_composite(hw_data->hws[i]);
+}
+
+static int sun8i_pwm_probe(struct platform_device *pdev)
+{
+ const struct sun8i_pwm_data *data;
+ struct device *dev = &pdev->dev;
+ struct sun8i_pwm_chip *sun8i_chip;
+ struct pwm_chip *chip;
+ int ret;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return dev_err_probe(dev, -ENODEV,
+ "Missing specific data structure\n");
+
+ chip = devm_pwmchip_alloc(dev, data->npwm, sizeof(*sun8i_chip));
+ if (IS_ERR(chip))
+ return dev_err_probe(dev, PTR_ERR(chip),
+ "Failed to allocate pwmchip\n");
+
+ sun8i_chip = sun8i_pwm_from_chip(chip);
+ sun8i_chip->data = data;
+ sun8i_chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sun8i_chip->base))
+ return dev_err_probe(dev, PTR_ERR(sun8i_chip->base),
+ "Failed to get PWM base address\n");
+
+ sun8i_chip->bus_clk = devm_clk_get_enabled(dev, "bus");
+ if (IS_ERR(sun8i_chip->bus_clk))
+ return dev_err_probe(dev, PTR_ERR(sun8i_chip->bus_clk),
+ "Failed to get bus clock\n");
+
+ sun8i_chip->channels = devm_kmalloc_array(dev, data->npwm,
+ sizeof(*(sun8i_chip->channels)),
+ GFP_KERNEL);
+ if (!sun8i_chip->channels)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate %d channels array\n",
+ data->npwm);
+
+ chip->ops = &sun8i_pwm_ops;
+
+ ret = sun8i_pwm_init_clocks(pdev, sun8i_chip);
+ if (ret)
+ return ret;
+
+ for (unsigned int i = 0; i < data->npwm; i++) {
+ struct sun8i_pwm_channel *chan = &sun8i_chip->channels[i];
+ struct clk_hw **hw = &sun8i_chip->clk_pdata->hw_data->hws[i];
+
+ chan->pwm_clk = devm_clk_hw_get_clk(dev, *hw, NULL);
+ if (IS_ERR(chan->pwm_clk)) {
+ ret = dev_err_probe(dev, PTR_ERR(chan->pwm_clk),
+ "Failed to register PWM clock %d\n", i);
+ return ret;
+ }
+ chan->mode = SUN8I_PWM_MODE_NONE;
+ }
+
+ ret = devm_of_clk_add_hw_provider(dev, sun8i_pwm_of_clk_get, sun8i_chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add HW clock provider\n");
+
+ ret = devm_add_action_or_reset(dev, sun8i_pwm_unregister_clk,
+ sun8i_chip->clk_pdata->hw_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add devm action\n");
+
+ /* Deassert reset */
+ sun8i_chip->rst = devm_reset_control_get_shared_deasserted(dev, NULL);
+ if (IS_ERR(sun8i_chip->rst))
+ return dev_err_probe(dev, PTR_ERR(sun8i_chip->rst),
+ "Failed to get reset control\n");
+
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+
+ platform_set_drvdata(pdev, chip);
+
+ return 0;
+}
+
+static const struct sun8i_pwm_data sun50i_h616_pwm_data = {
+ .npwm = 6,
+};
+
+static const struct of_device_id sun8i_pwm_dt_ids[] = {
+ {
+ .compatible = "allwinner,sun50i-h616-pwm",
+ .data = &sun50i_h616_pwm_data,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, sun8i_pwm_dt_ids);
+
+static struct platform_driver sun8i_pwm_driver = {
+ .driver = {
+ .name = "sun8i-pwm",
+ .of_match_table = sun8i_pwm_dt_ids,
+ },
+ .probe = sun8i_pwm_probe,
+};
+module_platform_driver(sun8i_pwm_driver);
+
+MODULE_AUTHOR("Richard Genoud <richard.genoud@bootlin.com>");
+MODULE_DESCRIPTION("Allwinner sun8i PWM driver");
+MODULE_LICENSE("GPL");
^ permalink raw reply related
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