* Re: (subset) [PATCH 1/3] KVM: arm64: vgic: Fix IIDR revision field extracted from wrong value
From: David Woodhouse @ 2026-05-10 21:28 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Joey Gouly, Suzuki K Poulose,
Zenghui Yu, Catalin Marinas, Will Deacon, Paolo Bonzini,
Shuah Khan, Raghavendra Rao Ananta, Eric Auger, Kees Cook,
Arnd Bergmann, Nathan Chancellor, linux-arm-kernel, kvmarm,
linux-kernel, kvm, linux-kselftest
In-Reply-To: <106addd3f78918a9a584c43c181a9609aef1ceca.camel@infradead.org>
[-- Attachment #1: Type: text/plain, Size: 1468 bytes --]
On Fri, 2026-04-24 at 13:24 +0100, David Woodhouse wrote:
> On Fri, 2026-04-24 at 12:07 +0100, Marc Zyngier wrote:
> > On Tue, 07 Apr 2026 21:27:02 +0100, David Woodhouse wrote:
> > > The uaccess write handlers for GICD_IIDR in both GICv2 and GICv3
> > > extract the revision field from 'reg' (the current IIDR value read back
> > > from the emulated distributor) instead of 'val' (the value userspace is
> > > trying to write). This means userspace can never actually change the
> > > implementation revision — the extracted value is always the current one.
> > >
> > > Fix the FIELD_GET to use 'val' so that userspace can select a different
> > > revision for migration compatibility.
> > >
> > > [...]
> >
> > Applied to fixes, thanks!
> >
> > [1/3] KVM: arm64: vgic: Fix IIDR revision field extracted from wrong value
> > commit: a0e6ae45af17e8b27958830595799c702ffbab8d
>
> There was a v2 of this series which also cleaned up the weird
> inconsistency of the IIDR value with the actual behaviour, and which
> fixed the fact that it's currently not possible to maintain guest
> compatibility when upgrading from a pre-d53c2c29ae0d kernel to a new
> one — despite the fact that that kind of compatibility is *precisely*
> what the revision field in the IIDR is designed for.
>
> https://lore.kernel.org/all/20260408113256.2095505-1-dwmw2@infradead.org/
Is there a reason the rest of these fixes didn't make 7.1?
[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]
^ permalink raw reply
* [PATCH v5 3/6] iommu/arm-smmu-v3: Suppress EVTQ/PRIQ events in kdump kernel
From: Nicolin Chen @ 2026-05-10 21:23 UTC (permalink / raw)
To: will, robin.murphy, jgg, kevin.tian
Cc: joro, praan, kees, baolu.lu, miko.lenczewski, smostafa,
linux-arm-kernel, iommu, linux-kernel, stable, jamien
In-Reply-To: <cover.1778416609.git.nicolinc@nvidia.com>
In kdump cases, the crashed kernel's CDs and page tables can be corrupted,
which could trigger event spamming. Also, we cannot serve page requests.
Skip the IRQ setup for EVTQ/PRIQ in arm_smmu_setup_irqs(), and guard the
thread functions against being entered via a combined-IRQ delivery while
the queue is disabled.
Fixes: b63b3439b856 ("iommu/arm-smmu-v3: Abort all transactions if SMMU is enabled in kdump kernel")
Cc: stable@vger.kernel.org # v6.12+
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 23 +++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 579c8af82d6b6..ebb0826d74541 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2364,6 +2364,14 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
static DEFINE_RATELIMIT_STATE(rs, DEFAULT_RATELIMIT_INTERVAL,
DEFAULT_RATELIMIT_BURST);
+ /*
+ * A combined IRQ might call into this function with the queue disabled.
+ * E.g. kdump, where stale HW PROD vs SW CONS would drive a bogus drain
+ * and a CONS write to a disabled queue.
+ */
+ if (!(readl_relaxed(smmu->base + ARM_SMMU_CR0) & CR0_EVTQEN))
+ return IRQ_NONE;
+
do {
while (!queue_remove_raw(q, evt)) {
arm_smmu_decode_event(smmu, evt, &event);
@@ -2432,6 +2440,14 @@ static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
struct arm_smmu_ll_queue *llq = &q->llq;
u64 evt[PRIQ_ENT_DWORDS];
+ /*
+ * A combined IRQ might call into this function with the queue disabled.
+ * E.g. kdump, where stale HW PROD vs SW CONS would drive a bogus drain
+ * and a CONS write to a disabled queue.
+ */
+ if (!(readl_relaxed(smmu->base + ARM_SMMU_CR0) & CR0_PRIQEN))
+ return IRQ_NONE;
+
do {
while (!queue_remove_raw(q, evt))
arm_smmu_handle_ppr(smmu, evt);
@@ -5056,7 +5072,10 @@ static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)
static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
{
int ret, irq;
- u32 irqen_flags = IRQ_CTRL_EVTQ_IRQEN | IRQ_CTRL_GERROR_IRQEN;
+ u32 irqen_flags = IRQ_CTRL_GERROR_IRQEN;
+
+ if (!is_kdump_kernel())
+ irqen_flags |= IRQ_CTRL_EVTQ_IRQEN;
/* Disable IRQs first */
ret = arm_smmu_write_reg_sync(smmu, 0, ARM_SMMU_IRQ_CTRL,
@@ -5082,7 +5101,7 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
} else
arm_smmu_setup_unique_irqs(smmu);
- if (smmu->features & ARM_SMMU_FEAT_PRI)
+ if (!is_kdump_kernel() && (smmu->features & ARM_SMMU_FEAT_PRI))
irqen_flags |= IRQ_CTRL_PRIQ_IRQEN;
/* Enable interrupt generation on the SMMU */
--
2.43.0
^ permalink raw reply related
* [PATCH v5 6/6] iommu/arm-smmu-v3: Detect ARM_SMMU_OPT_KDUMP_ADOPT in probe()
From: Nicolin Chen @ 2026-05-10 21:23 UTC (permalink / raw)
To: will, robin.murphy, jgg, kevin.tian
Cc: joro, praan, kees, baolu.lu, miko.lenczewski, smostafa,
linux-arm-kernel, iommu, linux-kernel, stable, jamien
In-Reply-To: <cover.1778416609.git.nicolinc@nvidia.com>
arm_smmu_device_hw_probe() runs before arm_smmu_init_structures(), so it's
natural to decide whether the kdump kernel must adopt the crashed kernel's
stream table.
Given that memremap is used to adopt the old stream table, set this option
only on a coherent SMMU.
And make sure SMMU isn't in Service Failure Mode.
Fixes: b63b3439b856 ("iommu/arm-smmu-v3: Abort all transactions if SMMU is enabled in kdump kernel")
Cc: stable@vger.kernel.org # v6.12+
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 31 +++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index bb8cc580e7ad8..310f9cf7e5577 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -5403,6 +5403,33 @@ static void arm_smmu_get_httu(struct arm_smmu_device *smmu, u32 reg)
hw_features, fw_features);
}
+static void arm_smmu_device_hw_probe_kdump(struct arm_smmu_device *smmu)
+{
+ u32 gerror, gerrorn, active;
+
+ /* No adoption if SMMU is disabled (i.e., there is no in-flight DMA) */
+ if (!(readl_relaxed(smmu->base + ARM_SMMU_CR0) & CR0_SMMUEN))
+ return;
+
+ /* For now, only support a coherent SMMU that works with MEMREMAP_WB */
+ if (!(smmu->features & ARM_SMMU_FEAT_COHERENCY)) {
+ dev_warn(smmu->dev,
+ "kdump: non-coherent SMMU unsupported; reset to block all DMAs\n");
+ return;
+ }
+
+ gerror = readl_relaxed(smmu->base + ARM_SMMU_GERROR);
+ gerrorn = readl_relaxed(smmu->base + ARM_SMMU_GERRORN);
+ active = gerror ^ gerrorn;
+ if (active & GERROR_SFM_ERR) {
+ dev_warn(smmu->dev,
+ "kdump: SMMU in Service Failure Mode, must reset\n");
+ return;
+ }
+
+ smmu->options |= ARM_SMMU_OPT_KDUMP_ADOPT;
+}
+
static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
{
u32 reg;
@@ -5617,6 +5644,10 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
dev_info(smmu->dev, "oas %lu-bit (features 0x%08x)\n",
smmu->oas, smmu->features);
+
+ if (is_kdump_kernel())
+ arm_smmu_device_hw_probe_kdump(smmu);
+
return 0;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v5 0/6] iommu/arm-smmu-v3: Fix device crash on kdump kernel
From: Nicolin Chen @ 2026-05-10 21:22 UTC (permalink / raw)
To: will, robin.murphy, jgg, kevin.tian
Cc: joro, praan, kees, baolu.lu, miko.lenczewski, smostafa,
linux-arm-kernel, iommu, linux-kernel, stable, jamien
When transitioning to a kdump kernel, the primary kernel might have crashed
while endpoint devices were actively bus-mastering DMA. Currently, the SMMU
driver aggressively resets the hardware during probe by clearing CR0_SMMUEN
and setting the Global Bypass Attribute (GBPA) to ABORT.
In a kdump scenario, this aggressive reset is highly destructive:
a) If GBPA is set to ABORT, in-flight DMA will be aborted, generating fatal
PCIe AER or SErrors that may panic the kdump kernel
b) If GBPA is set to BYPASS, in-flight DMA targeting some IOVAs will bypass
the SMMU and corrupt the physical memory at those 1:1 mapped IOVAs.
To safely absorb in-flight DMA, the kdump kernel must leave SMMUEN=1 intact
and avoid modifying STRTAB_BASE. This allows HW to continue translating in-
flight DMA using the crashed kernel's page tables until the endpoint device
drivers probe and quiesce their respective hardware.
However, the ARM SMMUv3 architecture specification states that updating the
SMMU_STRTAB_BASE register while SMMUEN == 1 is UNPREDICTABLE or ignored.
This leaves a kdump kernel no choice but to adopt the stream table from the
crashed kernel.
In this series:
- Introduce an ARM_SMMU_OPT_KDUMP_ADOPT
- Skip SMMUEN and STRTAB_BASE resets in arm_smmu_device_reset()
- Skip EVENTQ and PRIQ setups including interrupts and their handlers
- Memremap the crashed kernel's stream tables into the kdump kernel [*]
- Defer any default domain attachment to retain STEs until device drivers
explicitly request it.
[*] For verification reason, this series only fixes coherent SMMUs.
For non-ARM_SMMU_OPT_KDUMP_ADOPT cases, keep a status quo since the commit
3f54c447df34f ("iommu/arm-smmu-v3: Don't disable SMMU in kdump kernel"):
full reset followed by driver-initiated reattach, potentially rejecting any
in-flight DMA.
Note that the series requires Jason's work that was merged in v6.12: commit
85196f54743d ("iommu/arm-smmu-v3: Reorganize struct arm_smmu_strtab_cfg").
I have a backported version that is verified with a v6.8 kernel. I can send
if we see a strong need after this version is accepted.
This is on Github:
https://github.com/nicolinc/iommufd/commits/smmuv3_kdump-v5
Changelog
v5
* Add Reviewed-by from Kevin
* Drop READ_ONCE on lazy-attach L1 read
* Split "Skip EVTQ/PRIQ setup" into two patches
* Tighten kdump probe comment and dev_warn message
* Use MEM + BUSY in arm_smmu_kdump_phys_is_corrupted
v4
https://lore.kernel.org/all/cover.1777446969.git.nicolinc@nvidia.com/
* Rebase v7.1-rc1
* s/arm_smmu_adopt/arm_smmu_kdump_adopt
* Revert alloc/memremap/fmt on fallback
* Reorder patches to avoid bisect regression
* Use IRQ_NONE for spurious evtq/priq entries
* Cap linear log2size by kdump's allocation bound
* Defer clearing FEAT_2_LVL_STRTAB on linear adopt
* Add arm_smmu_kdump_phys_is_corrupted() validation
* Defer l2 stream table memremap till master inserts
* Re-validate L1 desc on master insert with READ_ONCE
v3
https://lore.kernel.org/all/cover.1777150307.git.nicolinc@nvidia.com/
* s/OPT_KDUMP/OPT_KDUMP_ADOPT
* Do not adopt if GERROR_SFM_ERR
* Retain CR0_ATSCHK beside CR0_SMMUEN
* Clear latched GERROR bits (e.g. CMDQ_ERR)
* Assert ARM_SMMU_FEAT_COHERENCY in adopt functions
* Add STE.Cfg check in arm_smmu_is_attach_deferred()
* Fix validations on return codes from devm_memremap()
* Sanitize crashed kernel register values in adopt functions
* Drop unnecessary l2ptrs guard in arm_smmu_is_attach_deferred()
* Don't enable PRIQ/EVTQ irqs and guard the irq functions for combined
irq cases
v2
https://lore.kernel.org/all/cover.1776286352.git.nicolinc@nvidia.com/
* Add warning in non-coherent SMMU cases
* Keep eventq/priq disabled v.s. enabling-and-disabling-later
* Check KDUMP option in the beginning of arm_smmu_device_reset()
* Validate STRTAB format matches HW capability instead of forcing flags
v1:
https://lore.kernel.org/all/cover.1775763475.git.nicolinc@nvidia.com/
Nicolin Chen (6):
iommu/arm-smmu-v3: Add arm_smmu_kdump_adopt_strtab() for kdump
iommu/arm-smmu-v3: Implement is_attach_deferred() for kdump
iommu/arm-smmu-v3: Suppress EVTQ/PRIQ events in kdump kernel
iommu/arm-smmu-v3: Skip EVTQ/PRIQ setup in kdump kernel
iommu/arm-smmu-v3: Retain CR0_SMMUEN during kdump device reset
iommu/arm-smmu-v3: Detect ARM_SMMU_OPT_KDUMP_ADOPT in probe()
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 473 +++++++++++++++++++-
2 files changed, 450 insertions(+), 24 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH v5 4/6] iommu/arm-smmu-v3: Skip EVTQ/PRIQ setup in kdump kernel
From: Nicolin Chen @ 2026-05-10 21:23 UTC (permalink / raw)
To: will, robin.murphy, jgg, kevin.tian
Cc: joro, praan, kees, baolu.lu, miko.lenczewski, smostafa,
linux-arm-kernel, iommu, linux-kernel, stable, jamien
In-Reply-To: <cover.1778416609.git.nicolinc@nvidia.com>
In kdump cases, the crashed kernel's CDs and page tables can be corrupted,
which could trigger event spamming. Also, we cannot serve page requests.
Skip the EVTQ/PRIQ setup entirely rather than enabling then disabling them.
Also add some inline comments explaining that.
Fixes: b63b3439b856 ("iommu/arm-smmu-v3: Abort all transactions if SMMU is enabled in kdump kernel")
Cc: stable@vger.kernel.org # v6.12+
Suggested-by: Kevin Tian <kevin.tian@intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 43 +++++++++++++--------
1 file changed, 27 insertions(+), 16 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index ebb0826d74541..b7298218bac9a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -5211,21 +5211,35 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
cmd.opcode = CMDQ_OP_TLBI_NSNH_ALL;
arm_smmu_cmdq_issue_cmd_with_sync(smmu, &cmd);
- /* Event queue */
- writeq_relaxed(smmu->evtq.q.q_base, smmu->base + ARM_SMMU_EVTQ_BASE);
- writel_relaxed(smmu->evtq.q.llq.prod, smmu->page1 + ARM_SMMU_EVTQ_PROD);
- writel_relaxed(smmu->evtq.q.llq.cons, smmu->page1 + ARM_SMMU_EVTQ_CONS);
-
- enables |= CR0_EVTQEN;
- ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
- ARM_SMMU_CR0ACK);
- if (ret) {
- dev_err(smmu->dev, "failed to enable event queue\n");
- return ret;
+ /*
+ * Event queue
+ *
+ * Do not enable in a kdump case, as the crashed kernel's CDs and page
+ * tables might be corrupted, triggering event spamming.
+ */
+ if (!is_kdump_kernel()) {
+ writeq_relaxed(smmu->evtq.q.q_base,
+ smmu->base + ARM_SMMU_EVTQ_BASE);
+ writel_relaxed(smmu->evtq.q.llq.prod,
+ smmu->page1 + ARM_SMMU_EVTQ_PROD);
+ writel_relaxed(smmu->evtq.q.llq.cons,
+ smmu->page1 + ARM_SMMU_EVTQ_CONS);
+
+ enables |= CR0_EVTQEN;
+ ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
+ ARM_SMMU_CR0ACK);
+ if (ret) {
+ dev_err(smmu->dev, "failed to enable event queue\n");
+ return ret;
+ }
}
- /* PRI queue */
- if (smmu->features & ARM_SMMU_FEAT_PRI) {
+ /*
+ * PRI queue
+ *
+ * Do not enable in a kdump case, as we cannot serve page requests.
+ */
+ if (!is_kdump_kernel() && (smmu->features & ARM_SMMU_FEAT_PRI)) {
writeq_relaxed(smmu->priq.q.q_base,
smmu->base + ARM_SMMU_PRIQ_BASE);
writel_relaxed(smmu->priq.q.llq.prod,
@@ -5258,9 +5272,6 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
return ret;
}
- if (is_kdump_kernel())
- enables &= ~(CR0_EVTQEN | CR0_PRIQEN);
-
/* Enable the SMMU interface */
enables |= CR0_SMMUEN;
ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
--
2.43.0
^ permalink raw reply related
* [PATCH v5 5/6] iommu/arm-smmu-v3: Retain CR0_SMMUEN during kdump device reset
From: Nicolin Chen @ 2026-05-10 21:23 UTC (permalink / raw)
To: will, robin.murphy, jgg, kevin.tian
Cc: joro, praan, kees, baolu.lu, miko.lenczewski, smostafa,
linux-arm-kernel, iommu, linux-kernel, stable, jamien
In-Reply-To: <cover.1778416609.git.nicolinc@nvidia.com>
When ARM_SMMU_OPT_KDUMP_ADOPT is detected, do not disable SMMUEN and skip
the CR1/CR2/STRTAB_BASE update sequence in arm_smmu_device_reset(). Those
register writes are all CONSTRAINED UNPREDICTABLE while CR0_SMMUEN==1, so
leaving them intact lets in-flight DMAs continue to be translated by the
adopted stream table.
Initialize 'enables' to 0 so it can carry CR0_SMMUEN in kdump case. Then,
preserve that when enabling the command queue.
Clear latched gerror bits if necessary.
Fixes: b63b3439b856 ("iommu/arm-smmu-v3: Abort all transactions if SMMU is enabled in kdump kernel")
Cc: stable@vger.kernel.org # v6.12+
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 47 +++++++++++++++++++--
1 file changed, 44 insertions(+), 3 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index b7298218bac9a..bb8cc580e7ad8 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -5151,11 +5151,28 @@ static void arm_smmu_write_strtab(struct arm_smmu_device *smmu)
static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
{
int ret;
- u32 reg, enables;
+ u32 reg, enables = 0;
struct arm_smmu_cmdq_ent cmd;
- /* Clear CR0 and sync (disables SMMU and queue processing) */
reg = readl_relaxed(smmu->base + ARM_SMMU_CR0);
+
+ /*
+ * In a kdump case (set when CR0_SMMUEN=1 and !GERROR_SFM_ERR), retain
+ * CR0_SMMUEN to avoid aborting in-flight DMA, and CR0_ATSCHK to carry
+ * on the ATS-check policy.
+ *
+ * According to spec, updating STRTAB_BASE/CR1/CR2 when CR0_SMMUEN=1 is
+ * CONSTRAINED UNPREDICTABLE. So, skip those register updates and rely
+ * on the adopted stream table from the crashed kernel.
+ */
+ if (smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) {
+ dev_info(smmu->dev,
+ "kdump: retaining SMMUEN for in-flight DMA\n");
+ enables = reg & (CR0_SMMUEN | CR0_ATSCHK);
+ goto reset_queues;
+ }
+
+ /* Clear CR0 and sync (disables SMMU and queue processing) */
if (reg & CR0_SMMUEN) {
dev_warn(smmu->dev, "SMMU currently enabled! Resetting...\n");
arm_smmu_update_gbpa(smmu, GBPA_ABORT, 0);
@@ -5185,12 +5202,36 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
/* Stream table */
arm_smmu_write_strtab(smmu);
+reset_queues:
+ if (smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) {
+ /* Disable queues since arm_smmu_device_disable() was skipped */
+ ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
+ ARM_SMMU_CR0ACK);
+ if (ret) {
+ dev_err(smmu->dev, "failed to disable queues\n");
+ return ret;
+ }
+ }
+
+ /*
+ * GERROR bits are latched. Read after queue disabling so that unhandled
+ * errors would be visible. Ack everything prior to re-enabling the CMDQ
+ * as a stale CMDQ_ERR would halt the CMDQ and new command will timeout.
+ */
+ if (is_kdump_kernel()) {
+ u32 gerror = readl_relaxed(smmu->base + ARM_SMMU_GERROR);
+ u32 gerrorn = readl_relaxed(smmu->base + ARM_SMMU_GERRORN);
+
+ if ((gerror ^ gerrorn) & GERROR_ERR_MASK)
+ writel(gerror, smmu->base + ARM_SMMU_GERRORN);
+ }
+
/* Command queue */
writeq_relaxed(smmu->cmdq.q.q_base, smmu->base + ARM_SMMU_CMDQ_BASE);
writel_relaxed(smmu->cmdq.q.llq.prod, smmu->base + ARM_SMMU_CMDQ_PROD);
writel_relaxed(smmu->cmdq.q.llq.cons, smmu->base + ARM_SMMU_CMDQ_CONS);
- enables = CR0_CMDQEN;
+ enables |= CR0_CMDQEN;
ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
ARM_SMMU_CR0ACK);
if (ret) {
--
2.43.0
^ permalink raw reply related
* [PATCH v5 1/6] iommu/arm-smmu-v3: Add arm_smmu_kdump_adopt_strtab() for kdump
From: Nicolin Chen @ 2026-05-10 21:23 UTC (permalink / raw)
To: will, robin.murphy, jgg, kevin.tian
Cc: joro, praan, kees, baolu.lu, miko.lenczewski, smostafa,
linux-arm-kernel, iommu, linux-kernel, stable, jamien
In-Reply-To: <cover.1778416609.git.nicolinc@nvidia.com>
When transitioning to a kdump kernel, the primary kernel might have crashed
while endpoint devices were actively bus-mastering DMA. Currently, the SMMU
driver aggressively resets the hardware during probe by clearing CR0_SMMUEN
and setting the Global Bypass Attribute (GBPA) to ABORT.
In a kdump scenario, this aggressive reset is highly destructive:
a) If GBPA is set to ABORT, in-flight DMA will be aborted, generating fatal
PCIe AER or SErrors that may panic the kdump kernel
b) If GBPA is set to BYPASS, in-flight DMA targeting some IOVAs will bypass
the SMMU and corrupt the physical memory at those 1:1 mapped IOVAs.
To safely absorb in-flight DMAs, a kdump kernel will have to leave SMMUEN=1
intact and avoid modifying STRTAB_BASE, allowing HW to continue translating
in-flight DMAs reusing the crashed kernel's page tables until the endpoint
device drivers probe and quiesce their respective hardware.
However, the ARM SMMUv3 architecture specification states that updating the
SMMU_STRTAB_BASE register while SMMUEN == 1 is UNPREDICTABLE or ignored.
This leaves a kdump kernel no choice but to adopt the stream table from the
crashed kernel.
Introduce ARM_SMMU_OPT_KDUMP_ADOPT and adopt functions memremapping all the
stream tables extracted from STRTAB_BASE and STRTAB_BASE_CFG.
Note that the adoption of the crashed kernel's stream table follows certain
strict rules, since the old stream table might be compromised. Thus, apply
a series of validations against the values read from the registers. If any
address or size doesn't pass the test, it means the stream table cannot be
trusted, so toss it entirely. To avoid OOM due to a deeply corrupted stream
table, the memremap for l2 tables is done on the kdump kernel's demand.
The new option will be set in a following change.
Fixes: b63b3439b856 ("iommu/arm-smmu-v3: Abort all transactions if SMMU is enabled in kdump kernel")
Cc: stable@vger.kernel.org # v6.12+
Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 305 +++++++++++++++++++-
2 files changed, 303 insertions(+), 3 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index ef42df4753ec4..cd60b692c3901 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -861,6 +861,7 @@ struct arm_smmu_device {
#define ARM_SMMU_OPT_MSIPOLL (1 << 2)
#define ARM_SMMU_OPT_CMDQ_FORCE_SYNC (1 << 3)
#define ARM_SMMU_OPT_TEGRA241_CMDQV (1 << 4)
+#define ARM_SMMU_OPT_KDUMP_ADOPT (1 << 5)
u32 options;
struct arm_smmu_cmdq cmdq;
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index e8d7dbe495f03..bab60e4b91716 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -14,6 +14,7 @@
#include <linux/bitops.h>
#include <linux/crash_dump.h>
#include <linux/delay.h>
+#include <linux/dma-direct.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io-pgtable.h>
@@ -2040,16 +2041,111 @@ static void arm_smmu_init_initial_stes(struct arm_smmu_ste *strtab,
}
}
+/*
+ * Adopting the crashed kernel's stream table has risks: the physical addresses
+ * read from ARM_SMMU_STRTAB_BASE / L1 descriptors may be corrupted. Reject any
+ * range that overlaps the kdump kernel's critical regions.
+ */
+static bool arm_smmu_kdump_phys_is_corrupted(phys_addr_t base, size_t size)
+{
+ /*
+ * On arm64 kdump, iomem_resource entries are typically:
+ * ------------------------------------------------------------
+ * | Entry | IORESOURCE_ Flags | IORES_DESC_ Desc |
+ * ------------------------------------------------------------
+ * | System RAM | MEM + BUSY + SYSRAM | NONE |
+ * | MMIO regions | MEM + BUSY | NONE |
+ * | Reserved memory | MEM | NONE |
+ * ------------------------------------------------------------
+ *
+ * Test and reject any overlap with MEM + BUSY, covering/excluding:
+ * + System RAM: silent corruption of kdump kernel's own memory
+ * + MMIO regions: fatal SError on cacheable speculative access
+ * - Reserved memory: crashed kernel's stream table might reside
+ */
+ if (region_intersects(base, size, IORESOURCE_MEM | IORESOURCE_BUSY,
+ IORES_DESC_NONE) != REGION_DISJOINT)
+ return true;
+
+ /*
+ * Note: physical holes are absent from iomem_resource, so a corrupted
+ * address pointing into one will not be caught here. Closing that gap
+ * requires a firmware memory map and is left as a future improvement.
+ */
+ return false;
+}
+
+static int arm_smmu_kdump_adopt_l2_strtab(struct arm_smmu_device *smmu, u32 sid,
+ u32 l1_idx, u64 l2_dma, u32 span,
+ struct arm_smmu_strtab_l2 **l2table)
+{
+ phys_addr_t base = dma_to_phys(smmu->dev, l2_dma);
+ struct arm_smmu_strtab_l2 *table;
+ size_t size;
+
+ /*
+ * Only a coherent SMMU is supported at this moment. For a non-coherent
+ * SMMU that wants to support ARM_SMMU_OPT_KDUMP_ADOPT, try MEMREMAP_WC.
+ */
+ if (WARN_ON(!(smmu->features & ARM_SMMU_FEAT_COHERENCY)))
+ return -EOPNOTSUPP;
+
+ /*
+ * Retest the memremap inputs in case the L1 descriptor was overwritten
+ * since adopt. Reject this master's insert; panic or SMMU-disable would
+ * either lose the vmcore or cascade aborts. Do not try to fix it, as it
+ * would break all other SIDs in the same bus (PCI case). The corruption
+ * blast radius is already bounded to that bus range.
+ */
+ if (span != STRTAB_SPLIT + 1) {
+ dev_err(smmu->dev,
+ "kdump: L1[%u] span %u changed since adopt (was %u)\n",
+ l1_idx, span, STRTAB_SPLIT + 1);
+ return -EINVAL;
+ }
+
+ size = (1UL << (span - 1)) * sizeof(struct arm_smmu_ste);
+ if (arm_smmu_kdump_phys_is_corrupted(base, size)) {
+ dev_err(smmu->dev,
+ "kdump: L1[%u] now points at a corrupt range\n",
+ l1_idx);
+ return -EINVAL;
+ }
+
+ table = devm_memremap(smmu->dev, base, size, MEMREMAP_WB);
+ if (IS_ERR(table)) {
+ dev_err(smmu->dev,
+ "kdump: failed to adopt l2 stream table for SID %u\n",
+ sid);
+ return PTR_ERR(table);
+ }
+
+ *l2table = table;
+ return 0;
+}
+
static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
{
dma_addr_t l2ptr_dma;
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
struct arm_smmu_strtab_l2 **l2table;
+ u32 l1_idx = arm_smmu_strtab_l1_idx(sid);
- l2table = &cfg->l2.l2ptrs[arm_smmu_strtab_l1_idx(sid)];
+ l2table = &cfg->l2.l2ptrs[l1_idx];
if (*l2table)
return 0;
+ /* Deferred adoption of the crashed kernel's L2 table */
+ if (smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) {
+ u64 l2ptr = le64_to_cpu(cfg->l2.l1tab[l1_idx].l2ptr);
+ dma_addr_t l2_dma = l2ptr & STRTAB_L1_DESC_L2PTR_MASK;
+ u32 span = FIELD_GET(STRTAB_L1_DESC_SPAN, l2ptr);
+
+ if (span && l2_dma)
+ return arm_smmu_kdump_adopt_l2_strtab(
+ smmu, sid, l1_idx, l2_dma, span, l2table);
+ }
+
*l2table = dmam_alloc_coherent(smmu->dev, sizeof(**l2table),
&l2ptr_dma, GFP_KERNEL);
if (!*l2table) {
@@ -2061,8 +2157,7 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
arm_smmu_init_initial_stes((*l2table)->stes,
ARRAY_SIZE((*l2table)->stes));
- arm_smmu_write_strtab_l1_desc(&cfg->l2.l1tab[arm_smmu_strtab_l1_idx(sid)],
- l2ptr_dma);
+ arm_smmu_write_strtab_l1_desc(&cfg->l2.l1tab[l1_idx], l2ptr_dma);
return 0;
}
@@ -4556,10 +4651,213 @@ static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu)
return 0;
}
+static int arm_smmu_kdump_adopt_strtab_2lvl(struct arm_smmu_device *smmu,
+ u32 cfg_reg, dma_addr_t dma)
+{
+ u32 log2size = FIELD_GET(STRTAB_BASE_CFG_LOG2SIZE, cfg_reg);
+ u32 split = FIELD_GET(STRTAB_BASE_CFG_SPLIT, cfg_reg);
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ phys_addr_t base;
+ u32 num_l1_ents;
+ size_t size;
+ int i;
+
+ /*
+ * Only a coherent SMMU is supported at this moment. For a non-coherent
+ * SMMU that wants to support ARM_SMMU_OPT_KDUMP_ADOPT, try MEMREMAP_WC.
+ */
+ if (WARN_ON(!(smmu->features & ARM_SMMU_FEAT_COHERENCY)))
+ return -EOPNOTSUPP;
+
+ if (log2size < split || log2size > smmu->sid_bits) {
+ dev_err(smmu->dev, "kdump: log2size %u out of range [%u, %u]\n",
+ log2size, split, smmu->sid_bits);
+ return -EINVAL;
+ }
+ if (split != STRTAB_SPLIT) {
+ dev_err(smmu->dev,
+ "kdump: unsupported STRTAB_SPLIT %u (expected %u)\n",
+ split, STRTAB_SPLIT);
+ return -EINVAL;
+ }
+
+ num_l1_ents = 1U << (log2size - split);
+ if (num_l1_ents > STRTAB_MAX_L1_ENTRIES) {
+ dev_err(smmu->dev, "kdump: l1 entries %u exceeds max %u\n",
+ num_l1_ents, STRTAB_MAX_L1_ENTRIES);
+ return -EINVAL;
+ }
+
+ cfg->l2.l1_dma = dma;
+ cfg->l2.num_l1_ents = num_l1_ents;
+
+ base = dma_to_phys(smmu->dev, dma);
+ size = num_l1_ents * sizeof(struct arm_smmu_strtab_l1);
+ if (arm_smmu_kdump_phys_is_corrupted(base, size)) {
+ dev_err(smmu->dev, "kdump: l1 stream table is corrupted\n");
+ return -EINVAL;
+ }
+
+ cfg->l2.l1tab = devm_memremap(smmu->dev, base, size, MEMREMAP_WB);
+ if (IS_ERR(cfg->l2.l1tab))
+ return PTR_ERR(cfg->l2.l1tab);
+
+ cfg->l2.l2ptrs = devm_kcalloc(smmu->dev, num_l1_ents,
+ sizeof(*cfg->l2.l2ptrs), GFP_KERNEL);
+ if (!cfg->l2.l2ptrs)
+ return -ENOMEM;
+
+ for (i = 0; i < num_l1_ents; i++) {
+ u64 l2ptr = le64_to_cpu(cfg->l2.l1tab[i].l2ptr);
+ dma_addr_t l2_dma = l2ptr & STRTAB_L1_DESC_L2PTR_MASK;
+ u32 span = FIELD_GET(STRTAB_L1_DESC_SPAN, l2ptr);
+
+ if (!span || !l2_dma)
+ continue;
+
+ if (span != STRTAB_SPLIT + 1) {
+ dev_err(smmu->dev,
+ "kdump: L1[%u] unsupported span %u (vs %u)\n",
+ i, span, STRTAB_SPLIT + 1);
+ return -EINVAL;
+ }
+
+ base = dma_to_phys(smmu->dev, l2_dma);
+ size = (1UL << (span - 1)) * sizeof(struct arm_smmu_ste);
+ if (arm_smmu_kdump_phys_is_corrupted(base, size)) {
+ dev_err(smmu->dev,
+ "kdump: l2 stream table is corrupted\n");
+ return -EINVAL;
+ }
+
+ /*
+ * If the crashed kernel's l1 descriptors are deeply corrupted,
+ * blindly memremapping every l2 table here could lead to OOM.
+ *
+ * Defer the l2 memremap to arm_smmu_init_l2_strtab(), so peak
+ * memory is bounded by the kdump kernel's actual demand.
+ */
+ }
+
+ return 0;
+}
+
+static int arm_smmu_kdump_adopt_strtab_linear(struct arm_smmu_device *smmu,
+ u32 cfg_reg, dma_addr_t dma)
+{
+ u32 log2size = FIELD_GET(STRTAB_BASE_CFG_LOG2SIZE, cfg_reg);
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+ unsigned int max_log2size;
+ phys_addr_t base;
+ size_t size;
+
+ /*
+ * Only a coherent SMMU is supported at this moment. For a non-coherent
+ * SMMU that wants to support ARM_SMMU_OPT_KDUMP_ADOPT, try MEMREMAP_WC.
+ */
+ if (WARN_ON(!(smmu->features & ARM_SMMU_FEAT_COHERENCY)))
+ return -EOPNOTSUPP;
+
+ /* Cap the size at what the kdump kernel itself would have allocated */
+ if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
+ max_log2size =
+ ilog2(STRTAB_MAX_L1_ENTRIES * STRTAB_NUM_L2_STES);
+ else
+ max_log2size = smmu->sid_bits;
+
+ /* cfg->linear.num_ents is unsigned int, so cap log2size at 31 */
+ max_log2size = min(max_log2size, 31U);
+ if (log2size > max_log2size) {
+ dev_err(smmu->dev, "kdump: unsupported log2size %u (> %u)\n",
+ log2size, max_log2size);
+ return -EINVAL;
+ }
+
+ /*
+ * We might end up with a num_ents != sid_bits, which is fine. In the
+ * ARM_SMMU_OPT_KDUMP_ADOPT case, arm_smmu_write_strtab() is bypassed.
+ */
+ cfg->linear.num_ents = 1U << log2size;
+ cfg->linear.ste_dma = dma;
+
+ base = dma_to_phys(smmu->dev, dma);
+ size = cfg->linear.num_ents * sizeof(struct arm_smmu_ste);
+ if (arm_smmu_kdump_phys_is_corrupted(base, size)) {
+ dev_err(smmu->dev, "kdump: stream table is corrupted\n");
+ return -EINVAL;
+ }
+
+ cfg->linear.table = devm_memremap(smmu->dev, base, size, MEMREMAP_WB);
+ if (IS_ERR(cfg->linear.table))
+ return PTR_ERR(cfg->linear.table);
+ return 0;
+}
+
+static void arm_smmu_kdump_adopt_cleanup(struct arm_smmu_device *smmu, u32 fmt)
+{
+ struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
+
+ if (fmt == STRTAB_BASE_CFG_FMT_2LVL) {
+ if (cfg->l2.l2ptrs)
+ devm_kfree(smmu->dev, cfg->l2.l2ptrs);
+ if (!IS_ERR_OR_NULL(cfg->l2.l1tab))
+ devm_memunmap(smmu->dev, cfg->l2.l1tab);
+ } else if (fmt == STRTAB_BASE_CFG_FMT_LINEAR) {
+ if (!IS_ERR_OR_NULL(cfg->linear.table))
+ devm_memunmap(smmu->dev, cfg->linear.table);
+ }
+}
+
+static int arm_smmu_kdump_adopt_strtab(struct arm_smmu_device *smmu)
+{
+ u32 cfg_reg = readl_relaxed(smmu->base + ARM_SMMU_STRTAB_BASE_CFG);
+ u64 base_reg = readq_relaxed(smmu->base + ARM_SMMU_STRTAB_BASE);
+ u32 fmt = FIELD_GET(STRTAB_BASE_CFG_FMT, cfg_reg);
+ dma_addr_t dma = base_reg & STRTAB_BASE_ADDR_MASK;
+ int ret;
+
+ dev_info(smmu->dev, "kdump: adopting crashed kernel's stream table\n");
+
+ if (fmt == STRTAB_BASE_CFG_FMT_2LVL) {
+ /*
+ * Both kernels run on the same hardware, so it's impossible for
+ * kdump kernel to see the support for linear stream table only.
+ */
+ if (WARN_ON(!(smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)))
+ ret = -EINVAL;
+ else
+ ret = arm_smmu_kdump_adopt_strtab_2lvl(smmu, cfg_reg,
+ dma);
+ } else if (fmt == STRTAB_BASE_CFG_FMT_LINEAR) {
+ /*
+ * In case that the old kernel for some reason used the linear
+ * format, enforce the same format to match the adopted table.
+ */
+ ret = arm_smmu_kdump_adopt_strtab_linear(smmu, cfg_reg, dma);
+ if (!ret)
+ smmu->features &= ~ARM_SMMU_FEAT_2_LVL_STRTAB;
+ } else {
+ dev_err(smmu->dev, "kdump: invalid STRTAB format %u\n", fmt);
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ dev_warn(smmu->dev, "kdump: falling back to full reset\n");
+ arm_smmu_kdump_adopt_cleanup(smmu, fmt);
+ smmu->options &= ~ARM_SMMU_OPT_KDUMP_ADOPT;
+ memset(&smmu->strtab_cfg, 0, sizeof(smmu->strtab_cfg));
+ }
+ return ret;
+}
+
static int arm_smmu_init_strtab(struct arm_smmu_device *smmu)
{
int ret;
+ if ((smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT) &&
+ !arm_smmu_kdump_adopt_strtab(smmu))
+ goto out;
+
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)
ret = arm_smmu_init_strtab_2lvl(smmu);
else
@@ -4567,6 +4865,7 @@ static int arm_smmu_init_strtab(struct arm_smmu_device *smmu)
if (ret)
return ret;
+out:
ida_init(&smmu->vmid_map);
return 0;
--
2.43.0
^ permalink raw reply related
* [PATCH v5 2/6] iommu/arm-smmu-v3: Implement is_attach_deferred() for kdump
From: Nicolin Chen @ 2026-05-10 21:23 UTC (permalink / raw)
To: will, robin.murphy, jgg, kevin.tian
Cc: joro, praan, kees, baolu.lu, miko.lenczewski, smostafa,
linux-arm-kernel, iommu, linux-kernel, stable, jamien
In-Reply-To: <cover.1778416609.git.nicolinc@nvidia.com>
Though the kdump kernel adopts the crashed kernel's stream table, the iommu
core will still try to attach each probed device to a default domain, which
overwrites the adopted STE and breaks in-flight DMA from that device.
Implement an is_attach_deferred() callback to prevent this. For each device
that has STE.V=1 and STE.Cfg!=Abort in the adopted table, defer the default
domain attachment, until the device driver explicitly requests it.
Fixes: b63b3439b856 ("iommu/arm-smmu-v3: Abort all transactions if SMMU is enabled in kdump kernel")
Cc: stable@vger.kernel.org # v6.12+
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 24 +++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index bab60e4b91716..579c8af82d6b6 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4310,6 +4310,29 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
kfree(master->build_invs);
}
+static bool arm_smmu_is_attach_deferred(struct device *dev)
+{
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ struct arm_smmu_device *smmu = master->smmu;
+ int i;
+
+ if (!(smmu->options & ARM_SMMU_OPT_KDUMP_ADOPT))
+ return false;
+
+ for (i = 0; i < master->num_streams; i++) {
+ struct arm_smmu_ste *ste =
+ arm_smmu_get_step_for_sid(smmu, master->streams[i].id);
+ u64 ent0 = le64_to_cpu(ste->data[0]);
+
+ /* Defer only when there might be in-flight DMAs */
+ if ((ent0 & STRTAB_STE_0_V) &&
+ FIELD_GET(STRTAB_STE_0_CFG, ent0) != STRTAB_STE_0_CFG_ABORT)
+ return true;
+ }
+
+ return false;
+}
+
static struct iommu_device *arm_smmu_probe_device(struct device *dev)
{
int ret;
@@ -4472,6 +4495,7 @@ static const struct iommu_ops arm_smmu_ops = {
.hw_info = arm_smmu_hw_info,
.domain_alloc_sva = arm_smmu_sva_domain_alloc,
.domain_alloc_paging_flags = arm_smmu_domain_alloc_paging_flags,
+ .is_attach_deferred = arm_smmu_is_attach_deferred,
.probe_device = arm_smmu_probe_device,
.release_device = arm_smmu_release_device,
.device_group = arm_smmu_device_group,
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] drivers: altera_edac: Fix OCRAM ECC init for warm reset
From: Borislav Petkov @ 2026-05-10 20:46 UTC (permalink / raw)
To: Dinh Nguyen
Cc: muhammad.nazim.amirul.nazle.asmade, tony.luck, linux-edac,
linux-arm-kernel, linux-kernel
In-Reply-To: <59ce1037-b6fb-4af7-a213-d605ba5c9a3d@kernel.org>
On Sun, May 10, 2026 at 03:31:56PM -0500, Dinh Nguyen wrote:
>
>
> On 5/9/26 09:38, muhammad.nazim.amirul.nazle.asmade@altera.com wrote:
> > From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
> >
> > The OCRAM ECC is always enabled either by the BootROM or by the
> > Secure Device Manager (SDM) during a power-on reset on SoCFPGA.
> >
> > However, during a warm reset, the OCRAM content is retained to
> > preserve data, while the control and status registers are reset to
> > their default values. As a result, ECC must be explicitly re-enabled
> > after a warm reset.
> >
> > Signed-off-by: Niravkumar L Rabara <nirav.rabara@altera.com>
> > Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
And fix your SOB chain:
https://kernel.org/doc/html/latest/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin
From the above, I have no clue what Niravkumar has done.
--
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette
^ permalink raw reply
* Re: [PATCH] drivers: altera_edac: Fix OCRAM ECC init for warm reset
From: Dinh Nguyen @ 2026-05-10 20:31 UTC (permalink / raw)
To: muhammad.nazim.amirul.nazle.asmade, bp, tony.luck
Cc: linux-edac, linux-arm-kernel, linux-kernel
In-Reply-To: <20260509143803.7500-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
On 5/9/26 09:38, muhammad.nazim.amirul.nazle.asmade@altera.com wrote:
> From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
>
> The OCRAM ECC is always enabled either by the BootROM or by the
> Secure Device Manager (SDM) during a power-on reset on SoCFPGA.
>
> However, during a warm reset, the OCRAM content is retained to
> preserve data, while the control and status registers are reset to
> their default values. As a result, ECC must be explicitly re-enabled
> after a warm reset.
>
> Signed-off-by: Niravkumar L Rabara <nirav.rabara@altera.com>
> Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
> ---
> drivers/edac/altera_edac.c | 10 ++++++++--
> 1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c
> index 103b2c2eba2a..9e6a9786a881 100644
> --- a/drivers/edac/altera_edac.c
> +++ b/drivers/edac/altera_edac.c
> @@ -1186,8 +1186,14 @@ altr_check_ocram_deps_init(struct altr_edac_device_dev *device)
>
> /* Verify OCRAM has been initialized */
> if (!ecc_test_bits(ALTR_A10_ECC_INITCOMPLETEA,
> - (base + ALTR_A10_ECC_INITSTAT_OFST)))
> - return -ENODEV;
> + (base + ALTR_A10_ECC_INITSTAT_OFST))) {
> + if (!ecc_test_bits(ALTR_A10_ECC_EN,
> + (base + ALTR_A10_ECC_CTRL_OFST)))
> + ecc_set_bits(ALTR_A10_ECC_EN,
> + (base + ALTR_A10_ECC_CTRL_OFST));
> + else
> + return -ENODEV;
> + }
>
> /* Enable IRQ on Single Bit Error */
> writel(ALTR_A10_ECC_SERRINTEN, (base + ALTR_A10_ECC_ERRINTENS_OFST));
This patch fails to apply to both v7.1-rc1 and linux-next. Please base
your patch to the latest and resend.
Thanks,
Dinh
^ permalink raw reply
* Re: [PATCH net v2 1/4] net: sparx5: defer VCAP debugfs creation until after netdev registration
From: Daniel Machon @ 2026-05-10 20:24 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni,
Steen Hegelund, UNGLinuxDriver, Sebastian Andrzej Siewior,
Clark Williams, Steven Rostedt, Bjarni Jonasson, Lars Povlsen,
Philipp Zabel, kees, linux-kernel, netdev, linux-arm-kernel,
linux-rt-devel
In-Reply-To: <20260508162213.6b755d8c@kernel.org>
> On Thu, 7 May 2026 20:47:14 +0200 Daniel Machon wrote:
> > > On Wed, 6 May 2026 09:25:36 +0200 Daniel Machon wrote:
> > > > Move the debugfs setup into a new sparx5_debugfs() helper in
> > > > sparx5_debugfs.c, invoked after sparx5_register_notifier_blocks()
> > > > succeeds so the netdev names are finalized. sparx5_vcap_init() now
> > > > only deals with VCAP state. The sparx5/ debugfs root is created in
> > > > the new helper as well.
> > >
> > > netdev names are never final :( User can change them at any time.
> > > The best practice is to name the debugfs file by some stable hw-related
> > > property, bus, port number etc.
> >
> > Right, but they are finalized in the sense that we have a name we can use for the
> > debugfs files (which we dont pre-patch).
> >
> > Hmm. I think this patch fixes an actual issue, where you cannot query the
> > debugfs files, because a previous patch broke the ordering. I agree that the
> > names chosen (netdev_name()) for the files were poor, but is that really a fix
> > for this series? Should that not be adressed in a future patch for net-next (it
> > involves changing an VCAP API function that is not only used by Sparx5/lan969x,
> > but also lan966x.).
>
> Dunno, if we are aiming to switch to a different naming scheme we can
> just do it now, I reckon. It will not make the fix much longer.
> And presumably it will alleviate the need to reshuffle the ordering.
OK, sounds good to me. I will make the changes for v3. Thanks!
/Daniel
^ permalink raw reply
* [PATCH v2 1/3] dt-bindings: vendor-prefixes: Add Shenzhen Baijie Technology Co., Ltd.
From: Alexander Sverdlin @ 2026-05-10 20:16 UTC (permalink / raw)
To: linux-sunxi
Cc: Alexander Sverdlin, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, devicetree, linux-arm-kernel, linux-kernel,
Conor Dooley
In-Reply-To: <20260510201644.4143710-1-alexander.sverdlin@gmail.com>
Shenzhen Baijie Technology Co., Ltd. focuses on R&D and production of
embedded products as well as customization of embedded solutions.
Link: https://szbaijie.com/
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 28784d66ae7b..095cf654787f 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -229,6 +229,8 @@ patternProperties:
description: Azoteq (Pty) Ltd
"^azw,.*":
description: Shenzhen AZW Technology Co., Ltd.
+ "^baijie,.*":
+ description: Shenzhen Baijie Technology Co., Ltd.
"^baikal,.*":
description: BAIKAL ELECTRONICS, JSC
"^bananapi,.*":
--
2.54.0
^ permalink raw reply related
* [PATCH v2 2/3] dt-bindings: arm: sunxi: Add Baijie HelperBoard A133 compatible
From: Alexander Sverdlin @ 2026-05-10 20:16 UTC (permalink / raw)
To: linux-sunxi
Cc: Alexander Sverdlin, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <20260510201644.4143710-1-alexander.sverdlin@gmail.com>
Baijie HelperBoard A133 is a development board around their A133 Core
board. Introduce a compatible for both the Core and the development
boards.
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>
---
Changelog:
v2:
- introduced baijie,helper-a133-core compatible for the Core (SoM) board
Documentation/devicetree/bindings/arm/sunxi.yaml | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/sunxi.yaml b/Documentation/devicetree/bindings/arm/sunxi.yaml
index e6443c266fa1..d7b9dec81165 100644
--- a/Documentation/devicetree/bindings/arm/sunxi.yaml
+++ b/Documentation/devicetree/bindings/arm/sunxi.yaml
@@ -96,6 +96,17 @@ properties:
- const: allwinner,ba10-tvbox
- const: allwinner,sun4i-a10
+ - description: Baijie Helper A133
+ items:
+ - const: baijie,helper-a133
+ - const: baijie,helper-a133-core
+ - const: allwinner,sun50i-a100
+
+ - description: HelperBoardA133 Core
+ items:
+ - const: baijie,helper-a133-core
+ - const: allwinner,sun50i-a100
+
- description: BananaPi
items:
- const: lemaker,bananapi
--
2.54.0
^ permalink raw reply related
* [PATCH v2 3/3] arm64: dts: allwinner: A133: add support for Baijie Helper A133 board
From: Alexander Sverdlin @ 2026-05-10 20:16 UTC (permalink / raw)
To: linux-sunxi
Cc: Alexander Sverdlin, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <20260510201644.4143710-1-alexander.sverdlin@gmail.com>
Baijie Helper A133 board is a development board around Baijie A133 Core
SBC. Features:
- 1/2/4GiB LPDDR4 DRAM
- 8/16/32GiB eMMC
- AXP707 PMIC
- 2 USB 2.0 ports
- MicroSD slot and on-board eMMC module
- Gigabit Ethernet
- Bluetooth
- WiFi
Add initial support for both the Helper and Core boards, including UART,
PMU, eMMC, USB, Ethernet.
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@gmail.com>
---
Changelog:
v2:
- introduced baijie,helper-a133-core compatible for the Core (SoM) board
arch/arm64/boot/dts/allwinner/Makefile | 1 +
.../dts/allwinner/sun50i-a133-baije-core.dtsi | 162 ++++++++++++++++++
.../allwinner/sun50i-a133-baijie-helper.dts | 94 ++++++++++
3 files changed, 257 insertions(+)
create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a133-baije-core.dtsi
create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a133-baijie-helper.dts
diff --git a/arch/arm64/boot/dts/allwinner/Makefile b/arch/arm64/boot/dts/allwinner/Makefile
index d116864b6c2b..926dfa851100 100644
--- a/arch/arm64/boot/dts/allwinner/Makefile
+++ b/arch/arm64/boot/dts/allwinner/Makefile
@@ -18,6 +18,7 @@ dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a64-sopine-baseboard.dtb
dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a64-teres-i.dtb
dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h64-remix-mini-pc.dtb
dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a100-allwinner-perf1.dtb
+dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a133-baijie-helper.dtb
dtb-$(CONFIG_ARCH_SUNXI) += sun50i-a133-liontron-h-a133l.dtb
dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h5-bananapi-m2-plus.dtb
dtb-$(CONFIG_ARCH_SUNXI) += sun50i-h5-bananapi-m2-plus-v1.2.dtb
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a133-baije-core.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a133-baije-core.dtsi
new file mode 100644
index 000000000000..65b094f30bf5
--- /dev/null
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a133-baije-core.dtsi
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2025 Arm Ltd.
+ */
+
+/dts-v1/;
+
+#include "sun50i-a100.dtsi"
+#include "sun50i-a100-cpu-opp.dtsi"
+
+/{
+ compatible = "baijie,helper-a133-core",
+ "allwinner,sun50i-a100";
+
+ aliases {
+ serial1 = &uart1; /* BT module */
+ };
+};
+
+&cpu0 {
+ cpu-supply = <®_dcdc2>;
+};
+
+&pio {
+ vcc-pb-supply = <®_dcdc1>;
+ vcc-pc-supply = <®_eldo1>;
+ vcc-pd-supply = <®_dcdc1>;
+ vcc-pe-supply = <®_dldo2>;
+ vcc-pf-supply = <®_dcdc1>;
+ vcc-pg-supply = <®_dldo1>;
+ vcc-ph-supply = <®_dcdc1>;
+};
+
+&mmc2 {
+ vmmc-supply = <®_dcdc1>;
+ vqmmc-supply = <®_eldo1>;
+ cap-mmc-hw-reset;
+ non-removable;
+ bus-width = <8>;
+ mmc-ddr-1_8v;
+ mmc-hs200-1_8v;
+ status = "okay";
+};
+
+&r_i2c0 {
+ status = "okay";
+
+ axp803: pmic@34 {
+ compatible = "x-powers,axp803";
+ reg = <0x34>;
+ interrupt-parent = <&r_intc>;
+ interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
+ };
+};
+
+#include "axp803.dtsi"
+
+&ac_power_supply {
+ status = "okay";
+};
+
+®_aldo1 {
+ regulator-always-on;
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <3300000>;
+};
+
+®_aldo2 {
+ regulator-always-on;
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <3300000>;
+};
+
+®_aldo3 {
+ regulator-always-on;
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-enable-ramp-delay = <1000>;
+};
+
+®_dcdc1 {
+ regulator-always-on;
+ regulator-min-microvolt = <1600000>;
+ regulator-max-microvolt = <3400000>;
+ regulator-name = "vcc-3v3";
+};
+
+®_dcdc2 {
+ regulator-always-on;
+ regulator-min-microvolt = <500000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-name = "vdd-cpu";
+};
+
+®_dcdc3 {
+ regulator-always-on;
+ regulator-min-microvolt = <500000>;
+ regulator-max-microvolt = <1300000>;
+};
+
+®_dcdc4 {
+ regulator-always-on;
+ regulator-min-microvolt = <500000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-name = "vdd-sys";
+};
+
+®_dcdc5 {
+ regulator-always-on;
+ regulator-min-microvolt = <800000>;
+ regulator-max-microvolt = <1840000>;
+ regulator-name = "vcc-dram";
+};
+
+/* DCDC6 unused */
+
+®_dldo1 {
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-enable-ramp-delay = <1000>;
+};
+
+®_dldo2 {
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <3400000>;
+ regulator-enable-ramp-delay = <1000>;
+};
+
+®_dldo3 {
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-name = "avdd-csi";
+};
+
+®_dldo4 {
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-enable-ramp-delay = <1000>;
+};
+
+®_eldo1 {
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <1900000>;
+ regulator-enable-ramp-delay = <1000>;
+};
+
+®_eldo2 {
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <1900000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-name = "dvdd-csi";
+};
+
+/* ELDO3 unused */
+
+®_fldo1 {
+ regulator-always-on;
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <1450000>;
+ regulator-name = "vdd-cpus-usb";
+};
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a133-baijie-helper.dts b/arch/arm64/boot/dts/allwinner/sun50i-a133-baijie-helper.dts
new file mode 100644
index 000000000000..ccbca5d0a40c
--- /dev/null
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a133-baijie-helper.dts
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2025 Arm Ltd.
+ */
+
+/dts-v1/;
+
+#include "sun50i-a133-baije-core.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/leds/common.h>
+
+/{
+ model = "HelperBoard A133";
+ compatible = "baijie,helper-a133",
+ "baijie,helper-a133-core",
+ "allwinner,sun50i-a100";
+
+ aliases {
+ serial0 = &uart0;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ led {
+ function = LED_FUNCTION_INDICATOR;
+ color = <LED_COLOR_ID_GREEN>;
+ gpios = <&pio 7 13 GPIO_ACTIVE_LOW>; /* PH13 */
+ };
+ };
+};
+
+&mmc0 {
+ vmmc-supply = <®_dcdc1>;
+ cd-gpios = <&pio 5 6 GPIO_ACTIVE_LOW>; /* PF6 */
+ bus-width = <4>;
+ status = "okay";
+};
+
+&uart0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&uart0_pb_pins>;
+ status = "okay";
+};
+
+&rgmii0_pins {
+ drive-strength = <30>;
+};
+
+&emac0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&rgmii0_pins>;
+ phy-handle = <ð_phy>;
+ phy-mode = "rgmii-id";
+ allwinner,rx-delay-ps = <200>;
+ allwinner,tx-delay-ps = <200>;
+ status = "okay";
+};
+
+&mdio0 {
+ reset-gpios = <&pio 7 11 GPIO_ACTIVE_LOW>; /* PH11 */
+ reset-delay-us = <10000>;
+ reset-post-delay-us = <150000>;
+
+ eth_phy: ethernet-phy@1 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <1>;
+ };
+};
+
+&usbphy {
+ status = "okay";
+};
+
+&ehci0 {
+ status = "okay";
+};
+
+&ohci0 {
+ status = "okay";
+};
+
+&ehci1 {
+ status = "okay";
+};
+
+&ohci1 {
+ status = "okay";
+};
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/3] Add support for Baijie Helper A133 board
From: Alexander Sverdlin @ 2026-05-10 20:16 UTC (permalink / raw)
To: linux-sunxi
Cc: Alexander Sverdlin, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
Andre Przywara, devicetree, linux-arm-kernel, linux-kernel
Baijie Helper A133 board is a development board around Baijie A133 Core
SBC. Features:
- 1/2/4GiB LPDDR4 DRAM
- 8/16/32GiB eMMC
- AXP707 PMIC
- 2 USB 2.0 ports
- MicroSD slot and on-board eMMC module
- Gigabit Ethernet
- Bluetooth
- WiFi
Add initial support for both the Helper and Core boards, including UART,
PMU, eMMC, USB, Ethernet.
Link: https://szbaijie.com/index/product/product_detail.html?product_id=23&language=en
Changelog:
v2:
- introduced baijie,helper-a133-core compatible for the Core (SoM) board
v1:
- https://lore.kernel.org/all/20260503191842.2736130-1-alexander.sverdlin@gmail.com/
Alexander Sverdlin (3):
dt-bindings: vendor-prefixes: Add Shenzhen Baijie Technology Co., Ltd.
dt-bindings: arm: sunxi: Add Baijie HelperBoard A133 compatible
arm64: dts: allwinner: A133: add support for Baijie Helper A133 board
.../devicetree/bindings/arm/sunxi.yaml | 11 ++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
arch/arm64/boot/dts/allwinner/Makefile | 1 +
.../dts/allwinner/sun50i-a133-baije-core.dtsi | 162 ++++++++++++++++++
.../allwinner/sun50i-a133-baijie-helper.dts | 94 ++++++++++
5 files changed, 270 insertions(+)
create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a133-baije-core.dtsi
create mode 100644 arch/arm64/boot/dts/allwinner/sun50i-a133-baijie-helper.dts
--
2.54.0
^ permalink raw reply
* [PATCH] irqchip/mvebu: Allow EBU irqchips to be compile-tested
From: Rosen Penev @ 2026-05-10 19:50 UTC (permalink / raw)
To: linux-kernel
Cc: Thomas Gleixner, Andrew Lunn, Gregory Clement,
Sebastian Hesselbarth, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt,
moderated list:ARM/Marvell Kirkwood and Armada 370, 375, 38x,...,
open list:CLANG/LLVM BUILD SUPPORT:Keyword:b(?i:clang|llvm)b
The Marvell EBU interrupt controller Kconfig symbols are hidden and
selected only by platform code. This prevents build coverage for the
drivers on other architectures even though the code only needs OF and
MMIO support.
Add COMPILE_TEST prompts and the required dependencies for the GICP,
ICU, ODMI, PIC and SEI irqchips. While touching PIC for this coverage,
use GENMASK() and BIT() for its masks so that 32-bit platforms can
compile this safely without running into issues.
Tested: make LLVM=1 ARCH=s390 drivers/irqchip/
Assisted-by: Codex:GPT-5.5
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/irqchip/Kconfig | 31 ++++++++++++++++++++++++++-----
drivers/irqchip/irq-mvebu-pic.c | 8 ++++----
2 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e7d559472790..cf3aea96866b 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -451,22 +451,43 @@ config MSCC_OCELOT_IRQ
select GENERIC_IRQ_CHIP
config MVEBU_GICP
+ bool "Marvell EBU GICP interrupt controller" if COMPILE_TEST
+ depends on OF
+ depends on HAS_IOMEM
select IRQ_MSI_LIB
- bool
+ help
+ Support the Marvell EBU GICP interrupt controller.
config MVEBU_ICU
- bool
+ bool "Marvell EBU ICU interrupt controller" if COMPILE_TEST
+ depends on OF
+ depends on HAS_IOMEM
+ select GENERIC_MSI_IRQ
+ help
+ Support the Marvell EBU ICU interrupt controller.
config MVEBU_ODMI
- bool
+ bool "Marvell EBU ODMI interrupt controller" if COMPILE_TEST
+ depends on OF
+ depends on HAS_IOMEM
select IRQ_MSI_LIB
select GENERIC_MSI_IRQ
+ help
+ Support the Marvell EBU ODMI interrupt controller.
config MVEBU_PIC
- bool
+ bool "Marvell EBU PIC interrupt controller" if COMPILE_TEST
+ depends on OF
+ depends on HAS_IOMEM
+ help
+ Support the Marvell EBU PIC interrupt controller.
config MVEBU_SEI
- bool
+ bool "Marvell EBU SEI interrupt controller" if COMPILE_TEST
+ depends on OF
+ depends on HAS_IOMEM
+ help
+ Support the Marvell EBU SEI interrupt controller.
config LS_EXTIRQ
bool "Freescale Layerscape external IRQ support" if COMPILE_TEST
diff --git a/drivers/irqchip/irq-mvebu-pic.c b/drivers/irqchip/irq-mvebu-pic.c
index 10b85128183a..95090d8efc06 100644
--- a/drivers/irqchip/irq-mvebu-pic.c
+++ b/drivers/irqchip/irq-mvebu-pic.c
@@ -24,7 +24,7 @@
#define PIC_MASK 0x4
#define PIC_MAX_IRQS 32
-#define PIC_MAX_IRQ_MASK ((1UL << PIC_MAX_IRQS) - 1)
+#define PIC_MAX_IRQ_MASK GENMASK(PIC_MAX_IRQS - 1, 0)
struct mvebu_pic {
void __iomem *base;
@@ -44,7 +44,7 @@ static void mvebu_pic_eoi_irq(struct irq_data *d)
{
struct mvebu_pic *pic = irq_data_get_irq_chip_data(d);
- writel(1 << d->hwirq, pic->base + PIC_CAUSE);
+ writel(BIT(d->hwirq), pic->base + PIC_CAUSE);
}
static void mvebu_pic_mask_irq(struct irq_data *d)
@@ -53,7 +53,7 @@ static void mvebu_pic_mask_irq(struct irq_data *d)
u32 reg;
reg = readl(pic->base + PIC_MASK);
- reg |= (1 << d->hwirq);
+ reg |= BIT(d->hwirq);
writel(reg, pic->base + PIC_MASK);
}
@@ -63,7 +63,7 @@ static void mvebu_pic_unmask_irq(struct irq_data *d)
u32 reg;
reg = readl(pic->base + PIC_MASK);
- reg &= ~(1 << d->hwirq);
+ reg &= ~BIT(d->hwirq);
writel(reg, pic->base + PIC_MASK);
}
--
2.54.0
^ permalink raw reply related
* Re: [PATCH 1/2] [RFC] debugobjects: avoid gcc-16.0.1 section mismatch
From: Thomas Gleixner @ 2026-05-10 19:31 UTC (permalink / raw)
To: Arnd Bergmann, Will Deacon, Robin Murphy, Joerg Roedel,
Andrew Morton
Cc: linux-arm-kernel, iommu, linux-kernel, Arnd Bergmann,
Sebastian Andrzej Siewior
In-Reply-To: <20260203162406.2215716-1-arnd@kernel.org>
On Tue, Feb 03 2026 at 17:23, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> gcc-16 has gained some more advanced inlining techniques that enable
> it to inline the is_static_object() function pointer into a specialized
> version of lookup_object_or_alloc:
>
> WARNING: modpost: vmlinux: section mismatch in reference: lookup_object_or_alloc.part.0+0x1ac (section: .text) -> is_static_object (section: .init.text)
>
> From what I can tell, the transformation is correct, as this
> is only called when lookup_object_or_alloc() is called from
> debug_objects_selftest(), which is also __init.
So clearly the compiler is buggy. It creates an __init specific copy of
lookup_object_or_alloc() and then fails to attribute it correctly.
> I have not come up with a good workaround, so this simply marks
> is_static_object() as not __init. Since there are currently only two
> files where this happens, that may be an easy way out.
That's a horrible hack and while it's only two files today, this sounds
like the start of a whack a mole game.
Aside of that five weeks down the road some clever AI bot creates a
patch which marks the function __init again (rightfully so).
> If anyone has a better idea for how to deal with that, let me know!
Mark the compiler broken and wait until GCC people get their act
together.
Thanks,
tglx
^ permalink raw reply
* Re: Regression in split ARM MMIO timer driver
From: Sudeep Holla @ 2026-05-10 19:23 UTC (permalink / raw)
To: Marc Zyngier
Cc: Jack Matthews, Mark Rutland, Sudeep Holla, linux-arm-kernel,
regressions, linux-kernel, linux-arm-msm
In-Reply-To: <87wlxca2jb.wl-maz@kernel.org>
On Sat, May 09, 2026 at 06:58:00PM +0100, Marc Zyngier wrote:
> On Sat, 09 May 2026 13:19:56 +0100,
> Sudeep Holla <sudeep.holla@kernel.org> wrote:
> >
> > On Fri, May 08, 2026 at 03:48:14PM -0400, Jack Matthews wrote:
> > > Hello,
> > >
> > > I am working on mainlining an old chip, Qualcomm's MDM9625 modem.
> > > I had previously booted 6.17-rc3 before putting this project to the side,
> > > but when I restarted work on 7.0 I was unable to boot.
> > > I have bisected this to commit 0f67b56d84b4c49adfd61f19f81f84ec613ab51a
> > > (https://lore.kernel.org/all/20250814154622.10193-4-maz@kernel.org/) and
> > > reverting this commit makes the device boot successfully. Unfortunately I do
> > > not have access to low level debugging such as UART so I have not been able
> > > to pinpoint exactly what is missing.
> > > My changes for this chip are all available here in case it is an issue of my
> > > own doing: https://github.com/jackmthws/linux/commits/mdm9625-latest/.
> >
> > Looking briefly into the DTS file, I couldn't find the sysreg based
> > arch timer node in the DT. It could be just an overlook unless there
> > is some issue with it that it's not added. After the above mentioned
> > commit, the MMIO timer gets initialised bit late in the boot and
> > could be the reason for boot failure. Unless you have intentionally
> > not added it, I would suggest to add it and try.
>
> Ah, that's a good point. Not having per-CPU timers is not going to
> fly, I'm afraid. The MMIO timer will kick in very late, and we
> probably need to schedule threads way before that.
>
> This looks to be a single Cortex-A5 however, not an A5MP, meaning it
> does not have the TWD, and relies on a global timer, which is a
> terrible thing to have. Not to mention that MMIO accesses on the
> counter is going to be even worse.
>
Ah, my bad I misread it as Cortex-A15 and not Cortex-A5, so assumed
missing sysreg based per-cpu arch timers.
--
Regards,
Sudeep
^ permalink raw reply
* Re: [PATCH v1 3/4] drm/rockchip: dw_hdmi: Apply DRM_BRIDGE_ATTACH_NO_CONNECTOR and the bridge-connector
From: Jonas Karlman @ 2026-05-10 19:12 UTC (permalink / raw)
To: Damon Ding
Cc: andrzej.hajda, neil.armstrong, rfoss, maarten.lankhorst, mripard,
tzimmermann, airlied, simona, hjc, heiko, andy.yan, wens, samuel,
luca.ceresoli, Laurent.pinchart, jernej.skrabec, victor.liu,
dmitry.baryshkov, shengjiu.wang, dri-devel, linux-kernel,
linux-arm-kernel, linux-rockchip, linux-sunxi
In-Reply-To: <20260403070032.447102-4-damon.ding@rock-chips.com>
Hi Damon,
On 4/3/2026 9:00 AM, Damon Ding wrote:
> Convert this driver to DRM_BRIDGE_ATTACH_NO_CONNECTOR and to the
> drm_bridge_connector framework which is the current DRM bridge best
> practice.
Changing to use the bridge connector at this stage will cause the
Rockchip platform to drop features the dw-hdmi connector currently only
can provide, e.g. a CEC notifier, some connector properties and more.
I am also currently working on a multi series effort to:
- phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v2]
- drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format [v1]
- drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v5]
- drm/bridge: dw-hdmi: Improve input/output bus format handling
- drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector
- drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops
- drm/meson: hdmi: Misc cleanup and use CEC notifier helpers
- drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes
Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260508-rk-hdmi-v3/
Part of that effort include to fully convert the dw-hdmi bridge into a
HDMI bridge, something that is required to fully be able to replace all
features currently provided by the use of the dw-hdmi connector.
In that effort I have also taken a different approach and instead
replace the dw-hdmi connector with the bridge connector directly from
inside the dw-hdmi bridge driver.
Regards,
Jonas
> Signed-off-by: Damon Ding <damon.ding@rock-chips.com>
> ---
> drivers/gpu/drm/rockchip/Kconfig | 1 +
> drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 19 +++++++++++++++----
> 2 files changed, 16 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
> index e7f49fe845ea..69b832d0c8c4 100644
> --- a/drivers/gpu/drm/rockchip/Kconfig
> +++ b/drivers/gpu/drm/rockchip/Kconfig
> @@ -76,6 +76,7 @@ config ROCKCHIP_DW_DP
>
> config ROCKCHIP_DW_HDMI
> bool "Rockchip specific extensions for Synopsys DW HDMI"
> + select DRM_BRIDGE_CONNECTOR
> help
> This selects support for Rockchip SoC specific extensions
> for the Synopsys DesignWare HDMI driver. If you want to
> diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
> index 21b141b7cb9c..b5cfcb936078 100644
> --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
> +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
> @@ -13,6 +13,7 @@
> #include <linux/regulator/consumer.h>
>
> #include <drm/bridge/dw_hdmi.h>
> +#include <drm/drm_bridge_connector.h>
> #include <drm/drm_edid.h>
> #include <drm/drm_of.h>
> #include <drm/drm_probe_helper.h>
> @@ -542,6 +543,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
> struct drm_device *drm = data;
> struct drm_encoder *encoder;
> struct rockchip_hdmi *hdmi;
> + struct drm_connector *connector;
> int ret;
>
> if (!pdev->dev.of_node)
> @@ -608,7 +610,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
>
> platform_set_drvdata(pdev, hdmi);
>
> - hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data, 0);
> + hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
>
> /*
> * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
> @@ -616,12 +618,21 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
> */
> if (IS_ERR(hdmi->hdmi)) {
> ret = PTR_ERR(hdmi->hdmi);
> - goto err_bind;
> + goto err_cleanup_encoder;
> }
>
> - return 0;
> + connector = drm_bridge_connector_init(drm, encoder);
> + if (IS_ERR(connector)) {
> + ret = PTR_ERR(connector);
> + dev_err(hdmi->dev, "Failed to initialize bridge_connector\n");
> + goto err_unbind_bridge;
> + }
>
> -err_bind:
> + return drm_connector_attach_encoder(connector, encoder);
> +
> +err_unbind_bridge:
> + dw_hdmi_unbind(hdmi->hdmi);
> +err_cleanup_encoder:
> drm_encoder_cleanup(encoder);
>
> return ret;
^ permalink raw reply
* [PATCH RFC 2/4] clk: zte: Introduce a driver for zx297520v3 top clocks and resets.
From: Stefan Dösinger @ 2026-05-10 19:01 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
In-Reply-To: <20260510-zx29clk-v1-0-e1bacfffe967@gmail.com>
This register space controls core devices: PLLs, the AHB bus, a lot of
timers, the USB controller, the Cortex M0 processor that boots the board
and a few other devices. For some reason the LTE coprocessor is also
partially controlled by it. The main application processor and DDR
memory are not found here though.
The register to reboot the board is also found here.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/zte/Kconfig | 18 ++
drivers/clk/zte/Makefile | 2 +
drivers/clk/zte/clk-zx297520v3.c | 584 +++++++++++++++++++++++++++++++++++++++
5 files changed, 606 insertions(+)
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 3d803b4cf5c1..971ea6daa2b6 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -539,6 +539,7 @@ source "drivers/clk/visconti/Kconfig"
source "drivers/clk/x86/Kconfig"
source "drivers/clk/xilinx/Kconfig"
source "drivers/clk/zynqmp/Kconfig"
+source "drivers/clk/zte/Kconfig"
# Kunit test cases
config CLK_KUNIT_TEST
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f7bce3951a30..c164a3de2b14 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -165,5 +165,6 @@ ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_X86) += x86/
endif
obj-y += xilinx/
+obj-$(CONFIG_ARCH_ZTE) += zte/
obj-$(CONFIG_ARCH_ZYNQ) += zynq/
obj-$(CONFIG_COMMON_CLK_ZYNQMP) += zynqmp/
diff --git a/drivers/clk/zte/Kconfig b/drivers/clk/zte/Kconfig
new file mode 100644
index 000000000000..e7acd28832cd
--- /dev/null
+++ b/drivers/clk/zte/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# ZTE Clock Drivers
+#
+menu "Clock driver for ZTE SoC"
+ depends on ARCH_ZTE || COMPILE_TEST
+
+config COMMON_CLK_ZX297520V3
+ tristate "Clock driver for ZTE zx297520v3"
+ default SOC_ZX297520V3
+ help
+ This driver supports ZTE zx297520v3 basic clocks.
+
+ Enable this if you want to build a kernel that is able to run on
+ boards based on this SoC. You can safely enable multiple clock
+ drivers. The one(s) matching the device tree will be used.
+
+endmenu
diff --git a/drivers/clk/zte/Makefile b/drivers/clk/zte/Makefile
new file mode 100644
index 000000000000..3751ebcba0b0
--- /dev/null
+++ b/drivers/clk/zte/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_COMMON_CLK_ZX297520V3) += clk-zx297520v3.o
diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
new file mode 100644
index 000000000000..f73f5c006641
--- /dev/null
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Stefan Dösinger
+ */
+#include <dt-bindings/clock/zte,zx297520v3-clk.h>
+#include <linux/reset-controller.h>
+#include <linux/platform_device.h>
+#include <linux/clk-provider.h>
+#include <linux/of_address.h>
+#include <linux/reboot.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+static DEFINE_SPINLOCK(reg_lock);
+
+struct zx29_reset_reg {
+ void __iomem *reg;
+ u32 mask;
+};
+
+struct zx29_clk_controller {
+ void __iomem *base;
+ struct clk_hw_onecell_data *clocks;
+ struct reset_controller_dev rcdev;
+ struct zx29_reset_reg resets[];
+};
+
+static int zx297520v3_rst_assert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ struct zx29_clk_controller *data = container_of(rcdev, struct zx29_clk_controller, rcdev);
+ u32 val;
+
+ val = readl(data->resets[id].reg);
+ val &= ~data->resets[id].mask;
+ writel(val, data->resets[id].reg);
+
+ return 0;
+}
+
+static int zx297520v3_rst_deassert(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ struct zx29_clk_controller *data = container_of(rcdev, struct zx29_clk_controller, rcdev);
+ u32 val;
+
+ val = readl(data->resets[id].reg);
+ val |= data->resets[id].mask;
+ writel(val, data->resets[id].reg);
+
+ return 0;
+}
+
+static int zx297520v3_rst_reset(struct reset_controller_dev *rcdev, unsigned long id)
+{
+ int ret;
+
+ ret = zx297520v3_rst_assert(rcdev, id);
+ if (ret)
+ return ret;
+
+ usleep_range(100, 100 * 2);
+
+ return zx297520v3_rst_deassert(rcdev, id);
+}
+
+static int zx297520v3_rst_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct zx29_clk_controller *data = container_of(rcdev, struct zx29_clk_controller, rcdev);
+ u32 val;
+
+ val = readl(data->resets[id].reg);
+
+ return (val & data->resets[id].mask) == data->resets[id].mask;
+}
+
+const struct reset_control_ops zx297520v3_rst_ops = {
+ .assert = zx297520v3_rst_assert,
+ .deassert = zx297520v3_rst_deassert,
+ .reset = zx297520v3_rst_reset,
+ .status = zx297520v3_rst_status,
+};
+
+/* Used for gates where we don't know the parent input(s). Assume general bus clock. */
+static const char * const clk_unknown[] = {
+ "osc26m",
+};
+
+/* Used for gates where we know it is using the 26 mhz main clock. */
+static const char * const clk_main[] = {
+ "osc26m",
+};
+
+/* Top and matrix clocks are chaotic - I haven't found a consistent pattern behind their register
+ * and bit locations. Generally there are two gates (pclk, wclk), one mux, one reset and sometimes
+ * one divider, but exceptions apply. For some devices there is only a reset and some general
+ * (parent) clocks need setup. This structure plus macro handles the somewhat regular parts.
+ *
+ * There are some patterns that can be observed.
+ * mux 0x3c, div 0x48, gate 0x54
+ * mux 0x40, div 0x4c, gate 0x5c
+ * mux 0x44, div 0x50, gate 0x60
+ *
+ * For a 0 - 0xc - 0x18 pattern. Muxes from 0x3c to 0x44, dividers from 0x48 to 0x50, gates 0x54 to
+ * 0x60. The pattern is broken for timer t17 though.
+ *
+ * Gates have 4 bits per clock - bit 0 for wclk, bit 1 for pclk, bit 2 for something the ZTE kernel
+ * calls "gate" (the bits we use here are called "en"), which I don't know what it does, and bit 3
+ * seems unused. E.g. offset 0x54 accepts all bits in 0xF77F7F7F - suggesting RTC, I2C0 have an
+ * extra gate bit.
+ */
+struct zx297520v3_composite {
+ u32 reset_id, wclk_id, pclk_id;
+ const char *name;
+ u32 reset_reg, reset_shift;
+ u32 gate_reg, wclk_gate_shift, pclk_gate_shift;
+ const char *pclk_parent;
+ u32 mux_reg, mux_shift, mux_size;
+ const char * const *mux_sel;
+ u32 mux_sel_count;
+ u32 div_reg, div_shift, div_size;
+ u32 flags;
+};
+
+struct zx297520v3_gate {
+ u32 id;
+ const char *name, *parent;
+ u32 reg, shift;
+};
+
+#define _ZX_CLK(name, reset_reg, reset_shift, gate_reg, wclk_shift, pclk_shift, pclk_parent,\
+ mux_reg, mux_shift, mux_size, mux_sel,\
+ div_reg, div_shift, div_size, flags) \
+ {ZX297520V3_##name##_RESET, ZX297520V3_##name##_WCLK, ZX297520V3_##name##_PCLK,\
+ #name, reset_reg, reset_shift, gate_reg, wclk_shift, pclk_shift, pclk_parent,\
+ mux_reg, mux_shift, mux_size, mux_sel, ARRAY_SIZE(mux_sel),\
+ div_reg, div_shift, div_size, flags}
+
+#define ZX_CLK(name, reset_reg, reset_shift, gate_reg, wclk_shift, pclk_shift,\
+ mux_reg, mux_shift, mux_size, mux_sel,\
+ div_reg, div_shift, div_size) \
+ _ZX_CLK(name, reset_reg, reset_shift, gate_reg, wclk_shift, pclk_shift, "osc26m",\
+ mux_reg, mux_shift, mux_size, mux_sel,\
+ div_reg, div_shift, div_size, 0)
+
+#define ZX_CLK_CRIT(name, reset_reg, reset_shift, gate_reg, wclk_shift, pclk_shift,\
+ mux_reg, mux_shift, mux_size, mux_sel,\
+ div_reg, div_shift, div_size) \
+ _ZX_CLK(name, reset_reg, reset_shift, gate_reg, wclk_shift, pclk_shift, "osc26m",\
+ mux_reg, mux_shift, mux_size, mux_sel,\
+ div_reg, div_shift, div_size, CLK_IS_CRITICAL)
+
+/* The default mpll settings multiply the 26 MHz reference clock times 24. A mux selection of 26 MHz
+ * could mean using the 26 MHz oscillator directly, or passing it through the PLL and divide by 24.
+ *
+ * If a UART is set to mpl_d6 (default 104 MHz), changing the mpll multipliers does affect UART
+ * timing as it should. This does not happen when the UART is set to 26 MHz input or timers that
+ * read 26 MHz input. This suggests 26 MHz clocks use the reference clock directly.
+ */
+static const char * const ahb_sel[] = {
+ "osc26m",
+ "mpll_d6", /* 104 mhz */
+ "mpll_d8", /* 78 mhz */
+ "mpll_d8", /* 78 mhz */
+};
+
+static const char * const timer_top_sel[] = {
+ "osc32k",
+ "osc26m",
+};
+
+static const char * const uart_top_sel[] = {
+ "osc26m",
+ "mpll_d6", /* 104 mhz */
+};
+
+static const char * const m0_sel[] = {
+ "osc26m",
+ "mpll_d6", /* 104 mhz */
+ "mpll_d8", /* 78 mhz */
+ "osc32k", /* Yes, tested. It is SLLLLOOOOOWWW. */
+};
+
+static const struct zx297520v3_composite top_clocks[] = {
+ /* (NAME, RESET, GATE, MUX, DIV ), */
+
+ /* AHB: Don't turn this one off. The clock mux works and impact can be tested e.g. with
+ * iperf speed testing of the USB network connection. Values 2 and 3 give the same speed.
+ */
+ ZX_CLK_CRIT(AHB, 0x70, 0, 0x54, 12, 13, 0x3c, 4, 2, ahb_sel, 0, 0, 0),
+
+ /* Pinmux (AON, TOP, IOCFG but not PDCFG). Critical as well until we have a driver that
+ * consumes it. I don't think we'll realistically shut this off ever.
+ *
+ * Setting either bit 0 or 1 in register 0x58 makes the device work.
+ */
+ ZX_CLK_CRIT(PMM, 0x74, 0, 0x58, 0, 1, 0x00, 0, 0, clk_unknown, 0, 0, 0),
+
+ /* Timers. We don't use any of them, just shut them off. The timers are named and sorted
+ * by the IO address of the main timer controls. Some of the controls are documented in
+ * ZTE's kernel. Some I found by trial and error.
+ *
+ * Timer T17 is used by the ZSP firmware. The rproc driver will enable them as needed.
+ */
+ ZX_CLK(TIMER_T08, 0x78, 4, 0x5c, 8, 9, 0x40, 1, 1, timer_top_sel, 0x4c, 8, 4),
+ ZX_CLK(TIMER_T09, 0x78, 2, 0x5c, 4, 5, 0x40, 0, 1, timer_top_sel, 0x4c, 0, 4),
+ ZX_CLK(TIMER_T12, 0x74, 6, 0x54, 4, 5, 0x3c, 0, 1, timer_top_sel, 0x48, 0, 4),
+ ZX_CLK(TIMER_T13, 0x7c, 0, 0x60, 0, 1, 0x44, 0, 1, timer_top_sel, 0x50, 0, 4),
+ ZX_CLK(TIMER_T14, 0x7c, 2, 0x60, 4, 5, 0x44, 1, 1, timer_top_sel, 0x50, 4, 4),
+ ZX_CLK(TIMER_T15, 0x74, 10, 0x54, 20, 21, 0x3c, 3, 1, timer_top_sel, 0x48, 4, 4),
+ ZX_CLK(TIMER_T16, 0x7c, 4, 0x60, 8, 9, 0x44, 2, 1, timer_top_sel, 0x50, 8, 4),
+ ZX_CLK(TIMER_T17, 0x12c, 0, 0x128, 0, 1, 0x120, 0, 1, timer_top_sel, 0x124, 0, 4),
+
+ /* This watchdog is set up by the bootloader and in normal operation the m0 firmware will
+ * feed the dog. The m0 firmware in turn wants to be fed in its own way. Since we normally
+ * don't run any m0 firmware we shut it off by default and expose it to userspace via the
+ * watchdog driver.
+ */
+ ZX_CLK(WDT_T18, 0x74, 12, 0x54, 24, 25, 0x3c, 6, 1, timer_top_sel, 0x48, 8, 4),
+
+ ZX_CLK(I2C0, 0x74, 8, 0x54, 8, 9, 0x3c, 1, 1, uart_top_sel, 0, 0, 0),
+ ZX_CLK(UART0, 0x78, 6, 0x5c, 12, 13, 0x40, 2, 1, uart_top_sel, 0, 0, 0),
+
+ /* How does this RTC work? I don't know, the ZTE kernel does not talk to it. It has an
+ * external RTC connected to I2C0.
+ */
+ ZX_CLK(RTC, 0x74, 4, 0x54, 0, 1, 0x00, 0, 0, timer_top_sel, 0, 0, 0),
+
+ /* This doesn't see to be talking to the physical SIM card. I can turn it off on the ZTE
+ * firmware without breaking LTE, and the "uicc" IRQ count keeps climbing. I think this is
+ * a eSim-like chip that can be provisioned with data at runtime, but I have no idea how to
+ * do it.
+ */
+ ZX_CLK(USIM1, 0x74, 14, 0x54, 28, 29, 0x00, 0, 0, clk_main, 0x48, 12, 1),
+
+ /* (NAME, RESET, GATE, MUX, DIV ), */
+};
+
+/* Stand-alone topclk gates. */
+static const struct zx297520v3_gate top_gates[] = {
+ {ZX297520V3_USB_24M, "usb_24m", "mpll_d26", 0x6c, 3},
+ {ZX297520V3_USB_AHB, "usb_ahb", "AHB_wclk", 0x6c, 4},
+ /* LTE: gate only as far as I can see. I looked for resets and did not find any. There may
+ * be mux/div, but without understanding the behavior of this hardware it is impossible to
+ * tell. They are sorted by physical MMIO address of the devices, which happens to be the
+ * inverse order of the bits.
+ *
+ * I don't know what "LPM", "TD" and "W" mean. I copied them from ZTE's names.
+ */
+ {ZX297520V3_LPM_GSM_WCLK, "LPM_GSM_wclk", clk_unknown[0], 0x58, 10},
+ {ZX297520V3_LPM_GSM_PCLK, "LPM_GSM_pclk", clk_unknown[0], 0x58, 11},
+ {ZX297520V3_LPM_LTE_WCLK, "LPM_LTE_wclk", clk_unknown[0], 0x58, 8},
+ {ZX297520V3_LPM_LTE_PCLK, "LPM_LTE_pclk", clk_unknown[0], 0x58, 9},
+ {ZX297520V3_LPM_TD_WCLK, "LPM_TD_wclk", clk_unknown[0], 0x58, 6},
+ {ZX297520V3_LPM_TD_PCLK, "LPM_TD_pclk", clk_unknown[0], 0x58, 7},
+ {ZX297520V3_LPM_W_WCLK, "LPM_W_wclk", clk_unknown[0], 0x58, 4},
+ {ZX297520V3_LPM_W_PCLK, "LPM_W_pclk", clk_unknown[0], 0x58, 5},
+ /* There are PCLKs for BROM/SRAM2 in 0x54, bit 16 and SRAM1 in 0x54, bit 18. Turning them
+ * off locks up the Cortex M0 coproc. Not added to the kernel until a way is found to
+ * recover the Cortex M0 or evidence of power savings.
+ */
+};
+
+static int zx297520v3_pll(struct device *dev, void __iomem *base, const char *name,
+ struct clk *parent)
+{
+ /* These are the fractionals of the PLLs I have seen. There should be a better way to
+ * generate them than hardcode the list.
+ */
+ static const unsigned int pll_fract[] = {2, 3, 4, 5, 6, 8, 12, 26};
+
+ unsigned long ref, refdiv, fbdiv, vco, postdiv1, postdiv2, freq;
+ struct clk_hw *hw;
+ char plldiv[16];
+ unsigned int i;
+ u32 val;
+
+ /* PLLs are configured by the boot rom, we only read their settings to know how the rate
+ * of the derived clocks. ZTE's sources explain the PLL register contents only in a .cmm
+ * file (A Lauterback TRACE32 script). When calculating the frequencies with the default
+ * PLL configuration the results match the fixed rate clocks from their clock driver.
+ *
+ * The 26mhz and 32khz clocks can be easily observed with the timers. The 104mhz output
+ * can be observed through the UART. All others can only be indirectly observed by e.g.
+ * comparing the CPU speed at 26mhz and 624mhz.
+ *
+ * The contents of the PLL registers is as follows:
+ *
+ * Bit 31: PLL Locked
+ * Bit 30: PLL disable bit (0 = PLL enabled, 1 = PLL disabled)
+ * Bits 29:25: Unknown. Could be a parent mux
+ * Bits 24:18: refdiv
+ * Bits 17:6: fbdiv
+ * Bits 5:3: post vco divider 1
+ * Bits 2:0: post vco divider 2
+ *
+ * There is a second register following immediately afterwards that is supposed to have
+ * another value that gets added to fbdiv, but it doesn't seem to make a difference in my
+ * testing and it is always 0 in the preconfigured values.
+ */
+ ref = clk_get_rate(parent);
+ val = readl(base);
+
+ refdiv = (val & GENMASK(24, 18)) >> 18;
+ fbdiv = (val & GENMASK(17, 6)) >> 6;
+ postdiv1 = (val & GENMASK(5, 3)) >> 3;
+ postdiv2 = (val & GENMASK(2, 0));
+ dev_dbg(dev, "%s: reference clock %lu HZ, PLL setting 0x%08x\n", name, ref, val);
+
+ if (!refdiv || !postdiv1 || !postdiv2)
+ return -EINVAL;
+
+ vco = (ref / refdiv) * fbdiv;
+ freq = vco / postdiv1 / postdiv2;
+ dev_dbg(dev, "%s: %lu MHZ\n", name, freq / 1000000);
+
+ hw = devm_clk_hw_register_fixed_factor(dev, name, __clk_get_name(parent), 0, fbdiv,
+ refdiv * postdiv1 * postdiv2);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+
+ for (i = 0; i < ARRAY_SIZE(pll_fract); ++i) {
+ sprintf(plldiv, "%s_d%u", name, pll_fract[i]);
+ hw = devm_clk_hw_register_fixed_factor(dev, plldiv, name, 0, 1, pll_fract[i]);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ dev_dbg(dev, "%s: %lu hz\n", clk_hw_get_name(hw), clk_hw_get_rate(hw));
+ }
+
+ return 0;
+}
+
+static int zx297520v3_composite(struct device *dev, void __iomem *base,
+ struct clk_hw_onecell_data *clocks, struct zx29_reset_reg *resets,
+ const struct zx297520v3_composite *input, size_t size)
+{
+ char pclk_name[32], wclk_name[32], mux_name[32], div_name[32];
+ const char *wclk_parent, *div_parent;
+ struct clk_hw *hw;
+ size_t copy_res;
+ unsigned int i;
+
+ for (i = 0; i < size; ++i) {
+ strscpy(wclk_name, input[i].name, ARRAY_SIZE(wclk_name));
+ strcat(wclk_name, "_wclk");
+ strscpy(pclk_name, input[i].name, ARRAY_SIZE(pclk_name));
+ strcat(pclk_name, "_pclk");
+ strscpy(mux_name, input[i].name, ARRAY_SIZE(mux_name));
+ strcat(mux_name, "_mux");
+ strscpy(div_name, input[i].name, ARRAY_SIZE(div_name));
+ strcat(div_name, "_div");
+
+ resets[input[i].reset_id].reg = base + input[i].reset_reg;
+ resets[input[i].reset_id].mask = BIT(input[i].reset_shift) +
+ BIT(input[i].reset_shift + 1);
+
+ if (input[i].div_size)
+ wclk_parent = div_name;
+ else if (input[i].mux_size)
+ wclk_parent = mux_name;
+ else
+ wclk_parent = input[i].mux_sel[0];
+
+ hw = devm_clk_hw_register_gate(dev, pclk_name, input[i].pclk_parent, input[i].flags,
+ base + input[i].gate_reg, input[i].pclk_gate_shift,
+ 0, ®_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ clocks->hws[input[i].pclk_id] = hw;
+
+ if (input[i].mux_size) {
+ hw = devm_clk_hw_register_mux(dev, mux_name, input[i].mux_sel,
+ input[i].mux_sel_count, 0,
+ base + input[i].mux_reg,
+ input[i].mux_shift, input[i].mux_size, 0,
+ ®_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ div_parent = mux_name;
+ } else {
+ div_parent = input[i].mux_sel[0];
+ }
+
+ hw = devm_clk_hw_register_gate(dev, wclk_name, wclk_parent,
+ input[i].flags | CLK_SET_RATE_PARENT,
+ base + input[i].gate_reg, input[i].wclk_gate_shift,
+ 0, ®_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ clocks->hws[input[i].wclk_id] = hw;
+
+ if (!input[i].div_size)
+ continue;
+
+ hw = devm_clk_hw_register_divider(dev, div_name, div_parent, CLK_SET_RATE_PARENT,
+ base + input[i].div_reg, input[i].div_shift,
+ input[i].div_size, 0, ®_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ }
+
+ return 0;
+}
+
+static int zx297520v3_gate(struct device *dev, void __iomem *base,
+ struct clk_hw_onecell_data *clocks,
+ const struct zx297520v3_gate *input, size_t size)
+{
+ struct clk_hw *hw;
+ unsigned int i;
+
+ for (i = 0; i < size; ++i) {
+ hw = devm_clk_hw_register_gate(dev, input[i].name, input[i].parent,
+ CLK_SET_RATE_PARENT, base + input[i].reg,
+ input[i].shift, 0, ®_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ clocks->hws[input[i].id] = hw;
+ }
+
+ return 0;
+}
+
+static int zx_restart_handle(struct sys_off_data *data)
+{
+ struct zx29_clk_controller *top = data->cb_data;
+
+ writel(1, top->base);
+ mdelay(1000);
+
+ pr_emerg("Unable to restart system\n");
+ return NOTIFY_DONE;
+}
+
+static int zx297520_topclk_probe(struct platform_device *pdev)
+{
+ struct zx29_clk_controller *top;
+ struct device *dev = &pdev->dev;
+ struct clk_hw *hw;
+ struct clk *clk;
+ int res;
+
+ dev_info(dev, "Registering zx297520v3 top clocks and resets\n");
+ top = devm_kzalloc(dev, offsetof(struct zx29_clk_controller,
+ resets[ZX297520V3_TOPRST_END]), GFP_KERNEL);
+ if (!top)
+ return -ENOMEM;
+
+ top->clocks = devm_kzalloc(dev, struct_size(top->clocks, hws,
+ ZX297520V3_TOPCLK_END), GFP_KERNEL);
+ if (!top->clocks)
+ return -ENOMEM;
+ top->clocks->num = ZX297520V3_TOPCLK_END;
+
+ top->base = devm_platform_ioremap_resource(pdev, 0);
+ WARN_ON(!top->base);
+
+ /* Offset 0x0 is the global board reset
+ * Offset 0x4 gives some static boot information - raw NAND or SPI NAND
+ */
+
+ clk = devm_clk_get_prepared(dev, "osc32k");
+ if (IS_ERR(clk)) {
+ dev_err(dev, "32 KHz input clock not found 1\n");
+ return PTR_ERR(clk);
+ }
+
+ clk = devm_clk_get_prepared(dev, "osc26m");
+ if (IS_ERR(clk)) {
+ dev_err(dev, "26 MHz input clock not found\n");
+ return PTR_ERR(clk);
+ }
+
+ /* Default setting: 0x48040c11. 624/312/156... */
+ res = zx297520v3_pll(dev, top->base + 0x8, "mpll", clk);
+ if (res)
+ return res;
+
+ /* There is a PLL at 0x10 called "upll" in ZTE's code, but I don't see any documented
+ * consumers. Default setting 0x48347811. 480/240/160 MHz.
+ */
+
+ /* Default value 0x4834902d. Feeds dpll. 46.08 MHz */
+ res = zx297520v3_pll(dev, top->base + 0x100, "unknownpll", clk);
+ if (res)
+ return res;
+
+ /* The documentation says 491.52 MHz and measurement with the LSP TDM device supports this.
+ * The default value is 0x480C2011. To get to 491.52 with these settings it needs a 23.04
+ * MHz reference clock, which matches unknownpll_d2. If unknownpll is disabled, dpll loses
+ * its lock.
+ *
+ * The proprietary LTE driver or coproc enables and disables it. TDM and I2S can use it.
+ *
+ * FIXME: Isn't there a nicer way to get the struct clk for unknownpll_d2? I don't want to
+ * return all generated clocks from zx297520v3_pll or store them in the controller because
+ * I need one of them here. I could always pass the parent by name though.
+ */
+ res = zx297520v3_pll(dev, top->base + 0x18, "dpll", __clk_lookup("unknownpll_d2"));
+ if (res)
+ return res;
+ res = zx297520v3_pll(dev, top->base + 0x110, "gpll", clk);
+ if (res)
+ return res;
+
+ res = zx297520v3_composite(dev, top->base, top->clocks, top->resets,
+ top_clocks, ARRAY_SIZE(top_clocks));
+ if (res)
+ return res;
+
+ res = zx297520v3_gate(dev, top->base, top->clocks, top_gates, ARRAY_SIZE(top_gates));
+ if (res)
+ return res;
+
+ /* The Cortex M0 coprocessor. It is responsible for booting the board and runs some power
+ * management helper code on the stock firmware, but isn't critical. We can run custom code
+ * on it but currently do not. These bits control the speed and the values are mentioned in
+ * ZTE's uboot. It isn't clear to me if this is directly responsible for the m0 clock, or
+ * if it is the input to another clock. I also haven't found a gate that shuts the m0 off
+ * and allows restarting. There don't seem to be resets either.
+ *
+ * Also note the comment about SRAM1 and SRAM2 PCLKs. They can be turned off to crash the
+ * M0 by feeding it garbage instructions.
+ */
+ hw = devm_clk_hw_register_mux(dev, "m0_wclk", m0_sel, ARRAY_SIZE(m0_sel),
+ 0, top->base + 0x38, 0, 2, 0, ®_lock);
+ if (IS_ERR(hw))
+ return PTR_ERR(hw);
+ top->clocks->hws[ZX297520V3_M0_WCLK] = hw;
+
+ of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, top->clocks);
+
+ res = devm_register_restart_handler(dev, zx_restart_handle, top);
+ if (res)
+ dev_err(dev, "can't register restart handler (res=%d)\n", res);
+
+ /* Stray reset bits follow.
+ *
+ * I haven't found any clocks for GPIO. It probably wouldn't make much
+ * sense anyway. Only one bit per controller.
+ */
+ top->resets[ZX297520V3_GPIO8_RESET].reg = top->base + 0x74;
+ top->resets[ZX297520V3_GPIO8_RESET].mask = BIT(2);
+ top->resets[ZX297520V3_GPIO_RESET].reg = top->base + 0x74;
+ top->resets[ZX297520V3_GPIO_RESET].mask = BIT(3);
+
+ /* USB reset. This is slightly special because it needs to wait for a ready bit after
+ * deasserting.
+ *
+ * FIXME: Actually implement this waiting.
+ */
+ top->resets[ZX297520V3_USB_RESET].reg = top->base + 0x80;
+ top->resets[ZX297520V3_USB_RESET].mask = BIT(3) | BIT(4) | BIT(5);
+
+ /* This bit is set by ZTE's cpko.ko blob, it looks like a reset bit for the LTE DSP
+ * coprocessor. Clocks for it are in matrixclk.
+ */
+ top->resets[ZX297520V3_ZSP_RESET].reg = top->base + 0x13c;
+ top->resets[ZX297520V3_ZSP_RESET].mask = BIT(0);
+
+ top->rcdev.owner = THIS_MODULE;
+ top->rcdev.nr_resets = ZX297520V3_TOPRST_END;
+ top->rcdev.ops = &zx297520v3_rst_ops;
+ top->rcdev.of_node = dev->of_node;
+ return devm_reset_controller_register(dev, &top->rcdev);
+}
+
+static const struct of_device_id of_match_zx297520v3_topclk[] = {
+ { .compatible = "zte,zx297520v3-topclk"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_match_zx297520v3_topclk);
+
+static struct platform_driver clk_zx297520v3_topclk = {
+ .probe = zx297520_topclk_probe,
+ .driver = {
+ .name = "clk-zx297520v3-topclk",
+ .of_match_table = of_match_zx297520v3_topclk,
+ },
+};
+module_platform_driver(clk_zx297520v3_topclk);
+
+MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
+MODULE_DESCRIPTION("ZTE zx297520v3 clock driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH RFC 4/4] clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets.
From: Stefan Dösinger @ 2026-05-10 19:01 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
In-Reply-To: <20260510-zx29clk-v1-0-e1bacfffe967@gmail.com>
"LSP" is ZTE's term for this part of the SoC, I suspect it stands for
"low speed peripherals". The main UART is here, together with the flash
controller and more surplus proprietary timers.
It also has two more I2C controllers that supposedly connect to a
battery charger, SPI for displays and I2S for analog telephones. The
boards I have don't have any of these components though.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
drivers/clk/zte/clk-zx297520v3.c | 183 +++++++++++++++++++++++++++++++++++++++
1 file changed, 183 insertions(+)
diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
index 0c06add433ee..e69be686c1b1 100644
--- a/drivers/clk/zte/clk-zx297520v3.c
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -792,6 +792,189 @@ static struct platform_driver clk_zx297520v3_matrixclk = {
};
module_platform_driver(clk_zx297520v3_matrixclk);
+/* LSP clock entries have a common pattern: Bit 0 for PCLK, Bit 1 for WCLK. Bit 4 (and sometimes
+ * more) for WCLK mux.
+ *
+ * Bit 8 and 9 are reset bits. I don't know the difference between the two, but they both
+ * need to be set to deassert the reset.
+ *
+ * Bits 12-16 can be a divisor, but not all clocks have it. Some clocks have a divisor in 16-20.
+ *
+ * The ID given in this table is the first register in the device's MMIO space. ZTE's drivers
+ * usually call this a version register, but it looks more like a device identifier.
+ *
+ * It looks like the registers map to devices like this:
+ *
+ * Timer reg function div dev offset(lsp + xxxx) ID
+ * 0x0: Read-only, probably device identifier 0x00752100
+ * 0x4: timer_l1 Y 0x1000 0x02020000
+ * 0x8: watchdog_l2 Y 0x2000 0x02020000
+ * 0xc: watchdog_l3 Y 0x3000 0x02020000
+ * 0x10: i2c1 N 0x4000 0x01020000
+ * 0x14: i2s0 Yh 0x5000 0x01030000
+ * 0x18: always 0 N -
+ * 0x1c: i2s1 Yh 0x6000 0x01030000
+ * 0x20: always 0 N -
+ * 0x24: qspi N 0x7000 0x01040000
+ * 0x28: uart1 N 0x8000 0x01060000
+ * 0x2c: i2c2 N 0x9000 0x01020000
+ * 0x30: spi0 Y 0xa000 0x01040000
+ * 0x34: timer_lb Y 0xb000 0x02020000
+ * 0x38: timer_lc Y 0xc000 0x02020000
+ * 0x3c: uart2 N 0xd000 0x01060000
+ * 0x40: watchdog_le Y 0xe000 0x02020000
+ * 0x44: timer_lf Y 0xf000 0x02020000
+ * 0x48: spi1 Y 0x10000 0x01040000
+ * 0x4c: timer_l11 Y 0x11000 0x02020000
+ * 0x50: tdm Y 0x12000 0x01040000
+ *
+ * Registers 0x58, 0x5c, 0x60, 0x64, 0x68 seem to contain more controls for i2s and tdm.
+ */
+
+static const char * const timer_lsp_sel[] = {
+ "lsp_osc32k",
+ "lsp_osc26m",
+};
+
+static const char * const uart_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_mpll_d6",
+};
+
+static const char * const i2s_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_dpll_d4",
+ "lsp_mpll_d6",
+ /* Unknown */
+};
+
+static const char * const tdm_lsp_sel[] = {
+ "lsp_tdm_wclk",
+};
+
+static const char * const spi_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_mpll_d4",
+ "lsp_mpll_d6",
+ /* Unknown */
+};
+
+static const char * const qspi_lsp_sel[] = {
+ "lsp_osc26m",
+ "lsp_mpll_d4",
+ "lsp_mpll_d5",
+ "lsp_mpll_d6",
+ "lsp_mpll_d8",
+ "lsp_mpll_d12",
+ "lsp_osc26m",
+ "lsp_osc26m",
+};
+
+#define LSP_CLOCK(offset, name, mux, div_shift, div_size) {\
+ ZX297520V3_##name##_RESET, ZX297520V3_##name##_WCLK, ZX297520V3_##name##_PCLK,\
+ #name, offset, 8, offset, 0, 1, "lsp_pclk", offset, 4, 4, mux, ARRAY_SIZE(mux),\
+ offset, div_shift, div_size, 0}
+
+static const struct zx297520v3_composite lsp_clocks[] = {
+ LSP_CLOCK(0x4, TIMER_L1, timer_lsp_sel, 0, 0),
+ LSP_CLOCK(0x8, WDT_L2, timer_lsp_sel, 0, 0),
+ LSP_CLOCK(0xc, WDT_L3, timer_lsp_sel, 0, 0),
+ LSP_CLOCK(0x10, I2C1, uart_lsp_sel, 0, 0),
+ LSP_CLOCK(0x14, I2S0, i2s_lsp_sel, 16, 4),
+ LSP_CLOCK(0x1c, I2S1, i2s_lsp_sel, 16, 4),
+ LSP_CLOCK(0x24, QSPI, qspi_lsp_sel, 0, 0),
+ LSP_CLOCK(0x28, UART1, uart_lsp_sel, 0, 0),
+ LSP_CLOCK(0x2C, I2C2, uart_lsp_sel, 0, 0),
+ LSP_CLOCK(0x30, SPI0, spi_lsp_sel, 12, 4),
+ LSP_CLOCK(0x34, TIMER_LB, timer_lsp_sel, 12, 4),
+ LSP_CLOCK(0x38, TIMER_LC, timer_lsp_sel, 12, 4),
+ LSP_CLOCK(0x3c, UART2, uart_lsp_sel, 0, 0),
+ LSP_CLOCK(0x40, WDT_LE, timer_lsp_sel, 12, 4),
+ LSP_CLOCK(0x44, TIMER_LF, timer_lsp_sel, 12, 4),
+ LSP_CLOCK(0x48, SPI1, spi_lsp_sel, 12, 4),
+ LSP_CLOCK(0x4c, TIMER_L11, timer_lsp_sel, 12, 4),
+ LSP_CLOCK(0x50, TDM, tdm_lsp_sel, 16, 4),
+};
+
+#undef LSP_CLOCK
+
+static int zx297520_lspclk_probe(struct platform_device *pdev)
+{
+ static const char * const *parent_names[] = { "mpll_d5", "mpll_d4", "mpll_d6", "mpll_d8",
+ "mpll_d12", "osc26m", "osc32k", "pclk" };
+
+ struct zx29_clk_controller *lsp;
+ struct device *dev = &pdev->dev;
+ struct clk *parent;
+ unsigned int i;
+ int res;
+
+ dev_info(dev, "Registering zx297520v3 LSP clocks and resets\n");
+
+ lsp = devm_kzalloc(dev, offsetof(struct zx29_clk_controller,
+ resets[ZX297520V3_LSPRST_END]), GFP_KERNEL);
+ if (!lsp)
+ return -ENOMEM;
+
+ lsp->clocks = devm_kzalloc(dev, struct_size(lsp->clocks, hws,
+ ZX297520V3_LSPCLK_END), GFP_KERNEL);
+ if (!lsp->clocks)
+ return -ENOMEM;
+ lsp->clocks->num = ZX297520V3_LSPCLK_END;
+
+ lsp->base = devm_platform_ioremap_resource(pdev, 0);
+ WARN_ON(!lsp->base);
+
+ /* TODO: Technically we can disable the pclk if all LSP devices are shut down, but that
+ * needs custom clk ops to tiptoe around a disabled LSP pclk before attempting to access
+ * the actual clock. In normal operation it is unlikely that all LSP devices are shut down
+ * simultaneously though as UART and NAND are located here.
+ */
+ parent = devm_clk_get_enabled(dev, "pclk");
+ if (IS_ERR(parent)) {
+ dev_err(dev, "failed to find lsp pclk\n");
+ return PTR_ERR(parent);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(parent_names); ++i) {
+ parent = devm_clk_get(dev, parent_names[i]);
+ if (IS_ERR(parent)) {
+ dev_err(dev, "failed to find lsp %s clock\n", parent_names[i]);
+ return PTR_ERR(parent);
+ }
+ }
+
+ res = zx297520v3_composite(dev, lsp->base, lsp->clocks, lsp->resets,
+ lsp_clocks, ARRAY_SIZE(lsp_clocks));
+ if (res)
+ return res;
+
+ res = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, lsp->clocks);
+ if (res)
+ return res;
+
+ lsp->rcdev.owner = THIS_MODULE;
+ lsp->rcdev.nr_resets = ZX297520V3_LSPRST_END;
+ lsp->rcdev.ops = &zx297520v3_rst_ops;
+ lsp->rcdev.of_node = dev->of_node;
+ return devm_reset_controller_register(dev, &lsp->rcdev);
+}
+
+static const struct of_device_id of_match_zx297520v3_lspclk[] = {
+ { .compatible = "zte,zx297520v3-lspclk"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_match_zx297520v3_lspclk);
+
+static struct platform_driver zx297520v3_lspclk = {
+ .probe = zx297520_lspclk_probe,
+ .driver = {
+ .name = "clk-zx297520v3-lspclk",
+ .of_match_table = of_match_zx297520v3_lspclk,
+ },
+};
+module_platform_driver(zx297520v3_lspclk);
+
MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
MODULE_DESCRIPTION("ZTE zx297520v3 clock driver");
MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH RFC 3/4] clk: zte: Introduce a driver for zx297520v3 matrix clocks and resets.
From: Stefan Dösinger @ 2026-05-10 19:01 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
In-Reply-To: <20260510-zx29clk-v1-0-e1bacfffe967@gmail.com>
This controls the CPU, DSP, DDR RAM, ethernet, SDIO controllers and a
few more devices. It also contains a number of clock gates to pass
clock signals down to the next controller.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
drivers/clk/zte/clk-zx297520v3.c | 215 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 214 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/zte/clk-zx297520v3.c b/drivers/clk/zte/clk-zx297520v3.c
index f73f5c006641..0c06add433ee 100644
--- a/drivers/clk/zte/clk-zx297520v3.c
+++ b/drivers/clk/zte/clk-zx297520v3.c
@@ -266,7 +266,7 @@ static int zx297520v3_pll(struct device *dev, void __iomem *base, const char *na
/* These are the fractionals of the PLLs I have seen. There should be a better way to
* generate them than hardcode the list.
*/
- static const unsigned int pll_fract[] = {2, 3, 4, 5, 6, 8, 12, 26};
+ static const unsigned int pll_fract[] = {2, 3, 4, 5, 6, 8, 12, 16, 26};
unsigned long ref, refdiv, fbdiv, vco, postdiv1, postdiv2, freq;
struct clk_hw *hw;
@@ -579,6 +579,219 @@ static struct platform_driver clk_zx297520v3_topclk = {
};
module_platform_driver(clk_zx297520v3_topclk);
+static const char * const cpu_sel[] = {
+ "osc26m",
+ "mpll", /* 624 MHz */
+ "mpll_d2", /* 312 MHz */
+ "mpll_d4", /* 156 MHz */
+};
+
+static const char * const sd0_sel[] = {
+ "osc26m",
+ "mpll_d4", /* 156 MHz */
+ "gpll_d2", /* 100 MHz */
+ "mpll_d8", /* 78 MHz */
+ "gpll_d4", /* 50 MHz */
+ "gpll_d8", /* 25 MHz */
+};
+
+static const char * const sd1_sel[] = {
+ "osc26m",
+ "gpll_d2", /* 100 MHz */
+ "mpll_d8", /* 78 MHz */
+ "gpll_d4", /* 50 MHz */
+ "mpll_d16", /* 39 MHz */
+ "gpll_d8", /* 25 MHz */
+};
+
+static const char * const nand_sel[] = {
+ "mpll_d4", /* 156 MHz */
+ "osc26m",
+};
+
+static const char * const edcp_sel[] = {
+ "osc26m",
+ "mpll_d4", /* 156 MHz */
+ "mpll_d5", /* 124.8 MHz */
+ "mpll_d6", /* 104 MHz */
+};
+
+static const char * const tdm_sel[] = {
+ "osc26m",
+ "dpll_d4", /* 122.88 MHz */
+ "mpll_d6", /* 104 MHz */
+};
+
+static const struct zx297520v3_composite matrix_clocks[] = {
+ /* Both 0x24 and 0x28 bits 1 and 2 stop the CPU. There is also a bit in topclk+0x138, which
+ * ZTE's uboot calls "A53 reset", which also stops the CPU. I can't really tell the
+ * difference between matrix+28 and top+138. The clock can be disabled and enabled from the
+ * Cortex M0 and it will nicely stop and restart the A53, retaining all state.
+ *
+ * 0x50, bits 0-3 have the DDR clock. A lot of DDR gates and resets are in 0x100.
+ */
+ ZX_CLK_CRIT(CPU, 0x28, 1, 0x24, 1, 2, 0x20, 0, 2, cpu_sel, 0, 0, 0),
+ /* TODO: 0x54 bit 14 and 0x54 bit 6 are supposed to be card detection clocks. */
+ ZX_CLK(SD0, 0x58, 1, 0x54, 12, 13, 0x50, 4, 3, sd0_sel, 0, 0, 0),
+ ZX_CLK(SD1, 0x58, 0, 0x54, 4, 5, 0x50, 8, 3, sd1_sel, 0, 0, 0),
+ /* This is some "denali" NAND, not the qspi connected one. */
+ ZX_CLK(NAND, 0x58, 4, 0x54, 20, 21, 0x50, 12, 2, nand_sel, 0, 0, 0),
+ ZX_CLK(SSC, 0x94, 24, 0x84, 1, 2, 0, 0, 0, clk_unknown, 0, 0, 0),
+ ZX_CLK(EDCP, 0x68, 0, 0x64, 2, 1, 0x50, 16, 2, edcp_sel, 0, 0, 0),
+ /* PDCFG. Like PMM, either clock bit will allow the device to function. */
+ ZX_CLK_CRIT(PDCFG, 0x94, 20, 0x88, 0, 1, 0x50, 16, 2, clk_unknown, 0, 0, 0),
+ /* There are a lot more VOU related controls in these registers, but turning off the main
+ * clock seems to shut off the entire VOU MMIO range.
+ */
+ ZX_CLK(VOU, 0x16c, 0, 0x168, 0, 1, 0, 0, 0, clk_main, 0, 0, 0),
+};
+
+static const struct zx297520v3_gate matrix_gates[] = {
+ /* ZTE's driver has a statemt to the effect of *(matrix->base+0x11C) = 5, with a comment
+ * suggesting that this sets a 50 mhz clock. The clock code itself lists the parents of
+ * these clock as 50mhz pll output, but the GMAC driver never enables the clocks.
+ *
+ * The clocks below are enabled by the boot loader though, so they are on. And it turns
+ * out that they are necessary for proper operation of the ethernet hardware. As far as
+ * I can see trough experimentation, bit 1 affects the PHY whereas 0 and 2 affect the
+ * MAC chip itself.
+ *
+ * Chain the wclk and rmii clk together for now. I haven't found a way to make either
+ * the mdio node or the phy node enable a clock. According to ethernet-phy.yaml it is
+ * supposed to be possible, but I can't find code to that effect in of_mdio.c.
+ */
+ {ZX297520V3_GMAC_PCLK, "gmac_pclk", "gpll_d4", 0x110, 0},
+ {ZX297520V3_GMAC_RMII, "gmac_rmii", "gpll_d4", 0x110, 1},
+ {ZX297520V3_GMAC_RMII, "gmac_wclk", "gmac_rmii", 0x110, 2},
+
+ /* ZSP aka LTE DSP clock. I think there is a mux at matrix+0x30, but I have no idea
+ * about the frequencies it selects. Gate is at matrix+0x3c.
+ */
+ {ZX297520V3_ZSP_WCLK, "zsp_wclk", "osc26m", 0x3c, 0},
+
+ /* Mailbox. I haven't found a reset for this. It seems to have a PCLK only - turning it off
+ * makes the MMIO area read 0x0. It looks like it does not need a WCLK. It generates IRQs
+ * fine with just bit 2 set. Bits 1 and 3 are 0 by default in this register.
+ */
+ {ZX297520V3_MBOX_PCLK, "mbox_pclk", "osc26m", 0x88, 2},
+
+ /* DMA Controller. It has a reset and PCLK, but no WCLK. */
+ {ZX297520V3_DMA_PCLK, "dma_pclk", "osc26m", 0x94, 3},
+
+ /* There is another clock controlling some "GSM" IP at 0xF3000000 in 0x88, bit 8. It appears
+ * to be a PCLK, but I have not found a matching WCLK or reset yet.
+ */
+
+ /* LSP uplink clocks. The PCLK is fairly obvious (disabling it shuts off the entire LSP
+ * register area). The WCLK speeds were deduced by setting timers and qspi muxes to a
+ * specific speed and seeing which bit in matrix+0x7c needs to be enabled for the device
+ * to work.
+ *
+ * Due to the timers I am certain about the 26mhz and 32khz clocks. I cannot directly
+ * observe the qspi mux frequency, so the clock rates depend on ZTE's qspi mux selection
+ * being correct.
+ *
+ * Two additional bits are specific to sound components - the mux for the LSP's TDM IP is
+ * in matrixclk and gets passed down. I2S has a mux in LSP, which can select the dpll_d4
+ * clock.
+ *
+ * This code is commented out until the next patch because disabling unused clocks without
+ * an LSP consumer breaks the UART.
+ */
+#if 0
+ {ZX297520V3_LSP_MPLL_D5_WCLK, "lsp_mpll_d5", "mpll_d5", 0x7c, 0},
+ {ZX297520V3_LSP_MPLL_D4_WCLK, "lsp_mpll_d4", "mpll_d4", 0x7c, 1},
+ {ZX297520V3_LSP_MPLL_D6_WCLK, "lsp_mpll_d6", "mpll_d6", 0x7c, 2},
+ {ZX297520V3_LSP_MPLL_D8_WCLK, "lsp_mpll_d8", "mpll_d8", 0x7c, 3},
+ {ZX297520V3_LSP_MPLL_D12_WCLK, "lsp_mpll_d12", "mpll_d12", 0x7c, 4},
+ {ZX297520V3_LSP_OSC26M_WCLK, "lsp_osc26m", "osc26m", 0x7c, 5},
+ {ZX297520V3_LSP_OSC32K_WCLK, "lsp_osc32k", "osc32k", 0x7c, 6},
+ {ZX297520V3_LSP_PCLK, "lsp_pclk", "osc26m", 0x7c, 7},
+ {ZX297520V3_LSP_TDM_WCLK, "lsp_tdm_wclk", "tdm_mux", 0x7c, 8},
+ {ZX297520V3_LSP_DPLL_D4_WCLK, "lsp_dpll_d4", "dpll_d4", 0x7c, 9},
+#endif
+};
+
+static int zx297520_matrixclk_probe(struct platform_device *pdev)
+{
+ struct zx29_clk_controller *matrix;
+ struct device *dev = &pdev->dev;
+ struct clk_hw *hw;
+ unsigned int i;
+ int res;
+
+ dev_info(dev, "Registering zx297520v3 matrix clocks\n");
+
+ matrix = devm_kzalloc(dev, offsetof(struct zx29_clk_controller,
+ resets[ZX297520V3_MATRIXRST_END]), GFP_KERNEL);
+ if (!matrix)
+ return -ENOMEM;
+
+ matrix->clocks = devm_kzalloc(dev, struct_size(matrix->clocks, hws,
+ ZX297520V3_MATRIXCLK_END), GFP_KERNEL);
+ if (!matrix->clocks)
+ return -ENOMEM;
+ matrix->clocks->num = ZX297520V3_MATRIXCLK_END;
+
+ matrix->base = devm_platform_ioremap_resource(pdev, 0);
+ WARN_ON(!matrix->base);
+
+ /* One stray mux: The TDM mux is in matrixclk and it is passed to the LSP controller. In a
+ * way the link gate (LSP_TDM_WCLK) could be considered a matching gate, but there is no
+ * reset and no pclk.
+ */
+ hw = devm_clk_hw_register_mux(dev, "tdm_mux", tdm_sel, ARRAY_SIZE(tdm_sel), 0,
+ matrix->base + 0x50, 24, 2, 0, ®_lock);
+
+ res = zx297520v3_composite(dev, matrix->base, matrix->clocks, matrix->resets,
+ matrix_clocks, ARRAY_SIZE(matrix_clocks));
+ if (res)
+ return res;
+
+ res = zx297520v3_gate(dev, matrix->base, matrix->clocks,
+ matrix_gates, ARRAY_SIZE(matrix_gates));
+ if (res)
+ return res;
+
+ for (i = 0; i < ZX297520V3_MATRIXCLK_END; i++) {
+ if (IS_ERR(matrix->clocks->hws[i])) {
+ pr_err("zx297520 clk %d: register failed with %ld\n",
+ i, PTR_ERR(matrix->clocks->hws[i]));
+ return -ENODEV;
+ }
+ }
+
+ res = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, matrix->clocks);
+ if (res)
+ return res;
+
+ matrix->resets[ZX297520V3_DMA_RESET].reg = matrix->base + 0x70;
+ matrix->resets[ZX297520V3_DMA_RESET].mask = BIT(0) | BIT(1);
+ matrix->resets[ZX297520V3_GMAC_RESET].reg = matrix->base + 0x114;
+ matrix->resets[ZX297520V3_GMAC_RESET].mask = BIT(0) | BIT(1);
+
+ matrix->rcdev.owner = THIS_MODULE;
+ matrix->rcdev.nr_resets = ZX297520V3_MATRIXRST_END;
+ matrix->rcdev.ops = &zx297520v3_rst_ops;
+ matrix->rcdev.of_node = dev->of_node;
+ return devm_reset_controller_register(dev, &matrix->rcdev);
+}
+
+static const struct of_device_id of_match_zx297520v3_matrixclk[] = {
+ { .compatible = "zte,zx297520v3-matrixclk"},
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_match_zx297520v3_matrixclk);
+
+static struct platform_driver clk_zx297520v3_matrixclk = {
+ .probe = zx297520_matrixclk_probe,
+ .driver = {
+ .name = "clk-zx297520v3-matrixclk",
+ .of_match_table = of_match_zx297520v3_matrixclk,
+ },
+};
+module_platform_driver(clk_zx297520v3_matrixclk);
+
MODULE_AUTHOR("Stefan Dösinger <stefandoesinger@gmail.com>");
MODULE_DESCRIPTION("ZTE zx297520v3 clock driver");
MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH RFC 1/4] dt-bindings: clk: zte: Add zx297520v3 clock and reset bindings.
From: Stefan Dösinger @ 2026-05-10 19:01 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
In-Reply-To: <20260510-zx29clk-v1-0-e1bacfffe967@gmail.com>
These SoCs have 3 clock and reset controllers. The "top" controller -
all names follow ZTE's naming - controls core devices like the AHB bus,
most timers and the Cortex M0 that brings up the board. The register
layout is fairly chaotic. Some patterns can be found, but nothing that
holds true for all devices it controls.
Generally every device has two clocks (one work clock, and one that
connects it to the bus, I call it PCLK), two reset bits (I don't know
what the difference is - sometimes asserting one is enough to reset the
device, sometimes both need to be asserted) and one mux. Some devices,
like the GPIO controller, only have reset bits and no clocks.
The top clock controller is fed by a 26mhz external oscillator and has 4
PLLs to generate other clock rates. ZTE's kernel does not manipulate the
PLLs at all and relies on BROM and the boot loader to set them up. The
bitfields in the control registers are somewhat documented in a
Lauterback TRACE32 debug file in the kernel sources though. At the
moment, my driver extracts clock rates from the PLLs, but cannot change
them. A proper PLL clk is on my TODO list before I remove the [RFC] tag
from the submission. It will be necessary for the LTE hardware with
replacement boot loaders because BROM does not set up the LTE-related
PLL.
The "matrix" controller controls the main Cortex A53 CPU, the LTE ZSP,
SDIO and a few others. It is even more chaotic than the "top"
controller.
The "LSP" controller - I suspect it stands for "low speed peripherals" -
is very regular. One 32 bit register for 2 clock gates, two resets, one
mux (1-3 bit) and in some cases a 4 bit divider.
Not all clocks will have an explicit user in the end. I am defining a
lot of them simply to shut them off. The boot loader sets up a few of
the proprietary timers, which will send regular IRQs (although the
kernel of course doesn't need to listen to them). I don't plan to add a
driver for the proprietary timer as I see no use for them - the ARM arch
timer works just fine. I will add a driver for the very similar
proprietary watchdog though.
The top and matrix list is not exhaustive. There are other bits
that are enabled, but I couldn't deduce what they are controlling by
trial and error. Some of them seem to do nothing. Others cause an
instant hang of the board when disabled. I isolated a few (SRAM PCLK,
arm arch timer clock) where I don't see a reason to manipulate them. It
is quite likely that a handful more clocks will be added in the future,
but not a large number.
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
.../bindings/clock/zte,zx297520v3-clk.yaml | 173 ++++++++++++++++++++
include/dt-bindings/clock/zte,zx297520v3-clk.h | 179 +++++++++++++++++++++
2 files changed, 352 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/zte,zx297520v3-clk.yaml b/Documentation/devicetree/bindings/clock/zte,zx297520v3-clk.yaml
new file mode 100644
index 000000000000..3b7084a18a97
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/zte,zx297520v3-clk.yaml
@@ -0,0 +1,173 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/zte,zx297520v3-clk.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE zx297520v3 SoC clock and reset controller
+
+maintainers:
+ - Stefan Dösinger <stefandoesinger@gmail.com>
+
+description: |
+ The zx297520v3's clock controller consists of 3 controllers, which generate
+ clocks for internal SoC devices. In addition to clocks it also has reset
+ controls for most, but not all, devices.
+
+ While there is a certain hierarchy among the controllers ("top" controlls core
+ parts like the boot-up Cortex M0, "matrix" controls the main CPU and LTE DSP,
+ "lsp" controls peripherals"), in practise all 3 are required to reasonably
+ operate the SoC.
+
+ The top controller has two inputs: a 26 MHz and a 32 KHz external oscillator.
+ They need to be provided as input clocks. The matrix controller controlls 10
+ clock lines that get fed into the LSP controller. The LSP device node needs
+ to list these input clocks.
+
+ The matrix controller consumes clocks generated by PLLs in the top
+ controller, but there are no controls in the top controller to sever this
+ link. The interface between these controllers is not expressed in the device
+ tree, but the matrix controller cannot work without the clock handles
+ registered by the top controller.
+
+ All available clocks are defined as preprocessor macros in
+ 'dt-bindings/clock/zte,zx297520v3-clk.h' header.
+
+properties:
+ compatible:
+ enum:
+ - zte,zx297520v3-topclk
+ - zte,zx297520v3-matrixclk
+ - zte,zx297520v3-lspclk
+
+ clocks:
+ minItems: 2
+ maxItems: 10
+
+ clock-names:
+ minItems: 2
+ maxItems: 10
+
+ "#clock-cells":
+ const: 1
+
+ "#reset-cells":
+ const: 1
+
+ reg:
+ maxItems: 1
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: zte,zx297520v3-topclk
+ then:
+ properties:
+ clocks:
+ items:
+ - description: External reference clock (26 MHz)
+ - description: External reference clock (32 KHz)
+ clock-names:
+ items:
+ - const: osc26m
+ - const: osc32k
+ required:
+ - clocks
+ - clock-names
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: zte,zx297520v3-lspclk
+ then:
+ properties:
+ clocks:
+ items:
+ - description: Main PLL divided by 5 output from matrixclk (124.8 MHz)
+ - description: Main PLL divided by 4 output from matrixclk (156 MHz)
+ - description: Main PLL divided by 6 output from matrixclk (104 MHz)
+ - description: Main PLL divided by 8 output from matrixclk (78 MHz)
+ - description: Main PLL divided by 12 output from matrixclk (52 MHz)
+ - description: Main oscillator output from matrixclk (26 MHz)
+ - description: Timer oscillator output from matrixclk (32 KHz)
+ - description: LSP pclk output from matrixclk (26 MHz)
+ - description: TDM wclk mux output from matrixclk
+ - description: DPLL divided by 4 output from matrixclk (122.88 MHz)
+ clock-names:
+ items:
+ - const: mpll_d5
+ - const: mpll_d4
+ - const: mpll_d6
+ - const: mpll_d8
+ - const: mpll_d12
+ - const: osc26m
+ - const: osc32k
+ - const: pclk
+ - const: tdm_wclk
+ - const: dpll_d4
+ required:
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+required:
+ - compatible
+ - '#clock-cells'
+ - reg
+ - '#reset-cells'
+
+examples:
+ - |
+ #include <dt-bindings/clock/zte,zx297520v3-clk.h>
+
+ osc26m: osc26m {
+ compatible = "fixed-clock";
+ clock-output-names = "osc26m";
+ #clock-cells = <0>;
+ };
+
+ osc32k: osc32k {
+ compatible = "fixed-clock";
+ clock-output-names = "osc32k";
+ #clock-cells = <0>;
+ };
+
+ topclk: topclk@13b000 {
+ compatible = "zte,zx297520v3-topclk";
+ reg = <0x0013b000 0x400>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ clocks = <&osc26m>, <&osc32k>;
+ clock-names = "osc26m", "osc32k";
+ };
+
+ matrixclk: matrixclk@1306000 {
+ compatible = "zte,zx297520v3-matrixclk";
+ reg = <0x01306000 0x400>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ };
+
+ lspclk: lspclk@1400000 {
+ compatible = "zte,zx297520v3-lspclk";
+ reg = <0x01400000 0x100>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+
+ clocks = <&matrixclk ZX297520V3_LSP_MPLL_D5_WCLK>,
+ <&matrixclk ZX297520V3_LSP_MPLL_D4_WCLK>,
+ <&matrixclk ZX297520V3_LSP_MPLL_D6_WCLK>,
+ <&matrixclk ZX297520V3_LSP_MPLL_D8_WCLK>,
+ <&matrixclk ZX297520V3_LSP_MPLL_D12_WCLK>,
+ <&matrixclk ZX297520V3_LSP_OSC26M_WCLK>,
+ <&matrixclk ZX297520V3_LSP_OSC32K_WCLK>,
+ <&matrixclk ZX297520V3_LSP_PCLK>,
+ <&matrixclk ZX297520V3_LSP_TDM_WCLK>,
+ <&matrixclk ZX297520V3_LSP_DPLL_D4_WCLK>;
+ clock-names = "mpll_d5", "mpll_d4", "mpll_d6", "mpll_d8", "mpll_d12",
+ "osc26m", "osc32k", "pclk", "tdm_wclk", "dpll_d4";
+ };
diff --git a/include/dt-bindings/clock/zte,zx297520v3-clk.h b/include/dt-bindings/clock/zte,zx297520v3-clk.h
new file mode 100644
index 000000000000..322b53be8b12
--- /dev/null
+++ b/include/dt-bindings/clock/zte,zx297520v3-clk.h
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Stefan Dösinger.
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_ZX297520V3_H
+#define __DT_BINDINGS_CLOCK_ZX297520V3_H
+
+#define ZX297520V3_AHB_WCLK 0
+#define ZX297520V3_AHB_PCLK 1
+#define ZX297520V3_PMM_WCLK 2
+#define ZX297520V3_PMM_PCLK 3
+#define ZX297520V3_USB_24M 4
+#define ZX297520V3_USB_AHB 5
+#define ZX297520V3_TIMER_T08_WCLK 6
+#define ZX297520V3_TIMER_T08_PCLK 7
+#define ZX297520V3_TIMER_T09_WCLK 8
+#define ZX297520V3_TIMER_T09_PCLK 9
+#define ZX297520V3_TIMER_T12_WCLK 10
+#define ZX297520V3_TIMER_T12_PCLK 11
+#define ZX297520V3_TIMER_T13_WCLK 12
+#define ZX297520V3_TIMER_T13_PCLK 13
+#define ZX297520V3_TIMER_T14_WCLK 14
+#define ZX297520V3_TIMER_T14_PCLK 15
+#define ZX297520V3_TIMER_T15_WCLK 16
+#define ZX297520V3_TIMER_T15_PCLK 17
+#define ZX297520V3_TIMER_T16_WCLK 18
+#define ZX297520V3_TIMER_T16_PCLK 19
+#define ZX297520V3_TIMER_T17_WCLK 20
+#define ZX297520V3_TIMER_T17_PCLK 21
+#define ZX297520V3_WDT_T18_WCLK 22
+#define ZX297520V3_WDT_T18_PCLK 23
+#define ZX297520V3_UART0_WCLK 24
+#define ZX297520V3_UART0_PCLK 25
+#define ZX297520V3_I2C0_WCLK 26
+#define ZX297520V3_I2C0_PCLK 27
+#define ZX297520V3_RTC_WCLK 28
+#define ZX297520V3_RTC_PCLK 29
+#define ZX297520V3_LPM_GSM_WCLK 30
+#define ZX297520V3_LPM_GSM_PCLK 31
+#define ZX297520V3_LPM_LTE_WCLK 32
+#define ZX297520V3_LPM_LTE_PCLK 33
+#define ZX297520V3_LPM_TD_WCLK 34
+#define ZX297520V3_LPM_TD_PCLK 35
+#define ZX297520V3_LPM_W_WCLK 36
+#define ZX297520V3_LPM_W_PCLK 37
+#define ZX297520V3_USIM1_WCLK 38
+#define ZX297520V3_USIM1_PCLK 39
+#define ZX297520V3_M0_WCLK 40
+#define ZX297520V3_TOPCLK_END 41
+
+#define ZX297520V3_AHB_RESET 0
+#define ZX297520V3_TIMER_T08_RESET 1
+#define ZX297520V3_TIMER_T09_RESET 2
+#define ZX297520V3_TIMER_T12_RESET 3
+#define ZX297520V3_TIMER_T13_RESET 4
+#define ZX297520V3_TIMER_T14_RESET 5
+#define ZX297520V3_TIMER_T15_RESET 6
+#define ZX297520V3_TIMER_T16_RESET 7
+#define ZX297520V3_TIMER_T17_RESET 8
+#define ZX297520V3_WDT_T18_RESET 9
+#define ZX297520V3_UART0_RESET 10
+#define ZX297520V3_I2C0_RESET 11
+#define ZX297520V3_RTC_RESET 12
+#define ZX297520V3_USIM1_RESET 13
+#define ZX297520V3_PMM_RESET 14
+#define ZX297520V3_GPIO8_RESET 15
+#define ZX297520V3_GPIO_RESET 16
+#define ZX297520V3_ZSP_RESET 17
+#define ZX297520V3_USB_RESET 18
+#define ZX297520V3_TOPRST_END 19
+
+#define ZX297520V3_CPU_WCLK 0
+#define ZX297520V3_CPU_PCLK 1
+#define ZX297520V3_SD0_WCLK 2
+#define ZX297520V3_SD0_PCLK 3
+#define ZX297520V3_SD1_WCLK 4
+#define ZX297520V3_SD1_PCLK 5
+#define ZX297520V3_SD1_CDET 6
+#define ZX297520V3_NAND_WCLK 7
+#define ZX297520V3_NAND_PCLK 8
+#define ZX297520V3_SSC_WCLK 9
+#define ZX297520V3_SSC_PCLK 10
+#define ZX297520V3_EDCP_WCLK 11
+#define ZX297520V3_EDCP_PCLK 12
+#define ZX297520V3_EDCP_SYNCAXI 13
+#define ZX297520V3_VOU_WCLK 14
+#define ZX297520V3_VOU_PCLK 15
+#define ZX297520V3_PDCFG_WCLK 16
+#define ZX297520V3_PDCFG_PCLK 17
+#define ZX297520V3_GMAC_WCLK 18
+#define ZX297520V3_GMAC_RMII 19
+#define ZX297520V3_GMAC_PCLK 20
+#define ZX297520V3_ZSP_WCLK 21
+#define ZX297520V3_MBOX_PCLK 22
+#define ZX297520V3_DMA_PCLK 23
+#define ZX297520V3_LSP_MPLL_D5_WCLK 24
+#define ZX297520V3_LSP_MPLL_D4_WCLK 25
+#define ZX297520V3_LSP_MPLL_D6_WCLK 26
+#define ZX297520V3_LSP_MPLL_D8_WCLK 27
+#define ZX297520V3_LSP_MPLL_D12_WCLK 28
+#define ZX297520V3_LSP_OSC26M_WCLK 29
+#define ZX297520V3_LSP_OSC32K_WCLK 30
+#define ZX297520V3_LSP_PCLK 31
+#define ZX297520V3_LSP_TDM_WCLK 32
+#define ZX297520V3_LSP_DPLL_D4_WCLK 33
+#define ZX297520V3_MATRIXCLK_END 34
+
+#define ZX297520V3_CPU_RESET 0
+#define ZX297520V3_SD0_RESET 1
+#define ZX297520V3_SD1_RESET 2
+#define ZX297520V3_NAND_RESET 3
+#define ZX297520V3_SSC_RESET 4
+#define ZX297520V3_EDCP_RESET 5
+#define ZX297520V3_VOU_RESET 6
+#define ZX297520V3_PDCFG_RESET 7
+#define ZX297520V3_GMAC_RESET 8
+#define ZX297520V3_DMA_RESET 9
+#define ZX297520V3_MATRIXRST_END 10
+
+#define ZX297520V3_TIMER_L1_WCLK 0
+#define ZX297520V3_TIMER_L1_PCLK 1
+#define ZX297520V3_WDT_L2_WCLK 2
+#define ZX297520V3_WDT_L2_PCLK 3
+#define ZX297520V3_WDT_L3_WCLK 4
+#define ZX297520V3_WDT_L3_PCLK 5
+#define ZX297520V3_I2C1_WCLK 6
+#define ZX297520V3_I2C1_PCLK 7
+#define ZX297520V3_I2S0_WCLK 8
+#define ZX297520V3_I2S0_PCLK 9
+#define ZX297520V3_I2S1_WCLK 10
+#define ZX297520V3_I2S1_PCLK 11
+#define ZX297520V3_QSPI_WCLK 12
+#define ZX297520V3_QSPI_PCLK 13
+#define ZX297520V3_UART1_WCLK 14
+#define ZX297520V3_UART1_PCLK 15
+#define ZX297520V3_I2C2_WCLK 16
+#define ZX297520V3_I2C2_PCLK 17
+#define ZX297520V3_SPI0_WCLK 18
+#define ZX297520V3_SPI0_PCLK 19
+#define ZX297520V3_TIMER_LB_WCLK 20
+#define ZX297520V3_TIMER_LB_PCLK 21
+#define ZX297520V3_TIMER_LC_WCLK 22
+#define ZX297520V3_TIMER_LC_PCLK 23
+#define ZX297520V3_UART2_WCLK 24
+#define ZX297520V3_UART2_PCLK 25
+#define ZX297520V3_WDT_LE_WCLK 26
+#define ZX297520V3_WDT_LE_PCLK 27
+#define ZX297520V3_TIMER_LF_WCLK 28
+#define ZX297520V3_TIMER_LF_PCLK 29
+#define ZX297520V3_SPI1_WCLK 30
+#define ZX297520V3_SPI1_PCLK 31
+#define ZX297520V3_TIMER_L11_WCLK 32
+#define ZX297520V3_TIMER_L11_PCLK 33
+#define ZX297520V3_TDM_WCLK 34
+#define ZX297520V3_TDM_PCLK 35
+#define ZX297520V3_LSPCLK_END 36
+
+#define ZX297520V3_TIMER_L1_RESET 0
+#define ZX297520V3_WDT_L2_RESET 1
+#define ZX297520V3_WDT_L3_RESET 2
+#define ZX297520V3_I2C1_RESET 3
+#define ZX297520V3_I2S0_RESET 4
+#define ZX297520V3_I2S1_RESET 5
+#define ZX297520V3_QSPI_RESET 6
+#define ZX297520V3_UART1_RESET 7
+#define ZX297520V3_I2C2_RESET 8
+#define ZX297520V3_SPI0_RESET 9
+#define ZX297520V3_TIMER_LB_RESET 10
+#define ZX297520V3_TIMER_LC_RESET 11
+#define ZX297520V3_UART2_RESET 12
+#define ZX297520V3_WDT_LE_RESET 13
+#define ZX297520V3_TIMER_LF_RESET 14
+#define ZX297520V3_SPI1_RESET 15
+#define ZX297520V3_TIMER_L11_RESET 16
+#define ZX297520V3_TDM_RESET 17
+#define ZX297520V3_LSPRST_END 18
+
+#endif /* __DT_BINDINGS_CLOCK_ZX297520V3_H */
--
2.53.0
^ permalink raw reply related
* [PATCH RFC 0/4] ZTE zx297520v3 clock bindings and driver
From: Stefan Dösinger @ 2026-05-10 19:00 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel
Cc: linux-clk, devicetree, linux-kernel, linux-arm-kernel,
Stefan Dösinger
Hi,
I am sending my clk driver for zx297520v3 boards for comment. The patch
series sits on top of my previously sent patches for very basic support
for this board [0]. The first caveat is that b4 dependencies don't seem
to do what I expected, so I have backed out the MAINTAINERS and DTSI
changes from this RFC submission. It should still be good enough to
review the actual clock driver and bindings.
I have pretty good understanding of the clock hardware, but a number of
questions about the prefered way of implementing such a driver. Either
the clock framework and its documentation are constantly evolving or I
am just bad at discovering things:
1) The clock controls on this board consist of 3 controllers that I
called - following ZTE's nomenclature - top, matrix and LSP. The top
controller is fed by two oscillators and generates other frequencies
with PLLs. I have expressed this in the device tree: The clk driver
binding requires these two clocks.
The matrix controller feeds 10 clocks to the LSP controller and can turn
them on and off on a per-clock bases. The DT bindings express this as
well - the LSP binding expects those 10 clock handles.
There are no explicit HW controls for the top->matrix link, but the
muxes and gates in the matrix controller use the PLL outputs as parents.
I have not expressed that in the binding as it would require 25 or so
clock handles. Instead, the matrix driver quietly expects the right
parent clock names to be registered by the top driver. (OTOH having 60
or so clock parameters didn't stop st,stm32mp21-rcc)
The entire LSP part (I guess the name stands for low speed peripherals)
looks like it might be reused on different baords. The top and matrix
part are very interdependent.
2) The clk-reset interaction: Both clocks and resets are in the same IO
space, sometimes in the same registers. I see a number of clk drivers
that register a reset control. I noticed Yu-Chun Lin's RTD1625 clock
submission added an aux device and placed the reset code in
drivers/reset instead. Is there a preference for either way or any
guideline of which way to use in which circumstances?
3) Unused clocks: I looked at recently introduced clk drivers
(mediatek,mt8196-clock.h, sun55i-a523-mcu-ccu.h) and they do add all a
lot of clocks that do not have an active consumer - which in a way means
unused ABI. Please let me know if you prefer to add clocks one by one as
their consumers are added.
That said, there are a lot of clocks that I want to define for the sole
purpose of shutting them off. The boot loader leaves pretty much every
device enabled, including proprietary timers that I don't even plan to
write a driver for. Registering their clocks in the kernel will allow
the kernel to shut them off, so they aren't entirely unused.
4) I took some naming from the old zx2967 code. In particular, each
device has two clocks: "WCLK" for the device operation and "PCLK" for
register access. Are there more standard names for them? Likewise I took
some device names from ZTE's downstream sources and I am open to better
suggestions.
5) I took care to test unbinding and rebinding my clock driver to the
hardware. It works at least as no clock consumers are defined. It seems
mutually exclusive though with declaring static initialization data as
__initdata, as it will be gone after the first time the driver is bound.
I also don't see how unbinding and rebinding will be tested later on
when core peripherals are clock consumers.
6) Clock name string matching vs passing pointers: A presentation by
Chen-Yu Tsai from 2024 [1] gave me the impression that the kernel is
trying to move away from building the clock tree with string matching. I
see APIs for passing a struct clk_hw * as parent instead of strings, but
that makes it more difficult to build static initialization tables. I
think the static tables make the code a lot easier to read and I prefer
that over boot time performance.
I think the list of clocks in my driver is fairly complete; It is
certainly a lot better than what the downstream ZTE drivers have. I
deduced a lot of it by trial and error. I am sure there are some clocks
missing that will need to be added to the binding later. Afaiu adding
clocks is not an issue, but removing or reordering them is an ABI break.
0: https://lore.kernel.org/linux-arm-kernel/20260506-send-v8-0-f1bdf3243b34@gmail.com/
1: https://www.youtube.com/watch?v=d1VIAnVb3hI
Signed-off-by: Stefan Dösinger <stefandoesinger@gmail.com>
---
Stefan Dösinger (4):
dt-bindings: clk: zte: Add zx297520v3 clock and reset bindings.
clk: zte: Introduce a driver for zx297520v3 top clocks and resets.
clk: zte: Introduce a driver for zx297520v3 matrix clocks and resets.
clk: zte: Introduce a driver for zx297520v3 LSP clocks and resets.
.../bindings/clock/zte,zx297520v3-clk.yaml | 173 ++++
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/zte/Kconfig | 18 +
drivers/clk/zte/Makefile | 2 +
drivers/clk/zte/clk-zx297520v3.c | 980 +++++++++++++++++++++
include/dt-bindings/clock/zte,zx297520v3-clk.h | 179 ++++
7 files changed, 1354 insertions(+)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260510-zx29clk-2e4d39e3128c
prerequisite-change-id: 20260416-send-5c08e095e5c9:v8
prerequisite-patch-id: 94a9b9f889829e5c1899cb1be89b7ee9899b2626
prerequisite-patch-id: 8a849ffe79ba35ef560250fb38587487cc5009fb
prerequisite-patch-id: ef282d0a261dc1097f05057cd43e9e75ae52d92b
prerequisite-patch-id: 5d615c9f855fca6da461168f45a5670a3c3cde81
prerequisite-patch-id: 980a7e66a1031cdcc244a5e461220d68c72309a5
prerequisite-patch-id: 45b6fc60cee81a793cd69e704bf098f1a68769a9
Best regards,
--
Stefan Dösinger <stefandoesinger@gmail.com>
^ permalink raw reply
* Re: [PATCH 6/9] iommu/arm-smmu-v3: Directly encode simple commands
From: Pranjal Shrivastava @ 2026-05-10 18:59 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Jonathan Hunter, Joerg Roedel, linux-arm-kernel,
linux-tegra, Robin Murphy, Thierry Reding, Krishna Reddy,
Will Deacon, David Matlack, Pasha Tatashin, patches,
Samiullah Khawaja, Mostafa Saleh
In-Reply-To: <20260508233629.GI9254@nvidia.com>
On Fri, May 08, 2026 at 08:36:29PM -0300, Jason Gunthorpe wrote:
> On Fri, May 08, 2026 at 08:09:33PM +0000, Pranjal Shrivastava wrote:
> > > > > +static void arm_smmu_cmdq_batch_add_cmd_p(struct arm_smmu_device *smmu,
> > > > > + struct arm_smmu_cmdq_batch *cmds,
> > > > > + struct arm_smmu_cmd *cmd)
> > > >
> > > > Nit: Same here, why not __arm_smmu_cmdq_batch_add_cmd? I understand
> > > > that _p just means we'll aceept ptr.. but the name's kinda wonky.
> > >
> > > Which becomes a fairly widly used public entry point, so I didn't want
> > > to have the __
> > >
> > > Though there is no external user of arm_smmu_cmdq_issue_cmd_p()
> > >
> >
> > It's just that we're calling "arm_smmu_cmdq_batch_add_cmd_p" at one
> > place and using `arm_smmu_make_cmd_<cmd_name>` at the other. It makes
> > one think what's "_p" in issue_cmd, only to realize "_p: pointer variant
> >
> > I guess I didn't like the new _p ones but I guess it's fine. Happy to
> > leave it at your discretion.
>
> I don't much care for the _p (and yes it means pointer variation) but
> do like it better than __ and failed to come up with another idea :)
>
Yea, I can't come up with much either (without __ or _p), maybe we could
leave this as is..
The only alternatives in my mind are arm_smmu_cmdq_issue_cmd_ptr() which
is similar.
Thanks,
Praan
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox