Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* 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

* [PATCH v6 1/4] dt-bindings: pwm: allwinner: add h616 pwm compatible
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, Conor Dooley
In-Reply-To: <20260416134037.3160537-1-richard.genoud@bootlin.com>

Allwinner H616 PWM block is quite different from the A10 or H6, but at
the end, it uses the same clocks as the H6; so the sun4i pwm binding can
be used.
It has 6 channels than can generate PWM waveforms.
If the bypass is enabled (one bypass per channel) the output is no more
a PWM waveform, but a clock that can (and is) used as input for other
devices, like the AC300 PHY.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
 .../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
index 1197858e431f..4f58110ec98f 100644
--- a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
+++ b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
@@ -14,6 +14,9 @@ properties:
   "#pwm-cells":
     const: 3
 
+  "#clock-cells":
+    const: 1
+
   compatible:
     oneOf:
       - const: allwinner,sun4i-a10-pwm
@@ -36,6 +39,7 @@ properties:
           - const: allwinner,sun50i-h5-pwm
           - const: allwinner,sun5i-a13-pwm
       - const: allwinner,sun50i-h6-pwm
+      - const: allwinner,sun50i-h616-pwm
 
   reg:
     maxItems: 1
@@ -62,7 +66,9 @@ allOf:
       properties:
         compatible:
           contains:
-            const: allwinner,sun50i-h6-pwm
+            enum:
+              - allwinner,sun50i-h6-pwm
+              - allwinner,sun50i-h616-pwm
 
     then:
       properties:
@@ -83,6 +89,17 @@ allOf:
         clocks:
           maxItems: 1
 
+  - if:
+      not:
+        properties:
+          compatible:
+            contains:
+              const: allwinner,sun50i-h616-pwm
+
+    then:
+      properties:
+        "#clock-cells": false
+
 required:
   - compatible
   - reg


^ permalink raw reply related

* [PATCH v6 0/4] Introduce Allwinner H616 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

Allwinner H616 PWM controller is quite different from the A10 one.

It can drive 6 PWM channels, and like for the A10, each channel has a
bypass that permits to output a clock, bypassing the PWM logic, when
enabled.

But, the channels are paired 2 by 2, sharing a first set of
MUX/prescaler/gate.
Then, for each channel, there's another prescaler (that will be bypassed
if the bypass is enabled for this channel).

It looks like that:
            _____      ______      ________
OSC24M --->|     |    |      |    |        |
APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy
           |_____|    |______|    |________|
                          ________
                         |        |
                      +->| /div_k |---> PWM_clock_x
                      |  |________|
                      |    ______
                      |   |      |
                      +-->| Gate |----> PWM_bypass_clock_x
                      |   |______|
PWM_clock_src_xy -----+   ________
                      |  |        |
                      +->| /div_k |---> PWM_clock_y
                      |  |________|
                      |    ______
                      |   |      |
                      +-->| Gate |----> PWM_bypass_clock_y
                          |______|

Where xy can be 0/1, 2/3, 4/5

PWM_clock_x/y serve for the PWM purpose.
PWM_bypass_clock_x/y serve for the clock-provider purpose.
The common clock framework has been used to manage those clocks.

This PWM driver serves as a clock-provider for PWM_bypass_clocks.
This is needed for example by the embedded AC300 PHY which clock comes
from PMW5 pin (PB12).

Usually, to get a clock from a PWM driver, we use the pwm-clock driver
so that the PWM driver doesn't need to be a clk-provider itself.
While this works in most cases, here it just doesn't.
That's because the pwm-clock request a period from the PWM driver,
without any clue that it actually wants a clock at a specific frequency,
and not a PWM signal with duty cycle capability.
So, the PWM driver doesn't know if it can use the bypass or not, it
doesn't even have the real accurate frequency information (23809524 Hz
instead of 24MHz) because PWM drivers only deal with periods.

With pwm-clock, we loose a precious information along the way (that we
actually want a clock and not a PWM signal).
That's ok with simple PWM drivers that don't have multiple input clocks,
but in this case, without this information, we can't know for sure which
clock to use.
And here, for instance, if we ask for a 24MHz clock, pwm-clock will
requests 42ns (assigned-clocks doesn't help for that matter). The logic
is to select the highest clock (100MHz) with no prescaler and a duty
cycle value of 2/4 => we have 25MHz instead of 24MHz.
And that's a perfectly fine choice for a PMW, because we still can
change the duty cycle in the range [0-4]/4.
But obviously for a clock, we don't care about the duty cycle, but more
about the clock accuracy.

And actually, this PWM is really a PWM AND a real clock when the bypass
is set.

This series is based onto v7.0

NB: checkpatch is not happy with patch 2, but it's a false positive.
It doesn't detect that PWM_XY_SRC_MUX/GATE/DIV are structures, but as
it's more readable like that, I prefer keeping it that way.

Changes since v5:
- remove trailing junk after commit message in patch 4
- remove Tested-by when it doesn't make sense.
(sorry for the noise)

Changes since v4:
- Fix a bug on bypass for channels greater than 1
- add colons to clarify 2 debug messages
- switch from H616 to sun8i prefix (in code, filename, module name)
- fix consistency issues in macro parameters
- rename some macros with a confusing naming
- rebase on v7.0

Changes since v3:
- gather Acked-by/Tested-by
- fix cast from pointer to integer of different size (kernel test robot
  with arc platform)
- add devm_action for clk_hw_unregister_composite as suggested by Philipp
- remove now unused pwm_remove as suggested by Philipp

Changes since v2:
- use U32_MAX instead of defining UINT32_MAX
- add a comment on U32_MAX usage in clk_round_rate()
- change clk_table_div_m (use macros)
- fix formatting (double space, superfluous comma, extra line feed)
- fix the parent clock order
- simplify code by using scoped_guard()
- add missing const in to_h616_pwm_chip() and rename to
h616_pwm_from_chip()
- add/remove missing/superfluous error messages
- rename cnt->period_ticks, duty_cnt->duty_ticks
- fix PWM_PERIOD_MAX
- add .remove() callback
- fix DIV_ROUND_CLOSEST_ULL->DIV_ROUND_UP_ULL
- add H616_ prefix
- protect _reg in macros
- switch to waveforms instead of apply/get_state
- shrink struct h616_pwm_channel
- rebase on v6.19-rc4

Changes since v1:
- rebase onto v6.19-rc1
- add missing headers
- remove MODULE_ALIAS (suggested by Krzysztof)
- use sun4i-pwm binding instead of creating a new one (suggested by Krzysztof)
- retrieve the parent clocks from the devicetree
- switch num_parents to unsigned int

Richard Genoud (4):
  dt-bindings: pwm: allwinner: add h616 pwm compatible
  pwm: sun8i: Add H616 PWM support
  arm64: dts: allwinner: h616: add PWM controller
  MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver

 .../bindings/pwm/allwinner,sun4i-a10-pwm.yaml |  19 +-
 MAINTAINERS                                   |   5 +
 .../arm64/boot/dts/allwinner/sun50i-h616.dtsi |  47 +
 drivers/pwm/Kconfig                           |  12 +
 drivers/pwm/Makefile                          |   1 +
 drivers/pwm/pwm-sun8i.c                       | 938 ++++++++++++++++++
 6 files changed, 1021 insertions(+), 1 deletion(-)
 create mode 100644 drivers/pwm/pwm-sun8i.c


base-commit: 028ef9c96e96197026887c0f092424679298aae8


^ permalink raw reply

* Re: [PATCH v2] raid6: arm64: add SVE optimized implementation for syndrome generation
From: Ard Biesheuvel @ 2026-04-16 13:39 UTC (permalink / raw)
  To: Demian Shulhan, Christoph Hellwig
  Cc: 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: <CAOLeWCtxPk7q_bVvrcKaipSKr+_=57Auobcj0cFRvXxXMdH58g@mail.gmail.com>

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 net v2] net: airoha: Add missing bits in airoha_qdma_cleanup_tx_queue()
From: Simon Horman @ 2026-04-16 13:29 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <ad_DrrC6duye_lR0@lore-desk>

On Wed, Apr 15, 2026 at 06:58:22PM +0200, Lorenzo Bianconi wrote:
> On Apr 15, Simon Horman wrote:
> > On Tue, Apr 14, 2026 at 08:50:52AM +0200, Lorenzo Bianconi wrote:
> > > Similar to airoha_qdma_cleanup_rx_queue(), reset DMA TX descriptors in
> > > airoha_qdma_cleanup_tx_queue routine. Moreover, reset TX_DMA_IDX to
> > > TX_CPU_IDX to notify the NIC the QDMA TX ring is empty.
> > > 
> > > Fixes: 23020f0493270 ("net: airoha: Introduce ethernet support for EN7581 SoC")
> > > Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> > > ---
> > > Changes in v2:
> > > - Move q->ndesc initialization at end of airoha_qdma_init_tx routine in
> > >   order to avoid any possible NULL pointer dereference in
> > >   airoha_qdma_cleanup_tx_queue()
> > 
> > This seems to be a separate issue.
> > If so, I think it should be split out into a separate patch.
> > 
> > > - Check if q->tx_list is empty in airoha_qdma_cleanup_tx_queue()
> > > - Link to v1: https://lore.kernel.org/r/20260410-airoha_qdma_cleanup_tx_queue-fix-net-v1-1-b7171c8f1e78@kernel.org
> > 
> > I think it was covered in the review Jakub forwarded for v1.  But FTR,
> > Sashiko has some feedback on this patch in the form of an existing bug
> > (that should almost certainly be handled separately from this patch).
> 
> Hi Simon,
> 
> I took a look to the Sashiko's report [0] but this issue is not introduced by
> this patch and, even if it would be a better approach, I guess the hw is
> capable of managing out-of-order TX descriptors. So I guess this patch is fine
> in this way, agree?
> 
> [0] https://sashiko.dev/#/patchset/20260414-airoha_qdma_cleanup_tx_queue-fix-net-v2-1-875de57cc022%40kernel.org

Hi Lorenzo,

You responded in a different sub thread, so I think this is probably
implied. But FTR:

1. I agree [0] is not introduced by this patch
2. If the hw is capable of managing TX descriptors then
   I think that [0] is a false positive

Regardless, [0] doesn't effect this patch.


^ permalink raw reply

* Re: [PATCH v5 4/4] MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver
From: Krzysztof Kozlowski @ 2026-04-16 13:24 UTC (permalink / raw)
  To: Richard Genoud, 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
In-Reply-To: <20260416131419.3152419-5-richard.genoud@bootlin.com>

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>
> Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
> 
> maintainer
> ---

And you have commit msg trailing junk.

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v5 1/4] dt-bindings: pwm: allwinner: add h616 pwm compatible
From: Krzysztof Kozlowski @ 2026-04-16 13:24 UTC (permalink / raw)
  To: Richard Genoud, 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, Conor Dooley
In-Reply-To: <20260416131419.3152419-2-richard.genoud@bootlin.com>

On 16/04/2026 15:14, Richard Genoud wrote:
> Allwinner H616 PWM block is quite different from the A10 or H6, but at
> the end, it uses the same clocks as the H6; so the sun4i pwm binding can
> be used.
> It has 6 channels than can generate PWM waveforms.
> If the bypass is enabled (one bypass per channel) the output is no more
> a PWM waveform, but a clock that can (and is) used as input for other
> devices, like the AC300 PHY.
> 
> Acked-by: Conor Dooley <conor.dooley@microchip.com>
> Tested-by: John Stultz <jstultz@google.com>


Please drop or help me understand how one can test bindings on the
device? Build process tools are not testing.

Don't blindly copy tags from cover letter.

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH v5 4/4] MAINTAINERS: Add entry on Allwinner sun8i/H616 PWM driver
From: Krzysztof Kozlowski @ 2026-04-16 13:23 UTC (permalink / raw)
  To: Richard Genoud, 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
In-Reply-To: <20260416131419.3152419-5-richard.genoud@bootlin.com>

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.


Best regards,
Krzysztof


^ permalink raw reply

* [PATCH] MAINTAINERS: Move Peter De Schrijver to CREDITS
From: Thierry Reding @ 2026-04-16 13:18 UTC (permalink / raw)
  To: Thierry Reding
  Cc: linux-tegra, linux-arm-kernel, linux-pm, linux-omap, linux-kernel,
	Paul Walmsley

From: Thierry Reding <treding@nvidia.com>

Peter sadly passed away a while back. Paul did a much better job at
finding the right words to mourn this loss than I ever could, so I will
leave this link here:

  https://lore.kernel.org/lkml/alpine.DEB.2.21.999.2407240345480.11116@utopia.booyaka.com/T/#u

Co-developed-by: Paul Walmsley <pjw@kernel.org>
Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 CREDITS     | 6 ++++++
 MAINTAINERS | 1 -
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/CREDITS b/CREDITS
index 885fb05d8816..29fcfa679430 100644
--- a/CREDITS
+++ b/CREDITS
@@ -3645,7 +3645,13 @@ D: Macintosh IDE Driver
 
 N: Peter De Schrijver
 E: stud11@cc4.kuleuven.ac.be
+E: p2@mind.be
+E: peter.de-schrijver@nokia.com
+E: pdeschrijver@nvidia.com
+E: p2@psychaos.be
 D: Mitsumi CD-ROM driver patches March version
+D: OMAP power management
+D: NVIDIA Tegra clock and BPMP drivers, among many other things
 S: Molenbaan 29
 S: B2240 Zandhoven
 S: Belgium
diff --git a/MAINTAINERS b/MAINTAINERS
index ef978bfca514..ffe20d770249 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26145,7 +26145,6 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git
 N:	[^a-z]tegra
 
 TEGRA CLOCK DRIVER
-M:	Peter De Schrijver <pdeschrijver@nvidia.com>
 M:	Prashant Gaikwad <pgaikwad@nvidia.com>
 S:	Supported
 F:	drivers/clk/tegra/
-- 
2.52.0



^ permalink raw reply related

* [PATCH v5 1/4] dt-bindings: pwm: allwinner: add h616 pwm compatible
From: Richard Genoud @ 2026-04-16 13:14 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, Conor Dooley
In-Reply-To: <20260416131419.3152419-1-richard.genoud@bootlin.com>

Allwinner H616 PWM block is quite different from the A10 or H6, but at
the end, it uses the same clocks as the H6; so the sun4i pwm binding can
be used.
It has 6 channels than can generate PWM waveforms.
If the bypass is enabled (one bypass per channel) the output is no more
a PWM waveform, but a clock that can (and is) used as input for other
devices, like the AC300 PHY.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Tested-by: John Stultz <jstultz@google.com>
Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
 .../bindings/pwm/allwinner,sun4i-a10-pwm.yaml | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
index 1197858e431f..4f58110ec98f 100644
--- a/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
+++ b/Documentation/devicetree/bindings/pwm/allwinner,sun4i-a10-pwm.yaml
@@ -14,6 +14,9 @@ properties:
   "#pwm-cells":
     const: 3
 
+  "#clock-cells":
+    const: 1
+
   compatible:
     oneOf:
       - const: allwinner,sun4i-a10-pwm
@@ -36,6 +39,7 @@ properties:
           - const: allwinner,sun50i-h5-pwm
           - const: allwinner,sun5i-a13-pwm
       - const: allwinner,sun50i-h6-pwm
+      - const: allwinner,sun50i-h616-pwm
 
   reg:
     maxItems: 1
@@ -62,7 +66,9 @@ allOf:
       properties:
         compatible:
           contains:
-            const: allwinner,sun50i-h6-pwm
+            enum:
+              - allwinner,sun50i-h6-pwm
+              - allwinner,sun50i-h616-pwm
 
     then:
       properties:
@@ -83,6 +89,17 @@ allOf:
         clocks:
           maxItems: 1
 
+  - if:
+      not:
+        properties:
+          compatible:
+            contains:
+              const: allwinner,sun50i-h616-pwm
+
+    then:
+      properties:
+        "#clock-cells": false
+
 required:
   - compatible
   - reg


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox