From 1fb7392ee3408494d4d62c09a8c3e5f5934caba7 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:31 +0000 Subject: [PATCH 001/189] fsl-mc: Remove MSI domain propagation to sub-devices Only the root device generates MSIs (it is the only one talking to the ITS), so propagating the domain is pretty pointless. Remove it. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-2-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-bus.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index c117745cf206..1a6c8e3c0b27 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -854,14 +854,12 @@ int fsl_mc_device_add(struct fsl_mc_obj_desc *obj_desc, } else { /* * A non-DPRC object has to be a child of a DPRC, use the - * parent's ICID and interrupt domain. + * parent's ICID. */ mc_dev->icid = parent_mc_dev->icid; mc_dev->dma_mask = FSL_MC_DEFAULT_DMA_MASK; mc_dev->dev.dma_mask = &mc_dev->dma_mask; mc_dev->dev.coherent_dma_mask = mc_dev->dma_mask; - dev_set_msi_domain(&mc_dev->dev, - dev_get_msi_domain(&parent_mc_dev->dev)); } /* From 0c9f522f2d41c7e055a602a0d2c41dc7af01010b Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:32 +0000 Subject: [PATCH 002/189] fsl-mc: Add minimal infrastructure to use platform MSI Add the tiny bit of infrastructure required to use platform MSI instead of the current hack. This means providing a write_msi_msg callback, as well as irq domain and devid retrieval helpers. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-3-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-msi.c | 50 ++++++++++++++++++++++++----- drivers/bus/fsl-mc/fsl-mc-private.h | 1 + include/linux/fsl/mc.h | 2 ++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/drivers/bus/fsl-mc/fsl-mc-msi.c b/drivers/bus/fsl-mc/fsl-mc-msi.c index 82cd69f7884c..c9f50969e88c 100644 --- a/drivers/bus/fsl-mc/fsl-mc-msi.c +++ b/drivers/bus/fsl-mc/fsl-mc-msi.c @@ -110,13 +110,8 @@ static void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev, } } -/* - * NOTE: This function is invoked with interrupts disabled - */ -static void fsl_mc_msi_write_msg(struct irq_data *irq_data, - struct msi_msg *msg) +static void fsl_mc_write_msi_msg(struct msi_desc *msi_desc, struct msi_msg *msg) { - struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); struct fsl_mc_device_irq *mc_dev_irq = @@ -130,6 +125,17 @@ static void fsl_mc_msi_write_msg(struct irq_data *irq_data, __fsl_mc_msi_write_msg(mc_bus_dev, mc_dev_irq, msi_desc); } +/* + * NOTE: This function is invoked with interrupts disabled + */ +static void fsl_mc_msi_write_msg(struct irq_data *irq_data, + struct msi_msg *msg) +{ + struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); + + fsl_mc_write_msi_msg(msi_desc, msg); +} + static void fsl_mc_msi_update_chip_ops(struct msi_domain_info *info) { struct irq_chip *chip = info->chip; @@ -209,6 +215,20 @@ struct irq_domain *fsl_mc_find_msi_domain(struct device *dev) return msi_domain; } +struct irq_domain *fsl_mc_get_msi_parent(struct device *dev) +{ + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + struct device *root_dprc_dev; + struct device *bus_dev; + + fsl_mc_get_root_dprc(dev, &root_dprc_dev); + bus_dev = root_dprc_dev->parent; + + return (bus_dev->of_node ? + of_msi_get_domain(bus_dev, bus_dev->of_node, DOMAIN_BUS_NEXUS) : + iort_get_device_domain(bus_dev, mc_dev->icid, DOMAIN_BUS_NEXUS)); +} + int fsl_mc_msi_domain_alloc_irqs(struct device *dev, unsigned int irq_count) { int error = msi_setup_device_data(dev); @@ -220,8 +240,10 @@ int fsl_mc_msi_domain_alloc_irqs(struct device *dev, unsigned int irq_count) * NOTE: Calling this function will trigger the invocation of the * its_fsl_mc_msi_prepare() callback */ - error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1); - + if (!irq_domain_is_msi_parent(dev_get_msi_domain(dev))) + error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1); + else + error = platform_device_msi_init_and_alloc_irqs(dev, irq_count, fsl_mc_write_msi_msg); if (error) dev_err(dev, "Failed to allocate IRQs\n"); return error; @@ -231,3 +253,15 @@ void fsl_mc_msi_domain_free_irqs(struct device *dev) { msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN); } + +u32 fsl_mc_get_msi_id(struct device *dev) +{ + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + struct device *root_dprc_dev; + + fsl_mc_get_root_dprc(dev, &root_dprc_dev); + + return (root_dprc_dev->parent->of_node ? + of_msi_xlate(dev, NULL, mc_dev->icid) : + iort_msi_map_id(dev, mc_dev->icid)); +} diff --git a/drivers/bus/fsl-mc/fsl-mc-private.h b/drivers/bus/fsl-mc/fsl-mc-private.h index beed4c53533d..44868f874fd6 100644 --- a/drivers/bus/fsl-mc/fsl-mc-private.h +++ b/drivers/bus/fsl-mc/fsl-mc-private.h @@ -642,6 +642,7 @@ int fsl_mc_msi_domain_alloc_irqs(struct device *dev, void fsl_mc_msi_domain_free_irqs(struct device *dev); struct irq_domain *fsl_mc_find_msi_domain(struct device *dev); +struct irq_domain *fsl_mc_get_msi_parent(struct device *dev); int __must_check fsl_create_mc_io(struct device *dev, phys_addr_t mc_portal_phys_addr, diff --git a/include/linux/fsl/mc.h b/include/linux/fsl/mc.h index 897d6211c163..bcc38c0fc230 100644 --- a/include/linux/fsl/mc.h +++ b/include/linux/fsl/mc.h @@ -361,9 +361,11 @@ int mc_send_command(struct fsl_mc_io *mc_io, struct fsl_mc_command *cmd); #ifdef CONFIG_FSL_MC_BUS #define dev_is_fsl_mc(_dev) ((_dev)->bus == &fsl_mc_bus_type) +u32 fsl_mc_get_msi_id(struct device *dev); #else /* If fsl-mc bus is not present device cannot belong to fsl-mc bus */ #define dev_is_fsl_mc(_dev) (0) +#define fsl_mc_get_msi_id(_dev) (0) #endif /* Macro to check if a device is a container device */ From cf3179b4e53f527aba9f0c6c3b921619c8adf761 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:33 +0000 Subject: [PATCH 003/189] irqchip/gic-v3-its: Add fsl_mc device plumbing to the msi-parent handling Make the ITS code aware of fsl_mc devices by plumbing the devid retrieval primitive. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-4-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/irqchip/irq-gic-its-msi-parent.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-gic-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c index a832cdb2e697..d36b278ae66c 100644 --- a/drivers/irqchip/irq-gic-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-its-msi-parent.c @@ -5,6 +5,7 @@ // Copyright (C) 2022 Intel #include +#include #include #include @@ -187,9 +188,11 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, { struct msi_domain_info *msi_info; u32 dev_id; - int ret; + int ret = 0; - if (dev->of_node) + if (dev_is_fsl_mc(dev)) + dev_id = fsl_mc_get_msi_id(dev); + else if (dev->of_node) ret = of_pmsi_get_msi_info(domain->parent, dev, &dev_id, NULL); else ret = iort_pmsi_get_msi_info(dev, &dev_id, NULL); From 4a958e47c246fa3fb8954f4303e0da15ab3d026d Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:34 +0000 Subject: [PATCH 004/189] fsl-mc: Switch over to per-device platform MSI Obtain the msi-parent irqdomain instead of the fsl_mc domain, which magically engages the per-device infrastructure. Additionally, simplify the overly complicated error handling. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-5-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/dprc-driver.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/bus/fsl-mc/dprc-driver.c b/drivers/bus/fsl-mc/dprc-driver.c index db67442addad..a85706826fa0 100644 --- a/drivers/bus/fsl-mc/dprc-driver.c +++ b/drivers/bus/fsl-mc/dprc-driver.c @@ -609,9 +609,8 @@ int dprc_setup(struct fsl_mc_device *mc_dev) { struct device *parent_dev = mc_dev->dev.parent; struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); - struct irq_domain *mc_msi_domain; + struct irq_domain *mc_msi_domain = NULL; bool mc_io_created = false; - bool msi_domain_set = false; bool uapi_created = false; u16 major_ver, minor_ver; size_t region_size; @@ -652,14 +651,12 @@ int dprc_setup(struct fsl_mc_device *mc_dev) uapi_created = true; } - mc_msi_domain = fsl_mc_find_msi_domain(&mc_dev->dev); - if (!mc_msi_domain) { + mc_msi_domain = fsl_mc_get_msi_parent(&mc_dev->dev); + if (!mc_msi_domain) dev_warn(&mc_dev->dev, "WARNING: MC bus without interrupt support\n"); - } else { + else dev_set_msi_domain(&mc_dev->dev, mc_msi_domain); - msi_domain_set = true; - } error = dprc_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id, &mc_dev->mc_handle); @@ -699,8 +696,7 @@ int dprc_setup(struct fsl_mc_device *mc_dev) (void)dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); error_cleanup_msi_domain: - if (msi_domain_set) - dev_set_msi_domain(&mc_dev->dev, NULL); + dev_set_msi_domain(&mc_dev->dev, NULL); if (mc_io_created) { fsl_destroy_mc_io(mc_dev->mc_io); From 14b1cbcc6cec0b02298f4adf717646cd943b7ef6 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:35 +0000 Subject: [PATCH 005/189] fsl-mc: Remove legacy MSI implementation Get rid of most of the fsl_mc MSI infrastructure, which is now replaced by common code. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-6-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-msi.c | 166 +------------------ drivers/bus/fsl-mc/fsl-mc-private.h | 1 - drivers/irqchip/Kconfig | 6 - drivers/irqchip/Makefile | 1 - drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c | 168 -------------------- include/linux/fsl/mc.h | 4 - include/linux/irqdomain_defs.h | 1 - 7 files changed, 8 insertions(+), 339 deletions(-) delete mode 100644 drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c diff --git a/drivers/bus/fsl-mc/fsl-mc-msi.c b/drivers/bus/fsl-mc/fsl-mc-msi.c index c9f50969e88c..be38b43803de 100644 --- a/drivers/bus/fsl-mc/fsl-mc-msi.c +++ b/drivers/bus/fsl-mc/fsl-mc-msi.c @@ -15,53 +15,16 @@ #include "fsl-mc-private.h" -#ifdef GENERIC_MSI_DOMAIN_OPS -/* - * Generate a unique ID identifying the interrupt (only used within the MSI - * irqdomain. Combine the icid with the interrupt index. - */ -static irq_hw_number_t fsl_mc_domain_calc_hwirq(struct fsl_mc_device *dev, - struct msi_desc *desc) +static void fsl_mc_write_msi_msg(struct msi_desc *msi_desc, struct msi_msg *msg) { - /* - * Make the base hwirq value for ICID*10000 so it is readable - * as a decimal value in /proc/interrupts. - */ - return (irq_hw_number_t)(desc->msi_index + (dev->icid * 10000)); -} - -static void fsl_mc_msi_set_desc(msi_alloc_info_t *arg, - struct msi_desc *desc) -{ - arg->desc = desc; - arg->hwirq = fsl_mc_domain_calc_hwirq(to_fsl_mc_device(desc->dev), - desc); -} -#else -#define fsl_mc_msi_set_desc NULL -#endif - -static void fsl_mc_msi_update_dom_ops(struct msi_domain_info *info) -{ - struct msi_domain_ops *ops = info->ops; - - if (!ops) - return; - - /* - * set_desc should not be set by the caller - */ - if (!ops->set_desc) - ops->set_desc = fsl_mc_msi_set_desc; -} - -static void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev, - struct fsl_mc_device_irq *mc_dev_irq, - struct msi_desc *msi_desc) -{ - int error; + struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); + struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); + struct fsl_mc_device_irq *mc_dev_irq = &mc_bus->irq_resources[msi_desc->msi_index]; struct fsl_mc_device *owner_mc_dev = mc_dev_irq->mc_dev; struct dprc_irq_cfg irq_cfg; + int error; + + msi_desc->msg = *msg; /* * msi_desc->msg.address is 0x0 when this function is invoked in @@ -110,111 +73,6 @@ static void __fsl_mc_msi_write_msg(struct fsl_mc_device *mc_bus_dev, } } -static void fsl_mc_write_msi_msg(struct msi_desc *msi_desc, struct msi_msg *msg) -{ - struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); - struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); - struct fsl_mc_device_irq *mc_dev_irq = - &mc_bus->irq_resources[msi_desc->msi_index]; - - msi_desc->msg = *msg; - - /* - * Program the MSI (paddr, value) pair in the device: - */ - __fsl_mc_msi_write_msg(mc_bus_dev, mc_dev_irq, msi_desc); -} - -/* - * NOTE: This function is invoked with interrupts disabled - */ -static void fsl_mc_msi_write_msg(struct irq_data *irq_data, - struct msi_msg *msg) -{ - struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); - - fsl_mc_write_msi_msg(msi_desc, msg); -} - -static void fsl_mc_msi_update_chip_ops(struct msi_domain_info *info) -{ - struct irq_chip *chip = info->chip; - - if (!chip) - return; - - /* - * irq_write_msi_msg should not be set by the caller - */ - if (!chip->irq_write_msi_msg) - chip->irq_write_msi_msg = fsl_mc_msi_write_msg; -} - -/** - * fsl_mc_msi_create_irq_domain - Create a fsl-mc MSI interrupt domain - * @fwnode: Optional firmware node of the interrupt controller - * @info: MSI domain info - * @parent: Parent irq domain - * - * Updates the domain and chip ops and creates a fsl-mc MSI - * interrupt domain. - * - * Returns: - * A domain pointer or NULL in case of failure. - */ -struct irq_domain *fsl_mc_msi_create_irq_domain(struct fwnode_handle *fwnode, - struct msi_domain_info *info, - struct irq_domain *parent) -{ - struct irq_domain *domain; - - if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE))) - info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; - if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) - fsl_mc_msi_update_dom_ops(info); - if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) - fsl_mc_msi_update_chip_ops(info); - info->flags |= MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS; - - domain = msi_create_irq_domain(fwnode, info, parent); - if (domain) - irq_domain_update_bus_token(domain, DOMAIN_BUS_FSL_MC_MSI); - - return domain; -} - -struct irq_domain *fsl_mc_find_msi_domain(struct device *dev) -{ - struct device *root_dprc_dev; - struct device *bus_dev; - struct irq_domain *msi_domain; - struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); - - fsl_mc_get_root_dprc(dev, &root_dprc_dev); - bus_dev = root_dprc_dev->parent; - - if (bus_dev->of_node) { - msi_domain = of_msi_map_get_device_domain(dev, - mc_dev->icid, - DOMAIN_BUS_FSL_MC_MSI); - - /* - * if the msi-map property is missing assume that all the - * child containers inherit the domain from the parent - */ - if (!msi_domain) - - msi_domain = of_msi_get_domain(bus_dev, - bus_dev->of_node, - DOMAIN_BUS_FSL_MC_MSI); - } else { - msi_domain = iort_get_device_domain(dev, mc_dev->icid, - DOMAIN_BUS_FSL_MC_MSI); - } - - return msi_domain; -} - struct irq_domain *fsl_mc_get_msi_parent(struct device *dev) { struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); @@ -232,18 +90,10 @@ struct irq_domain *fsl_mc_get_msi_parent(struct device *dev) int fsl_mc_msi_domain_alloc_irqs(struct device *dev, unsigned int irq_count) { int error = msi_setup_device_data(dev); - if (error) return error; - /* - * NOTE: Calling this function will trigger the invocation of the - * its_fsl_mc_msi_prepare() callback - */ - if (!irq_domain_is_msi_parent(dev_get_msi_domain(dev))) - error = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, irq_count - 1); - else - error = platform_device_msi_init_and_alloc_irqs(dev, irq_count, fsl_mc_write_msi_msg); + error = platform_device_msi_init_and_alloc_irqs(dev, irq_count, fsl_mc_write_msi_msg); if (error) dev_err(dev, "Failed to allocate IRQs\n"); return error; diff --git a/drivers/bus/fsl-mc/fsl-mc-private.h b/drivers/bus/fsl-mc/fsl-mc-private.h index 44868f874fd6..197edcc8cde4 100644 --- a/drivers/bus/fsl-mc/fsl-mc-private.h +++ b/drivers/bus/fsl-mc/fsl-mc-private.h @@ -641,7 +641,6 @@ int fsl_mc_msi_domain_alloc_irqs(struct device *dev, void fsl_mc_msi_domain_free_irqs(struct device *dev); -struct irq_domain *fsl_mc_find_msi_domain(struct device *dev); struct irq_domain *fsl_mc_get_msi_parent(struct device *dev); int __must_check fsl_create_mc_io(struct device *dev, diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index f07b00d7fef9..3e5b2040cb4e 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -51,12 +51,6 @@ config ARM_GIC_V3_ITS default ARM_GIC_V3 select IRQ_MSI_IOMMU -config ARM_GIC_V3_ITS_FSL_MC - bool - depends on ARM_GIC_V3_ITS - depends on FSL_MC_BUS - default ARM_GIC_V3_ITS - config ARM_GIC_V5 bool select IRQ_DOMAIN_HIERARCHY diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 26aa3b6ec99f..d5a28cee0d8e 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -35,7 +35,6 @@ obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o obj-$(CONFIG_ARM_GIC_ITS_PARENT) += irq-gic-its-msi-parent.o obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o -obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o obj-$(CONFIG_ARM_GIC_V5) += irq-gic-v5.o irq-gic-v5-irs.o irq-gic-v5-its.o \ irq-gic-v5-iwb.o obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o diff --git a/drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c b/drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c deleted file mode 100644 index b5785472765a..000000000000 --- a/drivers/irqchip/irq-gic-v3-its-fsl-mc-msi.c +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Freescale Management Complex (MC) bus driver MSI support - * - * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. - * Author: German Rivera - * - */ - -#include -#include -#include -#include -#include -#include -#include - -static struct irq_chip its_msi_irq_chip = { - .name = "ITS-fMSI", - .irq_mask = irq_chip_mask_parent, - .irq_unmask = irq_chip_unmask_parent, - .irq_eoi = irq_chip_eoi_parent, - .irq_set_affinity = msi_domain_set_affinity -}; - -static u32 fsl_mc_msi_domain_get_msi_id(struct irq_domain *domain, - struct fsl_mc_device *mc_dev) -{ - struct device_node *of_node; - u32 out_id; - - of_node = irq_domain_get_of_node(domain); - out_id = of_node ? of_msi_xlate(&mc_dev->dev, &of_node, mc_dev->icid) : - iort_msi_map_id(&mc_dev->dev, mc_dev->icid); - - return out_id; -} - -static int its_fsl_mc_msi_prepare(struct irq_domain *msi_domain, - struct device *dev, - int nvec, msi_alloc_info_t *info) -{ - struct fsl_mc_device *mc_bus_dev; - struct msi_domain_info *msi_info; - - if (!dev_is_fsl_mc(dev)) - return -EINVAL; - - mc_bus_dev = to_fsl_mc_device(dev); - if (!(mc_bus_dev->flags & FSL_MC_IS_DPRC)) - return -EINVAL; - - /* - * Set the device Id to be passed to the GIC-ITS: - * - * NOTE: This device id corresponds to the IOMMU stream ID - * associated with the DPRC object (ICID). - */ - info->scratchpad[0].ul = fsl_mc_msi_domain_get_msi_id(msi_domain, - mc_bus_dev); - msi_info = msi_get_domain_info(msi_domain->parent); - - /* Allocate at least 32 MSIs, and always as a power of 2 */ - nvec = max_t(int, 32, roundup_pow_of_two(nvec)); - return msi_info->ops->msi_prepare(msi_domain->parent, dev, nvec, info); -} - -static struct msi_domain_ops its_fsl_mc_msi_ops __ro_after_init = { - .msi_prepare = its_fsl_mc_msi_prepare, -}; - -static struct msi_domain_info its_fsl_mc_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), - .ops = &its_fsl_mc_msi_ops, - .chip = &its_msi_irq_chip, -}; - -static const struct of_device_id its_device_id[] = { - { .compatible = "arm,gic-v3-its", }, - {}, -}; - -static void __init its_fsl_mc_msi_init_one(struct fwnode_handle *handle, - const char *name) -{ - struct irq_domain *parent; - struct irq_domain *mc_msi_domain; - - parent = irq_find_matching_fwnode(handle, DOMAIN_BUS_NEXUS); - if (!parent || !msi_get_domain_info(parent)) { - pr_err("%s: unable to locate ITS domain\n", name); - return; - } - - mc_msi_domain = fsl_mc_msi_create_irq_domain(handle, - &its_fsl_mc_msi_domain_info, - parent); - if (!mc_msi_domain) { - pr_err("%s: unable to create fsl-mc domain\n", name); - return; - } - - pr_info("fsl-mc MSI: %s domain created\n", name); -} - -#ifdef CONFIG_ACPI -static int __init -its_fsl_mc_msi_parse_madt(union acpi_subtable_headers *header, - const unsigned long end) -{ - struct acpi_madt_generic_translator *its_entry; - struct fwnode_handle *dom_handle; - const char *node_name; - int err = 0; - - its_entry = (struct acpi_madt_generic_translator *)header; - node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx", - (long)its_entry->base_address); - - dom_handle = iort_find_domain_token(its_entry->translation_id); - if (!dom_handle) { - pr_err("%s: Unable to locate ITS domain handle\n", node_name); - err = -ENXIO; - goto out; - } - - its_fsl_mc_msi_init_one(dom_handle, node_name); - -out: - kfree(node_name); - return err; -} - - -static void __init its_fsl_mc_acpi_msi_init(void) -{ - acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, - its_fsl_mc_msi_parse_madt, 0); -} -#else -static inline void its_fsl_mc_acpi_msi_init(void) { } -#endif - -static void __init its_fsl_mc_of_msi_init(void) -{ - struct device_node *np; - - for (np = of_find_matching_node(NULL, its_device_id); np; - np = of_find_matching_node(np, its_device_id)) { - if (!of_device_is_available(np)) - continue; - if (!of_property_read_bool(np, "msi-controller")) - continue; - - its_fsl_mc_msi_init_one(of_fwnode_handle(np), - np->full_name); - } -} - -static int __init its_fsl_mc_msi_init(void) -{ - its_fsl_mc_of_msi_init(); - its_fsl_mc_acpi_msi_init(); - - return 0; -} - -early_initcall(its_fsl_mc_msi_init); diff --git a/include/linux/fsl/mc.h b/include/linux/fsl/mc.h index bcc38c0fc230..e9522233f9b5 100644 --- a/include/linux/fsl/mc.h +++ b/include/linux/fsl/mc.h @@ -425,10 +425,6 @@ int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev, void fsl_mc_object_free(struct fsl_mc_device *mc_adev); -struct irq_domain *fsl_mc_msi_create_irq_domain(struct fwnode_handle *fwnode, - struct msi_domain_info *info, - struct irq_domain *parent); - int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev); void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev); diff --git a/include/linux/irqdomain_defs.h b/include/linux/irqdomain_defs.h index 36653e2ee1c9..3a03bdfeeee9 100644 --- a/include/linux/irqdomain_defs.h +++ b/include/linux/irqdomain_defs.h @@ -17,7 +17,6 @@ enum irq_domain_bus_token { DOMAIN_BUS_PLATFORM_MSI, DOMAIN_BUS_NEXUS, DOMAIN_BUS_IPI, - DOMAIN_BUS_FSL_MC_MSI, DOMAIN_BUS_TI_SCI_INTA_MSI, DOMAIN_BUS_WAKEUP, DOMAIN_BUS_VMD_MSI, From f0a2eac6a597268034fd40d92c1469182438b53d Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:36 +0000 Subject: [PATCH 006/189] platform-msi: Remove stale comment The backward compatibility code for the previous incarnation of platform MSI was removed in e9894248994ca ("genirq/msi: Remove platform MSI leftovers"), but the comment about that removal is still present. Remove it. Reviewed-by: Ioana Ciornei Tested-by: Ioana Ciornei # LX2160ARDB, LS2088ARDB Tested-by: Sascha Bischoff Signed-off-by: Marc Zyngier Acked-by: Thomas Gleixner Link: https://lore.kernel.org/r/20260224100936.3752303-7-maz@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/base/platform-msi.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/base/platform-msi.c b/drivers/base/platform-msi.c index 70db08f3ac6f..69eed058eb20 100644 --- a/drivers/base/platform-msi.c +++ b/drivers/base/platform-msi.c @@ -61,10 +61,6 @@ static const struct msi_domain_template platform_msi_template = { * parent. The parent domain sets up the new domain. The domain has * a fixed size of @nvec. The domain is managed by devres and will * be removed when the device is removed. - * - * Note: For migration purposes this falls back to the original platform_msi code - * up to the point where all platforms have been converted to the MSI - * parent model. */ int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nvec, irq_write_msi_msg_t write_msi_msg) From cf8e9203cc653f0a82639f7ce8089fa92afe6739 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 26 Feb 2026 14:09:42 -0800 Subject: [PATCH 007/189] virt: fsl_hypervisor: fix header kernel-doc warnings Correct struct member names to placate kernel-doc warnings: Warning: include/uapi/linux/fsl_hypervisor.h:148 struct member 'local_vaddr' not described in 'fsl_hv_ioctl_memcpy' Warning: include/uapi/linux/fsl_hypervisor.h:148 struct member 'remote_paddr' not described in 'fsl_hv_ioctl_memcpy' Fixes: 6db7199407ca ("drivers/virt: introduce Freescale hypervisor management driver") Signed-off-by: Randy Dunlap Link: https://lore.kernel.org/r/20260226220942.1035295-1-rdunlap@infradead.org Signed-off-by: Christophe Leroy (CS GROUP) --- include/uapi/linux/fsl_hypervisor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/uapi/linux/fsl_hypervisor.h b/include/uapi/linux/fsl_hypervisor.h index 1e237fba951f..ab4388441e80 100644 --- a/include/uapi/linux/fsl_hypervisor.h +++ b/include/uapi/linux/fsl_hypervisor.h @@ -114,9 +114,9 @@ struct fsl_hv_ioctl_stop { * @target: the partition ID of the target partition, or -1 for this * partition * @reserved: reserved, must be set to 0 - * @local_addr: user-space virtual address of a buffer in the local + * @local_vaddr: user-space virtual address of a buffer in the local * partition - * @remote_addr: guest physical address of a buffer in the + * @remote_paddr: guest physical address of a buffer in the * remote partition * @count: the number of bytes to copy. Both the local and remote * buffers must be at least 'count' bytes long From 7dad18a179741dbad9f40799e549fa9111987c0c Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Tue, 10 Mar 2026 00:25:30 +0800 Subject: [PATCH 008/189] soc: fsl: qe_ports_ic: Add missing cleanup on device removal Add a devm action handler to properly clean up the irq_domain and chained handler when the device is removed. Fixes: f0bcd784e1b7 ("soc: fsl: qe: Add an interrupt controller for QUICC Engine Ports") Signed-off-by: Felix Gu Link: https://lore.kernel.org/r/20260310-qe_ports_ic-v1-1-608293026561@gmail.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 8e2107e2cde5..5e3fae19f314 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -17,6 +17,7 @@ struct qepic_data { void __iomem *reg; struct irq_domain *host; + int irq; }; static void qepic_mask(struct irq_data *d) @@ -92,11 +93,18 @@ static const struct irq_domain_ops qepic_host_ops = { .map = qepic_host_map, }; +static void qepic_remove(void *res) +{ + struct qepic_data *data = res; + + irq_set_chained_handler_and_data(data->irq, NULL, NULL); + irq_domain_remove(data->host); +} + static int qepic_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct qepic_data *data; - int irq; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -106,17 +114,18 @@ static int qepic_probe(struct platform_device *pdev) if (IS_ERR(data->reg)) return PTR_ERR(data->reg); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) + return data->irq; data->host = irq_domain_add_linear(dev->of_node, 32, &qepic_host_ops, data); if (!data->host) return -ENODEV; - irq_set_chained_handler_and_data(irq, qepic_cascade, data); + irq_set_chained_handler_and_data(data->irq, qepic_cascade, data); + + return devm_add_action_or_reset(dev, qepic_remove, data); - return 0; } static const struct of_device_id qepic_match[] = { From 7b9233b25131fa54ef9c64ebbe95aef6ad3de47c Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Tue, 24 Mar 2026 23:34:16 +0100 Subject: [PATCH 009/189] soc: fsl: qe_ports_ic: switch to irq_domain_create_linear() irq_domain_add_linear() is about to be removed, replace by the more generic irq_domain_create_linear(), see commit 42b8b16fe56c ("irqdomain: Drop irq_domain_add_*() functions") for details. Link: https://lore.kernel.org/r/a9de2f351ea71e4b794baaea8d9d790fbfac8d26.1774391374.git.chleroy@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 5e3fae19f314..9b0bba64e91e 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -118,7 +118,7 @@ static int qepic_probe(struct platform_device *pdev) if (data->irq < 0) return data->irq; - data->host = irq_domain_add_linear(dev->of_node, 32, &qepic_host_ops, data); + data->host = irq_domain_create_linear(dev_fwnode(dev), 32, &qepic_host_ops, data); if (!data->host) return -ENODEV; From a0fe29d20e7822182e12324905af5115c1b3aed3 Mon Sep 17 00:00:00 2001 From: Wang Jun <1742789905@qq.com> Date: Fri, 27 Mar 2026 08:12:25 +0800 Subject: [PATCH 010/189] soc: fsl: qe: panic on ioremap() failure in qe_reset() When ioremap() fails in qe_reset(), the global pointer qe_immr remains NULL, leading to a subsequent NULL pointer dereference when the pointer is accessed. Since this happens early in the boot process, a failure to map a few bytes of I/O memory indicates a fatal error from which the system cannot recover. Follow the same pattern as qe_sdma_init() and panic immediately when ioremap() fails. This avoids a silent NULL pointer dereference later and makes the error explicit. Fixes: 986585385131 ("[POWERPC] Add QUICC Engine (QE) infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Wang Jun <1742789905@qq.com> Link: https://lore.kernel.org/r/tencent_FED49CF5331CC0C7910618883332A08E2606@qq.com [chleroy: Rearranged change to reduce churn] Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/soc/fsl/qe/qe.c b/drivers/soc/fsl/qe/qe.c index 70b6eddb867b..3ecc4ce9cfa2 100644 --- a/drivers/soc/fsl/qe/qe.c +++ b/drivers/soc/fsl/qe/qe.c @@ -89,6 +89,9 @@ void qe_reset(void) if (qe_immr == NULL) qe_immr = ioremap(get_qe_base(), QE_IMMAP_SIZE); + if (!qe_immr) + panic("QE:ioremap failed!"); + qe_snums_init(); qe_issue_cmd(QE_RESET, QE_CR_SUBBLOCK_INVALID, From 208858b1b48eba83d073542372329cf8ed606526 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Wed, 1 Apr 2026 17:45:08 +0300 Subject: [PATCH 011/189] bus: fsl-mc: wait for the MC firmware to complete its boot There are use cases in which the Management Complex firmware boot process is started by the bootloader which does not wait for the boot to complete. This is mainly done in order to reduce the overall boot time of a DPAA2 based SoC. In this kind of circumstance, the fsl-mc bus driver needs to make sure that the MC firmware boot process is finished before proceeding to the usual operations such as interrogating the firmware to gather all existent DPAA2 objects, creating the fsl-mc devices on the bus etc. Add this kind of check early in the boot process of the fsl-mc bus and defer the probe in case the firmware is still in its boot process. Signed-off-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260401144508.3062019-1-ioana.ciornei@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-bus.c | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index 1a6c8e3c0b27..994834a87b2f 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -66,6 +66,13 @@ struct fsl_mc_addr_translation_range { #define GCR1_P1_STOP BIT(31) #define GCR1_P2_STOP BIT(30) +#define FSL_MC_GSR 0x8 +#define FSL_MC_GSR_BOOT_DONE BIT(0) +#define FSL_MC_GSR_MCS_MASK GENMASK(7, 0) +#define FSL_MC_GSR_MCS_ERR_MASK GENMASK(7, 1) +#define FSL_MC_GSR_BC_MASK GENMASK(15, 8) +#define FSL_MC_GSR_BC_SHIFT 8 + #define FSL_MC_FAPR 0x28 #define MC_FAPR_PL BIT(18) #define MC_FAPR_BMT BIT(17) @@ -1019,6 +1026,41 @@ static int get_mc_addr_translation_ranges(struct device *dev, return 0; } +static u32 fsl_mc_read_gsr(struct fsl_mc *mc) +{ + return readl(mc->fsl_mc_regs + FSL_MC_GSR); +} + +static int fsl_mc_firmware_check(struct platform_device *pdev) +{ + struct fsl_mc *mc = platform_get_drvdata(pdev); + u32 gsr, boot_done, boot_code, mcs; + + gsr = fsl_mc_read_gsr(mc); + boot_code = (gsr & FSL_MC_GSR_BC_MASK) >> FSL_MC_GSR_BC_SHIFT; + if (boot_code == 0xDD) { + dev_err(&pdev->dev, + "fsl-mc: DPL processing was not started, DPAA2 will not work!\n"); + return -EOPNOTSUPP; + } + + boot_done = gsr & FSL_MC_GSR_BOOT_DONE; + if (!boot_done) { + dev_dbg(&pdev->dev, + "fsl-mc: DPL processing in progress, defer probe\n"); + return -EPROBE_DEFER; + } + + mcs = gsr & FSL_MC_GSR_MCS_MASK; + if (mcs & FSL_MC_GSR_MCS_ERR_MASK) { + dev_err(&pdev->dev, + "fsl-mc: MC boot completed with error 0x%x\n", mcs); + return -EINVAL; + } + + return 0; +} + /* * fsl_mc_bus_probe - callback invoked when the root MC bus is being * added @@ -1083,6 +1125,10 @@ static int fsl_mc_bus_probe(struct platform_device *pdev) mc->fsl_mc_regs + FSL_MC_GCR1); } + error = fsl_mc_firmware_check(pdev); + if (error) + return error; + /* * Get physical address of MC portal for the root DPRC: */ From 47ec5ac9d4e9b7e99102a282a79c47c3c6ca8b5c Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Fri, 3 Apr 2026 17:13:37 +0300 Subject: [PATCH 012/189] soc: renesas: r9a08g045-sysc: Move common code to a helper Move common code from rzg3s_regmap_{readable,writeable}_reg() to a helper and use it to avoid code duplication. Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260403141341.2851926-2-claudiu.beznea.uj@bp.reneasas.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/r9a08g045-sysc.c | 41 ++++++++++++---------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/drivers/soc/renesas/r9a08g045-sysc.c b/drivers/soc/renesas/r9a08g045-sysc.c index 03d653d5cde5..63e4aa6a7cd0 100644 --- a/drivers/soc/renesas/r9a08g045-sysc.c +++ b/drivers/soc/renesas/r9a08g045-sysc.c @@ -37,18 +37,14 @@ static const struct rz_sysc_soc_id_init_data rzg3s_sysc_soc_id_init_data __initc .specific_id_mask = GENMASK(27, 0), }; -static bool rzg3s_regmap_readable_reg(struct device *dev, unsigned int reg) +static bool rzg3s_regmap_readable_writeable_reg(unsigned int reg) { switch (reg) { case SYS_XSPI_MAP_STAADD_CS0: case SYS_XSPI_MAP_ENDADD_CS0: case SYS_XSPI_MAP_STAADD_CS1: case SYS_XSPI_MAP_ENDADD_CS1: - case SYS_GETH0_CFG: - case SYS_GETH1_CFG: case SYS_PCIE_CFG: - case SYS_PCIE_MON: - case SYS_PCIE_ERR_MON: case SYS_PCIE_PHY: case SYS_I2C0_CFG: case SYS_I2C1_CFG: @@ -63,26 +59,25 @@ static bool rzg3s_regmap_readable_reg(struct device *dev, unsigned int reg) } } +static bool rzg3s_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + if (rzg3s_regmap_readable_writeable_reg(reg)) + return true; + + switch (reg) { + case SYS_GETH0_CFG: + case SYS_GETH1_CFG: + case SYS_PCIE_MON: + case SYS_PCIE_ERR_MON: + return true; + default: + return false; + } +} + static bool rzg3s_regmap_writeable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case SYS_XSPI_MAP_STAADD_CS0: - case SYS_XSPI_MAP_ENDADD_CS0: - case SYS_XSPI_MAP_STAADD_CS1: - case SYS_XSPI_MAP_ENDADD_CS1: - case SYS_PCIE_CFG: - case SYS_PCIE_PHY: - case SYS_I2C0_CFG: - case SYS_I2C1_CFG: - case SYS_I2C2_CFG: - case SYS_I2C3_CFG: - case SYS_I3C_CFG: - case SYS_USB_PWRRDY: - case SYS_PCIE_RST_RSM_B: - return true; - default: - return false; - } + return rzg3s_regmap_readable_writeable_reg(reg); } const struct rz_sysc_init_data rzg3s_sysc_init_data __initconst = { From f47cc240848124d739a6441df962cee7a0af6514 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Fri, 3 Apr 2026 17:13:38 +0300 Subject: [PATCH 013/189] soc: renesas: r9a08g046-sysc: Move common code to a helper Move common code from rzg3l_regmap_{readable,writeable}_reg() to a helper and use it to avoid code duplication. Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260403141341.2851926-3-claudiu.beznea.uj@bp.reneasas.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/r9a08g046-sysc.c | 31 ++++++++++++---------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/drivers/soc/renesas/r9a08g046-sysc.c b/drivers/soc/renesas/r9a08g046-sysc.c index fd98df196d0a..90db9d383539 100644 --- a/drivers/soc/renesas/r9a08g046-sysc.c +++ b/drivers/soc/renesas/r9a08g046-sysc.c @@ -28,17 +28,14 @@ #define SYS_PWRRDY_N 0xd70 #define SYS_IPCONT_SEL_CLONECH 0xe2c -static bool rzg3l_regmap_readable_reg(struct device *dev, unsigned int reg) +static bool rzg3l_regmap_readable_writeable_reg(unsigned int reg) { switch (reg) { case SYS_XSPI_MAP_STAADD_CS0: case SYS_XSPI_MAP_ENDADD_CS0: case SYS_XSPI_MAP_STAADD_CS1: case SYS_XSPI_MAP_ENDADD_CS1: - case SYS_GETH0_CFG: - case SYS_GETH1_CFG: case SYS_PCIE_CFG: - case SYS_PCIE_MON: case SYS_PCIE_PHY: case SYS_I2C0_CFG: case SYS_I2C1_CFG: @@ -53,28 +50,26 @@ static bool rzg3l_regmap_readable_reg(struct device *dev, unsigned int reg) } } -static bool rzg3l_regmap_writeable_reg(struct device *dev, unsigned int reg) +static bool rzg3l_regmap_readable_reg(struct device *dev, unsigned int reg) { + if (rzg3l_regmap_readable_writeable_reg(reg)) + return true; + switch (reg) { - case SYS_XSPI_MAP_STAADD_CS0: - case SYS_XSPI_MAP_ENDADD_CS0: - case SYS_XSPI_MAP_STAADD_CS1: - case SYS_XSPI_MAP_ENDADD_CS1: - case SYS_PCIE_CFG: - case SYS_PCIE_PHY: - case SYS_I2C0_CFG: - case SYS_I2C1_CFG: - case SYS_I2C2_CFG: - case SYS_I2C3_CFG: - case SYS_I3C_CFG: - case SYS_PWRRDY_N: - case SYS_IPCONT_SEL_CLONECH: + case SYS_GETH0_CFG: + case SYS_GETH1_CFG: + case SYS_PCIE_MON: return true; default: return false; } } +static bool rzg3l_regmap_writeable_reg(struct device *dev, unsigned int reg) +{ + return rzg3l_regmap_readable_writeable_reg(reg); +} + static const struct rz_sysc_soc_id_init_data rzg3l_sysc_soc_id_init_data __initconst = { .family = "RZ/G3L", .id = 0x87d9447, From 3e9f1b1f771c686fcd0a46ee6a9155e398ed7afe Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Fri, 3 Apr 2026 17:13:39 +0300 Subject: [PATCH 014/189] soc: renesas: r9a09g047-sys: Move common code to a helper Move common code from rzg3e_regmap_{readable,writeable}_reg() to a helper and use it to avoid code duplication. Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260403141341.2851926-4-claudiu.beznea.uj@bp.reneasas.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/r9a09g047-sys.c | 42 +++++++++++------------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/drivers/soc/renesas/r9a09g047-sys.c b/drivers/soc/renesas/r9a09g047-sys.c index ea3ca10fcc33..b617fb0bde7b 100644 --- a/drivers/soc/renesas/r9a09g047-sys.c +++ b/drivers/soc/renesas/r9a09g047-sys.c @@ -83,11 +83,9 @@ static const struct rz_sysc_soc_id_init_data rzg3e_sys_soc_id_init_data __initco .print_id = rzg3e_sys_print_id, }; -static bool rzg3e_regmap_readable_reg(struct device *dev, unsigned int reg) +static bool rzg3e_regmap_readable_writeable_reg(unsigned int reg) { switch (reg) { - case SYS_LSI_OTPTSU1TRMVAL0: - case SYS_LSI_OTPTSU1TRMVAL1: case SYS_SPI_STAADDCS0: case SYS_SPI_ENDADDCS0: case SYS_SPI_STAADDCS1: @@ -112,31 +110,23 @@ static bool rzg3e_regmap_readable_reg(struct device *dev, unsigned int reg) } } +static bool rzg3e_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + if (rzg3e_regmap_readable_writeable_reg(reg)) + return true; + + switch (reg) { + case SYS_LSI_OTPTSU1TRMVAL0: + case SYS_LSI_OTPTSU1TRMVAL1: + return true; + default: + return false; + } +} + static bool rzg3e_regmap_writeable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case SYS_SPI_STAADDCS0: - case SYS_SPI_ENDADDCS0: - case SYS_SPI_STAADDCS1: - case SYS_SPI_ENDADDCS1: - case SYS_VSP_CLK: - case SYS_GBETH0_CFG: - case SYS_GBETH1_CFG: - case SYS_PCIE_INTX_CH0: - case SYS_PCIE_MSI1_CH0: - case SYS_PCIE_MSI2_CH0: - case SYS_PCIE_MSI3_CH0: - case SYS_PCIE_MSI4_CH0: - case SYS_PCIE_MSI5_CH0: - case SYS_PCIE_PME_CH0: - case SYS_PCIE_ACK_CH0: - case SYS_PCIE_MISC_CH0: - case SYS_PCIE_MODE_CH0: - case SYS_ADC_CFG: - return true; - default: - return false; - } + return rzg3e_regmap_readable_writeable_reg(reg); } const struct rz_sysc_init_data rzg3e_sys_init_data __initconst = { From ac8fae64acd105f4943a635f2a9f7c6e3328c58b Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Fri, 3 Apr 2026 17:13:40 +0300 Subject: [PATCH 015/189] soc: renesas: r9a09g056-sys: Move common code to a helper Move common code from rzv2n_regmap_{readable,writeable}_reg() to a helper and use it to avoid code duplication. Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260403141341.2851926-5-claudiu.beznea.uj@bp.reneasas.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/r9a09g056-sys.c | 41 +++++++++++++---------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/drivers/soc/renesas/r9a09g056-sys.c b/drivers/soc/renesas/r9a09g056-sys.c index 2a8ebc209961..e6a66688423f 100644 --- a/drivers/soc/renesas/r9a09g056-sys.c +++ b/drivers/soc/renesas/r9a09g056-sys.c @@ -88,13 +88,9 @@ static const struct rz_sysc_soc_id_init_data rzv2n_sys_soc_id_init_data __initco .print_id = rzv2n_sys_print_id, }; -static bool rzv2n_regmap_readable_reg(struct device *dev, unsigned int reg) +static bool rzv2n_regmap_readable_writeable_reg(unsigned int reg) { switch (reg) { - case SYS_LSI_OTPTSU0TRMVAL0: - case SYS_LSI_OTPTSU0TRMVAL1: - case SYS_LSI_OTPTSU1TRMVAL0: - case SYS_LSI_OTPTSU1TRMVAL1: case SYS_GBETH0_CFG: case SYS_GBETH1_CFG: case SYS_PCIE_INTX_CH0: @@ -114,26 +110,25 @@ static bool rzv2n_regmap_readable_reg(struct device *dev, unsigned int reg) } } +static bool rzv2n_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + if (rzv2n_regmap_readable_writeable_reg(reg)) + return true; + + switch (reg) { + case SYS_LSI_OTPTSU0TRMVAL0: + case SYS_LSI_OTPTSU0TRMVAL1: + case SYS_LSI_OTPTSU1TRMVAL0: + case SYS_LSI_OTPTSU1TRMVAL1: + return true; + default: + return false; + } +} + static bool rzv2n_regmap_writeable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case SYS_GBETH0_CFG: - case SYS_GBETH1_CFG: - case SYS_PCIE_INTX_CH0: - case SYS_PCIE_MSI1_CH0: - case SYS_PCIE_MSI2_CH0: - case SYS_PCIE_MSI3_CH0: - case SYS_PCIE_MSI4_CH0: - case SYS_PCIE_MSI5_CH0: - case SYS_PCIE_PME_CH0: - case SYS_PCIE_ACK_CH0: - case SYS_PCIE_MISC_CH0: - case SYS_PCIE_MODE_CH0: - case SYS_ADC_CFG: - return true; - default: - return false; - } + return rzv2n_regmap_readable_writeable_reg(reg); } const struct rz_sysc_init_data rzv2n_sys_init_data __initconst = { From 269337466db3649abea4c294b4154a916e0e3a99 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Fri, 3 Apr 2026 17:13:41 +0300 Subject: [PATCH 016/189] soc: renesas: r9a09g057-sys: Move common code to a helper Move common code from rzv2h_regmap_{readable,writeable}_reg() to a helper and use it to avoid code duplication. Signed-off-by: Claudiu Beznea Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260403141341.2851926-6-claudiu.beznea.uj@bp.reneasas.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/r9a09g057-sys.c | 52 ++++++++++------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/drivers/soc/renesas/r9a09g057-sys.c b/drivers/soc/renesas/r9a09g057-sys.c index f3e054206acb..308492c31acb 100644 --- a/drivers/soc/renesas/r9a09g057-sys.c +++ b/drivers/soc/renesas/r9a09g057-sys.c @@ -91,13 +91,9 @@ static const struct rz_sysc_soc_id_init_data rzv2h_sys_soc_id_init_data __initco .print_id = rzv2h_sys_print_id, }; -static bool rzv2h_regmap_readable_reg(struct device *dev, unsigned int reg) +static bool rzv2h_regmap_readable_writeable_reg(unsigned int reg) { switch (reg) { - case SYS_LSI_OTPTSU0TRMVAL0: - case SYS_LSI_OTPTSU0TRMVAL1: - case SYS_LSI_OTPTSU1TRMVAL0: - case SYS_LSI_OTPTSU1TRMVAL1: case SYS_GBETH0_CFG: case SYS_GBETH1_CFG: case SYS_PCIE_INTX_CH0: @@ -128,37 +124,25 @@ static bool rzv2h_regmap_readable_reg(struct device *dev, unsigned int reg) } } +static bool rzv2h_regmap_readable_reg(struct device *dev, unsigned int reg) +{ + if (rzv2h_regmap_readable_writeable_reg(reg)) + return true; + + switch (reg) { + case SYS_LSI_OTPTSU0TRMVAL0: + case SYS_LSI_OTPTSU0TRMVAL1: + case SYS_LSI_OTPTSU1TRMVAL0: + case SYS_LSI_OTPTSU1TRMVAL1: + return true; + default: + return false; + } +} + static bool rzv2h_regmap_writeable_reg(struct device *dev, unsigned int reg) { - switch (reg) { - case SYS_GBETH0_CFG: - case SYS_GBETH1_CFG: - case SYS_PCIE_INTX_CH0: - case SYS_PCIE_MSI1_CH0: - case SYS_PCIE_MSI2_CH0: - case SYS_PCIE_MSI3_CH0: - case SYS_PCIE_MSI4_CH0: - case SYS_PCIE_MSI5_CH0: - case SYS_PCIE_PME_CH0: - case SYS_PCIE_ACK_CH0: - case SYS_PCIE_MISC_CH0: - case SYS_PCIE_MODE_CH0: - case SYS_PCIE_INTX_CH1: - case SYS_PCIE_MSI1_CH1: - case SYS_PCIE_MSI2_CH1: - case SYS_PCIE_MSI3_CH1: - case SYS_PCIE_MSI4_CH1: - case SYS_PCIE_MSI5_CH1: - case SYS_PCIE_PME_CH1: - case SYS_PCIE_ACK_CH1: - case SYS_PCIE_MISC_CH1: - case SYS_PCIE_MODE_CH1: - case SYS_PCIE_MODE: - case SYS_ADC_CFG: - return true; - default: - return false; - } + return rzv2h_regmap_readable_writeable_reg(reg); } const struct rz_sysc_init_data rzv2h_sys_init_data __initconst = { From 84a1e6179bd24dc10e47f6fa2b36f0ed345e68d8 Mon Sep 17 00:00:00 2001 From: Rijo Thomas Date: Fri, 27 Mar 2026 09:26:07 +0000 Subject: [PATCH 017/189] tee: amdtee: store buffer ID in tee_shm->sec_world_id Drop struct amdtee_shm_data and the per-context shm_list. In handle_map_shmem() save the returned buf_id in shm->sec_world_id instead of allocating a list node. Use shm->sec_world_id (with get_buffer_id() removed) in amdtee_unmap_shmem() and in call.c when building memref params. Remove shm_list and shm_mutex from amdtee_context_data. Aligns amdtee with other TEE drivers (optee, tstee, qcomtee) that use tee_shm->sec_world_id for the secure-world handle. Signed-off-by: Rijo Thomas Reviewed-by: Devaraj Rangasamy Signed-off-by: Jens Wiklander --- drivers/tee/amdtee/amdtee_private.h | 16 --------- drivers/tee/amdtee/call.c | 2 +- drivers/tee/amdtee/core.c | 52 +++-------------------------- 3 files changed, 5 insertions(+), 65 deletions(-) diff --git a/drivers/tee/amdtee/amdtee_private.h b/drivers/tee/amdtee/amdtee_private.h index d87050033894..2c5ba02258b8 100644 --- a/drivers/tee/amdtee/amdtee_private.h +++ b/drivers/tee/amdtee/amdtee_private.h @@ -65,13 +65,9 @@ struct amdtee_session { /** * struct amdtee_context_data - AMD-TEE driver context data * @sess_list: Keeps track of sessions opened in current TEE context - * @shm_list: Keeps track of buffers allocated and mapped in current TEE - * context */ struct amdtee_context_data { struct list_head sess_list; - struct list_head shm_list; - struct mutex shm_mutex; /* synchronizes access to @shm_list */ }; struct amdtee_driver_data { @@ -83,17 +79,6 @@ struct shmem_desc { u64 size; }; -/** - * struct amdtee_shm_data - Shared memory data - * @kaddr: Kernel virtual address of shared memory - * @buf_id: Buffer id of memory mapped by TEE_CMD_ID_MAP_SHARED_MEM - */ -struct amdtee_shm_data { - struct list_head shm_node; - void *kaddr; - u32 buf_id; -}; - /** * struct amdtee_ta_data - Keeps track of all TAs loaded in AMD Secure * Processor @@ -168,5 +153,4 @@ int handle_invoke_cmd(struct tee_ioctl_invoke_arg *arg, u32 sinfo, struct tee_shm_pool *amdtee_config_shm(void); -u32 get_buffer_id(struct tee_shm *shm); #endif /*AMDTEE_PRIVATE_H*/ diff --git a/drivers/tee/amdtee/call.c b/drivers/tee/amdtee/call.c index 441b2ceaafc3..23ccd0f037a7 100644 --- a/drivers/tee/amdtee/call.c +++ b/drivers/tee/amdtee/call.c @@ -45,7 +45,7 @@ static int tee_params_to_amd_params(struct tee_param *tee, u32 count, /* It is assumed that all values are within 2^32-1 */ if (type > TEE_OP_PARAM_TYPE_VALUE_INOUT) { - u32 buf_id = get_buffer_id(tee[i].u.memref.shm); + u32 buf_id = (u32)tee[i].u.memref.shm->sec_world_id; amd->params[i].mref.buf_id = buf_id; amd->params[i].mref.offset = tee[i].u.memref.shm_offs; diff --git a/drivers/tee/amdtee/core.c b/drivers/tee/amdtee/core.c index a1347d04b3ac..0e56f4cf2697 100644 --- a/drivers/tee/amdtee/core.c +++ b/drivers/tee/amdtee/core.c @@ -43,8 +43,6 @@ static int amdtee_open(struct tee_context *ctx) return -ENOMEM; INIT_LIST_HEAD(&ctxdata->sess_list); - INIT_LIST_HEAD(&ctxdata->shm_list); - mutex_init(&ctxdata->shm_mutex); ctx->data = ctxdata; return 0; @@ -87,7 +85,6 @@ static void amdtee_release(struct tee_context *ctx) list_del(&sess->list_node); release_session(sess); } - mutex_destroy(&ctxdata->shm_mutex); kfree(ctxdata); ctx->data = NULL; @@ -152,23 +149,6 @@ static struct amdtee_session *find_session(struct amdtee_context_data *ctxdata, return NULL; } -u32 get_buffer_id(struct tee_shm *shm) -{ - struct amdtee_context_data *ctxdata = shm->ctx->data; - struct amdtee_shm_data *shmdata; - u32 buf_id = 0; - - mutex_lock(&ctxdata->shm_mutex); - list_for_each_entry(shmdata, &ctxdata->shm_list, shm_node) - if (shmdata->kaddr == shm->kaddr) { - buf_id = shmdata->buf_id; - break; - } - mutex_unlock(&ctxdata->shm_mutex); - - return buf_id; -} - static DEFINE_MUTEX(drv_mutex); static int copy_ta_binary(struct tee_context *ctx, void *ptr, void **ta, size_t *ta_size) @@ -342,8 +322,6 @@ int amdtee_close_session(struct tee_context *ctx, u32 session) int amdtee_map_shmem(struct tee_shm *shm) { - struct amdtee_context_data *ctxdata; - struct amdtee_shm_data *shmnode; struct shmem_desc shmem; int rc, count; u32 buf_id; @@ -351,10 +329,6 @@ int amdtee_map_shmem(struct tee_shm *shm) if (!shm) return -EINVAL; - shmnode = kmalloc_obj(*shmnode); - if (!shmnode) - return -ENOMEM; - count = 1; shmem.kaddr = shm->kaddr; shmem.size = shm->size; @@ -366,44 +340,26 @@ int amdtee_map_shmem(struct tee_shm *shm) rc = handle_map_shmem(count, &shmem, &buf_id); if (rc) { pr_err("map_shmem failed: ret = %d\n", rc); - kfree(shmnode); return rc; } - shmnode->kaddr = shm->kaddr; - shmnode->buf_id = buf_id; - ctxdata = shm->ctx->data; - mutex_lock(&ctxdata->shm_mutex); - list_add(&shmnode->shm_node, &ctxdata->shm_list); - mutex_unlock(&ctxdata->shm_mutex); + shm->sec_world_id = buf_id; - pr_debug("buf_id :[%x] kaddr[%p]\n", shmnode->buf_id, shmnode->kaddr); + pr_debug("buf_id :[%x] kaddr[%p]\n", buf_id, shm->kaddr); return 0; } void amdtee_unmap_shmem(struct tee_shm *shm) { - struct amdtee_context_data *ctxdata; - struct amdtee_shm_data *shmnode; u32 buf_id; if (!shm) return; - buf_id = get_buffer_id(shm); - /* Unmap the shared memory from TEE */ + buf_id = (u32)shm->sec_world_id; handle_unmap_shmem(buf_id); - - ctxdata = shm->ctx->data; - mutex_lock(&ctxdata->shm_mutex); - list_for_each_entry(shmnode, &ctxdata->shm_list, shm_node) - if (buf_id == shmnode->buf_id) { - list_del(&shmnode->shm_node); - kfree(shmnode); - break; - } - mutex_unlock(&ctxdata->shm_mutex); + shm->sec_world_id = 0; } int amdtee_invoke_func(struct tee_context *ctx, From 1a6e94a8ff32e7879effd1e4a45bf112e506edc1 Mon Sep 17 00:00:00 2001 From: Hirokazu Honda Date: Thu, 16 Apr 2026 15:18:19 -0700 Subject: [PATCH 018/189] tee: optee: Allow MT_NORMAL_TAGGED shared memory On ARM64, shared memory can have MT_NORMAL_TAGGED attribute when using the Memory Tagging Extension (MTE). The OP-TEE driver needs to recognize this as normal memory to allow sharing such buffers with the Secure World. Signed-off-by: Hirokazu Honda Reviewed-by: Sumit Garg Signed-off-by: Jens Wiklander --- drivers/tee/optee/call.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tee/optee/call.c b/drivers/tee/optee/call.c index 9effe88049e9..e046aff61828 100644 --- a/drivers/tee/optee/call.c +++ b/drivers/tee/optee/call.c @@ -602,7 +602,8 @@ static bool is_normal_memory(pgprot_t p) return (((pgprot_val(p) & L_PTE_MT_MASK) == L_PTE_MT_WRITEALLOC) || ((pgprot_val(p) & L_PTE_MT_MASK) == L_PTE_MT_WRITEBACK)); #elif defined(CONFIG_ARM64) - return (pgprot_val(p) & PTE_ATTRINDX_MASK) == PTE_ATTRINDX(MT_NORMAL); + return ((pgprot_val(p) & PTE_ATTRINDX_MASK) == PTE_ATTRINDX(MT_NORMAL)) || + ((pgprot_val(p) & PTE_ATTRINDX_MASK) == PTE_ATTRINDX(MT_NORMAL_TAGGED)); #else #error "Unsupported architecture" #endif From 3faa6774ffc5973af39ac9c4f1fe93aa92ec9407 Mon Sep 17 00:00:00 2001 From: Rodrigo Zaiden Date: Sun, 22 Mar 2026 13:31:14 -0300 Subject: [PATCH 019/189] tee: optee: fix kernel-doc warnings Fix kernel-doc issues in optee_private.h and optee_msg.h: - Add missing documentation for struct members: optee_msg_param_value, optee_msg_param, optee_msg_arg, optee, and optee_ffa; - Ensure member descriptions follow the order of declaration; - Use consistent formatting (lowercase descriptions and ':' after member names); - Adjust indentation for better alignment; This resolves kernel-doc warnings such as missing member descriptions and incorrect prototype documentation. No functional changes. Signed-off-by: Rodrigo Zaiden Reviewed-by: Sumit Garg Signed-off-by: Jens Wiklander --- drivers/tee/optee/optee_msg.h | 50 +++++++------ drivers/tee/optee/optee_private.h | 120 ++++++++++++++++-------------- 2 files changed, 92 insertions(+), 78 deletions(-) diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 838e1d4a22f0..7d9b12e71c03 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -103,9 +103,9 @@ /** * struct optee_msg_param_tmem - temporary memory reference parameter - * @buf_ptr: Address of the buffer - * @size: Size of the buffer - * @shm_ref: Temporary shared memory reference, pointer to a struct tee_shm + * @buf_ptr: address of the buffer + * @size: size of the buffer + * @shm_ref: temporary shared memory reference, pointer to a struct tee_shm * * Secure and normal world communicates pointers as physical address * instead of the virtual address. This is because secure and normal world @@ -122,9 +122,9 @@ struct optee_msg_param_tmem { /** * struct optee_msg_param_rmem - registered memory reference parameter - * @offs: Offset into shared memory reference - * @size: Size of the buffer - * @shm_ref: Shared memory reference, pointer to a struct tee_shm + * @offs: offset into shared memory reference + * @size: size of the buffer + * @shm_ref: shared memory reference, pointer to a struct tee_shm */ struct optee_msg_param_rmem { u64 offs; @@ -134,12 +134,12 @@ struct optee_msg_param_rmem { /** * struct optee_msg_param_fmem - FF-A memory reference parameter - * @offs_lower: Lower bits of offset into shared memory reference - * @offs_upper: Upper bits of offset into shared memory reference - * @internal_offs: Internal offset into the first page of shared memory - * reference - * @size: Size of the buffer - * @global_id: Global identifier of the shared memory + * @offs_low: lower bits of offset into shared memory reference + * @offs_high: higher bits of offset into shared memory reference + * @internal_offs: internal offset into the first page of shared memory + * reference + * @size: size of the buffer + * @global_id: global identifier of the shared memory */ struct optee_msg_param_fmem { u32 offs_low; @@ -151,6 +151,9 @@ struct optee_msg_param_fmem { /** * struct optee_msg_param_value - opaque value parameter + * @a: first opaque value + * @b: second opaque value + * @c: third opaque value * * Value parameters are passed unchecked between normal and secure world. */ @@ -168,6 +171,7 @@ struct optee_msg_param_value { * @fmem: parameter by FF-A registered memory reference * @value: parameter by opaque value * @octets: parameter by octet string + * @u: union holding OP-TEE msg parameter * * @attr & OPTEE_MSG_ATTR_TYPE_MASK indicates if tmem, rmem or value is used in * the union. OPTEE_MSG_ATTR_TYPE_VALUE_* indicates value or octets, @@ -189,16 +193,18 @@ struct optee_msg_param { /** * struct optee_msg_arg - call argument - * @cmd: Command, one of OPTEE_MSG_CMD_* or OPTEE_MSG_RPC_CMD_* - * @func: Trusted Application function, specific to the Trusted Application, - * used if cmd == OPTEE_MSG_CMD_INVOKE_COMMAND - * @session: In parameter for all OPTEE_MSG_CMD_* except - * OPTEE_MSG_CMD_OPEN_SESSION where it's an output parameter instead - * @cancel_id: Cancellation id, a unique value to identify this request - * @ret: return value - * @ret_origin: origin of the return value - * @num_params: number of parameters supplied to the OS Command - * @params: the parameters supplied to the OS Command + * @cmd: command, one of OPTEE_MSG_CMD_* or OPTEE_MSG_RPC_CMD_* + * @func: Trusted Application function, specific to the Trusted + * Application, used if cmd == OPTEE_MSG_CMD_INVOKE_COMMAND + * @session: in parameter for all OPTEE_MSG_CMD_* except + * OPTEE_MSG_CMD_OPEN_SESSION where it's an output parameter + * instead + * @cancel_id: cancellation id, a unique value to identify this request + * @pad: padding for alignment + * @ret: return value + * @ret_origin: origin of the return value + * @num_params: number of parameters supplied to the OS Command + * @params: the parameters supplied to the OS Command * * All normal calls to Trusted OS uses this struct. If cmd requires further * information than what these fields hold it can be passed as a parameter diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h index acd3051c4879..aefe1e6f5689 100644 --- a/drivers/tee/optee/optee_private.h +++ b/drivers/tee/optee/optee_private.h @@ -47,11 +47,11 @@ typedef void (optee_invoke_fn)(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, struct arm_smccc_res *); -/* +/** * struct optee_call_waiter - TEE entry may need to wait for a free TEE thread - * @list_node Reference in waiters list - * @c Waiting completion reference - * @sys_thread True if waiter belongs to a system thread + * @list_node: reference in waiters list + * @c: waiting completion reference + * @sys_thread: true if waiter belongs to a system thread */ struct optee_call_waiter { struct list_head list_node; @@ -59,13 +59,13 @@ struct optee_call_waiter { bool sys_thread; }; -/* +/** * struct optee_call_queue - OP-TEE call queue management - * @mutex Serializes access to this struct - * @waiters List of threads waiting to enter OP-TEE - * @total_thread_count Overall number of thread context in OP-TEE or 0 - * @free_thread_count Number of threads context free in OP-TEE - * @sys_thread_req_count Number of registered system thread sessions + * @mutex: serializes access to this struct + * @waiters: list of threads waiting to enter OP-TEE + * @total_thread_count: overall number of thread context in OP-TEE or 0 + * @free_thread_count: number of threads context free in OP-TEE + * @sys_thread_req_count: number of registered system thread sessions */ struct optee_call_queue { /* Serializes access to this struct */ @@ -96,17 +96,17 @@ struct optee_shm_arg_cache { /** * struct optee_supp - supplicant synchronization struct - * @ctx the context of current connected supplicant. - * if !NULL the supplicant device is available for use, - * else busy - * @mutex: held while accessing content of this struct - * @req_id: current request id if supplicant is doing synchronous - * communication, else -1 - * @reqs: queued request not yet retrieved by supplicant - * @idr: IDR holding all requests currently being processed - * by supplicant - * @reqs_c: completion used by supplicant when waiting for a - * request to be queued. + * @mutex: held while accessing content of this struct + * @ctx: the context of current connected supplicant. + * if !NULL the supplicant device is available for use, + * else busy + * @req_id: current request id if supplicant is doing synchronous + * communication, else -1 + * @reqs: queued request not yet retrieved by supplicant + * @idr: IDR holding all requests currently being processed + * by supplicant + * @reqs_c: completion used by supplicant when waiting for a + * request to be queued. */ struct optee_supp { /* Serializes access to this struct */ @@ -119,25 +119,25 @@ struct optee_supp { struct completion reqs_c; }; -/* +/** * struct optee_pcpu - per cpu notif private struct passed to work functions - * @optee optee device reference + * @optee: optee device reference */ struct optee_pcpu { struct optee *optee; }; -/* +/** * struct optee_smc - optee smc communication struct - * @invoke_fn handler function to invoke secure monitor - * @memremaped_shm virtual address of memory in shared memory pool + * @invoke_fn: handler function to invoke secure monitor + * @memremaped_shm: virtual address of memory in shared memory pool * @sec_caps: secure world capabilities defined by * OPTEE_SMC_SEC_CAP_* in optee_smc.h - * @notif_irq interrupt used as async notification by OP-TEE or 0 - * @optee_pcpu per_cpu optee instance for per cpu work or NULL - * @notif_pcpu_wq workqueue for per cpu asynchronous notification or NULL - * @notif_pcpu_work work for per cpu asynchronous notification - * @notif_cpuhp_state CPU hotplug state assigned for pcpu interrupt management + * @notif_irq: interrupt used as async notification by OP-TEE or 0 + * @optee_pcpu: per_cpu optee instance for per cpu work or NULL + * @notif_pcpu_wq: workqueue for per cpu asynchronous notification or NULL + * @notif_pcpu_work: work for per cpu asynchronous notification + * @notif_cpuhp_state: CPU hotplug state assigned for pcpu interrupt management */ struct optee_smc { optee_invoke_fn *invoke_fn; @@ -151,13 +151,15 @@ struct optee_smc { }; /** - * struct optee_ffa_data - FFA communication struct - * @ffa_dev FFA device, contains the destination id, the id of + * struct optee_ffa - FFA communication struct + * @ffa_dev: FFA device, contains the destination id, the id of * OP-TEE in secure world - * @bottom_half_value Notification ID used for bottom half signalling or + * @bottom_half_value: notification ID used for bottom half signalling or * U32_MAX if unused - * @mutex Serializes access to @global_ids - * @global_ids FF-A shared memory global handle translation + * @mutex: serializes access to @global_ids + * @global_ids: FF-A shared memory global handle translation + * @notif_wq: workqueue for FF-A asynchronous notification + * @notif_work: work for FF-A asynchronous notification */ struct optee_ffa { struct ffa_device *ffa_dev; @@ -222,26 +224,32 @@ struct optee_ops { /** * struct optee - main service struct - * @supp_teedev: supplicant device - * @teedev: client device - * @ops: internal callbacks for different ways to reach secure - * world - * @ctx: driver internal TEE context - * @smc: specific to SMC ABI - * @ffa: specific to FF-A ABI - * @call_queue: queue of threads waiting to call @invoke_fn - * @notif: notification synchronization struct - * @supp: supplicant synchronization struct for RPC to supplicant - * @pool: shared memory pool - * @mutex: mutex protecting @rpmb_dev - * @rpmb_dev: current RPMB device or NULL - * @rpmb_scan_bus_done flag if device registation of RPMB dependent devices - * was already done - * @rpmb_scan_bus_work workq to for an RPMB device and to scan optee bus - * and register RPMB dependent optee drivers - * @rpc_param_count: If > 0 number of RPC parameters to make room for - * @scan_bus_done flag if device registation was already done. - * @scan_bus_work workq to scan optee bus and register optee drivers + * @supp_teedev: supplicant device + * @teedev: client device + * @ops: internal callbacks for different ways to reach + * secure world + * @ctx: driver internal TEE context + * @smc: specific to SMC ABI + * @ffa: specific to FF-A ABI + * @shm_arg_cache: shared memory cache argument + * @call_queue: queue of threads waiting to call @invoke_fn + * @notif: notification synchronization struct + * @supp: supplicant synchronization struct for RPC to + * supplicant + * @pool: shared memory pool + * @rpmb_dev_mutex: mutex protecting @rpmb_dev + * @rpmb_dev: current RPMB device or NULL + * @rpmb_intf: RPMB notifier block + * @rpc_param_count: if > 0 number of RPC parameters to make room for + * @scan_bus_done: flag if device registation was already done + * @rpmb_scan_bus_done: flag if device registation of RPMB dependent + * devices was already done + * @in_kernel_rpmb_routing: flag if OP-TEE supports in-kernel RPMB routing + * @scan_bus_work: workq to scan optee bus and register optee + * drivers + * @rpmb_scan_bus_work: workq to for an RPMB device and to scan optee + * bus and register RPMB dependent optee drivers + * @revision: OP-TEE OS revision */ struct optee { struct tee_device *supp_teedev; From 5b7f39687b173f042cb9530bcee5f5805020bffd Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 25 Mar 2026 00:43:49 +0800 Subject: [PATCH 020/189] dt-bindings: sram: Document Allwinner H616 VE SRAM The Allwinner H616 has two switchable peripheral SRAM regions: - The VE SRAM is a 2 MB dedicated SRAM for the Video Engine. CPU access to this region is enabled by default. CPU access can be disabled, after which reads will show the same stale value for all addresses, while writes are ignored. The mux value for this region is different from previous generations, and thus needs a completely new compatible. - The SRAM C region is an alias of the first 128 KB of VE SRAM, plus 64 KB of DE SRAM. The latter is otherwise unaccessible from the CPU. When CPU access is disabled, the whole region reads as zero, while writes are ignored. The mux value for this region is the same as on the A64 and H6. The existing compatible for the A64 already covers this. Add the compatible for the VE SRAM to the list of covered compatibles in the generic SRAM region binding. Acked-by: Rob Herring (Arm) Acked-by: Jernej Skrabec Link: https://patch.msgid.link/20260324164357.1607247-2-wens@kernel.org Signed-off-by: Chen-Yu Tsai --- Documentation/devicetree/bindings/sram/sram.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/sram/sram.yaml b/Documentation/devicetree/bindings/sram/sram.yaml index 8985f89170be..9019c43d37a6 100644 --- a/Documentation/devicetree/bindings/sram/sram.yaml +++ b/Documentation/devicetree/bindings/sram/sram.yaml @@ -83,6 +83,7 @@ patternProperties: - allwinner,sun4i-a10-sram-d - allwinner,sun9i-a80-smp-sram - allwinner,sun50i-a64-sram-c + - allwinner,sun50i-h616-ve-sram - amlogic,meson8-ao-arc-sram - amlogic,meson8b-ao-arc-sram - amlogic,meson8-smp-sram From 775c75e4ae2b0277b5e55644f9890afef4dedee9 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 25 Mar 2026 00:43:50 +0800 Subject: [PATCH 021/189] dt-bindings: sram: sunxi-sram: Add H616 SRAM regions The Allwinner H616 has two switchable peripheral SRAM regions: - The VE SRAM is a 2 MB dedicated SRAM for the Video Engine. CPU access to this region is enabled by default. CPU access can be disabled, after which reads will show the same stale value for all addresses, while writes are ignored. The mux value for this region is different from previous generations. - The SRAM C region is an alias of the first 128 KB of VE SRAM, plus 64 KB of DE SRAM. The latter is otherwise unaccessible from the CPU. When CPU access is disabled, the whole region reads as zero, while writes are ignored. The mux value for this region is the same as on the A64 and H6. Add compatible strings for both of them. Acked-by: Rob Herring (Arm) Acked-by: Jernej Skrabec Link: https://patch.msgid.link/20260324164357.1607247-3-wens@kernel.org Signed-off-by: Chen-Yu Tsai --- .../bindings/sram/allwinner,sun4i-a10-system-control.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml b/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml index e7f7cf72719e..6e6ab2168a2a 100644 --- a/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml +++ b/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml @@ -80,6 +80,7 @@ patternProperties: - const: allwinner,sun4i-a10-sram-c1 - const: allwinner,sun4i-a10-sram-d - const: allwinner,sun50i-a64-sram-c + - const: allwinner,sun50i-h616-ve-sram - items: - enum: - allwinner,sun5i-a13-sram-a3-a4 @@ -103,7 +104,9 @@ patternProperties: - allwinner,sun7i-a20-sram-d - const: allwinner,sun4i-a10-sram-d - items: - - const: allwinner,sun50i-h6-sram-c + - enum: + - allwinner,sun50i-h6-sram-c + - allwinner,sun50i-h616-sram-c - const: allwinner,sun50i-a64-sram-c required: From 7765752f528b5b516d8cdf94faaabcba935dff41 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 25 Mar 2026 00:43:51 +0800 Subject: [PATCH 022/189] soc: sunxi: sram: Const-ify sunxi_sram_func data and references sunxi_sram_func contains value mapping that do not change at runtime. Const-ify them. Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/20260324164357.1607247-4-wens@kernel.org Signed-off-by: Chen-Yu Tsai --- drivers/soc/sunxi/sunxi_sram.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/soc/sunxi/sunxi_sram.c b/drivers/soc/sunxi/sunxi_sram.c index 446b9fc1f175..5e8c80ae3509 100644 --- a/drivers/soc/sunxi/sunxi_sram.c +++ b/drivers/soc/sunxi/sunxi_sram.c @@ -29,11 +29,11 @@ struct sunxi_sram_func { }; struct sunxi_sram_data { - char *name; - u8 reg; - u8 offset; - u8 width; - struct sunxi_sram_func *func; + char *name; + u8 reg; + u8 offset; + u8 width; + const struct sunxi_sram_func *func; }; struct sunxi_sram_desc { @@ -54,7 +54,7 @@ struct sunxi_sram_desc { .reg = _reg, \ .offset = _off, \ .width = _width, \ - .func = (struct sunxi_sram_func[]){ \ + .func = (const struct sunxi_sram_func[]){ \ __VA_ARGS__, { } }, \ } @@ -111,7 +111,7 @@ static int sunxi_sram_show(struct seq_file *s, void *data) struct device_node *sram_node, *section_node; const struct sunxi_sram_data *sram_data; const struct of_device_id *match; - struct sunxi_sram_func *func; + const struct sunxi_sram_func *func; const __be32 *sram_addr_p, *section_addr_p; u32 val; @@ -169,7 +169,7 @@ static const struct sunxi_sram_data *sunxi_sram_of_parse(struct device_node *nod { const struct of_device_id *match; const struct sunxi_sram_data *data; - struct sunxi_sram_func *func; + const struct sunxi_sram_func *func; struct of_phandle_args args; u8 val; int ret; From 67890da74dd1b85a47769561bfc6e0c542762d45 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 25 Mar 2026 00:43:52 +0800 Subject: [PATCH 023/189] soc: sunxi: sram: Allow SRAM to be claimed multiple times On the H616, the SRAM C region is an alias mapping to part of the VE SRAM (accessible in whole at a different address) and part of the DE SRAM (otherwise unaccessible). As such both the VE and DE need to claim this SRAM region to prevent access from the CPU. The SRAM claim API is designed so that a "claim" routes the SRAM to the peripheral device, disabling access from the CPU. So long as the written register value is the same for all the claimants involved, allowing multiple or repeated claims is trivial. This is indeed the case for all supported SRAM regions. The only known SRAM region to have multiple different settings is the SRAM C2 region; this can be claimed by the AE, CE, or ACE (assumed to be AE + CE). This region is not supported, and likely will never be needed nor supported, as there is no documentation for the peripherals involved. Change the SRAM region "claimed" field from a boolean to a reference count. A claim will increment the count, while a release decreases it. The first claim will trigger the register value write. The driver otherwise behaves as before. Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/20260324164357.1607247-5-wens@kernel.org Signed-off-by: Chen-Yu Tsai --- drivers/soc/sunxi/sunxi_sram.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/soc/sunxi/sunxi_sram.c b/drivers/soc/sunxi/sunxi_sram.c index 5e8c80ae3509..aba155379ccc 100644 --- a/drivers/soc/sunxi/sunxi_sram.c +++ b/drivers/soc/sunxi/sunxi_sram.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -38,7 +39,7 @@ struct sunxi_sram_data { struct sunxi_sram_desc { struct sunxi_sram_data data; - bool claimed; + u8 claim_cnt; }; #define SUNXI_SRAM_MAP(_reg_val, _val, _func) \ @@ -244,9 +245,11 @@ int sunxi_sram_claim(struct device *dev) spin_lock(&sram_lock); - if (sram_desc->claimed) { + if (sram_desc->claim_cnt) { + if (!WARN_ON(sram_desc->claim_cnt == U8_MAX)) + sram_desc->claim_cnt++; spin_unlock(&sram_lock); - return -EBUSY; + return 0; } mask = GENMASK(sram_data->offset + sram_data->width - 1, @@ -256,7 +259,7 @@ int sunxi_sram_claim(struct device *dev) writel(val | ((device << sram_data->offset) & mask), base + sram_data->reg); - sram_desc->claimed = true; + sram_desc->claim_cnt++; spin_unlock(&sram_lock); return 0; @@ -278,7 +281,8 @@ void sunxi_sram_release(struct device *dev) sram_desc = to_sram_desc(sram_data); spin_lock(&sram_lock); - sram_desc->claimed = false; + if (!WARN_ON(sram_desc->claim_cnt == 0)) + sram_desc->claim_cnt--; spin_unlock(&sram_lock); } EXPORT_SYMBOL(sunxi_sram_release); From be99eb936b4ffffbf87d34c4a202e15c05b30417 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 25 Mar 2026 00:43:53 +0800 Subject: [PATCH 024/189] soc: sunxi: sram: Support claiming multiple regions per device On the H616, the video engine needs to claim two SRAM regions. Support claiming multiple regions per device. Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/20260324164357.1607247-6-wens@kernel.org Signed-off-by: Chen-Yu Tsai --- drivers/soc/sunxi/sunxi_sram.c | 164 +++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 68 deletions(-) diff --git a/drivers/soc/sunxi/sunxi_sram.c b/drivers/soc/sunxi/sunxi_sram.c index aba155379ccc..4d81897179e7 100644 --- a/drivers/soc/sunxi/sunxi_sram.c +++ b/drivers/soc/sunxi/sunxi_sram.c @@ -165,79 +165,48 @@ static inline struct sunxi_sram_desc *to_sram_desc(const struct sunxi_sram_data return container_of(data, struct sunxi_sram_desc, data); } -static const struct sunxi_sram_data *sunxi_sram_of_parse(struct device_node *node, - unsigned int *reg_value) +static const struct sunxi_sram_data *sunxi_sram_get_match(struct device_node *np, u8 val, + unsigned int *reg_value) { const struct of_device_id *match; const struct sunxi_sram_data *data; const struct sunxi_sram_func *func; - struct of_phandle_args args; - u8 val; - int ret; - ret = of_parse_phandle_with_fixed_args(node, "allwinner,sram", 1, 0, - &args); - if (ret) - return ERR_PTR(ret); + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); - if (!of_device_is_available(args.np)) { - ret = -EBUSY; - goto err; - } - - val = args.args[0]; - - match = of_match_node(sunxi_sram_dt_ids, args.np); - if (!match) { - ret = -EINVAL; - goto err; - } + match = of_match_node(sunxi_sram_dt_ids, np); + if (!match) + return ERR_PTR(-ENODEV); data = match->data; - if (!data) { - ret = -EINVAL; - goto err; - } - - for (func = data->func; func->func; func++) { - if (val == func->val) { - if (reg_value) - *reg_value = func->reg_val; + if (!data) + return ERR_PTR(-EINVAL); + for (func = data->func; func->func; func++) + if (val == func->val) break; - } - } - if (!func->func) { - ret = -EINVAL; - goto err; - } + if (!func->func) + return ERR_PTR(-EINVAL); - of_node_put(args.np); - return match->data; + if (reg_value) + *reg_value = func->reg_val; -err: - of_node_put(args.np); - return ERR_PTR(ret); + return data; } -int sunxi_sram_claim(struct device *dev) +#define SUNXI_SRAM_PROP "allwinner,sram" +#define SUNXI_SRAM_CELLS 1 + +static int sunxi_sram_claim_one(struct device_node *np, u8 arg) { const struct sunxi_sram_data *sram_data; struct sunxi_sram_desc *sram_desc; unsigned int device; u32 val, mask; - if (IS_ERR(base)) - return PTR_ERR(base); - - if (!base) - return -EPROBE_DEFER; - - if (!dev || !dev->of_node) - return -EINVAL; - - sram_data = sunxi_sram_of_parse(dev->of_node, &device); + sram_data = sunxi_sram_get_match(np, arg, &device); if (IS_ERR(sram_data)) return PTR_ERR(sram_data); @@ -248,33 +217,28 @@ int sunxi_sram_claim(struct device *dev) if (sram_desc->claim_cnt) { if (!WARN_ON(sram_desc->claim_cnt == U8_MAX)) sram_desc->claim_cnt++; - spin_unlock(&sram_lock); - return 0; + } else { + mask = GENMASK(sram_data->offset + sram_data->width - 1, + sram_data->offset); + val = readl(base + sram_data->reg); + val &= ~mask; + writel(val | ((device << sram_data->offset) & mask), + base + sram_data->reg); + + sram_desc->claim_cnt++; } - mask = GENMASK(sram_data->offset + sram_data->width - 1, - sram_data->offset); - val = readl(base + sram_data->reg); - val &= ~mask; - writel(val | ((device << sram_data->offset) & mask), - base + sram_data->reg); - - sram_desc->claim_cnt++; spin_unlock(&sram_lock); return 0; } -EXPORT_SYMBOL(sunxi_sram_claim); -void sunxi_sram_release(struct device *dev) +static void sunxi_sram_release_one(struct device_node *np, u8 arg) { const struct sunxi_sram_data *sram_data; struct sunxi_sram_desc *sram_desc; - if (!dev || !dev->of_node) - return; - - sram_data = sunxi_sram_of_parse(dev->of_node, NULL); + sram_data = sunxi_sram_get_match(np, arg, NULL); if (IS_ERR(sram_data)) return; @@ -285,6 +249,70 @@ void sunxi_sram_release(struct device *dev) sram_desc->claim_cnt--; spin_unlock(&sram_lock); } + +int sunxi_sram_claim(struct device *dev) +{ + struct of_phandle_iterator it; + int err; + int count = 0; + + if (IS_ERR(base)) + return PTR_ERR(base); + + if (!base) + return -EPROBE_DEFER; + + if (!dev || !dev->of_node) + return -EINVAL; + + of_for_each_phandle(&it, err, dev->of_node, SUNXI_SRAM_PROP, + NULL, SUNXI_SRAM_CELLS) { + u32 args[SUNXI_SRAM_CELLS]; + + of_phandle_iterator_args(&it, args, SUNXI_SRAM_CELLS); + + err = sunxi_sram_claim_one(it.node, args[0]); + if (err) + goto err; + + count++; + } + + if (count == 0) + return -ENOENT; + + return 0; + +err: + while (count--) { + struct of_phandle_args args; + + of_parse_phandle_with_fixed_args(dev->of_node, SUNXI_SRAM_PROP, + SUNXI_SRAM_CELLS, count, &args); + sunxi_sram_release_one(args.np, args.args[0]); + } + + return err; +} +EXPORT_SYMBOL(sunxi_sram_claim); + +void sunxi_sram_release(struct device *dev) +{ + struct of_phandle_iterator it; + int err; + + if (!dev || !dev->of_node) + return; + + of_for_each_phandle(&it, err, dev->of_node, SUNXI_SRAM_PROP, + NULL, SUNXI_SRAM_CELLS) { + u32 args[SUNXI_SRAM_CELLS]; + + of_phandle_iterator_args(&it, args, SUNXI_SRAM_CELLS); + + sunxi_sram_release_one(it.node, args[0]); + } +} EXPORT_SYMBOL(sunxi_sram_release); struct sunxi_sramc_variant { From b708294322745ce30035d960a1118b4b8d857120 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Wed, 25 Mar 2026 00:43:54 +0800 Subject: [PATCH 025/189] soc: sunxi: sram: Add H616 SRAM regions The Allwinner H616 has two switchable peripheral SRAM regions: - The VE SRAM is a 2 MB dedicated SRAM for the Video Engine. CPU access to this region is enabled by default. CPU access can be disabled, after which reads will show the same stale value for all addresses, while writes are ignored. The mux value for this region is different from previous generations. - The SRAM C region is an alias of the first 128 KB of VE SRAM, plus 64 KB of DE SRAM. The latter is otherwise unaccessible from the CPU. When CPU access is disabled, the whole region reads as zero, while writes are ignored. The mux value for this region is the same as on the A64 and H6. Add data for the VE SRAM. The register values were taken from the BSP vendor kernel. Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/20260324164357.1607247-7-wens@kernel.org Signed-off-by: Chen-Yu Tsai --- drivers/soc/sunxi/sunxi_sram.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/soc/sunxi/sunxi_sram.c b/drivers/soc/sunxi/sunxi_sram.c index 4d81897179e7..2ccaeacf8c70 100644 --- a/drivers/soc/sunxi/sunxi_sram.c +++ b/drivers/soc/sunxi/sunxi_sram.c @@ -83,6 +83,12 @@ static struct sunxi_sram_desc sun50i_a64_sram_c = { SUNXI_SRAM_MAP(0, 1, "de2")), }; +static struct sunxi_sram_desc sun50i_h616_ve_sram = { + .data = SUNXI_SRAM_DATA("VE", 0x0, 0, 1, + SUNXI_SRAM_MAP(1, 0, "cpu"), + SUNXI_SRAM_MAP(0, 1, "ve")), +}; + static const struct of_device_id sunxi_sram_dt_ids[] = { { .compatible = "allwinner,sun4i-a10-sram-a3-a4", @@ -100,6 +106,10 @@ static const struct of_device_id sunxi_sram_dt_ids[] = { .compatible = "allwinner,sun50i-a64-sram-c", .data = &sun50i_a64_sram_c.data, }, + { + .compatible = "allwinner,sun50i-h616-ve-sram", + .data = &sun50i_h616_ve_sram.data, + }, {} }; From 36df0ef3b4187c69c271af7a1d56f7abb788655e Mon Sep 17 00:00:00 2001 From: Sheetal Date: Wed, 18 Mar 2026 07:11:29 +0000 Subject: [PATCH 026/189] bus: tegra-aconnect: Use dev_err_probe for probe error paths Convert probe error handling to use dev_err_probe() which provides proper handling of -EPROBE_DEFER. Signed-off-by: Sheetal Reviewed-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/bus/tegra-aconnect.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/bus/tegra-aconnect.c b/drivers/bus/tegra-aconnect.c index 90e3b0a10816..9b2b227332b2 100644 --- a/drivers/bus/tegra-aconnect.c +++ b/drivers/bus/tegra-aconnect.c @@ -32,16 +32,14 @@ static int tegra_aconnect_probe(struct platform_device *pdev) return -ENOMEM; aconnect->ape_clk = devm_clk_get(&pdev->dev, "ape"); - if (IS_ERR(aconnect->ape_clk)) { - dev_err(&pdev->dev, "Can't retrieve ape clock\n"); - return PTR_ERR(aconnect->ape_clk); - } + if (IS_ERR(aconnect->ape_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(aconnect->ape_clk), + "can't retrieve ape clock\n"); aconnect->apb2ape_clk = devm_clk_get(&pdev->dev, "apb2ape"); - if (IS_ERR(aconnect->apb2ape_clk)) { - dev_err(&pdev->dev, "Can't retrieve apb2ape clock\n"); - return PTR_ERR(aconnect->apb2ape_clk); - } + if (IS_ERR(aconnect->apb2ape_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(aconnect->apb2ape_clk), + "can't retrieve apb2ape clock\n"); dev_set_drvdata(&pdev->dev, aconnect); pm_runtime_enable(&pdev->dev); From fb445935338405110baca8f541a2df3b4cb8d712 Mon Sep 17 00:00:00 2001 From: Prasanna Kumar T S M Date: Thu, 19 Mar 2026 23:03:06 -0700 Subject: [PATCH 027/189] soc: xilinx: Fix race condition in event registration The zynqmp_power driver registers handlers for suspend and subsystem restart events using register_event(). However, the work structures (zynqmp_pm_init_suspend_work and zynqmp_pm_init_restart_work) used by these handlers were allocated and initialized after the registration call. This created a race window where, if the firmware triggered an event immediately after registration but before allocation, the callback (suspend_event_callback or subsystem_restart_event_callback) would dereference a NULL pointer in work_pending(), leading to a crash. Fix this by allocating and initializing the work structures before registering the events. Fixes: fcf544ac6439 ("soc: xilinx: Add cb event for subsystem restart") Signed-off-by: Prasanna Kumar T S M Signed-off-by: Michal Simek Link: https://lore.kernel.org/r/20260320060306.1540928-1-ptsm@linux.microsoft.com --- drivers/soc/xilinx/zynqmp_power.c | 43 ++++++++++++------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/drivers/soc/xilinx/zynqmp_power.c b/drivers/soc/xilinx/zynqmp_power.c index 9085db1b480a..9dd938bd01d8 100644 --- a/drivers/soc/xilinx/zynqmp_power.c +++ b/drivers/soc/xilinx/zynqmp_power.c @@ -303,18 +303,18 @@ static int zynqmp_pm_probe(struct platform_device *pdev) * is not available to use) or -ENODEV(Xilinx Event Manager not compiled), * then use ipi-mailbox or interrupt method. */ + zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev, + sizeof(struct zynqmp_pm_work_struct), + GFP_KERNEL); + if (!zynqmp_pm_init_suspend_work) + return -ENOMEM; + + INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work, + zynqmp_pm_init_suspend_work_fn); + ret = register_event(&pdev->dev, PM_INIT_SUSPEND_CB, 0, 0, false, suspend_event_callback); if (!ret) { - zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev, - sizeof(struct zynqmp_pm_work_struct), - GFP_KERNEL); - if (!zynqmp_pm_init_suspend_work) - return -ENOMEM; - - INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work, - zynqmp_pm_init_suspend_work_fn); - ret = zynqmp_pm_get_family_info(&pm_family_code); if (ret < 0) return ret; @@ -326,14 +326,6 @@ static int zynqmp_pm_probe(struct platform_device *pdev) else return -ENODEV; - ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART, - false, subsystem_restart_event_callback); - if (ret) { - dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", - ret); - return ret; - } - zynqmp_pm_init_restart_work = devm_kzalloc(&pdev->dev, sizeof(struct zynqmp_pm_work_struct), GFP_KERNEL); @@ -342,19 +334,18 @@ static int zynqmp_pm_probe(struct platform_device *pdev) INIT_WORK(&zynqmp_pm_init_restart_work->callback_work, zynqmp_pm_subsystem_restart_work_fn); + + ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART, + false, subsystem_restart_event_callback); + if (ret) { + dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", + ret); + return ret; + } } else if (ret != -EACCES && ret != -ENODEV) { dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret); return ret; } else if (of_property_present(pdev->dev.of_node, "mboxes")) { - zynqmp_pm_init_suspend_work = - devm_kzalloc(&pdev->dev, - sizeof(struct zynqmp_pm_work_struct), - GFP_KERNEL); - if (!zynqmp_pm_init_suspend_work) - return -ENOMEM; - - INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work, - zynqmp_pm_init_suspend_work_fn); client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL); if (!client) return -ENOMEM; From fdee7c66c0d7b6869c36b9f9a915abf29ab5b550 Mon Sep 17 00:00:00 2001 From: Prasanna Kumar T S M Date: Thu, 19 Mar 2026 23:04:45 -0700 Subject: [PATCH 028/189] soc: xilinx: Shutdown and free rx mailbox channel A mbox rx channel is requested using mbox_request_channel_byname() in probe. In remove callback, the rx mailbox channel is cleaned up when the rx_chan is NULL due to incorrect condition check. The mailbox channel is not shutdown and it can receive messages even after the device removal. This leads to use after free. Also the channel resources are not freed. Fix this by checking the rx_chan correctly. Fixes: ffdbae28d9d1a ("drivers: soc: xilinx: Use mailbox IPI callback") Signed-off-by: Prasanna Kumar T S M Signed-off-by: Michal Simek Link: https://lore.kernel.org/r/20260320060445.1541017-1-ptsm@linux.microsoft.com --- drivers/soc/xilinx/zynqmp_power.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/soc/xilinx/zynqmp_power.c b/drivers/soc/xilinx/zynqmp_power.c index 9dd938bd01d8..370e61ac47d8 100644 --- a/drivers/soc/xilinx/zynqmp_power.c +++ b/drivers/soc/xilinx/zynqmp_power.c @@ -389,8 +389,10 @@ static void zynqmp_pm_remove(struct platform_device *pdev) { sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr); - if (!rx_chan) + if (rx_chan) { mbox_free_channel(rx_chan); + rx_chan = NULL; + } } static const struct of_device_id pm_of_match[] = { From 005fa59aeb86aa70815104d3d0c65ac0ea80513b Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 2 Apr 2026 13:27:06 +0200 Subject: [PATCH 029/189] soc: renesas: Add Renesas R-Car MFIS driver Renesas R-Car MFIS offers multiple features but most importantly mailboxes and hwspinlocks. Because they share a common register space and a common register unprotection mechanism, a single driver was chosen to handle all dependencies. (MFD and auxiliary bus have been tried as well, but they failed because of circular dependencies.) In this first step, the driver implements common register access and a mailbox controller. hwspinlock support will be added incrementally, once the subsystem allows out-of-directory drivers. Signed-off-by: Kuninori Morimoto Signed-off-by: Wolfram Sang Acked-by: Jassi Brar Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260402112709.13002-3-wsa+renesas@sang-engineering.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/Kconfig | 9 + drivers/soc/renesas/Makefile | 1 + drivers/soc/renesas/rcar-mfis.c | 344 ++++++++++++++++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 drivers/soc/renesas/rcar-mfis.c diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig index 26bed0fdceb0..2ab150d04bb1 100644 --- a/drivers/soc/renesas/Kconfig +++ b/drivers/soc/renesas/Kconfig @@ -465,6 +465,15 @@ config ARCH_R9A07G043 endif # RISCV +config RCAR_MFIS + tristate "Renesas R-Car MFIS driver" + depends on ARCH_RENESAS || COMPILE_TEST + depends on MAILBOX + help + Select this option to enable the Renesas R-Car MFIS core driver for + the MFIS device found on SoCs like R-Car. On families like Gen5, this + is needed to communicate with the SCP. + config PWC_RZV2M bool "Renesas RZ/V2M PWC support" if COMPILE_TEST diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile index 655dbcb08747..81bde85c2178 100644 --- a/drivers/soc/renesas/Makefile +++ b/drivers/soc/renesas/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_SYS_R9A09G057) += r9a09g057-sys.o # Family obj-$(CONFIG_PWC_RZV2M) += pwc-rzv2m.o +obj-$(CONFIG_RCAR_MFIS) += rcar-mfis.o obj-$(CONFIG_RST_RCAR) += rcar-rst.o obj-$(CONFIG_RZN1_IRQMUX) += rzn1_irqmux.o obj-$(CONFIG_SYSC_RZ) += rz-sysc.o diff --git a/drivers/soc/renesas/rcar-mfis.c b/drivers/soc/renesas/rcar-mfis.c new file mode 100644 index 000000000000..b334b35306ae --- /dev/null +++ b/drivers/soc/renesas/rcar-mfis.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Renesas R-Car MFIS (Multifunctional Interface) driver + * + * Copyright (C) Renesas Solutions Corp. + * Kuninori Morimoto + * Wolfram Sang + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MFISWPCNTR 0x0900 +#define MFISWACNTR 0x0904 + +#define MFIS_X5H_IICR(i) ((i) * 0x1000 + 0x00) +#define MFIS_X5H_EICR(i) ((i) * 0x1000 + 0x04) + +#define MFIS_UNPROTECT_KEY 0xACCE0000 + +struct mfis_priv; + +struct mfis_reg { + void __iomem *base; + resource_size_t start; + struct mfis_priv *priv; +}; + +struct mfis_info { + u32 unprotect_mask; + unsigned int mb_num_channels; + unsigned int mb_reg_comes_from_dt:1; + unsigned int mb_tx_uses_eicr:1; + unsigned int mb_channels_are_unidir:1; +}; + +struct mfis_chan_priv { + u32 reg; + int irq; +}; + +struct mfis_priv { + spinlock_t unprotect_lock; /* guards access to the unprotection reg */ + struct device *dev; + struct mfis_reg common_reg; + struct mfis_reg mbox_reg; + const struct mfis_info *info; + + /* mailbox private data */ + struct mbox_controller mbox; + struct mfis_chan_priv *chan_privs; +}; + +static u32 mfis_read(struct mfis_reg *mreg, unsigned int reg) +{ + return ioread32(mreg->base + reg); +} + +static void mfis_write(struct mfis_reg *mreg, u32 reg, u32 val) +{ + struct mfis_priv *priv = mreg->priv; + u32 unprotect_mask = priv->info->unprotect_mask; + unsigned long flags; + u32 unprotect_code; + + /* + * [Gen4] key: 0xACCE0000, mask: 0x0000FFFF + * [Gen5] key: 0xACC00000, mask: 0x000FFFFF + */ + unprotect_code = (MFIS_UNPROTECT_KEY & ~unprotect_mask) | + ((mreg->start + reg) & unprotect_mask); + + spin_lock_irqsave(&priv->unprotect_lock, flags); + iowrite32(unprotect_code, priv->common_reg.base + MFISWACNTR); + iowrite32(val, mreg->base + reg); + spin_unlock_irqrestore(&priv->unprotect_lock, flags); +} + +/******************************************************** + * Mailbox * + ********************************************************/ + +#define mfis_mb_mbox_to_priv(_m) container_of((_m), struct mfis_priv, mbox) + +static irqreturn_t mfis_mb_iicr_interrupt(int irq, void *data) +{ + struct mbox_chan *chan = data; + struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox); + struct mfis_chan_priv *chan_priv = chan->con_priv; + + mbox_chan_received_data(chan, NULL); + /* Stop remote(!) doorbell */ + mfis_write(&priv->mbox_reg, chan_priv->reg, 0); + + return IRQ_HANDLED; +} + +static int mfis_mb_startup(struct mbox_chan *chan) +{ + struct mfis_chan_priv *chan_priv = chan->con_priv; + + if (!chan_priv->irq) + return 0; + + return request_irq(chan_priv->irq, mfis_mb_iicr_interrupt, 0, + dev_name(chan->mbox->dev), chan); +} + +static void mfis_mb_shutdown(struct mbox_chan *chan) +{ + struct mfis_chan_priv *chan_priv = chan->con_priv; + + if (chan_priv->irq) + free_irq(chan_priv->irq, chan); +} + +static int mfis_mb_iicr_send_data(struct mbox_chan *chan, void *data) +{ + struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox); + struct mfis_chan_priv *chan_priv = chan->con_priv; + + /* Our doorbell still active? */ + if (mfis_read(&priv->mbox_reg, chan_priv->reg) & BIT(0)) + return -EBUSY; + + /* Start our doorbell */ + mfis_write(&priv->mbox_reg, chan_priv->reg, BIT(0)); + + return 0; +} + +static bool mfis_mb_iicr_last_tx_done(struct mbox_chan *chan) +{ + struct mfis_priv *priv = mfis_mb_mbox_to_priv(chan->mbox); + struct mfis_chan_priv *chan_priv = chan->con_priv; + + /* Our doorbell still active? */ + return !(mfis_read(&priv->mbox_reg, chan_priv->reg) & BIT(0)); +} + +/* For MFIS variants using the IICR/EICR register pair */ +static const struct mbox_chan_ops mfis_iicr_ops = { + .startup = mfis_mb_startup, + .shutdown = mfis_mb_shutdown, + .send_data = mfis_mb_iicr_send_data, + .last_tx_done = mfis_mb_iicr_last_tx_done, +}; + +static struct mbox_chan *mfis_mb_of_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *sp) +{ + struct mfis_priv *priv = mfis_mb_mbox_to_priv(mbox); + struct mfis_chan_priv *chan_priv; + bool tx_uses_eicr, is_only_rx; + u32 chan_num, chan_flags; + struct mbox_chan *chan; + + if (sp->args_count != 2) + return ERR_PTR(-EINVAL); + + chan_num = sp->args[0]; + chan_flags = sp->args[1]; + + if (chan_num >= priv->info->mb_num_channels) + return ERR_PTR(-EINVAL); + + /* Channel layout is described in mfis_mb_probe() */ + if (priv->info->mb_channels_are_unidir) { + is_only_rx = chan_flags & MFIS_CHANNEL_RX; + chan = mbox->chans + 2 * chan_num + is_only_rx; + } else { + is_only_rx = false; + chan = mbox->chans + chan_num; + } + + if (priv->info->mb_reg_comes_from_dt) { + tx_uses_eicr = chan_flags & MFIS_CHANNEL_EICR; + if (tx_uses_eicr) + chan += mbox->num_chans / 2; + } else { + tx_uses_eicr = priv->info->mb_tx_uses_eicr; + } + + chan_priv = chan->con_priv; + chan_priv->reg = (tx_uses_eicr ^ is_only_rx) ? MFIS_X5H_EICR(chan_num) : + MFIS_X5H_IICR(chan_num); + + if (!priv->info->mb_channels_are_unidir || is_only_rx) { + char irqname[8]; + char suffix = tx_uses_eicr ? 'i' : 'e'; + + /* "ch0i" or "ch0e" */ + scnprintf(irqname, sizeof(irqname), "ch%u%c", chan_num, suffix); + + chan_priv->irq = of_irq_get_byname(mbox->dev->of_node, irqname); + if (chan_priv->irq < 0) + return ERR_PTR(chan_priv->irq); + if (chan_priv->irq == 0) + return ERR_PTR(-ENOENT); + } + + return chan; +} + +static int mfis_mb_probe(struct mfis_priv *priv) +{ + unsigned int num_chan = priv->info->mb_num_channels; + struct device *dev = priv->dev; + struct mbox_controller *mbox; + struct mbox_chan *chan; + + if (priv->info->mb_channels_are_unidir) { + /* Channel layout: Ch0-TX, Ch0-RX, Ch1-TX... */ + num_chan *= 2; + } + + if (priv->info->mb_reg_comes_from_dt) { + /* Channel layout: IICR channels, EICR channels */ + num_chan *= 2; + } + + chan = devm_kcalloc(dev, num_chan, sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + priv->chan_privs = devm_kcalloc(dev, num_chan, sizeof(*priv->chan_privs), + GFP_KERNEL); + if (!priv->chan_privs) + return -ENOMEM; + + mbox = &priv->mbox; + + for (unsigned int i = 0; i < num_chan; i++) + chan[i].con_priv = &priv->chan_privs[i]; + + mbox->chans = chan; + mbox->num_chans = num_chan; + mbox->txdone_poll = true; + mbox->ops = &mfis_iicr_ops; + mbox->dev = dev; + mbox->of_xlate = mfis_mb_of_xlate; + + return devm_mbox_controller_register(dev, mbox); +} + +/******************************************************** + * Common * + ********************************************************/ +static int mfis_reg_probe(struct platform_device *pdev, struct mfis_priv *priv, + struct mfis_reg *mreg, const char *name, bool required) +{ + struct resource *res; + void __iomem *base; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + + /* If there is no mailbox resource, registers are in the common space */ + if (!res && !required) { + *mreg = priv->common_reg; + } else { + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + mreg->base = base; + mreg->start = res->start; + mreg->priv = priv; + } + + return 0; +} + +static int mfis_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mfis_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->info = of_device_get_match_data(dev); + if (!priv->info) + return -ENOENT; + + spin_lock_init(&priv->unprotect_lock); + + ret = mfis_reg_probe(pdev, priv, &priv->common_reg, "common", true); + if (ret) + return ret; + + ret = mfis_reg_probe(pdev, priv, &priv->mbox_reg, "mboxes", false); + if (ret) + return ret; + + return mfis_mb_probe(priv); +} + +static const struct mfis_info mfis_info_r8a78000 = { + .unprotect_mask = 0x000fffff, + .mb_num_channels = 64, + .mb_reg_comes_from_dt = true, + .mb_channels_are_unidir = true, +}; + +static const struct mfis_info mfis_info_r8a78000_scp = { + .unprotect_mask = 0x000fffff, + .mb_num_channels = 32, + .mb_tx_uses_eicr = true, + .mb_channels_are_unidir = true, +}; + +static const struct of_device_id mfis_mfd_of_match[] = { + { .compatible = "renesas,r8a78000-mfis", .data = &mfis_info_r8a78000, }, + { .compatible = "renesas,r8a78000-mfis-scp", .data = &mfis_info_r8a78000_scp, }, + {} +}; +MODULE_DEVICE_TABLE(of, mfis_mfd_of_match); + +static struct platform_driver mfis_driver = { + .driver = { + .name = "rcar-mfis", + .of_match_table = mfis_mfd_of_match, + .suppress_bind_attrs = true, + }, + .probe = mfis_probe, +}; +module_platform_driver(mfis_driver); + +MODULE_AUTHOR("Kuninori Morimoto "); +MODULE_AUTHOR("Wolfram Sang "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Renesas R-Car MFIS driver"); From 39abbc2f7a7036af4a59d00e7748961683845c6f Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 2 Apr 2026 13:27:07 +0200 Subject: [PATCH 030/189] soc: renesas: Add R-Car X5H PRR support On R-Car X5H, PRR is inside the MFIS memory block, so we need to access it similar to e.g. RZ/G2L. Suggested-by: Geert Uytterhoeven Signed-off-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260402112709.13002-4-wsa+renesas@sang-engineering.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/renesas-soc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c index 38ff0b823bda..60b09020c935 100644 --- a/drivers/soc/renesas/renesas-soc.c +++ b/drivers/soc/renesas/renesas-soc.c @@ -442,8 +442,14 @@ static const struct renesas_id id_prr __initconst = { .mask = 0xff00, }; +static const struct renesas_id id_mfis __initconst = { + .offset = 0x44, + .mask = 0xff00, +}; + static const struct of_device_id renesas_ids[] __initconst = { { .compatible = "renesas,bsid", .data = &id_bsid }, + { .compatible = "renesas,r8a78000-mfis", .data = &id_mfis }, { .compatible = "renesas,r9a07g043-sysc", .data = &id_rzg2l }, { .compatible = "renesas,r9a07g044-sysc", .data = &id_rzg2l }, { .compatible = "renesas,r9a07g054-sysc", .data = &id_rzg2l }, @@ -501,7 +507,7 @@ static int __init renesas_soc_init(void) product = readl(chipid + id->offset); iounmap(chipid); - if (id == &id_prr) { + if (id == &id_prr || id == &id_mfis) { /* R-Car M3-W ES1.1 incorrectly identifies as ES2.0 */ if ((product & 0x7fff) == 0x5210) product ^= 0x11; From 8879cff7f86f82e8922208668fb0f7227e4836db Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Fri, 1 May 2026 16:00:52 +0900 Subject: [PATCH 031/189] memory: tegra: Deduplicate rate request management code As is, the EMC drivers for each 32-bit platform contain almost identical duplicated code for aggregating rate requests. Move this code out to a shared tegra-emc-common file to reduce duplication, and add kerneldoc comments. Based on code from the tegra20-emc driver. Signed-off-by: Mikko Perttunen Link: https://patch.msgid.link/20260501-memory-refactor-v3-1-69fb1ae1a7ca@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/Kconfig | 6 + drivers/memory/tegra/Makefile | 1 + drivers/memory/tegra/tegra-emc-common.c | 143 ++++++++++++++++++++++++ drivers/memory/tegra/tegra-emc-common.h | 46 ++++++++ drivers/memory/tegra/tegra124-emc.c | 107 +----------------- drivers/memory/tegra/tegra20-emc.c | 110 ++---------------- drivers/memory/tegra/tegra30-emc.c | 107 +----------------- 7 files changed, 215 insertions(+), 305 deletions(-) create mode 100644 drivers/memory/tegra/tegra-emc-common.c create mode 100644 drivers/memory/tegra/tegra-emc-common.h diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig index fc5a27791826..92671f9df672 100644 --- a/drivers/memory/tegra/Kconfig +++ b/drivers/memory/tegra/Kconfig @@ -17,6 +17,7 @@ config TEGRA20_EMC select DEVFREQ_GOV_SIMPLE_ONDEMAND select PM_DEVFREQ select DDR + select TEGRA_EMC_COMMON help This driver is for the External Memory Controller (EMC) found on Tegra20 chips. The EMC controls the external DRAM on the board. @@ -29,6 +30,7 @@ config TEGRA30_EMC depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST select PM_OPP select DDR + select TEGRA_EMC_COMMON help This driver is for the External Memory Controller (EMC) found on Tegra30 chips. The EMC controls the external DRAM on the board. @@ -41,6 +43,7 @@ config TEGRA124_EMC depends on ARCH_TEGRA_124_SOC || COMPILE_TEST select TEGRA124_CLK_EMC if ARCH_TEGRA select PM_OPP + select TEGRA_EMC_COMMON help This driver is for the External Memory Controller (EMC) found on Tegra124 chips. The EMC controls the external DRAM on the board. @@ -61,4 +64,7 @@ config TEGRA210_EMC This driver is required to change memory timings / clock rate for external memory. +config TEGRA_EMC_COMMON + tristate + endif diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile index 6334601e6120..75ebb4cb4f29 100644 --- a/drivers/memory/tegra/Makefile +++ b/drivers/memory/tegra/Makefile @@ -14,6 +14,7 @@ tegra-mc-$(CONFIG_ARCH_TEGRA_264_SOC) += tegra186.o tegra264.o obj-$(CONFIG_TEGRA_MC) += tegra-mc.o +obj-$(CONFIG_TEGRA_EMC_COMMON) += tegra-emc-common.o obj-$(CONFIG_TEGRA20_EMC) += tegra20-emc.o obj-$(CONFIG_TEGRA30_EMC) += tegra30-emc.o obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o diff --git a/drivers/memory/tegra/tegra-emc-common.c b/drivers/memory/tegra/tegra-emc-common.c new file mode 100644 index 000000000000..bbe86cb4942c --- /dev/null +++ b/drivers/memory/tegra/tegra-emc-common.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +#include "tegra-emc-common.h" + +/** + * tegra_emc_rate_requests_init() - Initialize EMC rate request tracking + * @reqs: struct tegra_emc_rate_requests to initialize. + * @dev: EMC device. + * + * Initializes the rate request tracking state with default state + * (no active requests). Must be called before using @reqs with + * other functions. + */ +void tegra_emc_rate_requests_init(struct tegra_emc_rate_requests *reqs, + struct device *dev) +{ + unsigned int i; + + mutex_init(&reqs->rate_lock); + reqs->dev = dev; + + for (i = 0; i < TEGRA_EMC_RATE_TYPE_MAX; i++) { + reqs->requested_rate[i].min_rate = 0; + reqs->requested_rate[i].max_rate = ULONG_MAX; + } +} +EXPORT_SYMBOL_GPL(tegra_emc_rate_requests_init); + +/* Caller must hold reqs->rate_lock. */ +static int tegra_emc_request_rate(struct tegra_emc_rate_requests *reqs, + unsigned long new_min_rate, + unsigned long new_max_rate, + enum tegra_emc_rate_request_type type) +{ + struct tegra_emc_rate_request *req = reqs->requested_rate; + unsigned long min_rate = 0, max_rate = ULONG_MAX; + unsigned int i; + int err; + + lockdep_assert_held(&reqs->rate_lock); + + /* select minimum and maximum rates among the requested rates */ + for (i = 0; i < TEGRA_EMC_RATE_TYPE_MAX; i++, req++) { + if (i == type) { + min_rate = max(new_min_rate, min_rate); + max_rate = min(new_max_rate, max_rate); + } else { + min_rate = max(req->min_rate, min_rate); + max_rate = min(req->max_rate, max_rate); + } + } + + if (min_rate > max_rate) { + dev_err_ratelimited(reqs->dev, "%s: type %u: out of range: %lu %lu\n", + __func__, type, min_rate, max_rate); + return -ERANGE; + } + + /* + * EMC rate-changes should go via OPP API because it manages voltage + * changes. + */ + err = dev_pm_opp_set_rate(reqs->dev, min_rate); + if (err) + return err; + + reqs->requested_rate[type].min_rate = new_min_rate; + reqs->requested_rate[type].max_rate = new_max_rate; + + return 0; +} + +/** + * tegra_emc_set_min_rate() - Update minimum rate request for a request type + * @reqs: rate request tracking state + * @rate: new minimum rate in Hz requested by @type + * @type: type of request + * + * Records @rate as the new minimum rate request for @type, recalculates target + * rate based on all requests and applies new rate through the OPP API. + * + * Context: Sleeps. Requests to same @reqs are synchronized via mutex. + * + * Return: + * * %0 - success + * * %-ERANGE - request would cause minimum rate request to be higher than + * maximum rate request + * * other - setting new rate failed + */ +int tegra_emc_set_min_rate(struct tegra_emc_rate_requests *reqs, + unsigned long rate, + enum tegra_emc_rate_request_type type) +{ + struct tegra_emc_rate_request *req = &reqs->requested_rate[type]; + int ret; + + mutex_lock(&reqs->rate_lock); + ret = tegra_emc_request_rate(reqs, rate, req->max_rate, type); + mutex_unlock(&reqs->rate_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tegra_emc_set_min_rate); + +/** + * tegra_emc_set_max_rate() - Update maximum rate request for a request type + * @reqs: rate request tracking state + * @rate: new maximum rate in Hz requested by @type + * @type: type of request + * + * Records @rate as the new maximum rate request for @type, recalculates target + * rate based on all requests and applies new rate through the OPP API. + * + * Context: Sleeps. Requests to same @reqs are synchronized via mutex. + * + * Return: + * * %0 - success + * * %-ERANGE - request would cause minimum rate request to be higher than + * maximum rate request + * * other - setting new rate failed + */ +int tegra_emc_set_max_rate(struct tegra_emc_rate_requests *reqs, + unsigned long rate, + enum tegra_emc_rate_request_type type) +{ + struct tegra_emc_rate_request *req = &reqs->requested_rate[type]; + int ret; + + mutex_lock(&reqs->rate_lock); + ret = tegra_emc_request_rate(reqs, req->min_rate, rate, type); + mutex_unlock(&reqs->rate_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tegra_emc_set_max_rate); + +MODULE_DESCRIPTION("NVIDIA Tegra EMC common code"); +MODULE_LICENSE("GPL"); diff --git a/drivers/memory/tegra/tegra-emc-common.h b/drivers/memory/tegra/tegra-emc-common.h new file mode 100644 index 000000000000..157e27024342 --- /dev/null +++ b/drivers/memory/tegra/tegra-emc-common.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef TEGRA_EMC_COMMON_H +#define TEGRA_EMC_COMMON_H + +#include +#include + +/** + * enum tegra_emc_rate_request_type - source of rate request + * @TEGRA_EMC_RATE_DEVFREQ: rate requested by devfreq governor + * @TEGRA_EMC_RATE_DEBUG: rate requested through debugfs knobs + * @TEGRA_EMC_RATE_ICC: rate requested by ICC framework + * @TEGRA_EMC_RATE_TYPE_MAX: number of valid request types + */ +enum tegra_emc_rate_request_type { + TEGRA_EMC_RATE_DEVFREQ, + TEGRA_EMC_RATE_DEBUG, + TEGRA_EMC_RATE_ICC, + TEGRA_EMC_RATE_TYPE_MAX, +}; + +struct tegra_emc_rate_request { + unsigned long min_rate; + unsigned long max_rate; +}; + +struct tegra_emc_rate_requests { + struct tegra_emc_rate_request requested_rate[TEGRA_EMC_RATE_TYPE_MAX]; + /* Protects @requested_rate. */ + struct mutex rate_lock; + struct device *dev; +}; + +void tegra_emc_rate_requests_init(struct tegra_emc_rate_requests *reqs, + struct device *dev); + +int tegra_emc_set_min_rate(struct tegra_emc_rate_requests *reqs, + unsigned long rate, + enum tegra_emc_rate_request_type type); + +int tegra_emc_set_max_rate(struct tegra_emc_rate_requests *reqs, + unsigned long rate, + enum tegra_emc_rate_request_type type); + +#endif /* TEGRA_EMC_COMMON_H */ diff --git a/drivers/memory/tegra/tegra124-emc.c b/drivers/memory/tegra/tegra124-emc.c index 5cfbc169c5f9..555a6c753be7 100644 --- a/drivers/memory/tegra/tegra124-emc.c +++ b/drivers/memory/tegra/tegra124-emc.c @@ -27,6 +27,7 @@ #include #include "mc.h" +#include "tegra-emc-common.h" #define EMC_FBIO_CFG5 0x104 #define EMC_FBIO_CFG5_DRAM_TYPE_MASK 0x3 @@ -467,17 +468,6 @@ struct emc_timing { u32 emc_zcal_interval; }; -enum emc_rate_request_type { - EMC_RATE_DEBUG, - EMC_RATE_ICC, - EMC_RATE_TYPE_MAX, -}; - -struct emc_rate_request { - unsigned long min_rate; - unsigned long max_rate; -}; - struct tegra_emc { struct device *dev; @@ -503,14 +493,7 @@ struct tegra_emc { struct icc_provider provider; - /* - * There are multiple sources in the EMC driver which could request - * a min/max clock rate, these rates are contained in this array. - */ - struct emc_rate_request requested_rate[EMC_RATE_TYPE_MAX]; - - /* protect shared rate-change code path */ - struct mutex rate_lock; + struct tegra_emc_rate_requests reqs; }; /* Timing change sequence functions */ @@ -1041,83 +1024,6 @@ tegra124_emc_find_node_by_ram_code(struct device_node *node, u32 ram_code) return NULL; } -static void tegra124_emc_rate_requests_init(struct tegra_emc *emc) -{ - unsigned int i; - - for (i = 0; i < EMC_RATE_TYPE_MAX; i++) { - emc->requested_rate[i].min_rate = 0; - emc->requested_rate[i].max_rate = ULONG_MAX; - } -} - -static int emc_request_rate(struct tegra_emc *emc, - unsigned long new_min_rate, - unsigned long new_max_rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = emc->requested_rate; - unsigned long min_rate = 0, max_rate = ULONG_MAX; - unsigned int i; - int err; - - /* select minimum and maximum rates among the requested rates */ - for (i = 0; i < EMC_RATE_TYPE_MAX; i++, req++) { - if (i == type) { - min_rate = max(new_min_rate, min_rate); - max_rate = min(new_max_rate, max_rate); - } else { - min_rate = max(req->min_rate, min_rate); - max_rate = min(req->max_rate, max_rate); - } - } - - if (min_rate > max_rate) { - dev_err_ratelimited(emc->dev, "%s: type %u: out of range: %lu %lu\n", - __func__, type, min_rate, max_rate); - return -ERANGE; - } - - /* - * EMC rate-changes should go via OPP API because it manages voltage - * changes. - */ - err = dev_pm_opp_set_rate(emc->dev, min_rate); - if (err) - return err; - - emc->requested_rate[type].min_rate = new_min_rate; - emc->requested_rate[type].max_rate = new_max_rate; - - return 0; -} - -static int emc_set_min_rate(struct tegra_emc *emc, unsigned long rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = &emc->requested_rate[type]; - int ret; - - mutex_lock(&emc->rate_lock); - ret = emc_request_rate(emc, rate, req->max_rate, type); - mutex_unlock(&emc->rate_lock); - - return ret; -} - -static int emc_set_max_rate(struct tegra_emc *emc, unsigned long rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = &emc->requested_rate[type]; - int ret; - - mutex_lock(&emc->rate_lock); - ret = emc_request_rate(emc, req->min_rate, rate, type); - mutex_unlock(&emc->rate_lock); - - return ret; -} - /* * debugfs interface * @@ -1190,7 +1096,7 @@ static int tegra124_emc_debug_min_rate_set(void *data, u64 rate) if (!tegra124_emc_validate_rate(emc, rate)) return -EINVAL; - err = emc_set_min_rate(emc, rate, EMC_RATE_DEBUG); + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); if (err < 0) return err; @@ -1220,7 +1126,7 @@ static int tegra124_emc_debug_max_rate_set(void *data, u64 rate) if (!tegra124_emc_validate_rate(emc, rate)) return -EINVAL; - err = emc_set_max_rate(emc, rate, EMC_RATE_DEBUG); + err = tegra_emc_set_max_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); if (err < 0) return err; @@ -1327,7 +1233,7 @@ static int emc_icc_set(struct icc_node *src, struct icc_node *dst) do_div(rate, ddr * dram_data_bus_width_bytes); rate = min_t(u64, rate, U32_MAX); - err = emc_set_min_rate(emc, rate, EMC_RATE_ICC); + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_ICC); if (err) return err; @@ -1441,7 +1347,6 @@ static int tegra124_emc_probe(struct platform_device *pdev) if (!emc) return -ENOMEM; - mutex_init(&emc->rate_lock); emc->dev = &pdev->dev; emc->regs = devm_platform_ioremap_resource(pdev, 0); @@ -1487,7 +1392,7 @@ static int tegra124_emc_probe(struct platform_device *pdev) if (err) return err; - tegra124_emc_rate_requests_init(emc); + tegra_emc_rate_requests_init(&emc->reqs, &pdev->dev); if (IS_ENABLED(CONFIG_DEBUG_FS)) emc_debugfs_init(&pdev->dev, emc); diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c index a1fadefee7fd..1d564b80e2bb 100644 --- a/drivers/memory/tegra/tegra20-emc.c +++ b/drivers/memory/tegra/tegra20-emc.c @@ -32,6 +32,7 @@ #include "../of_memory.h" #include "mc.h" +#include "tegra-emc-common.h" #define EMC_INTSTATUS 0x000 #define EMC_INTMASK 0x004 @@ -182,18 +183,6 @@ struct emc_timing { u32 data[ARRAY_SIZE(emc_timing_registers)]; }; -enum emc_rate_request_type { - EMC_RATE_DEVFREQ, - EMC_RATE_DEBUG, - EMC_RATE_ICC, - EMC_RATE_TYPE_MAX, -}; - -struct emc_rate_request { - unsigned long min_rate; - unsigned long max_rate; -}; - struct tegra_emc { struct device *dev; struct tegra_mc *mc; @@ -212,14 +201,7 @@ struct tegra_emc { unsigned long max_rate; } debugfs; - /* - * There are multiple sources in the EMC driver which could request - * a min/max clock rate, these rates are contained in this array. - */ - struct emc_rate_request requested_rate[EMC_RATE_TYPE_MAX]; - - /* protect shared rate-change code path */ - struct mutex rate_lock; + struct tegra_emc_rate_requests reqs; struct devfreq_simple_ondemand_data ondemand_data; @@ -710,83 +692,6 @@ static long emc_round_rate(unsigned long rate, return timing->rate; } -static void tegra20_emc_rate_requests_init(struct tegra_emc *emc) -{ - unsigned int i; - - for (i = 0; i < EMC_RATE_TYPE_MAX; i++) { - emc->requested_rate[i].min_rate = 0; - emc->requested_rate[i].max_rate = ULONG_MAX; - } -} - -static int emc_request_rate(struct tegra_emc *emc, - unsigned long new_min_rate, - unsigned long new_max_rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = emc->requested_rate; - unsigned long min_rate = 0, max_rate = ULONG_MAX; - unsigned int i; - int err; - - /* select minimum and maximum rates among the requested rates */ - for (i = 0; i < EMC_RATE_TYPE_MAX; i++, req++) { - if (i == type) { - min_rate = max(new_min_rate, min_rate); - max_rate = min(new_max_rate, max_rate); - } else { - min_rate = max(req->min_rate, min_rate); - max_rate = min(req->max_rate, max_rate); - } - } - - if (min_rate > max_rate) { - dev_err_ratelimited(emc->dev, "%s: type %u: out of range: %lu %lu\n", - __func__, type, min_rate, max_rate); - return -ERANGE; - } - - /* - * EMC rate-changes should go via OPP API because it manages voltage - * changes. - */ - err = dev_pm_opp_set_rate(emc->dev, min_rate); - if (err) - return err; - - emc->requested_rate[type].min_rate = new_min_rate; - emc->requested_rate[type].max_rate = new_max_rate; - - return 0; -} - -static int emc_set_min_rate(struct tegra_emc *emc, unsigned long rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = &emc->requested_rate[type]; - int ret; - - mutex_lock(&emc->rate_lock); - ret = emc_request_rate(emc, rate, req->max_rate, type); - mutex_unlock(&emc->rate_lock); - - return ret; -} - -static int emc_set_max_rate(struct tegra_emc *emc, unsigned long rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = &emc->requested_rate[type]; - int ret; - - mutex_lock(&emc->rate_lock); - ret = emc_request_rate(emc, req->min_rate, rate, type); - mutex_unlock(&emc->rate_lock); - - return ret; -} - /* * debugfs interface * @@ -857,7 +762,7 @@ static int tegra20_emc_debug_min_rate_set(void *data, u64 rate) if (!tegra20_emc_validate_rate(emc, rate)) return -EINVAL; - err = emc_set_min_rate(emc, rate, EMC_RATE_DEBUG); + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); if (err < 0) return err; @@ -887,7 +792,7 @@ static int tegra20_emc_debug_max_rate_set(void *data, u64 rate) if (!tegra20_emc_validate_rate(emc, rate)) return -EINVAL; - err = emc_set_max_rate(emc, rate, EMC_RATE_DEBUG); + err = tegra_emc_set_max_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); if (err < 0) return err; @@ -993,7 +898,7 @@ static int emc_icc_set(struct icc_node *src, struct icc_node *dst) do_div(rate, dram_data_bus_width_bytes); rate = min_t(u64, rate, U32_MAX); - err = emc_set_min_rate(emc, rate, EMC_RATE_ICC); + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_ICC); if (err) return err; @@ -1111,7 +1016,7 @@ static int tegra20_emc_devfreq_target(struct device *dev, unsigned long *freq, rate = dev_pm_opp_get_freq(opp); dev_pm_opp_put(opp); - return emc_set_min_rate(emc, rate, EMC_RATE_DEVFREQ); + return tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEVFREQ); } static int tegra20_emc_devfreq_get_dev_status(struct device *dev, @@ -1190,7 +1095,6 @@ static int tegra20_emc_probe(struct platform_device *pdev) if (!emc) return -ENOMEM; - mutex_init(&emc->rate_lock); emc->clk_nb.notifier_call = tegra20_emc_clk_change_notify; emc->dev = &pdev->dev; @@ -1228,7 +1132,7 @@ static int tegra20_emc_probe(struct platform_device *pdev) return err; platform_set_drvdata(pdev, emc); - tegra20_emc_rate_requests_init(emc); + tegra_emc_rate_requests_init(&emc->reqs, &pdev->dev); tegra20_emc_debugfs_init(emc); tegra20_emc_interconnect_init(emc); tegra20_emc_devfreq_init(emc); diff --git a/drivers/memory/tegra/tegra30-emc.c b/drivers/memory/tegra/tegra30-emc.c index 5812c8cd6ce4..c1eacd66ce49 100644 --- a/drivers/memory/tegra/tegra30-emc.c +++ b/drivers/memory/tegra/tegra30-emc.c @@ -36,6 +36,7 @@ #include "../of_memory.h" #include "mc.h" +#include "tegra-emc-common.h" #define EMC_INTSTATUS 0x000 #define EMC_INTMASK 0x004 @@ -341,17 +342,6 @@ struct emc_timing { bool emc_cfg_dyn_self_ref; }; -enum emc_rate_request_type { - EMC_RATE_DEBUG, - EMC_RATE_ICC, - EMC_RATE_TYPE_MAX, -}; - -struct emc_rate_request { - unsigned long min_rate; - unsigned long max_rate; -}; - struct tegra_emc { struct device *dev; struct tegra_mc *mc; @@ -383,14 +373,7 @@ struct tegra_emc { unsigned long max_rate; } debugfs; - /* - * There are multiple sources in the EMC driver which could request - * a min/max clock rate, these rates are contained in this array. - */ - struct emc_rate_request requested_rate[EMC_RATE_TYPE_MAX]; - - /* protect shared rate-change code path */ - struct mutex rate_lock; + struct tegra_emc_rate_requests reqs; bool mrr_error; }; @@ -1228,83 +1211,6 @@ static long emc_round_rate(unsigned long rate, return timing->rate; } -static void tegra30_emc_rate_requests_init(struct tegra_emc *emc) -{ - unsigned int i; - - for (i = 0; i < EMC_RATE_TYPE_MAX; i++) { - emc->requested_rate[i].min_rate = 0; - emc->requested_rate[i].max_rate = ULONG_MAX; - } -} - -static int emc_request_rate(struct tegra_emc *emc, - unsigned long new_min_rate, - unsigned long new_max_rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = emc->requested_rate; - unsigned long min_rate = 0, max_rate = ULONG_MAX; - unsigned int i; - int err; - - /* select minimum and maximum rates among the requested rates */ - for (i = 0; i < EMC_RATE_TYPE_MAX; i++, req++) { - if (i == type) { - min_rate = max(new_min_rate, min_rate); - max_rate = min(new_max_rate, max_rate); - } else { - min_rate = max(req->min_rate, min_rate); - max_rate = min(req->max_rate, max_rate); - } - } - - if (min_rate > max_rate) { - dev_err_ratelimited(emc->dev, "%s: type %u: out of range: %lu %lu\n", - __func__, type, min_rate, max_rate); - return -ERANGE; - } - - /* - * EMC rate-changes should go via OPP API because it manages voltage - * changes. - */ - err = dev_pm_opp_set_rate(emc->dev, min_rate); - if (err) - return err; - - emc->requested_rate[type].min_rate = new_min_rate; - emc->requested_rate[type].max_rate = new_max_rate; - - return 0; -} - -static int emc_set_min_rate(struct tegra_emc *emc, unsigned long rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = &emc->requested_rate[type]; - int ret; - - mutex_lock(&emc->rate_lock); - ret = emc_request_rate(emc, rate, req->max_rate, type); - mutex_unlock(&emc->rate_lock); - - return ret; -} - -static int emc_set_max_rate(struct tegra_emc *emc, unsigned long rate, - enum emc_rate_request_type type) -{ - struct emc_rate_request *req = &emc->requested_rate[type]; - int ret; - - mutex_lock(&emc->rate_lock); - ret = emc_request_rate(emc, req->min_rate, rate, type); - mutex_unlock(&emc->rate_lock); - - return ret; -} - /* * debugfs interface * @@ -1375,7 +1281,7 @@ static int tegra30_emc_debug_min_rate_set(void *data, u64 rate) if (!tegra30_emc_validate_rate(emc, rate)) return -EINVAL; - err = emc_set_min_rate(emc, rate, EMC_RATE_DEBUG); + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); if (err < 0) return err; @@ -1405,7 +1311,7 @@ static int tegra30_emc_debug_max_rate_set(void *data, u64 rate) if (!tegra30_emc_validate_rate(emc, rate)) return -EINVAL; - err = emc_set_max_rate(emc, rate, EMC_RATE_DEBUG); + err = tegra_emc_set_max_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); if (err < 0) return err; @@ -1511,7 +1417,7 @@ static int emc_icc_set(struct icc_node *src, struct icc_node *dst) do_div(rate, ddr * dram_data_bus_width_bytes); rate = min_t(u64, rate, U32_MAX); - err = emc_set_min_rate(emc, rate, EMC_RATE_ICC); + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_ICC); if (err) return err; @@ -1622,7 +1528,6 @@ static int tegra30_emc_probe(struct platform_device *pdev) if (IS_ERR(emc->mc)) return PTR_ERR(emc->mc); - mutex_init(&emc->rate_lock); emc->clk_nb.notifier_call = emc_clk_change_notify; emc->dev = &pdev->dev; @@ -1664,7 +1569,7 @@ static int tegra30_emc_probe(struct platform_device *pdev) return err; platform_set_drvdata(pdev, emc); - tegra30_emc_rate_requests_init(emc); + tegra_emc_rate_requests_init(&emc->reqs, &pdev->dev); tegra30_emc_debugfs_init(emc); tegra30_emc_interconnect_init(emc); From 4f42beeb9796e24e8009c46d1a2d676803e5ab24 Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Thu, 30 Apr 2026 09:52:00 +0000 Subject: [PATCH 032/189] memory: tegra: Make ->resume() callback return void tegra186_mc_resume() is the only implementation of the SoC ->resume() op in struct tegra_mc_ops, and it can never fail as the SID override loop has no error path. The int return value is therefore not used. Change the prototype to return void so callers do not need to deal with a value that is always 0. If a future SoC needs to report failure from resume, an int return type can be reintroduced then. Signed-off-by: Ashish Mhetre Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260430095202.1167651-2-amhetre@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra186.c | 4 +--- include/soc/tegra/mc.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/memory/tegra/tegra186.c b/drivers/memory/tegra/tegra186.c index 91d56165605f..579d058da220 100644 --- a/drivers/memory/tegra/tegra186.c +++ b/drivers/memory/tegra/tegra186.c @@ -154,7 +154,7 @@ static int tegra186_mc_probe_device(struct tegra_mc *mc, struct device *dev) return 0; } -static int tegra186_mc_resume(struct tegra_mc *mc) +static void tegra186_mc_resume(struct tegra_mc *mc) { #if IS_ENABLED(CONFIG_IOMMU_API) unsigned int i; @@ -165,8 +165,6 @@ static int tegra186_mc_resume(struct tegra_mc *mc) tegra186_mc_client_sid_override(mc, client, client->sid); } #endif - - return 0; } const struct tegra_mc_ops tegra186_mc_ops = { diff --git a/include/soc/tegra/mc.h b/include/soc/tegra/mc.h index e6da035d1306..25d465d70493 100644 --- a/include/soc/tegra/mc.h +++ b/include/soc/tegra/mc.h @@ -164,7 +164,7 @@ struct tegra_mc_ops { */ int (*probe)(struct tegra_mc *mc); void (*remove)(struct tegra_mc *mc); - int (*resume)(struct tegra_mc *mc); + void (*resume)(struct tegra_mc *mc); int (*probe_device)(struct tegra_mc *mc, struct device *dev); }; From 2411c8d1e3e09910e94bab0d0a2c071fbc8a9e7b Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Thu, 30 Apr 2026 09:52:01 +0000 Subject: [PATCH 033/189] memory: tegra: Wire up system sleep PM ops The tegra-mc platform driver does not register any dev_pm_ops, so the SoC-specific ->resume() is never invoked (e.g. tegra186_mc_resume) on system wake. On Tegra186 and later this means MC client Stream-ID override registers are not reprogrammed, and clients behind the ARM SMMU fault on the first DMA after resume. Register a dev_pm_ops on the tegra-mc driver and route the system resume callback into mc->soc->ops->resume() so the existing SID restore path runs again on wake. No suspend callback is needed as the resume path reprograms all MC state from the static SoC tables, so there is nothing to save. Fixes: fe3b082a6eb8 ("memory: tegra: Add SID override programming for MC clients") Signed-off-by: Ashish Mhetre Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260430095202.1167651-3-amhetre@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/mc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index d620660da331..64e41338cdf2 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1010,10 +1011,23 @@ static void tegra_mc_sync_state(struct device *dev) icc_sync_state(dev); } +static int tegra_mc_resume(struct device *dev) +{ + struct tegra_mc *mc = dev_get_drvdata(dev); + + if (mc->soc->ops && mc->soc->ops->resume) + mc->soc->ops->resume(mc); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tegra_mc_pm_ops, NULL, tegra_mc_resume); + static struct platform_driver tegra_mc_driver = { .driver = { .name = "tegra-mc", .of_match_table = tegra_mc_of_match, + .pm = pm_sleep_ptr(&tegra_mc_pm_ops), .suppress_bind_attrs = true, .sync_state = tegra_mc_sync_state, }, From 35934fd08d17071c5ae0e99b95258f61f0cff763 Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Thu, 30 Apr 2026 09:52:02 +0000 Subject: [PATCH 034/189] memory: tegra: Restore MC interrupt masks on resume The MC interrupt mask registers lose their state across Tegra low power suspend state (aka. SC7). Without re-applying them on resume, MC interrupts that were enabled at probe remain masked after wake, so any post-resume MC error goes unreported. Factor the existing intmask programming out of tegra_mc_probe() into tegra_mc_setup_intmask() and reuse it from the system resume callback so the mask state is restored on wake. Signed-off-by: Ashish Mhetre Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260430095202.1167651-4-amhetre@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/mc.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index 64e41338cdf2..cfcfc7291106 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -911,6 +911,19 @@ static void tegra_mc_num_channel_enabled(struct tegra_mc *mc) } } +static void tegra_mc_setup_intmask(struct tegra_mc *mc) +{ + unsigned int i; + + for (i = 0; i < mc->soc->num_intmasks; i++) { + if (mc->soc->num_channels) + mc_ch_writel(mc, MC_BROADCAST_CHANNEL, mc->soc->intmasks[i].mask, + mc->soc->intmasks[i].reg); + else + mc_writel(mc, mc->soc->intmasks[i].mask, mc->soc->intmasks[i].reg); + } +} + static int tegra_mc_probe(struct platform_device *pdev) { struct tegra_mc *mc; @@ -971,13 +984,7 @@ static int tegra_mc_probe(struct platform_device *pdev) } } - for (i = 0; i < mc->soc->num_intmasks; i++) { - if (mc->soc->num_channels) - mc_ch_writel(mc, MC_BROADCAST_CHANNEL, mc->soc->intmasks[i].mask, - mc->soc->intmasks[i].reg); - else - mc_writel(mc, mc->soc->intmasks[i].mask, mc->soc->intmasks[i].reg); - } + tegra_mc_setup_intmask(mc); } if (mc->soc->reset_ops) { @@ -1018,6 +1025,8 @@ static int tegra_mc_resume(struct device *dev) if (mc->soc->ops && mc->soc->ops->resume) mc->soc->ops->resume(mc); + tegra_mc_setup_intmask(mc); + return 0; } From bf7cf25d4245fe49c71eba0e3b09b6260a336999 Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Mon, 27 Apr 2026 07:34:18 +0000 Subject: [PATCH 035/189] dt-bindings: memory: tegra: Add nvidia,tegra238-mc compatible Document the device tree binding for the Tegra238 memory controller. Tegra238 has 8 memory controller channels plus broadcast and stream-id registers. Add the stream ID header (nvidia,tegra238-mc.h) defining ISO and NISO stream IDs for SMMU configuration. Signed-off-by: Ashish Mhetre Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260427073419.567360-2-amhetre@nvidia.com Signed-off-by: Krzysztof Kozlowski --- .../nvidia,tegra186-mc.yaml | 31 ++++++++ .../dt-bindings/memory/nvidia,tegra238-mc.h | 74 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 include/dt-bindings/memory/nvidia,tegra238-mc.h diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra186-mc.yaml b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra186-mc.yaml index 7b03b589168b..6c374e2b1543 100644 --- a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra186-mc.yaml +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra186-mc.yaml @@ -32,6 +32,7 @@ properties: - nvidia,tegra186-mc - nvidia,tegra194-mc - nvidia,tegra234-mc + - nvidia,tegra238-mc - nvidia,tegra264-mc reg: @@ -266,6 +267,36 @@ allOf: interrupt-names: false + - if: + properties: + compatible: + const: nvidia,tegra238-mc + then: + properties: + reg: + minItems: 10 + maxItems: 10 + description: 9 memory controller channels and 1 for stream-id registers + + reg-names: + items: + - const: sid + - const: broadcast + - const: ch0 + - const: ch1 + - const: ch2 + - const: ch3 + - const: ch4 + - const: ch5 + - const: ch6 + - const: ch7 + + interrupts: + items: + - description: MC general interrupt + + interrupt-names: false + - if: properties: compatible: diff --git a/include/dt-bindings/memory/nvidia,tegra238-mc.h b/include/dt-bindings/memory/nvidia,tegra238-mc.h new file mode 100644 index 000000000000..be24c0eb3f15 --- /dev/null +++ b/include/dt-bindings/memory/nvidia,tegra238-mc.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. */ + +#ifndef DT_BINDINGS_MEMORY_TEGRA238_MC_H +#define DT_BINDINGS_MEMORY_TEGRA238_MC_H + +/* special clients */ +#define TEGRA238_SID_INVALID 0x0 +#define TEGRA238_SID_PASSTHROUGH 0x7f + +/* ISO stream IDs */ +#define TEGRA238_SID_ISO_NVDISPLAY 0x1 +#define TEGRA238_SID_ISO_APE0 0x2 +#define TEGRA238_SID_ISO_APE1 0x3 + +/* NISO stream IDs */ +#define TEGRA238_SID_AON 0x1 +#define TEGRA238_SID_BPMP 0x2 +#define TEGRA238_SID_ETR 0x3 +#define TEGRA238_SID_FDE 0x4 +#define TEGRA238_SID_HC 0x5 +#define TEGRA238_SID_HDA 0x6 +#define TEGRA238_SID_NVDEC 0x7 +#define TEGRA238_SID_NVDISPLAY 0x8 +#define TEGRA238_SID_NVENC 0x9 +#define TEGRA238_SID_OFA 0xa +#define TEGRA238_SID_PCIE0 0xb +#define TEGRA238_SID_PCIE1 0xc +#define TEGRA238_SID_PCIE2 0xd +#define TEGRA238_SID_PCIE3 0xe +#define TEGRA238_SID_HWMP_PMA 0xf +#define TEGRA238_SID_PSC 0x10 +#define TEGRA238_SID_SDMMC1A 0x11 +#define TEGRA238_SID_SDMMC4A 0x12 +#define TEGRA238_SID_SES_SE0 0x13 +#define TEGRA238_SID_SES_SE1 0x14 +#define TEGRA238_SID_SES_SE2 0x15 +#define TEGRA238_SID_SEU1_SE0 0x16 +#define TEGRA238_SID_SEU1_SE1 0x17 +#define TEGRA238_SID_SEU1_SE2 0x18 +#define TEGRA238_SID_TSEC 0x19 +#define TEGRA238_SID_UFSHC 0x1a +#define TEGRA238_SID_VIC 0x1b +#define TEGRA238_SID_XUSB_HOST 0x1c +#define TEGRA238_SID_XUSB_DEV 0x1d +#define TEGRA238_SID_GPCDMA_0 0x1e +#define TEGRA238_SID_SMMU_TEST 0x1f + +/* Host1x virtualization clients. */ +#define TEGRA238_SID_HOST1X_CTX0 0x20 +#define TEGRA238_SID_HOST1X_CTX1 0x21 +#define TEGRA238_SID_HOST1X_CTX2 0x22 +#define TEGRA238_SID_HOST1X_CTX3 0x23 +#define TEGRA238_SID_HOST1X_CTX4 0x24 +#define TEGRA238_SID_HOST1X_CTX5 0x25 +#define TEGRA238_SID_HOST1X_CTX6 0x26 +#define TEGRA238_SID_HOST1X_CTX7 0x27 + +#define TEGRA238_SID_XUSB_VF0 0x28 +#define TEGRA238_SID_XUSB_VF1 0x29 +#define TEGRA238_SID_XUSB_VF2 0x2a +#define TEGRA238_SID_XUSB_VF3 0x2b + +/* Host1x command buffers */ +#define TEGRA238_SID_HC_VM0 0x2c +#define TEGRA238_SID_HC_VM1 0x2d +#define TEGRA238_SID_HC_VM2 0x2e +#define TEGRA238_SID_HC_VM3 0x2f +#define TEGRA238_SID_HC_VM4 0x30 +#define TEGRA238_SID_HC_VM5 0x31 +#define TEGRA238_SID_HC_VM6 0x32 +#define TEGRA238_SID_HC_VM7 0x33 + +#endif From 907ef6a1baf0ad61f64d31a3f5040feb82f48929 Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Mon, 27 Apr 2026 07:34:19 +0000 Subject: [PATCH 036/189] memory: tegra: Add Tegra238 MC support Add Memory Controller driver support for Tegra238 SOC, including: - MC client definitions with Tegra238-specific stream IDs - Reuse of Tegra234 ICC operations for bandwidth management via BPMP-FW - Device tree compatible string "nvidia,tegra238-mc" Export tegra234_mc_icc_ops so it can be shared with the Tegra238 MC driver, as both SoCs use the same ICC aggregation and bandwidth management logic. Signed-off-by: Ashish Mhetre Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260427073419.567360-3-amhetre@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/Makefile | 1 + drivers/memory/tegra/mc.c | 3 + drivers/memory/tegra/mc.h | 9 + drivers/memory/tegra/tegra234.c | 2 +- drivers/memory/tegra/tegra238.c | 391 ++++++++++++++++++++++++++++++++ 5 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 drivers/memory/tegra/tegra238.c diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile index 75ebb4cb4f29..218361cdaf2d 100644 --- a/drivers/memory/tegra/Makefile +++ b/drivers/memory/tegra/Makefile @@ -10,6 +10,7 @@ tegra-mc-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o tegra-mc-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o tegra-mc-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra186.o tegra194.o tegra-mc-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra186.o tegra234.o +tegra-mc-$(CONFIG_ARCH_TEGRA_238_SOC) += tegra186.o tegra234.o tegra238.o tegra-mc-$(CONFIG_ARCH_TEGRA_264_SOC) += tegra186.o tegra264.o obj-$(CONFIG_TEGRA_MC) += tegra-mc.o diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index cfcfc7291106..ec80ea9cc173 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -50,6 +50,9 @@ static const struct of_device_id tegra_mc_of_match[] = { #ifdef CONFIG_ARCH_TEGRA_234_SOC { .compatible = "nvidia,tegra234-mc", .data = &tegra234_mc_soc }, #endif +#ifdef CONFIG_ARCH_TEGRA_238_SOC + { .compatible = "nvidia,tegra238-mc", .data = &tegra238_mc_soc }, +#endif #ifdef CONFIG_ARCH_TEGRA_264_SOC { .compatible = "nvidia,tegra264-mc", .data = &tegra264_mc_soc }, #endif diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h index 649b54369263..e94d265d7b67 100644 --- a/drivers/memory/tegra/mc.h +++ b/drivers/memory/tegra/mc.h @@ -240,6 +240,14 @@ extern const struct tegra_mc_soc tegra194_mc_soc; extern const struct tegra_mc_soc tegra234_mc_soc; #endif +#if defined(CONFIG_ARCH_TEGRA_234_SOC) || defined(CONFIG_ARCH_TEGRA_238_SOC) +extern const struct tegra_mc_icc_ops tegra234_mc_icc_ops; +#endif + +#ifdef CONFIG_ARCH_TEGRA_238_SOC +extern const struct tegra_mc_soc tegra238_mc_soc; +#endif + #ifdef CONFIG_ARCH_TEGRA_264_SOC extern const struct tegra_mc_soc tegra264_mc_soc; #endif @@ -256,6 +264,7 @@ extern const struct tegra_mc_ops tegra30_mc_ops; #if defined(CONFIG_ARCH_TEGRA_186_SOC) || \ defined(CONFIG_ARCH_TEGRA_194_SOC) || \ defined(CONFIG_ARCH_TEGRA_234_SOC) || \ + defined(CONFIG_ARCH_TEGRA_238_SOC) || \ defined(CONFIG_ARCH_TEGRA_264_SOC) extern const struct tegra_mc_ops tegra186_mc_ops; #endif diff --git a/drivers/memory/tegra/tegra234.c b/drivers/memory/tegra/tegra234.c index 87b22038a5fb..9fbd34d4abe0 100644 --- a/drivers/memory/tegra/tegra234.c +++ b/drivers/memory/tegra/tegra234.c @@ -1125,7 +1125,7 @@ static int tegra234_mc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *pea return 0; } -static const struct tegra_mc_icc_ops tegra234_mc_icc_ops = { +const struct tegra_mc_icc_ops tegra234_mc_icc_ops = { .xlate = tegra_mc_icc_xlate, .aggregate = tegra234_mc_icc_aggregate, .get_bw = tegra234_mc_icc_get_init_bw, diff --git a/drivers/memory/tegra/tegra238.c b/drivers/memory/tegra/tegra238.c new file mode 100644 index 000000000000..938c8985600e --- /dev/null +++ b/drivers/memory/tegra/tegra238.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026, NVIDIA CORPORATION. All rights reserved. + */ + +#include + +#include +#include +#include +#include + +#include +#include "mc.h" + +static const struct tegra_mc_client tegra238_mc_clients[] = { + { + .id = TEGRA234_MEMORY_CLIENT_HDAR, + .name = "hdar", + .bpmp_id = TEGRA_ICC_BPMP_HDA, + .type = TEGRA_ICC_ISO_AUDIO, + .sid = TEGRA238_SID_HDA, + .regs = { + .sid = { + .override = 0xa8, + .security = 0xac, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_HDAW, + .name = "hdaw", + .bpmp_id = TEGRA_ICC_BPMP_HDA, + .type = TEGRA_ICC_ISO_AUDIO, + .sid = TEGRA238_SID_HDA, + .regs = { + .sid = { + .override = 0x1a8, + .security = 0x1ac, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_SDMMCRAB, + .name = "sdmmcrab", + .bpmp_id = TEGRA_ICC_BPMP_SDMMC_4, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_SDMMC4A, + .regs = { + .sid = { + .override = 0x318, + .security = 0x31c, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_SDMMCWAB, + .name = "sdmmcwab", + .bpmp_id = TEGRA_ICC_BPMP_SDMMC_4, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_SDMMC4A, + .regs = { + .sid = { + .override = 0x338, + .security = 0x33c, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_APER, + .name = "aper", + .bpmp_id = TEGRA_ICC_BPMP_APE, + .type = TEGRA_ICC_ISO_AUDIO, + .sid = TEGRA238_SID_ISO_APE0, + .regs = { + .sid = { + .override = 0x3d0, + .security = 0x3d4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_APEW, + .name = "apew", + .bpmp_id = TEGRA_ICC_BPMP_APE, + .type = TEGRA_ICC_ISO_AUDIO, + .sid = TEGRA238_SID_ISO_APE0, + .regs = { + .sid = { + .override = 0x3d8, + .security = 0x3dc, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVDISPLAYR, + .name = "nvdisplayr", + .bpmp_id = TEGRA_ICC_BPMP_DISPLAY, + .type = TEGRA_ICC_ISO_DISPLAY, + .sid = TEGRA238_SID_ISO_NVDISPLAY, + .regs = { + .sid = { + .override = 0x490, + .security = 0x494, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVDISPLAYR1, + .name = "nvdisplayr1", + .bpmp_id = TEGRA_ICC_BPMP_DISPLAY, + .type = TEGRA_ICC_ISO_DISPLAY, + .sid = TEGRA238_SID_ISO_NVDISPLAY, + .regs = { + .sid = { + .override = 0x508, + .security = 0x50c, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_BPMPR, + .name = "bpmpr", + .sid = TEGRA238_SID_BPMP, + .regs = { + .sid = { + .override = 0x498, + .security = 0x49c, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_BPMPW, + .name = "bpmpw", + .sid = TEGRA238_SID_BPMP, + .regs = { + .sid = { + .override = 0x4a0, + .security = 0x4a4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_BPMPDMAR, + .name = "bpmpdmar", + .sid = TEGRA238_SID_BPMP, + .regs = { + .sid = { + .override = 0x4a8, + .security = 0x4ac, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_BPMPDMAW, + .name = "bpmpdmaw", + .sid = TEGRA238_SID_BPMP, + .regs = { + .sid = { + .override = 0x4b0, + .security = 0x4b4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_APEDMAR, + .name = "apedmar", + .bpmp_id = TEGRA_ICC_BPMP_APEDMA, + .type = TEGRA_ICC_ISO_AUDIO, + .sid = TEGRA238_SID_ISO_APE1, + .regs = { + .sid = { + .override = 0x4f8, + .security = 0x4fc, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_APEDMAW, + .name = "apedmaw", + .bpmp_id = TEGRA_ICC_BPMP_APEDMA, + .type = TEGRA_ICC_ISO_AUDIO, + .sid = TEGRA238_SID_ISO_APE1, + .regs = { + .sid = { + .override = 0x500, + .security = 0x504, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_VICSRD, + .name = "vicsrd", + .bpmp_id = TEGRA_ICC_BPMP_VIC, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_VIC, + .regs = { + .sid = { + .override = 0x360, + .security = 0x364, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_VICSWR, + .name = "vicswr", + .bpmp_id = TEGRA_ICC_BPMP_VIC, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_VIC, + .regs = { + .sid = { + .override = 0x368, + .security = 0x36c, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVDECSRD, + .name = "nvdecsrd", + .bpmp_id = TEGRA_ICC_BPMP_NVDEC, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_NVDEC, + .regs = { + .sid = { + .override = 0x3c0, + .security = 0x3c4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVDECSWR, + .name = "nvdecswr", + .bpmp_id = TEGRA_ICC_BPMP_NVDEC, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_NVDEC, + .regs = { + .sid = { + .override = 0x3c8, + .security = 0x3cc, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVENCSRD, + .name = "nvencsrd", + .bpmp_id = TEGRA_ICC_BPMP_NVENC, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_NVENC, + .regs = { + .sid = { + .override = 0xe0, + .security = 0xe4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVENCSWR, + .name = "nvencswr", + .bpmp_id = TEGRA_ICC_BPMP_NVENC, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_NVENC, + .regs = { + .sid = { + .override = 0x158, + .security = 0x15c, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE0R, + .name = "pcie0r", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_0, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE0, + .regs = { + .sid = { + .override = 0x6c0, + .security = 0x6c4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE0W, + .name = "pcie0w", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_0, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE0, + .regs = { + .sid = { + .override = 0x6c8, + .security = 0x6cc, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE1R, + .name = "pcie1r", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_1, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE1, + .regs = { + .sid = { + .override = 0x6d0, + .security = 0x6d4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE1W, + .name = "pcie1w", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_1, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE1, + .regs = { + .sid = { + .override = 0x6d8, + .security = 0x6dc, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE2AR, + .name = "pcie2ar", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_2, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE2, + .regs = { + .sid = { + .override = 0x6e0, + .security = 0x6e4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE2AW, + .name = "pcie2aw", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_2, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE2, + .regs = { + .sid = { + .override = 0x6e8, + .security = 0x6ec, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE3R, + .name = "pcie3r", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_3, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE3, + .regs = { + .sid = { + .override = 0x6f0, + .security = 0x6f4, + }, + }, + }, { + .id = TEGRA234_MEMORY_CLIENT_PCIE3W, + .name = "pcie3w", + .bpmp_id = TEGRA_ICC_BPMP_PCIE_3, + .type = TEGRA_ICC_NISO, + .sid = TEGRA238_SID_PCIE3, + .regs = { + .sid = { + .override = 0x6f8, + .security = 0x6fc, + }, + }, + }, { + .id = TEGRA_ICC_MC_CPU_CLUSTER0, + .name = "sw_cluster0", + .bpmp_id = TEGRA_ICC_BPMP_CPU_CLUSTER0, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVL1R, + .name = "nvl1r", + .bpmp_id = TEGRA_ICC_BPMP_GPU, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA234_MEMORY_CLIENT_NVL1W, + .name = "nvl1w", + .bpmp_id = TEGRA_ICC_BPMP_GPU, + .type = TEGRA_ICC_NISO, + } +}; + +static const struct tegra_mc_intmask tegra238_mc_intmasks[] = { + { + .reg = MC_INTMASK, + .mask = MC_INT_DECERR_ROUTE_SANITY | MC_INT_DECERR_GENERALIZED_CARVEOUT | + MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, + }, +}; + +const struct tegra_mc_soc tegra238_mc_soc = { + .num_clients = ARRAY_SIZE(tegra238_mc_clients), + .clients = tegra238_mc_clients, + .num_address_bits = 40, + .num_channels = 8, + .client_id_mask = 0x1ff, + .intmasks = tegra238_mc_intmasks, + .num_intmasks = ARRAY_SIZE(tegra238_mc_intmasks), + .has_addr_hi_reg = true, + .ops = &tegra186_mc_ops, + .icc_ops = &tegra234_mc_icc_ops, + .ch_intmask = 0x0000ff00, + .global_intstatus_channel_shift = 8, + .num_carveouts = 32, + .regs = &tegra20_mc_regs, + .handle_irq = tegra30_mc_irq_handlers, + .num_interrupts = ARRAY_SIZE(tegra30_mc_irq_handlers), + .mc_addr_hi_mask = 0x3, + .mc_err_status_type_mask = (0x7 << 28), +}; From 750fed9230ede9e43e234fbbc0d9b23c524a6c6a Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 27 Apr 2026 10:03:07 +0300 Subject: [PATCH 037/189] memory: tegra: Implement EMEM regs and ICC ops for Tegra114 Prepare Internal Memory Controller for introduction of External Memory Controller. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Mikko Perttunen Link: https://patch.msgid.link/20260427070312.81679-3-clamor95@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra114.c | 193 ++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/drivers/memory/tegra/tegra114.c b/drivers/memory/tegra/tegra114.c index 02dd4e26288a..2cb153091228 100644 --- a/drivers/memory/tegra/tegra114.c +++ b/drivers/memory/tegra/tegra114.c @@ -3,6 +3,7 @@ * Copyright (C) 2014-2026 NVIDIA CORPORATION. All rights reserved. */ +#include #include #include @@ -1101,6 +1102,195 @@ static const struct tegra_mc_reset tegra114_mc_resets[] = { TEGRA114_MC_RESET(VI, 0x200, 0x204, 17), }; +static void tegra114_mc_tune_client_latency(struct tegra_mc *mc, + const struct tegra_mc_client *client, + unsigned int bandwidth_mbytes_sec) +{ + u32 arb_tolerance_compensation_nsec, arb_tolerance_compensation_div; + unsigned int fifo_size = client->fifo_size; + u32 arb_nsec, la_ticks, value; + + /* see 20.3.1.1 Client Configuration in Tegra4 TRM v01p */ + if (bandwidth_mbytes_sec) + arb_nsec = fifo_size * NSEC_PER_USEC / bandwidth_mbytes_sec; + else + arb_nsec = U32_MAX; + + /* + * Latency allowness should be set with consideration for the module's + * latency tolerance and internal buffering capabilities. + * + * Display memory clients use isochronous transfers and have very low + * tolerance to a belated transfers. Hence we need to compensate the + * memory arbitration imperfection for them in order to prevent FIFO + * underflow condition when memory bus is busy. + * + * VI clients also need a stronger compensation. + */ + switch (client->swgroup) { + case TEGRA_SWGROUP_MPCORE: + case TEGRA_SWGROUP_PTC: + /* + * We always want lower latency for these clients, hence + * don't touch them. + */ + return; + + case TEGRA_SWGROUP_DC: + case TEGRA_SWGROUP_DCB: + arb_tolerance_compensation_nsec = 1050; + arb_tolerance_compensation_div = 2; + break; + + case TEGRA_SWGROUP_VI: + arb_tolerance_compensation_nsec = 1050; + arb_tolerance_compensation_div = 1; + break; + + default: + arb_tolerance_compensation_nsec = 150; + arb_tolerance_compensation_div = 1; + break; + } + + if (arb_nsec > arb_tolerance_compensation_nsec) + arb_nsec -= arb_tolerance_compensation_nsec; + else + arb_nsec = 0; + + arb_nsec /= arb_tolerance_compensation_div; + + /* + * Latency allowance is a number of ticks a request from a particular + * client may wait in the EMEM arbiter before it becomes a high-priority + * request. + */ + la_ticks = arb_nsec / mc->tick; + la_ticks = min(la_ticks, client->regs.la.mask); + + value = mc_readl(mc, client->regs.la.reg); + value &= ~(client->regs.la.mask << client->regs.la.shift); + value |= la_ticks << client->regs.la.shift; + mc_writel(mc, value, client->regs.la.reg); +} + +static int tegra114_mc_icc_set(struct icc_node *src, struct icc_node *dst) +{ + struct tegra_mc *mc = icc_provider_to_tegra_mc(src->provider); + const struct tegra_mc_client *client = &mc->soc->clients[src->id]; + u64 peak_bandwidth = icc_units_to_bps(src->peak_bw); + + /* + * Skip pre-initialization that is done by icc_node_add(), which sets + * bandwidth to maximum for all clients before drivers are loaded. + * + * This doesn't make sense for us because we don't have drivers for all + * clients and it's okay to keep configuration left from bootloader + * during boot, at least for today. + */ + if (src == dst) + return 0; + + /* convert bytes/sec to megabytes/sec */ + do_div(peak_bandwidth, 1000000); + + tegra114_mc_tune_client_latency(mc, client, peak_bandwidth); + + return 0; +} + +static int tegra114_mc_icc_aggreate(struct icc_node *node, u32 tag, u32 avg_bw, + u32 peak_bw, u32 *agg_avg, u32 *agg_peak) +{ + /* + * ISO clients need to reserve extra bandwidth up-front because + * there could be high bandwidth pressure during initial filling + * of the client's FIFO buffers. Secondly, we need to take into + * account impurities of the memory subsystem. + */ + if (tag & TEGRA_MC_ICC_TAG_ISO) + peak_bw = tegra_mc_scale_percents(peak_bw, 400); + + *agg_avg += avg_bw; + *agg_peak = max(*agg_peak, peak_bw); + + return 0; +} + +static struct icc_node_data * +tegra114_mc_of_icc_xlate_extended(const struct of_phandle_args *spec, void *data) +{ + struct tegra_mc *mc = icc_provider_to_tegra_mc(data); + const struct tegra_mc_client *client; + unsigned int i, idx = spec->args[0]; + struct icc_node_data *ndata; + struct icc_node *node; + + list_for_each_entry(node, &mc->provider.nodes, node_list) { + if (node->id != idx) + continue; + + ndata = kzalloc_obj(*ndata); + if (!ndata) + return ERR_PTR(-ENOMEM); + + client = &mc->soc->clients[idx]; + ndata->node = node; + + switch (client->swgroup) { + case TEGRA_SWGROUP_DC: + case TEGRA_SWGROUP_DCB: + case TEGRA_SWGROUP_PTC: + case TEGRA_SWGROUP_VI: + /* these clients are isochronous by default */ + ndata->tag = TEGRA_MC_ICC_TAG_ISO; + break; + + default: + ndata->tag = TEGRA_MC_ICC_TAG_DEFAULT; + break; + } + + return ndata; + } + + for (i = 0; i < mc->soc->num_clients; i++) { + if (mc->soc->clients[i].id == idx) + return ERR_PTR(-EPROBE_DEFER); + } + + dev_err(mc->dev, "invalid ICC client ID %u\n", idx); + + return ERR_PTR(-EINVAL); +} + +static const struct tegra_mc_icc_ops tegra114_mc_icc_ops = { + .xlate_extended = tegra114_mc_of_icc_xlate_extended, + .aggregate = tegra114_mc_icc_aggreate, + .set = tegra114_mc_icc_set, +}; + +static const unsigned long tegra114_mc_emem_regs[] = { + MC_EMEM_ARB_CFG, + MC_EMEM_ARB_OUTSTANDING_REQ, + MC_EMEM_ARB_TIMING_RCD, + MC_EMEM_ARB_TIMING_RP, + MC_EMEM_ARB_TIMING_RC, + MC_EMEM_ARB_TIMING_RAS, + MC_EMEM_ARB_TIMING_FAW, + MC_EMEM_ARB_TIMING_RRD, + MC_EMEM_ARB_TIMING_RAP2PRE, + MC_EMEM_ARB_TIMING_WAP2PRE, + MC_EMEM_ARB_TIMING_R2R, + MC_EMEM_ARB_TIMING_W2W, + MC_EMEM_ARB_TIMING_R2W, + MC_EMEM_ARB_TIMING_W2R, + MC_EMEM_ARB_DA_TURNS, + MC_EMEM_ARB_DA_COVERS, + MC_EMEM_ARB_MISC0, + MC_EMEM_ARB_RING1_THROTTLE, +}; + static const struct tegra_mc_intmask tegra114_mc_intmasks[] = { { .reg = MC_INTMASK, @@ -1116,11 +1306,14 @@ const struct tegra_mc_soc tegra114_mc_soc = { .atom_size = 32, .client_id_mask = 0x7f, .smmu = &tegra114_smmu_soc, + .emem_regs = tegra114_mc_emem_regs, + .num_emem_regs = ARRAY_SIZE(tegra114_mc_emem_regs), .intmasks = tegra114_mc_intmasks, .num_intmasks = ARRAY_SIZE(tegra114_mc_intmasks), .reset_ops = &tegra_mc_reset_ops_common, .resets = tegra114_mc_resets, .num_resets = ARRAY_SIZE(tegra114_mc_resets), + .icc_ops = &tegra114_mc_icc_ops, .ops = &tegra30_mc_ops, .regs = &tegra20_mc_regs, .handle_irq = tegra30_mc_irq_handlers, From dce208b5405f40c36320bb0ee07e9df8f7f2bea4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Mon, 27 Apr 2026 10:03:09 +0300 Subject: [PATCH 038/189] memory: tegra: Add Tegra114 EMC driver Introduce driver for the External Memory Controller (EMC) found in Tegra114 SoC. It controls the external DRAM on the board. The purpose of this driver is to program memory timing for external memory on the EMC clock rate change. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Mikko Perttunen Link: https://patch.msgid.link/20260427070312.81679-5-clamor95@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/Kconfig | 13 + drivers/memory/tegra/Makefile | 1 + drivers/memory/tegra/tegra114-emc.c | 1353 +++++++++++++++++++++++++++ 3 files changed, 1367 insertions(+) create mode 100644 drivers/memory/tegra/tegra114-emc.c diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig index 92671f9df672..cf0bfead8c45 100644 --- a/drivers/memory/tegra/Kconfig +++ b/drivers/memory/tegra/Kconfig @@ -37,6 +37,19 @@ config TEGRA30_EMC This driver is required to change memory timings / clock rate for external memory. +config TEGRA114_EMC + tristate "NVIDIA Tegra114 External Memory Controller driver" + default ARCH_TEGRA_114_SOC + depends on ARCH_TEGRA_114_SOC || COMPILE_TEST + select TEGRA124_CLK_EMC if ARCH_TEGRA + select PM_OPP + select TEGRA_EMC_COMMON + help + This driver is for the External Memory Controller (EMC) found on + Tegra114 chips. The EMC controls the external DRAM on the board. + This driver is required to change memory timings / clock rate for + external memory. + config TEGRA124_EMC tristate "NVIDIA Tegra124 External Memory Controller driver" default ARCH_TEGRA_124_SOC diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile index 218361cdaf2d..908a5046454f 100644 --- a/drivers/memory/tegra/Makefile +++ b/drivers/memory/tegra/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o obj-$(CONFIG_TEGRA_EMC_COMMON) += tegra-emc-common.o obj-$(CONFIG_TEGRA20_EMC) += tegra20-emc.o obj-$(CONFIG_TEGRA30_EMC) += tegra30-emc.o +obj-$(CONFIG_TEGRA114_EMC) += tegra114-emc.o obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o obj-$(CONFIG_TEGRA210_EMC_TABLE) += tegra210-emc-table.o obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o diff --git a/drivers/memory/tegra/tegra114-emc.c b/drivers/memory/tegra/tegra114-emc.c new file mode 100644 index 000000000000..be6c699cb656 --- /dev/null +++ b/drivers/memory/tegra/tegra114-emc.c @@ -0,0 +1,1353 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Tegra114 External Memory Controller driver + * + * Based on downstream driver from NVIDIA and tegra124-emc.c + * Copyright (C) 2011-2014 NVIDIA Corporation + * + * Copyright (C) 2024 Svyatoslav Ryhel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mc.h" +#include "tegra-emc-common.h" + +#define EMC_INTSTATUS 0x0 +#define EMC_INTSTATUS_REFRESH_OVERFLOW BIT(3) +#define EMC_INTSTATUS_CLKCHANGE_COMPLETE BIT(4) + +#define EMC_INTMASK 0x4 + +#define EMC_DBG 0x8 +#define EMC_DBG_READ_MUX_ASSEMBLY BIT(0) +#define EMC_DBG_WRITE_MUX_ACTIVE BIT(1) +#define EMC_DBG_FORCE_UPDATE BIT(2) +#define EMC_DBG_CFG_PRIORITY BIT(24) + +#define EMC_CFG 0xc +#define EMC_CFG_DSR_VTTGEN_DRV_EN BIT(18) +#define EMC_CFG_PWR_MASK ((0xF << 28) | BIT(18)) +#define EMC_CFG_DYN_SREF BIT(28) +#define EMC_CFG_DRAM_ACPD BIT(29) +#define EMC_CFG_DRAM_CLKSTOP_SR BIT(30) +#define EMC_CFG_DRAM_CLKSTOP_PD BIT(31) + +#define EMC_ADR_CFG 0x10 +#define EMC_ADR_CFG_EMEM_NUMDEV BIT(0) + +#define EMC_REFCTRL 0x20 +#define EMC_REFCTRL_DEV_SEL_SHIFT 0 +#define EMC_REFCTRL_ENABLE BIT(31) + +#define EMC_TIMING_CONTROL 0x28 +#define EMC_RC 0x2c +#define EMC_RFC 0x30 +#define EMC_RAS 0x34 +#define EMC_RP 0x38 +#define EMC_R2W 0x3c +#define EMC_W2R 0x40 +#define EMC_R2P 0x44 +#define EMC_W2P 0x48 +#define EMC_RD_RCD 0x4c +#define EMC_WR_RCD 0x50 +#define EMC_RRD 0x54 +#define EMC_REXT 0x58 +#define EMC_WDV 0x5c +#define EMC_QUSE 0x60 +#define EMC_QRST 0x64 +#define EMC_QSAFE 0x68 +#define EMC_RDV 0x6c +#define EMC_REFRESH 0x70 +#define EMC_BURST_REFRESH_NUM 0x74 +#define EMC_PDEX2WR 0x78 +#define EMC_PDEX2RD 0x7c +#define EMC_PCHG2PDEN 0x80 +#define EMC_ACT2PDEN 0x84 +#define EMC_AR2PDEN 0x88 +#define EMC_RW2PDEN 0x8c +#define EMC_TXSR 0x90 +#define EMC_TCKE 0x94 +#define EMC_TFAW 0x98 +#define EMC_TRPAB 0x9c +#define EMC_TCLKSTABLE 0xa0 +#define EMC_TCLKSTOP 0xa4 +#define EMC_TREFBW 0xa8 +#define EMC_QUSE_EXTRA 0xac +#define EMC_ODT_WRITE 0xb0 +#define EMC_ODT_READ 0xb4 +#define EMC_WEXT 0xb8 +#define EMC_CTT 0xbc +#define EMC_RFC_SLR 0xc0 +#define EMC_MRS_WAIT_CNT2 0xc4 + +#define EMC_MRS_WAIT_CNT 0xc8 +#define EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT 0 +#define EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK \ + (0x3FF << EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT) +#define EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT 16 +#define EMC_MRS_WAIT_CNT_LONG_WAIT_MASK \ + (0x3FF << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) + +#define EMC_MRS 0xcc +#define EMC_MODE_SET_DLL_RESET BIT(8) +#define EMC_MODE_SET_LONG_CNT BIT(26) +#define EMC_EMRS 0xd0 +#define EMC_REF 0xd4 +#define EMC_PRE 0xd8 + +#define EMC_SELF_REF 0xe0 +#define EMC_SELF_REF_CMD_ENABLED BIT(0) +#define EMC_SELF_REF_DEV_SEL_SHIFT 30 + +#define EMC_MRW 0xe8 + +#define EMC_MRR 0xec +#define EMC_MRR_MA_SHIFT 16 +#define LPDDR2_MR4_TEMP_SHIFT 0 + +#define EMC_XM2DQSPADCTRL3 0xf8 +#define EMC_FBIO_SPARE 0x100 + +#define EMC_FBIO_CFG5 0x104 +#define EMC_FBIO_CFG5_DRAM_TYPE_MASK 0x3 +#define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT 0 + +#define EMC_FBIO_CFG6 0x114 +#define EMC_EMRS2 0x12c +#define EMC_MRW2 0x134 +#define EMC_MRW4 0x13c +#define EMC_EINPUT 0x14c +#define EMC_EINPUT_DURATION 0x150 +#define EMC_PUTERM_EXTRA 0x154 +#define EMC_TCKESR 0x158 +#define EMC_TPD 0x15c + +#define EMC_AUTO_CAL_CONFIG 0x2a4 +#define EMC_AUTO_CAL_CONFIG_AUTO_CAL_START BIT(31) +#define EMC_AUTO_CAL_INTERVAL 0x2a8 +#define EMC_AUTO_CAL_STATUS 0x2ac +#define EMC_AUTO_CAL_STATUS_ACTIVE BIT(31) +#define EMC_STATUS 0x2b4 +#define EMC_STATUS_TIMING_UPDATE_STALLED BIT(23) + +#define EMC_CFG_2 0x2b8 +#define EMC_CFG_2_CLKCHANGE_REQ_ENABLE BIT(0) +#define EMC_CFG_2_CLKCHANGE_PD_ENABLE BIT(1) +#define EMC_CFG_2_CLKCHANGE_SR_ENABLE BIT(2) + +#define EMC_CFG_DIG_DLL 0x2bc +#define EMC_CFG_DIG_DLL_PERIOD 0x2c0 +#define EMC_RDV_MASK 0x2cc +#define EMC_WDV_MASK 0x2d0 +#define EMC_CTT_DURATION 0x2d8 +#define EMC_CTT_TERM_CTRL 0x2dc +#define EMC_ZCAL_INTERVAL 0x2e0 +#define EMC_ZCAL_WAIT_CNT 0x2e4 + +#define EMC_ZQ_CAL 0x2ec +#define EMC_ZQ_CAL_CMD BIT(0) +#define EMC_ZQ_CAL_LONG BIT(4) +#define EMC_ZQ_CAL_LONG_CMD_DEV0 \ + (DRAM_DEV_SEL_0 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD) +#define EMC_ZQ_CAL_LONG_CMD_DEV1 \ + (DRAM_DEV_SEL_1 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD) + +#define EMC_XM2CMDPADCTRL 0x2f0 +#define EMC_XM2DQSPADCTRL 0x2f8 +#define EMC_XM2DQSPADCTRL2 0x2fc +#define EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE BIT(0) +#define EMC_XM2DQSPADCTRL2_VREF_ENABLE BIT(5) +#define EMC_XM2DQPADCTRL 0x300 +#define EMC_XM2DQPADCTRL2 0x304 +#define EMC_XM2CLKPADCTRL 0x308 +#define EMC_XM2COMPPADCTRL 0x30c +#define EMC_XM2VTTGENPADCTRL 0x310 +#define EMC_XM2VTTGENPADCTRL2 0x314 +#define EMC_XM2QUSEPADCTRL 0x318 +#define EMC_XM2DQSPADCTRL4 0x320 +#define EMC_DLL_XFORM_DQS0 0x328 +#define EMC_DLL_XFORM_DQS1 0x32c +#define EMC_DLL_XFORM_DQS2 0x330 +#define EMC_DLL_XFORM_DQS3 0x334 +#define EMC_DLL_XFORM_DQS4 0x338 +#define EMC_DLL_XFORM_DQS5 0x33c +#define EMC_DLL_XFORM_DQS6 0x340 +#define EMC_DLL_XFORM_DQS7 0x344 +#define EMC_DLL_XFORM_QUSE0 0x348 +#define EMC_DLL_XFORM_QUSE1 0x34c +#define EMC_DLL_XFORM_QUSE2 0x350 +#define EMC_DLL_XFORM_QUSE3 0x354 +#define EMC_DLL_XFORM_QUSE4 0x358 +#define EMC_DLL_XFORM_QUSE5 0x35c +#define EMC_DLL_XFORM_QUSE6 0x360 +#define EMC_DLL_XFORM_QUSE7 0x364 +#define EMC_DLL_XFORM_DQ0 0x368 +#define EMC_DLL_XFORM_DQ1 0x36c +#define EMC_DLL_XFORM_DQ2 0x370 +#define EMC_DLL_XFORM_DQ3 0x374 +#define EMC_DLI_TRIM_TXDQS0 0x3a8 +#define EMC_DLI_TRIM_TXDQS1 0x3ac +#define EMC_DLI_TRIM_TXDQS2 0x3b0 +#define EMC_DLI_TRIM_TXDQS3 0x3b4 +#define EMC_DLI_TRIM_TXDQS4 0x3b8 +#define EMC_DLI_TRIM_TXDQS5 0x3bc +#define EMC_DLI_TRIM_TXDQS6 0x3c0 +#define EMC_DLI_TRIM_TXDQS7 0x3c4 +#define EMC_STALL_THEN_EXE_AFTER_CLKCHANGE 0x3cc +#define EMC_SEL_DPD_CTRL 0x3d8 +#define EMC_SEL_DPD_CTRL_DATA_SEL_DPD BIT(8) +#define EMC_SEL_DPD_CTRL_ODT_SEL_DPD BIT(5) +#define EMC_SEL_DPD_CTRL_RESET_SEL_DPD BIT(4) +#define EMC_SEL_DPD_CTRL_CA_SEL_DPD BIT(3) +#define EMC_SEL_DPD_CTRL_CLK_SEL_DPD BIT(2) +#define EMC_SEL_DPD_CTRL_DDR3_MASK \ + ((0xf << 2) | BIT(8)) +#define EMC_SEL_DPD_CTRL_MASK \ + ((0x3 << 2) | BIT(5) | BIT(8)) +#define EMC_PRE_REFRESH_REQ_CNT 0x3dc +#define EMC_DYN_SELF_REF_CONTROL 0x3e0 +#define EMC_TXSRDLL 0x3e4 +#define EMC_CCFIFO_ADDR 0x3e8 +#define EMC_CCFIFO_DATA 0x3ec +#define EMC_CCFIFO_STATUS 0x3f0 +#define EMC_CDB_CNTL_1 0x3f4 +#define EMC_CDB_CNTL_2 0x3f8 +#define EMC_XM2CLKPADCTRL2 0x3fc +#define EMC_AUTO_CAL_CONFIG2 0x458 +#define EMC_AUTO_CAL_CONFIG3 0x45c +#define EMC_IBDLY 0x468 +#define EMC_DLL_XFORM_ADDR0 0x46c +#define EMC_DLL_XFORM_ADDR1 0x470 +#define EMC_DLL_XFORM_ADDR2 0x474 +#define EMC_DSR_VTTGEN_DRV 0x47c +#define EMC_TXDSRVTTGEN 0x480 +#define EMC_XM2CMDPADCTRL4 0x484 + +#define DRAM_DEV_SEL_ALL 0 +#define DRAM_DEV_SEL_0 BIT(31) +#define DRAM_DEV_SEL_1 BIT(30) + +#define EMC_CFG_POWER_FEATURES_MASK \ + (EMC_CFG_DYN_SREF | EMC_CFG_DRAM_ACPD | EMC_CFG_DRAM_CLKSTOP_SR | \ + EMC_CFG_DRAM_CLKSTOP_PD | EMC_CFG_DSR_VTTGEN_DRV_EN) +#define EMC_REFCTRL_DEV_SEL(n) ((((n) > 1) ? 0 : 2) << EMC_REFCTRL_DEV_SEL_SHIFT) +#define EMC_DRAM_DEV_SEL(n) (((n) > 1) ? DRAM_DEV_SEL_ALL : DRAM_DEV_SEL_0) + +/* Maximum amount of time in us. to wait for changes to become effective */ +#define EMC_STATUS_UPDATE_TIMEOUT 1000 + +enum emc_dram_type { + DRAM_TYPE_DDR3, + DRAM_TYPE_DDR1, + DRAM_TYPE_LPDDR2, + DRAM_TYPE_DDR2 +}; + +enum emc_dll_change { + DLL_CHANGE_NONE, + DLL_CHANGE_ON, + DLL_CHANGE_OFF +}; + +static const unsigned long emc_burst_regs[] = { + EMC_RC, + EMC_RFC, + EMC_RAS, + EMC_RP, + EMC_R2W, + EMC_W2R, + EMC_R2P, + EMC_W2P, + EMC_RD_RCD, + EMC_WR_RCD, + EMC_RRD, + EMC_REXT, + EMC_WEXT, + EMC_WDV, + EMC_WDV_MASK, + EMC_QUSE, + EMC_IBDLY, + EMC_EINPUT, + EMC_EINPUT_DURATION, + EMC_PUTERM_EXTRA, + EMC_CDB_CNTL_1, + EMC_CDB_CNTL_2, + EMC_QRST, + EMC_QSAFE, + EMC_RDV, + EMC_RDV_MASK, + EMC_REFRESH, + EMC_BURST_REFRESH_NUM, + EMC_PRE_REFRESH_REQ_CNT, + EMC_PDEX2WR, + EMC_PDEX2RD, + EMC_PCHG2PDEN, + EMC_ACT2PDEN, + EMC_AR2PDEN, + EMC_RW2PDEN, + EMC_TXSR, + EMC_TXSRDLL, + EMC_TCKE, + EMC_TCKESR, + EMC_TPD, + EMC_TFAW, + EMC_TRPAB, + EMC_TCLKSTABLE, + EMC_TCLKSTOP, + EMC_TREFBW, + EMC_QUSE_EXTRA, + EMC_FBIO_CFG6, + EMC_ODT_WRITE, + EMC_ODT_READ, + EMC_FBIO_CFG5, + EMC_CFG_DIG_DLL, + EMC_CFG_DIG_DLL_PERIOD, + EMC_DLL_XFORM_DQS0, + EMC_DLL_XFORM_DQS1, + EMC_DLL_XFORM_DQS2, + EMC_DLL_XFORM_DQS3, + EMC_DLL_XFORM_DQS4, + EMC_DLL_XFORM_DQS5, + EMC_DLL_XFORM_DQS6, + EMC_DLL_XFORM_DQS7, + EMC_DLL_XFORM_QUSE0, + EMC_DLL_XFORM_QUSE1, + EMC_DLL_XFORM_QUSE2, + EMC_DLL_XFORM_QUSE3, + EMC_DLL_XFORM_QUSE4, + EMC_DLL_XFORM_QUSE5, + EMC_DLL_XFORM_QUSE6, + EMC_DLL_XFORM_QUSE7, + EMC_DLI_TRIM_TXDQS0, + EMC_DLI_TRIM_TXDQS1, + EMC_DLI_TRIM_TXDQS2, + EMC_DLI_TRIM_TXDQS3, + EMC_DLI_TRIM_TXDQS4, + EMC_DLI_TRIM_TXDQS5, + EMC_DLI_TRIM_TXDQS6, + EMC_DLI_TRIM_TXDQS7, + EMC_DLL_XFORM_DQ0, + EMC_DLL_XFORM_DQ1, + EMC_DLL_XFORM_DQ2, + EMC_DLL_XFORM_DQ3, + EMC_XM2CMDPADCTRL, + EMC_XM2CMDPADCTRL4, + EMC_XM2DQPADCTRL2, + EMC_XM2CLKPADCTRL, + EMC_XM2COMPPADCTRL, + EMC_XM2VTTGENPADCTRL, + EMC_XM2VTTGENPADCTRL2, + EMC_XM2DQSPADCTRL3, + EMC_XM2DQSPADCTRL4, + EMC_DSR_VTTGEN_DRV, + EMC_TXDSRVTTGEN, + EMC_FBIO_SPARE, + EMC_ZCAL_WAIT_CNT, + EMC_MRS_WAIT_CNT2, + EMC_CTT, + EMC_CTT_DURATION, + EMC_DYN_SELF_REF_CONTROL, +}; + +struct emc_timing { + unsigned long rate; + + u32 emc_burst_data[ARRAY_SIZE(emc_burst_regs)]; + + u32 emc_auto_cal_config; + u32 emc_auto_cal_config2; + u32 emc_auto_cal_config3; + u32 emc_auto_cal_interval; + u32 emc_cfg; + u32 emc_ctt_term_ctrl; + u32 emc_mode_1; + u32 emc_mode_2; + u32 emc_mode_4; + u32 emc_mode_reset; + u32 emc_mrs_wait_cnt; + u32 emc_sel_dpd_ctrl; + u32 emc_xm2dqspadctrl2; + u32 emc_zcal_cnt_long; + u32 emc_zcal_interval; +}; + +struct tegra_emc { + struct device *dev; + struct tegra_mc *mc; + void __iomem *regs; + unsigned int irq; + struct clk *clk; + + enum emc_dram_type dram_type; + unsigned int dram_num; + + struct emc_timing last_timing; + struct emc_timing *timings; + unsigned int num_timings; + + struct { + struct dentry *root; + unsigned long min_rate; + unsigned long max_rate; + } debugfs; + + struct icc_provider provider; + struct tegra_emc_rate_requests reqs; +}; + +static irqreturn_t tegra114_emc_isr(int irq, void *data) +{ + struct tegra_emc *emc = data; + u32 intmask = EMC_INTSTATUS_REFRESH_OVERFLOW; + u32 status; + + status = readl_relaxed(emc->regs + EMC_INTSTATUS) & intmask; + if (!status) + return IRQ_NONE; + + /* notify about HW problem */ + if (status & EMC_INTSTATUS_REFRESH_OVERFLOW) + dev_err_ratelimited(emc->dev, + "refresh request overflow timeout\n"); + + /* clear interrupts */ + writel_relaxed(status, emc->regs + EMC_INTSTATUS); + + return IRQ_HANDLED; +} + +/* Timing change sequence functions */ + +static void emc_ccfifo_writel(struct tegra_emc *emc, u32 value, + unsigned long offset) +{ + writel(value, emc->regs + EMC_CCFIFO_DATA); + writel(offset, emc->regs + EMC_CCFIFO_ADDR); +} + +static void emc_seq_update_timing(struct tegra_emc *emc) +{ + int ret; + u32 value; + + writel(1, emc->regs + EMC_TIMING_CONTROL); + + ret = readl_poll_timeout_atomic(emc->regs + EMC_STATUS, value, + !(value & EMC_STATUS_TIMING_UPDATE_STALLED), + 1, EMC_STATUS_UPDATE_TIMEOUT); + if (ret) + dev_err(emc->dev, "timing update timed out\n"); +} + +static void emc_seq_disable_auto_cal(struct tegra_emc *emc) +{ + int ret; + u32 value; + + writel(0, emc->regs + EMC_AUTO_CAL_INTERVAL); + + ret = readl_poll_timeout_atomic(emc->regs + EMC_AUTO_CAL_STATUS, value, + !(value & EMC_AUTO_CAL_STATUS_ACTIVE), + 1, EMC_STATUS_UPDATE_TIMEOUT); + if (ret) + dev_err(emc->dev, "auto cal disable timed out\n"); +} + +static void emc_seq_wait_clkchange(struct tegra_emc *emc) +{ + int ret; + u32 value; + + ret = readl_poll_timeout_atomic(emc->regs + EMC_INTSTATUS, value, + value & EMC_INTSTATUS_CLKCHANGE_COMPLETE, + 1, EMC_STATUS_UPDATE_TIMEOUT); + if (ret) + dev_err(emc->dev, "clock change timed out\n"); +} + +static struct emc_timing *tegra114_emc_find_timing(struct tegra_emc *emc, + unsigned long rate) +{ + struct emc_timing *timing = NULL; + unsigned int i; + + for (i = 0; i < emc->num_timings; i++) { + if (emc->timings[i].rate == rate) { + timing = &emc->timings[i]; + break; + } + } + + if (!timing) { + dev_err(emc->dev, "no timing for rate %lu\n", rate); + return NULL; + } + + return timing; +} + +static int tegra114_emc_prepare_timing_change(struct tegra_emc *emc, + unsigned long rate) +{ + struct emc_timing *timing = tegra114_emc_find_timing(emc, rate); + struct emc_timing *last = &emc->last_timing; + enum emc_dll_change dll_change; + unsigned int pre_wait = 0; + u32 val, mask; + bool next_dll_enabled = !(timing->emc_mode_1 & 0x1); + bool last_dll_enabled = !(last->emc_mode_1 & 0x1); + bool update = false; + unsigned int i; + + if (!timing) + return -ENOENT; + + if (next_dll_enabled == last_dll_enabled) + dll_change = DLL_CHANGE_NONE; + else if (next_dll_enabled) + dll_change = DLL_CHANGE_ON; + else + dll_change = DLL_CHANGE_OFF; + + /* Clear CLKCHANGE_COMPLETE interrupts */ + writel(EMC_INTSTATUS_CLKCHANGE_COMPLETE, emc->regs + EMC_INTSTATUS); + + /* Disable dynamic self-refresh */ + val = readl(emc->regs + EMC_CFG); + if (val & EMC_CFG_PWR_MASK) { + val &= ~EMC_CFG_POWER_FEATURES_MASK; + writel(val, emc->regs + EMC_CFG); + + pre_wait = 5; + } + + /* Disable SEL_DPD_CTRL for clock change */ + if (emc->dram_type == DRAM_TYPE_DDR3) + mask = EMC_SEL_DPD_CTRL_DDR3_MASK; + else + mask = EMC_SEL_DPD_CTRL_MASK; + + val = readl(emc->regs + EMC_SEL_DPD_CTRL); + if (val & mask) { + val &= ~mask; + writel(val, emc->regs + EMC_SEL_DPD_CTRL); + } + + /* Prepare DQ/DQS for clock change */ + val = readl(emc->regs + EMC_XM2DQSPADCTRL2); + if (timing->emc_xm2dqspadctrl2 & EMC_XM2DQSPADCTRL2_VREF_ENABLE && + !(val & EMC_XM2DQSPADCTRL2_VREF_ENABLE)) { + val |= EMC_XM2DQSPADCTRL2_VREF_ENABLE; + update = true; + } + + if (timing->emc_xm2dqspadctrl2 & EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE && + !(val & EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE)) { + val |= EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE; + update = true; + } + + if (update) { + writel(val, emc->regs + EMC_XM2DQSPADCTRL2); + if (pre_wait < 30) + pre_wait = 30; + } + + /* Wait to settle */ + if (pre_wait) { + emc_seq_update_timing(emc); + udelay(pre_wait); + } + + /* Program CTT_TERM control */ + if (last->emc_ctt_term_ctrl != timing->emc_ctt_term_ctrl) { + emc_seq_disable_auto_cal(emc); + writel(timing->emc_ctt_term_ctrl, + emc->regs + EMC_CTT_TERM_CTRL); + emc_seq_update_timing(emc); + } + + /* Program burst shadow registers */ + for (i = 0; i < ARRAY_SIZE(timing->emc_burst_data); ++i) + writel(timing->emc_burst_data[i], + emc->regs + emc_burst_regs[i]); + + writel(timing->emc_xm2dqspadctrl2, emc->regs + EMC_XM2DQSPADCTRL2); + writel(timing->emc_zcal_interval, emc->regs + EMC_ZCAL_INTERVAL); + + tegra_mc_write_emem_configuration(emc->mc, timing->rate); + + val = timing->emc_cfg & ~EMC_CFG_POWER_FEATURES_MASK; + emc_ccfifo_writel(emc, val, EMC_CFG); + + /* Program AUTO_CAL_CONFIG */ + if (timing->emc_auto_cal_config2 != last->emc_auto_cal_config2) + emc_ccfifo_writel(emc, timing->emc_auto_cal_config2, + EMC_AUTO_CAL_CONFIG2); + + if (timing->emc_auto_cal_config3 != last->emc_auto_cal_config3) + emc_ccfifo_writel(emc, timing->emc_auto_cal_config3, + EMC_AUTO_CAL_CONFIG3); + + if (timing->emc_auto_cal_config != last->emc_auto_cal_config) { + val = timing->emc_auto_cal_config; + val &= EMC_AUTO_CAL_CONFIG_AUTO_CAL_START; + emc_ccfifo_writel(emc, val, EMC_AUTO_CAL_CONFIG); + } + + /* DDR3: predict MRS long wait count */ + if (emc->dram_type == DRAM_TYPE_DDR3 && + dll_change == DLL_CHANGE_ON) { + u32 cnt = 512; + + if (timing->emc_zcal_interval != 0 && + last->emc_zcal_interval == 0) + cnt -= emc->dram_num * 256; + + val = (timing->emc_mrs_wait_cnt + & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK) + >> EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT; + if (cnt < val) + cnt = val; + + val = timing->emc_mrs_wait_cnt + & ~EMC_MRS_WAIT_CNT_LONG_WAIT_MASK; + val |= (cnt << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) + & EMC_MRS_WAIT_CNT_LONG_WAIT_MASK; + + writel(val, emc->regs + EMC_MRS_WAIT_CNT); + } + + /* DDR3: Turn off DLL and enter self-refresh */ + if (emc->dram_type == DRAM_TYPE_DDR3 && dll_change == DLL_CHANGE_OFF) + emc_ccfifo_writel(emc, timing->emc_mode_1, EMC_EMRS); + + /* Disable refresh controller */ + emc_ccfifo_writel(emc, EMC_REFCTRL_DEV_SEL(emc->dram_num), + EMC_REFCTRL); + if (emc->dram_type == DRAM_TYPE_DDR3) + emc_ccfifo_writel(emc, EMC_DRAM_DEV_SEL(emc->dram_num) | + EMC_SELF_REF_CMD_ENABLED, + EMC_SELF_REF); + + /* Flow control marker */ + emc_ccfifo_writel(emc, 1, EMC_STALL_THEN_EXE_AFTER_CLKCHANGE); + + /* DDR3: Exit self-refresh */ + if (emc->dram_type == DRAM_TYPE_DDR3) + emc_ccfifo_writel(emc, EMC_DRAM_DEV_SEL(emc->dram_num), + EMC_SELF_REF); + emc_ccfifo_writel(emc, EMC_REFCTRL_DEV_SEL(emc->dram_num) | + EMC_REFCTRL_ENABLE, + EMC_REFCTRL); + + /* Set DRAM mode registers */ + if (emc->dram_type == DRAM_TYPE_DDR3) { + if (timing->emc_mode_1 != last->emc_mode_1) + emc_ccfifo_writel(emc, timing->emc_mode_1, EMC_EMRS); + if (timing->emc_mode_2 != last->emc_mode_2) + emc_ccfifo_writel(emc, timing->emc_mode_2, EMC_EMRS2); + + if (timing->emc_mode_reset != last->emc_mode_reset || + dll_change == DLL_CHANGE_ON) { + val = timing->emc_mode_reset; + if (dll_change == DLL_CHANGE_ON) { + val |= EMC_MODE_SET_DLL_RESET; + val |= EMC_MODE_SET_LONG_CNT; + } else { + val &= ~EMC_MODE_SET_DLL_RESET; + } + emc_ccfifo_writel(emc, val, EMC_MRS); + } + } else { + if (timing->emc_mode_2 != last->emc_mode_2) + emc_ccfifo_writel(emc, timing->emc_mode_2, EMC_MRW2); + if (timing->emc_mode_1 != last->emc_mode_1) + emc_ccfifo_writel(emc, timing->emc_mode_1, EMC_MRW); + if (timing->emc_mode_4 != last->emc_mode_4) + emc_ccfifo_writel(emc, timing->emc_mode_4, EMC_MRW4); + } + + /* Issue ZCAL command if turning ZCAL on */ + if (timing->emc_zcal_interval != 0 && last->emc_zcal_interval == 0) { + emc_ccfifo_writel(emc, EMC_ZQ_CAL_LONG_CMD_DEV0, EMC_ZQ_CAL); + if (emc->dram_num > 1) + emc_ccfifo_writel(emc, EMC_ZQ_CAL_LONG_CMD_DEV1, + EMC_ZQ_CAL); + } + + /* Write to RO register to remove stall after change */ + emc_ccfifo_writel(emc, 0, EMC_CCFIFO_STATUS); + + /* Disable AUTO_CAL for clock change */ + emc_seq_disable_auto_cal(emc); + + /* Read register to wait until programming has settled */ + mc_readl(emc->mc, MC_EMEM_ADR_CFG); + + return 0; +} + +static void tegra114_emc_complete_timing_change(struct tegra_emc *emc, + unsigned long rate) +{ + struct emc_timing *timing = tegra114_emc_find_timing(emc, rate); + struct emc_timing *last = &emc->last_timing; + + if (!timing) + return; + + /* Wait until the state machine has settled */ + emc_seq_wait_clkchange(emc); + + /* Restore AUTO_CAL */ + if (timing->emc_ctt_term_ctrl != last->emc_ctt_term_ctrl) + writel(timing->emc_auto_cal_interval, + emc->regs + EMC_AUTO_CAL_INTERVAL); + + /* Restore dynamic self-refresh */ + if (timing->emc_cfg & EMC_CFG_PWR_MASK) + writel(timing->emc_cfg, emc->regs + EMC_CFG); + + /* Set ZCAL wait count */ + writel(timing->emc_zcal_cnt_long, emc->regs + EMC_ZCAL_WAIT_CNT); + + /* Wait for timing to settle */ + udelay(2); + + /* Reprogram SEL_DPD_CTRL */ + writel(timing->emc_sel_dpd_ctrl, emc->regs + EMC_SEL_DPD_CTRL); + emc_seq_update_timing(emc); + + emc->last_timing = *timing; +} + +/* Initialization and deinitialization */ + +static void emc_read_current_timing(struct tegra_emc *emc, + struct emc_timing *timing) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(emc_burst_regs); ++i) + timing->emc_burst_data[i] = + readl(emc->regs + emc_burst_regs[i]); + + timing->emc_cfg = readl(emc->regs + EMC_CFG); + + timing->emc_auto_cal_interval = 0; + timing->emc_zcal_cnt_long = 0; + timing->emc_mode_1 = 0; + timing->emc_mode_2 = 0; + timing->emc_mode_4 = 0; + timing->emc_mode_reset = 0; +} + +static void emc_init(struct tegra_emc *emc) +{ + u32 emc_cfg, emc_dbg; + u32 intmask = EMC_INTSTATUS_REFRESH_OVERFLOW; + const char *dram_type_str; + + emc->dram_type = readl(emc->regs + EMC_FBIO_CFG5); + + emc->dram_type &= EMC_FBIO_CFG5_DRAM_TYPE_MASK; + emc->dram_type >>= EMC_FBIO_CFG5_DRAM_TYPE_SHIFT; + + emc->dram_num = tegra_mc_get_emem_device_count(emc->mc); + + emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2); + + /* enable EMC and CAR to handshake on PLL divider/source changes */ + emc_cfg |= EMC_CFG_2_CLKCHANGE_REQ_ENABLE; + + /* configure clock change mode accordingly to DRAM type */ + if (emc->dram_type == DRAM_TYPE_LPDDR2) + emc_cfg |= EMC_CFG_2_CLKCHANGE_PD_ENABLE; + else + emc_cfg &= ~EMC_CFG_2_CLKCHANGE_PD_ENABLE; + + writel_relaxed(emc_cfg, emc->regs + EMC_CFG_2); + + /* initialize interrupt */ + writel_relaxed(intmask, emc->regs + EMC_INTMASK); + writel_relaxed(0xffffffff, emc->regs + EMC_INTSTATUS); + + /* ensure that unwanted debug features are disabled */ + emc_dbg = readl_relaxed(emc->regs + EMC_DBG); + emc_dbg |= EMC_DBG_CFG_PRIORITY; + emc_dbg &= ~EMC_DBG_READ_MUX_ASSEMBLY; + emc_dbg &= ~EMC_DBG_WRITE_MUX_ACTIVE; + emc_dbg &= ~EMC_DBG_FORCE_UPDATE; + writel_relaxed(emc_dbg, emc->regs + EMC_DBG); + + switch (emc->dram_type) { + case DRAM_TYPE_DDR1: + dram_type_str = "DDR1"; + break; + case DRAM_TYPE_LPDDR2: + dram_type_str = "LPDDR2"; + break; + case DRAM_TYPE_DDR2: + dram_type_str = "DDR2"; + break; + case DRAM_TYPE_DDR3: + dram_type_str = "DDR3"; + break; + } + + dev_info_once(emc->dev, "%u %s %s attached\n", emc->dram_num, + dram_type_str, emc->dram_num == 2 ? "devices" : "device"); + + emc_read_current_timing(emc, &emc->last_timing); +} + +static int load_one_timing_from_dt(struct tegra_emc *emc, + struct emc_timing *timing, + struct device_node *node) +{ + u32 value; + int err; + + err = of_property_read_u32(node, "clock-frequency", &value); + if (err) { + dev_err(emc->dev, "timing %pOFn: failed to read rate: %d\n", + node, err); + return err; + } + + timing->rate = value; + + err = of_property_read_u32_array(node, "nvidia,emc-configuration", + timing->emc_burst_data, + ARRAY_SIZE(timing->emc_burst_data)); + if (err) { + dev_err(emc->dev, + "timing %pOFn: failed to read emc burst data: %d\n", + node, err); + return err; + } + +#define EMC_READ_PROP(prop, dtprop) { \ + err = of_property_read_u32(node, dtprop, &timing->prop); \ + if (err) { \ + dev_err(emc->dev, "timing %pOFn: failed to read " #prop ": %d\n", \ + node, err); \ + return err; \ + } \ +} + + EMC_READ_PROP(emc_auto_cal_config, "nvidia,emc-auto-cal-config") + EMC_READ_PROP(emc_auto_cal_config2, "nvidia,emc-auto-cal-config2") + EMC_READ_PROP(emc_auto_cal_config3, "nvidia,emc-auto-cal-config3") + EMC_READ_PROP(emc_auto_cal_interval, "nvidia,emc-auto-cal-interval") + EMC_READ_PROP(emc_cfg, "nvidia,emc-cfg") + EMC_READ_PROP(emc_ctt_term_ctrl, "nvidia,emc-ctt-term-ctrl") + EMC_READ_PROP(emc_mode_1, "nvidia,emc-mode-1") + EMC_READ_PROP(emc_mode_2, "nvidia,emc-mode-2") + EMC_READ_PROP(emc_mode_4, "nvidia,emc-mode-4") + EMC_READ_PROP(emc_mode_reset, "nvidia,emc-mode-reset") + EMC_READ_PROP(emc_mrs_wait_cnt, "nvidia,emc-mrs-wait-cnt") + EMC_READ_PROP(emc_sel_dpd_ctrl, "nvidia,emc-sel-dpd-ctrl") + EMC_READ_PROP(emc_xm2dqspadctrl2, "nvidia,emc-xm2dqspadctrl2") + EMC_READ_PROP(emc_zcal_cnt_long, "nvidia,emc-zcal-cnt-long") + EMC_READ_PROP(emc_zcal_interval, "nvidia,emc-zcal-interval") + +#undef EMC_READ_PROP + + return 0; +} + +static int cmp_timings(const void *_a, const void *_b) +{ + const struct emc_timing *a = _a; + const struct emc_timing *b = _b; + + if (a->rate < b->rate) + return -1; + else if (a->rate == b->rate) + return 0; + else + return 1; +} + +static int emc_check_mc_timings(struct tegra_emc *emc) +{ + struct tegra_mc *mc = emc->mc; + unsigned int i; + + if (emc->num_timings != mc->num_timings) { + dev_err(emc->dev, "emc/mc timings number mismatch: %u %u\n", + emc->num_timings, mc->num_timings); + return -EINVAL; + } + + for (i = 0; i < mc->num_timings; i++) { + if (emc->timings[i].rate != mc->timings[i].rate) { + dev_err(emc->dev, + "emc/mc timing rate mismatch: %lu %lu\n", + emc->timings[i].rate, mc->timings[i].rate); + return -EINVAL; + } + } + + return 0; +} + +static int tegra114_emc_load_timings_from_dt(struct tegra_emc *emc, + struct device_node *node) +{ + int child_count = of_get_child_count(node); + struct emc_timing *timing; + unsigned int i = 0; + int err; + + emc->timings = devm_kcalloc(emc->dev, child_count, sizeof(*timing), + GFP_KERNEL); + if (!emc->timings) + return -ENOMEM; + + emc->num_timings = child_count; + + for_each_child_of_node_scoped(node, child) { + timing = &emc->timings[i++]; + + err = load_one_timing_from_dt(emc, timing, child); + if (err) + return err; + } + + sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings, + NULL); + + err = emc_check_mc_timings(emc); + if (err) + return err; + + dev_info_once(emc->dev, + "got %u timings for RAM code %u (min %luMHz max %luMHz)\n", + emc->num_timings, + tegra_read_ram_code(), + emc->timings[0].rate / 1000000, + emc->timings[emc->num_timings - 1].rate / 1000000); + + return 0; +} + +static struct device_node * +tegra114_emc_find_node_by_ram_code(struct device_node *node, u32 ram_code) +{ + struct device_node *np; + int err; + + for_each_child_of_node(node, np) { + u32 value; + + err = of_property_read_u32(np, "nvidia,ram-code", &value); + if (err || value != ram_code) + continue; + + return np; + } + + return NULL; +} + +/* + * debugfs interface + * + * The memory controller driver exposes some files in debugfs that can be used + * to control the EMC frequency. The top-level directory can be found here: + * + * /sys/kernel/debug/emc + * + * It contains the following files: + * + * - available_rates: This file contains a list of valid, space-separated + * EMC frequencies. + * + * - min_rate: Writing a value to this file sets the given frequency as the + * floor of the permitted range. If this is higher than the currently + * configured EMC frequency, this will cause the frequency to be + * increased so that it stays within the valid range. + * + * - max_rate: Similarly to the min_rate file, writing a value to this file + * sets the given frequency as the ceiling of the permitted range. If + * the value is lower than the currently configured EMC frequency, this + * will cause the frequency to be decreased so that it stays within the + * valid range. + */ + +static bool tegra114_emc_validate_rate(struct tegra_emc *emc, unsigned long rate) +{ + unsigned int i; + + for (i = 0; i < emc->num_timings; i++) + if (rate == emc->timings[i].rate) + return true; + + return false; +} + +static int tegra114_emc_debug_available_rates_show(struct seq_file *s, + void *data) +{ + struct tegra_emc *emc = s->private; + const char *prefix = ""; + unsigned int i; + + for (i = 0; i < emc->num_timings; i++) { + seq_printf(s, "%s%lu", prefix, emc->timings[i].rate); + prefix = " "; + } + + seq_puts(s, "\n"); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(tegra114_emc_debug_available_rates); + +static int tegra114_emc_debug_min_rate_get(void *data, u64 *rate) +{ + struct tegra_emc *emc = data; + + *rate = emc->debugfs.min_rate; + + return 0; +} + +static int tegra114_emc_debug_min_rate_set(void *data, u64 rate) +{ + struct tegra_emc *emc = data; + int err; + + if (!tegra114_emc_validate_rate(emc, rate)) + return -EINVAL; + + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); + if (err < 0) + return err; + + emc->debugfs.min_rate = rate; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(tegra114_emc_debug_min_rate_fops, + tegra114_emc_debug_min_rate_get, + tegra114_emc_debug_min_rate_set, "%llu\n"); + +static int tegra114_emc_debug_max_rate_get(void *data, u64 *rate) +{ + struct tegra_emc *emc = data; + + *rate = emc->debugfs.max_rate; + + return 0; +} + +static int tegra114_emc_debug_max_rate_set(void *data, u64 rate) +{ + struct tegra_emc *emc = data; + int err; + + if (!tegra114_emc_validate_rate(emc, rate)) + return -EINVAL; + + err = tegra_emc_set_max_rate(&emc->reqs, rate, TEGRA_EMC_RATE_DEBUG); + if (err < 0) + return err; + + emc->debugfs.max_rate = rate; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(tegra114_emc_debug_max_rate_fops, + tegra114_emc_debug_max_rate_get, + tegra114_emc_debug_max_rate_set, "%llu\n"); + +static void emc_debugfs_init(struct device *dev, struct tegra_emc *emc) +{ + unsigned int i; + int err; + + emc->debugfs.min_rate = ULONG_MAX; + emc->debugfs.max_rate = 0; + + for (i = 0; i < emc->num_timings; i++) { + if (emc->timings[i].rate < emc->debugfs.min_rate) + emc->debugfs.min_rate = emc->timings[i].rate; + + if (emc->timings[i].rate > emc->debugfs.max_rate) + emc->debugfs.max_rate = emc->timings[i].rate; + } + + if (!emc->num_timings) { + emc->debugfs.min_rate = clk_get_rate(emc->clk); + emc->debugfs.max_rate = emc->debugfs.min_rate; + } + + err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, + emc->debugfs.max_rate); + if (err < 0) { + dev_err(dev, "failed to set rate range [%lu-%lu] for %pC\n", + emc->debugfs.min_rate, emc->debugfs.max_rate, + emc->clk); + return; + } + + emc->debugfs.root = debugfs_create_dir("emc", NULL); + + debugfs_create_file("available_rates", 0444, emc->debugfs.root, emc, + &tegra114_emc_debug_available_rates_fops); + debugfs_create_file("min_rate", 0644, emc->debugfs.root, + emc, &tegra114_emc_debug_min_rate_fops); + debugfs_create_file("max_rate", 0644, emc->debugfs.root, + emc, &tegra114_emc_debug_max_rate_fops); +} + +static inline struct tegra_emc * +to_tegra_emc_provider(struct icc_provider *provider) +{ + return container_of(provider, struct tegra_emc, provider); +} + +static struct icc_node_data * +emc_of_icc_xlate_extended(const struct of_phandle_args *spec, void *data) +{ + struct icc_provider *provider = data; + struct icc_node_data *ndata; + struct icc_node *node; + + /* External Memory is the only possible ICC route */ + list_for_each_entry(node, &provider->nodes, node_list) { + if (node->id != TEGRA_ICC_EMEM) + continue; + + ndata = kzalloc_obj(*ndata); + if (!ndata) + return ERR_PTR(-ENOMEM); + + /* + * SRC and DST nodes should have matching TAG in order to have + * it set by default for a requested path. + */ + ndata->tag = TEGRA_MC_ICC_TAG_ISO; + ndata->node = node; + + return ndata; + } + + return ERR_PTR(-EPROBE_DEFER); +} + +static int emc_icc_set(struct icc_node *src, struct icc_node *dst) +{ + struct tegra_emc *emc = to_tegra_emc_provider(dst->provider); + unsigned long long peak_bw = icc_units_to_bps(dst->peak_bw); + unsigned long long avg_bw = icc_units_to_bps(dst->avg_bw); + unsigned long long rate = max(avg_bw, peak_bw); + unsigned int dram_data_bus_width_bytes = 4; + const unsigned int ddr = 2; + int err; + + /* + * Tegra114 EMC runs on a clock rate of SDRAM bus. This means that + * EMC clock rate is twice smaller than the peak data rate because + * data is sampled on both EMC clock edges. + */ + do_div(rate, ddr * dram_data_bus_width_bytes); + rate = min_t(u64, rate, U32_MAX); + + err = tegra_emc_set_min_rate(&emc->reqs, rate, TEGRA_EMC_RATE_ICC); + if (err) + return err; + + return 0; +} + +static int tegra114_emc_interconnect_init(struct tegra_emc *emc) +{ + const struct tegra_mc_soc *soc = emc->mc->soc; + struct icc_node *node; + int err; + + emc->provider.dev = emc->dev; + emc->provider.set = emc_icc_set; + emc->provider.data = &emc->provider; + emc->provider.aggregate = soc->icc_ops->aggregate; + emc->provider.xlate_extended = emc_of_icc_xlate_extended; + + icc_provider_init(&emc->provider); + + /* create External Memory Controller node */ + node = icc_node_create(TEGRA_ICC_EMC); + if (IS_ERR(node)) { + err = PTR_ERR(node); + goto err_msg; + } + + node->name = "External Memory Controller"; + icc_node_add(node, &emc->provider); + + /* link External Memory Controller to External Memory (DRAM) */ + err = icc_link_create(node, TEGRA_ICC_EMEM); + if (err) + goto remove_nodes; + + /* create External Memory node */ + node = icc_node_create(TEGRA_ICC_EMEM); + if (IS_ERR(node)) { + err = PTR_ERR(node); + goto remove_nodes; + } + + node->name = "External Memory (DRAM)"; + icc_node_add(node, &emc->provider); + + err = icc_provider_register(&emc->provider); + if (err) + goto remove_nodes; + + return 0; + +remove_nodes: + icc_nodes_remove(&emc->provider); +err_msg: + dev_err(emc->dev, "failed to initialize ICC: %d\n", err); + + return err; +} + +static void devm_tegra114_emc_unset_callback(void *data) +{ + tegra124_clk_set_emc_callbacks(NULL, NULL); +} + +static int tegra114_emc_probe(struct platform_device *pdev) +{ + struct tegra_core_opp_params opp_params = {}; + struct device *dev = &pdev->dev; + struct device_node *np; + struct tegra_emc *emc; + u32 ram_code; + int err; + + emc = devm_kzalloc(dev, sizeof(*emc), GFP_KERNEL); + if (!emc) + return -ENOMEM; + + emc->dev = dev; + + emc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(emc->regs)) + return PTR_ERR(emc->regs); + + emc->mc = devm_tegra_memory_controller_get(dev); + if (IS_ERR(emc->mc)) + return PTR_ERR(emc->mc); + + ram_code = tegra_read_ram_code(); + + np = tegra114_emc_find_node_by_ram_code(dev->of_node, ram_code); + if (np) { + err = tegra114_emc_load_timings_from_dt(emc, np); + of_node_put(np); + if (err) + return err; + } else { + dev_info_once(dev, "no memory timings for RAM code %u found in DT\n", + ram_code); + } + + emc_init(emc); + + platform_set_drvdata(pdev, emc); + + tegra124_clk_set_emc_callbacks(tegra114_emc_prepare_timing_change, + tegra114_emc_complete_timing_change); + + err = devm_add_action_or_reset(dev, devm_tegra114_emc_unset_callback, + NULL); + if (err) + return err; + + err = platform_get_irq(pdev, 0); + if (err < 0) + return err; + + emc->irq = err; + + err = devm_request_irq(dev, emc->irq, tegra114_emc_isr, 0, + dev_name(dev), emc); + if (err) + return dev_err_probe(dev, err, "failed to request irq\n"); + + emc->clk = devm_clk_get(dev, "emc"); + if (IS_ERR(emc->clk)) + return dev_err_probe(dev, PTR_ERR(emc->clk), + "failed to get EMC clock\n"); + + opp_params.init_state = true; + + err = devm_tegra_core_dev_init_opp_table(dev, &opp_params); + if (err) + return err; + + tegra_emc_rate_requests_init(&emc->reqs, dev); + + if (IS_ENABLED(CONFIG_DEBUG_FS)) + emc_debugfs_init(dev, emc); + + tegra114_emc_interconnect_init(emc); + + /* + * Don't allow the kernel module to be unloaded. Unloading adds some + * extra complexity which doesn't really worth the effort in a case of + * this driver. + */ + try_module_get(THIS_MODULE); + + return 0; +}; + +static const struct of_device_id tegra114_emc_of_match[] = { + { .compatible = "nvidia,tegra114-emc" }, + { } +}; +MODULE_DEVICE_TABLE(of, tegra114_emc_of_match); + +static struct platform_driver tegra114_emc_driver = { + .probe = tegra114_emc_probe, + .driver = { + .name = "tegra114-emc", + .of_match_table = tegra114_emc_of_match, + .suppress_bind_attrs = true, + .sync_state = icc_sync_state, + }, +}; +module_platform_driver(tegra114_emc_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("NVIDIA Tegra114 EMC driver"); +MODULE_LICENSE("GPL"); From b0ea5175358f0872ffdc9c6073585637dc01815a Mon Sep 17 00:00:00 2001 From: Judith Mendez Date: Mon, 9 Feb 2026 11:23:29 -0600 Subject: [PATCH 039/189] dt-bindings: hwinfo: ti,k3-socinfo: Add nvmem-cells support Add optional nvmem-cells and nvmem-cell-names properties to support reading silicon revision information from alternate location using NVMEM providers. This is used on AM62P to read GP_SW1 register for accurate silicon revision detection. Signed-off-by: Judith Mendez Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260209172330.53623-2-jm@ti.com Signed-off-by: Nishanth Menon --- .../devicetree/bindings/hwinfo/ti,k3-socinfo.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/devicetree/bindings/hwinfo/ti,k3-socinfo.yaml b/Documentation/devicetree/bindings/hwinfo/ti,k3-socinfo.yaml index dada28b47ea0..2900224aac74 100644 --- a/Documentation/devicetree/bindings/hwinfo/ti,k3-socinfo.yaml +++ b/Documentation/devicetree/bindings/hwinfo/ti,k3-socinfo.yaml @@ -15,6 +15,9 @@ description: | represented by CTRLMMR_xxx_JTAGID register which contains information about SoC id and revision. + On some SoCs like AM62P, the silicon revision is determined by reading + alternative registers via NVMEM cells. + properties: $nodename: pattern: "^chipid@[0-9a-f]+$" @@ -26,6 +29,14 @@ properties: reg: maxItems: 1 + nvmem-cells: + items: + - description: Alternate silicon revision register + + nvmem-cell-names: + items: + - const: gpsw1 + required: - compatible - reg From 97cfbd30525ef0df3de0681a4ca04a80a06d4f16 Mon Sep 17 00:00:00 2001 From: Judith Mendez Date: Mon, 9 Feb 2026 11:23:30 -0600 Subject: [PATCH 040/189] soc: ti: k3-socinfo: Add support for AM62P variants via NVMEM Add support for detecting AM62P silicon revisions. On AM62P, silicon revision is discovered with GP_SW1 register instead of JTAGID register. Use the NVMEM framework to read GP_SW1 from the gpsw-efuse nvmem provider to determine SoC revision. Signed-off-by: Judith Mendez Reviewed-by: Andrew Davis Link: https://patch.msgid.link/20260209172330.53623-3-jm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/k3-socinfo.c | 41 ++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/drivers/soc/ti/k3-socinfo.c b/drivers/soc/ti/k3-socinfo.c index 676041879eca..48dbef3acceb 100644 --- a/drivers/soc/ti/k3-socinfo.c +++ b/drivers/soc/ti/k3-socinfo.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -25,6 +26,8 @@ #define CTRLMMR_WKUP_JTAGID_VARIANT_SHIFT (28) #define CTRLMMR_WKUP_JTAGID_VARIANT_MASK GENMASK(31, 28) +#define GP_SW1_ADR_MASK GENMASK(3, 0) + #define CTRLMMR_WKUP_JTAGID_PARTNO_SHIFT (12) #define CTRLMMR_WKUP_JTAGID_PARTNO_MASK GENMASK(27, 12) @@ -70,6 +73,23 @@ static const char * const am62lx_rev_string_map[] = { "1.0", "1.1", }; +static const char * const am62p_gpsw_rev_string_map[] = { + "1.0", "1.1", "1.2", +}; + +static int +k3_chipinfo_get_gpsw_variant(struct device *dev) +{ + u32 gpsw_val = 0; + int ret; + + ret = nvmem_cell_read_u32(dev, "gpsw1", &gpsw_val); + if (ret) + return ret; + + return gpsw_val & GP_SW1_ADR_MASK; +} + static int k3_chipinfo_partno_to_names(unsigned int partno, struct soc_device_attribute *soc_dev_attr) @@ -86,9 +106,11 @@ k3_chipinfo_partno_to_names(unsigned int partno, } static int -k3_chipinfo_variant_to_sr(unsigned int partno, unsigned int variant, - struct soc_device_attribute *soc_dev_attr) +k3_chipinfo_variant_to_sr(struct device *dev, unsigned int partno, + unsigned int variant, struct soc_device_attribute *soc_dev_attr) { + int gpsw_variant = 0; + switch (partno) { case JTAG_ID_PARTNO_J721E: if (variant >= ARRAY_SIZE(j721e_rev_string_map)) @@ -102,6 +124,19 @@ k3_chipinfo_variant_to_sr(unsigned int partno, unsigned int variant, soc_dev_attr->revision = kasprintf(GFP_KERNEL, "SR%s", am62lx_rev_string_map[variant]); break; + case JTAG_ID_PARTNO_AM62PX: + /* Check GP_SW1 for silicon revision */ + gpsw_variant = k3_chipinfo_get_gpsw_variant(dev); + if (gpsw_variant == -EPROBE_DEFER) + return gpsw_variant; + if (gpsw_variant < 0 || gpsw_variant >= ARRAY_SIZE(am62p_gpsw_rev_string_map)) { + dev_warn(dev, "Failed to get silicon variant (%d), set SR1.0\n", + gpsw_variant); + gpsw_variant = 0; + } + soc_dev_attr->revision = kasprintf(GFP_KERNEL, "SR%s", + am62p_gpsw_rev_string_map[gpsw_variant]); + break; default: variant++; soc_dev_attr->revision = kasprintf(GFP_KERNEL, "SR%x.0", @@ -173,7 +208,7 @@ static int k3_chipinfo_probe(struct platform_device *pdev) goto err; } - ret = k3_chipinfo_variant_to_sr(partno_id, variant, soc_dev_attr); + ret = k3_chipinfo_variant_to_sr(dev, partno_id, variant, soc_dev_attr); if (ret) { dev_err(dev, "Unknown SoC SR[0x%08X]: %d\n", jtag_id, ret); goto err; From af76cdcf02b7be9277af999f0e316943d94d7fd4 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 28 Feb 2026 17:12:28 -0800 Subject: [PATCH 041/189] soc: ti: knav_dma: fix all kernel-doc warnings in knav_dma.h Use correct struct member names and formats to avoid kernel-doc warnings: Warning: include/linux/soc/ti/knav_dma.h:83 struct member 'priority' not described in 'knav_dma_tx_cfg' Warning: include/linux/soc/ti/knav_dma.h:113 struct member 'err_mode' not described in 'knav_dma_rx_cfg' Warning: include/linux/soc/ti/knav_dma.h:113 struct member 'desc_type' not described in 'knav_dma_rx_cfg' Warning: include/linux/soc/ti/knav_dma.h:113 struct member 'fdq' not described in 'knav_dma_rx_cfg' Warning: include/linux/soc/ti/knav_dma.h:127 struct member 'direction' not described in 'knav_dma_cfg' Warning: include/linux/soc/ti/knav_dma.h:127 struct member 'u' not described in 'knav_dma_cfg' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260301011228.3064940-1-rdunlap@infradead.org Signed-off-by: Nishanth Menon --- include/linux/soc/ti/knav_dma.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/include/linux/soc/ti/knav_dma.h b/include/linux/soc/ti/knav_dma.h index 18d806a8e52c..eb1e6b014eaf 100644 --- a/include/linux/soc/ti/knav_dma.h +++ b/include/linux/soc/ti/knav_dma.h @@ -75,7 +75,7 @@ enum knav_dma_desc_type { * struct knav_dma_tx_cfg: Tx channel configuration * @filt_einfo: Filter extended packet info * @filt_pswords: Filter PS words present - * @knav_dma_tx_priority: Tx channel scheduling priority + * @priority: Tx channel scheduling priority */ struct knav_dma_tx_cfg { bool filt_einfo; @@ -87,13 +87,13 @@ struct knav_dma_tx_cfg { * struct knav_dma_rx_cfg: Rx flow configuration * @einfo_present: Extended packet info present * @psinfo_present: PS words present - * @knav_dma_rx_err_mode: Error during buffer starvation - * @knav_dma_desc_type: Host or Monolithic desc + * @err_mode: Error during buffer starvation + * @desc_type: Host or Monolithic desc * @psinfo_at_sop: PS word located at start of packet * @sop_offset: Start of packet offset * @dst_q: Destination queue for a given flow * @thresh: Rx flow size threshold - * @fdq[]: Free desc Queue array + * @fdq: Free desc Queue array * @sz_thresh0: RX packet size threshold 0 * @sz_thresh1: RX packet size threshold 1 * @sz_thresh2: RX packet size threshold 2 @@ -115,7 +115,8 @@ struct knav_dma_rx_cfg { /** * struct knav_dma_cfg: Pktdma channel configuration - * @sl_cfg: Slave configuration + * @direction: DMA transfer mode and direction + * @u: union containing @tx or @rx * @tx: Tx channel configuration * @rx: Rx flow configuration */ From dc7ccfc86c1af6406c5a0aeeac122e64711ea5da Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 5 Mar 2026 09:56:44 +0800 Subject: [PATCH 042/189] firmware: arm_scmi: imx: Support getting reset reason of MISC protocol MISC protocol supports getting reset reason per Logical Machine or System. Add the API for user to retrieve the information from System Manager. Signed-off-by: Peng Fan Reviewed-by: Daniel Baluta Link: https://patch.msgid.link/20260305-scmi-imx-reset-v1-1-18de78978ba9@nxp.com Signed-off-by: Sudeep Holla --- .../arm_scmi/vendors/imx/imx-sm-misc.c | 86 +++++++++++++++++++ include/linux/scmi_imx_protocol.h | 14 +++ 2 files changed, 100 insertions(+) diff --git a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c index 0ada753367ef..637973fb45e6 100644 --- a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c +++ b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c @@ -27,6 +27,7 @@ enum scmi_imx_misc_protocol_cmd { SCMI_IMX_MISC_CTRL_GET = 0x4, SCMI_IMX_MISC_DISCOVER_BUILD_INFO = 0x6, SCMI_IMX_MISC_CTRL_NOTIFY = 0x8, + SCMI_IMX_MISC_RESET_REASON_GET = 0xA, SCMI_IMX_MISC_CFG_INFO_GET = 0xC, SCMI_IMX_MISC_SYSLOG_GET = 0xD, SCMI_IMX_MISC_BOARD_INFO = 0xE, @@ -89,6 +90,37 @@ struct scmi_imx_misc_cfg_info_out { u8 cfgname[MISC_MAX_CFGNAME]; }; +struct scmi_imx_misc_reset_reason_in { +#define MISC_REASON_FLAG_SYSTEM BIT(0) + __le32 flags; +}; + +struct scmi_imx_misc_reset_reason_out { + /* Boot reason flags */ +#define MISC_BOOT_FLAG_VLD BIT(31) +#define MISC_BOOT_FLAG_ORG_VLD BIT(28) +#define MISC_BOOT_FLAG_ORIGIN GENMASK(27, 24) +#define MISC_BOOT_FLAG_O_SHIFT 24 +#define MISC_BOOT_FLAG_ERR_VLD BIT(23) +#define MISC_BOOT_FLAG_ERR_ID GENMASK(22, 8) +#define MISC_BOOT_FLAG_E_SHIFT 8 +#define MISC_BOOT_FLAG_REASON GENMASK(7, 0) + __le32 b_flags; + /* Shutdown reason flags */ +#define MISC_SHUTDOWN_FLAG_VLD BIT(31) +#define MISC_SHUTDOWN_FLAG_EXT_LEN GENMASK(30, 29) +#define MISC_SHUTDOWN_FLAG_ORG_VLD BIT(28) +#define MISC_SHUTDOWN_FLAG_ORIGIN GENMASK(27, 24) +#define MISC_SHUTDOWN_FLAG_O_SHIFT 24 +#define MISC_SHUTDOWN_FLAG_ERR_VLD BIT(23) +#define MISC_SHUTDOWN_FLAG_ERR_ID GENMASK(22, 8) +#define MISC_SHUTDOWN_FLAG_E_SHIFT 8 +#define MISC_SHUTDOWN_FLAG_REASON GENMASK(7, 0) + __le32 s_flags; + /* Array of extended info words */ + __le32 extinfo[MISC_EXT_INFO_LEN_MAX]; +}; + struct scmi_imx_misc_syslog_in { __le32 flags; __le32 index; @@ -452,11 +484,65 @@ static int scmi_imx_misc_syslog_get(const struct scmi_protocol_handle *ph, u16 * return ph->hops->iter_response_run(iter); } +static int scmi_imx_misc_reset_reason(const struct scmi_protocol_handle *ph, bool system, + struct scmi_imx_misc_reset_reason *boot_r, + struct scmi_imx_misc_reset_reason *shut_r, + u32 *extinfo) +{ + struct scmi_imx_misc_reset_reason_in *in; + struct scmi_imx_misc_reset_reason_out *out; + struct scmi_xfer *t; + int ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_RESET_REASON_GET, sizeof(*in), + sizeof(*out), &t); + if (ret) + return ret; + + in = t->tx.buf; + if (system) + in->flags = le32_encode_bits(1, MISC_REASON_FLAG_SYSTEM); + else + in->flags = cpu_to_le32(0); + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + out = t->rx.buf; + if (boot_r) { + boot_r->valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_VLD); + boot_r->orig_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORG_VLD); + boot_r->err_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_VLD); + boot_r->reason = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_REASON); + boot_r->origin = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORIGIN); + boot_r->errid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_ID); + } + + if (shut_r) { + shut_r->valid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_VLD); + shut_r->orig_valid = le32_get_bits(out->s_flags, + MISC_SHUTDOWN_FLAG_ORG_VLD); + shut_r->err_valid = le32_get_bits(out->s_flags, + MISC_SHUTDOWN_FLAG_ERR_VLD); + shut_r->reason = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_REASON); + shut_r->origin = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ORIGIN); + shut_r->errid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ERR_ID); + } + + if (extinfo) + memcpy_from_le32(extinfo, out->extinfo, MISC_EXT_INFO_LEN_MAX); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + static const struct scmi_imx_misc_proto_ops scmi_imx_misc_proto_ops = { .misc_ctrl_set = scmi_imx_misc_ctrl_set, .misc_ctrl_get = scmi_imx_misc_ctrl_get, .misc_ctrl_req_notify = scmi_imx_misc_ctrl_notify, .misc_syslog = scmi_imx_misc_syslog_get, + .misc_reset_reason = scmi_imx_misc_reset_reason, }; static int scmi_imx_misc_protocol_init(const struct scmi_protocol_handle *ph) diff --git a/include/linux/scmi_imx_protocol.h b/include/linux/scmi_imx_protocol.h index 2407d7693b6b..ab867463c08c 100644 --- a/include/linux/scmi_imx_protocol.h +++ b/include/linux/scmi_imx_protocol.h @@ -52,6 +52,17 @@ struct scmi_imx_misc_ctrl_notify_report { unsigned int flags; }; + +#define MISC_EXT_INFO_LEN_MAX 4 +struct scmi_imx_misc_reset_reason { + bool valid:1; + bool orig_valid:1; + bool err_valid:1; + u32 reason; + u32 origin; + u32 errid; +}; + struct scmi_imx_misc_proto_ops { int (*misc_ctrl_set)(const struct scmi_protocol_handle *ph, u32 id, u32 num, u32 *val); @@ -61,6 +72,9 @@ struct scmi_imx_misc_proto_ops { u32 ctrl_id, u32 evt_id, u32 flags); int (*misc_syslog)(const struct scmi_protocol_handle *ph, u16 *size, void *array); + int (*misc_reset_reason)(const struct scmi_protocol_handle *ph, + bool system, struct scmi_imx_misc_reset_reason *boot_r, + struct scmi_imx_misc_reset_reason *shut_r, u32 *extinfo); }; /* See LMM_ATTRIBUTES in imx95.rst */ From c17216c1a92241d622d2208cfebf62556c818789 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 5 Mar 2026 09:56:45 +0800 Subject: [PATCH 043/189] firmware: imx: sm-misc: Print boot/shutdown reasons Add reset reason string table for i.MX95 and introduce a helper (scmi_imx_misc_get_reason) to query and print both system and LM (Logical Machine) reset reasons via the SCMI MISC protocol. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260305-scmi-imx-reset-v1-2-18de78978ba9@nxp.com Acked-by: Frank Li Signed-off-by: Sudeep Holla --- drivers/firmware/imx/sm-misc.c | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/drivers/firmware/imx/sm-misc.c b/drivers/firmware/imx/sm-misc.c index 0a8ada329c9d..16b5ff833d21 100644 --- a/drivers/firmware/imx/sm-misc.c +++ b/drivers/firmware/imx/sm-misc.c @@ -18,6 +18,29 @@ static const struct scmi_imx_misc_proto_ops *imx_misc_ctrl_ops; static struct scmi_protocol_handle *ph; struct notifier_block scmi_imx_misc_ctrl_nb; +static const char * const rst_imx95[] = { + "cm33_lockup", "cm33_swreq", "cm7_lockup", "cm7_swreq", "fccu", + "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4", + "wdog5", "jtag", "cm33_exc", "bbm", "sw", "sm_err", "fusa_sreco", + "pmic", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "por", +}; + +static const char * const rst_imx94[] = { + "cm33_lockup", "cm33_swreq", "cm70_lockup", "cm70_swreq", "fccu", + "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4", + "wdog5", "jtag", "wdog6", "wdog7", "wdog8", "wo_netc", "cm33s_lockup", + "cm33s_swreq", "cm71_lockup", "cm71_swreq", "cm33_exc", "bbm", "sw", + "sm_err", "fusa_sreco", "pmic", "unused", "unused", "unused", "por", +}; + +static const struct of_device_id allowlist[] = { + { .compatible = "fsl,imx952", .data = rst_imx95 }, + { .compatible = "fsl,imx95", .data = rst_imx95 }, + { .compatible = "fsl,imx94", .data = rst_imx94 }, + { /* Sentinel */ } +}; + int scmi_imx_misc_ctrl_set(u32 id, u32 val) { if (!ph) @@ -75,6 +98,54 @@ static void scmi_imx_misc_put(void *p) debugfs_remove((struct dentry *)p); } +static int scmi_imx_misc_get_reason(struct scmi_device *sdev) +{ + struct scmi_imx_misc_reset_reason boot, shutdown; + const char **rst; + bool system = true; + int ret; + + if (!of_machine_device_match(allowlist)) + return 0; + + rst = (const char **)of_machine_get_match_data(allowlist); + + ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL); + if (!ret) { + if (boot.valid) + dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[boot.reason], + boot.orig_valid ? boot.origin : -1, + boot.err_valid ? boot.errid : -1); + if (shutdown.valid) + dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[shutdown.reason], + shutdown.orig_valid ? shutdown.origin : -1, + shutdown.err_valid ? shutdown.errid : -1); + } else { + dev_err(&sdev->dev, "Failed to get system reset reason: %d\n", ret); + } + + system = false; + ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL); + if (!ret) { + if (boot.valid) + dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[boot.reason], + boot.orig_valid ? boot.origin : -1, + boot.err_valid ? boot.errid : -1); + if (shutdown.valid) + dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[shutdown.reason], + shutdown.orig_valid ? shutdown.origin : -1, + shutdown.err_valid ? shutdown.errid : -1); + } else { + dev_err(&sdev->dev, "Failed to get lm reset reason: %d\n", ret); + } + + return 0; +} + static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev) { const struct scmi_handle *handle = sdev->handle; @@ -133,6 +204,8 @@ static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev) scmi_imx_dentry = debugfs_create_dir("scmi_imx", NULL); debugfs_create_file("syslog", 0444, scmi_imx_dentry, &sdev->dev, &syslog_fops); + scmi_imx_misc_get_reason(sdev); + return devm_add_action_or_reset(&sdev->dev, scmi_imx_misc_put, scmi_imx_dentry); } From 0c6eb5d019c1e4b9cfcfc47000e08858bbbc5e52 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 6 Apr 2026 17:52:54 +0200 Subject: [PATCH 044/189] firmware: arm_scmi: Rename struct scmi_revision_info to scmi_base_info Rename struct scmi_revision_info to struct scmi_base_info , to accurately represent its content. The scmi_revision_info is no longer accurate, because the structure now contains more than only SCMI base protocol revision, it now also contains number of protocols, agents, vendor and subvendor strings. All those are fetched from the base protocol, so rename the structure to scmi_base_info, to match the other scmi_*_info structure names. No functional change. Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260406155343.72087-1-marek.vasut+renesas@mailbox.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/base.c | 10 +++++----- drivers/firmware/arm_scmi/common.h | 2 +- drivers/firmware/arm_scmi/driver.c | 14 +++++++------- include/linux/scmi_protocol.h | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c index cd1331c2fc40..4df2620e3c5d 100644 --- a/drivers/firmware/arm_scmi/base.c +++ b/drivers/firmware/arm_scmi/base.c @@ -69,7 +69,7 @@ static int scmi_base_attributes_get(const struct scmi_protocol_handle *ph) int ret; struct scmi_xfer *t; struct scmi_msg_resp_base_attributes *attr_info; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, sizeof(*attr_info), &t); @@ -103,7 +103,7 @@ scmi_base_vendor_id_get(const struct scmi_protocol_handle *ph, bool sub_vendor) int ret, size; char *vendor_id; struct scmi_xfer *t; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); if (sub_vendor) { cmd = BASE_DISCOVER_SUB_VENDOR; @@ -143,7 +143,7 @@ scmi_base_implementation_version_get(const struct scmi_protocol_handle *ph) int ret; __le32 *impl_ver; struct scmi_xfer *t; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_IMPLEMENT_VERSION, 0, sizeof(*impl_ver), &t); @@ -180,7 +180,7 @@ scmi_base_implementation_list_get(const struct scmi_protocol_handle *ph, __le32 *num_skip, *num_ret; u32 tot_num_ret = 0, loop_num_ret; struct device *dev = ph->dev; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_LIST_PROTOCOLS, sizeof(*num_skip), 0, &t); @@ -377,7 +377,7 @@ static int scmi_base_protocol_init(const struct scmi_protocol_handle *ph) u8 *prot_imp; char name[SCMI_SHORT_NAME_MAX_SIZE]; struct device *dev = ph->dev; - struct scmi_revision_info *rev = scmi_revision_area_get(ph); + struct scmi_base_info *rev = scmi_revision_area_get(ph); rev->major_ver = PROTOCOL_REV_MAJOR(ph->version); rev->minor_ver = PROTOCOL_REV_MINOR(ph->version); diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 7c9617d080a0..07a127dec031 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -138,7 +138,7 @@ static inline void unpack_scmi_header(u32 msg_hdr, struct scmi_msg_hdr *hdr) xfer_; \ }) -struct scmi_revision_info * +struct scmi_base_info * scmi_revision_area_get(const struct scmi_protocol_handle *ph); void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph, u8 *prot_imp); diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index f167194f7cf6..f7ee1b1495d7 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -133,7 +133,7 @@ struct scmi_protocol_instance { * usage. * @protocols_mtx: A mutex to protect protocols instances initialization. * @protocols_imp: List of protocols implemented, currently maximum of - * scmi_revision_info.num_protocols elements allocated by the + * scmi_base_info.num_protocols elements allocated by the * base protocol * @active_protocols: IDR storing device_nodes for protocols actually defined * in the DT and confirmed as implemented by fw. @@ -151,7 +151,7 @@ struct scmi_info { int id; struct device *dev; const struct scmi_desc *desc; - struct scmi_revision_info version; + struct scmi_base_info version; struct scmi_handle handle; struct scmi_xfers_info tx_minfo; struct scmi_xfers_info rx_minfo; @@ -265,7 +265,7 @@ scmi_vendor_protocol_lookup(int protocol_id, char *vendor_id, } static const struct scmi_protocol * -scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version) +scmi_vendor_protocol_get(int protocol_id, struct scmi_base_info *version) { const struct scmi_protocol *proto; @@ -303,7 +303,7 @@ scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version) } static const struct scmi_protocol * -scmi_protocol_get(int protocol_id, struct scmi_revision_info *version) +scmi_protocol_get(int protocol_id, struct scmi_base_info *version) { const struct scmi_protocol *proto = NULL; @@ -2063,7 +2063,7 @@ static const struct scmi_proto_helpers_ops helpers_ops = { * Return: A reference to the version memory area associated to the SCMI * instance underlying this protocol handle. */ -struct scmi_revision_info * +struct scmi_base_info * scmi_revision_area_get(const struct scmi_protocol_handle *ph) { const struct scmi_protocol_instance *pi = ph_to_pi(ph); @@ -2376,7 +2376,7 @@ scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id) { int i; struct scmi_info *info = handle_to_scmi_info(handle); - struct scmi_revision_info *rev = handle->version; + struct scmi_base_info *rev = handle->version; if (!info->protocols_imp) return false; @@ -3171,7 +3171,7 @@ static const struct scmi_desc *scmi_transport_setup(struct device *dev) static void scmi_enable_matching_quirks(struct scmi_info *info) { - struct scmi_revision_info *rev = &info->version; + struct scmi_base_info *rev = &info->version; dev_dbg(info->dev, "Looking for quirks matching: %s/%s/0x%08X\n", rev->vendor_id, rev->sub_vendor_id, rev->impl_ver); diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index aafaac1496b0..8d0b106caed8 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -18,7 +18,7 @@ #define SCMI_MAX_NUM_RATES 16 /** - * struct scmi_revision_info - version information structure + * struct scmi_base_info - version information structure * * @major_ver: Major ABI version. Change here implies risk of backward * compatibility break. @@ -31,7 +31,7 @@ * @vendor_id: A vendor identifier(Null terminated ASCII string) * @sub_vendor_id: A sub-vendor identifier(Null terminated ASCII string) */ -struct scmi_revision_info { +struct scmi_base_info { u16 major_ver; u16 minor_ver; u8 num_protocols; @@ -901,7 +901,7 @@ struct scmi_notify_ops { */ struct scmi_handle { struct device *dev; - struct scmi_revision_info *version; + struct scmi_base_info *version; int __must_check (*devm_protocol_acquire)(struct scmi_device *sdev, u8 proto); From 6991e5de9709b0df1f37029e5d0ada6df69aea99 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 3 Apr 2026 10:41:29 +0200 Subject: [PATCH 045/189] firmware: arm_scmi: quirk: Improve quirk range parsing When a range contains only an end ("-X"), the number string is parsed twice, as both "sep == first" and "sep != last" are true. Fix this by dropping the superfluous number parsing for "sep == first". This does have a harmless functional impact for the unbounded range: "-" is now accepted, while it was rejected before. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/fe257b3b7b7b5c17fd0e5727bb9746c731bd7e3c.1775205358.git.geert+renesas@glider.be (sudeep.holla: Initialise ret to 0 as it will be uninitialise for "-" range) Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/quirks.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/firmware/arm_scmi/quirks.c b/drivers/firmware/arm_scmi/quirks.c index 03848283c2a0..b31f23e2bbbc 100644 --- a/drivers/firmware/arm_scmi/quirks.c +++ b/drivers/firmware/arm_scmi/quirks.c @@ -219,9 +219,9 @@ static unsigned int scmi_quirk_signature(const char *vend, const char *sub_vend) static int scmi_quirk_range_parse(struct scmi_quirk *quirk) { const char *last, *first __free(kfree) = NULL; + int ret = 0; size_t len; char *sep; - int ret; quirk->start_range = 0; quirk->end_range = 0xFFFFFFFF; @@ -238,16 +238,15 @@ static int scmi_quirk_range_parse(struct scmi_quirk *quirk) if (sep) *sep = '\0'; - if (sep == first) /* -X */ - ret = kstrtouint(first + 1, 0, &quirk->end_range); - else /* X OR X- OR X-y */ + if (sep != first) /* X OR X- OR X-y */ { ret = kstrtouint(first, 0, &quirk->start_range); - if (ret) - return ret; + if (ret) + return ret; + } if (!sep) quirk->end_range = quirk->start_range; - else if (sep != last) /* x-Y */ + else if (sep != last) /* -X OR x-Y */ ret = kstrtouint(sep + 1, 0, &quirk->end_range); if (quirk->start_range > quirk->end_range) From 5be6732f820c97ab2ab20a44770a9f85fa5c7099 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 3 Apr 2026 10:41:30 +0200 Subject: [PATCH 046/189] firmware: arm_scmi: quirk: Simplify quirk table iteration The current table entry is assigned in both the init and loop expressions of the for-statement. Merge this into a single assignment in the conditional expression, to simplify the code. While at it, make the loop counter unsigned and loop-local. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/8577f4b103cf04420c3b67dcaad528daff867287.1775205358.git.geert+renesas@glider.be Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/quirks.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/firmware/arm_scmi/quirks.c b/drivers/firmware/arm_scmi/quirks.c index b31f23e2bbbc..9ed5ce4feded 100644 --- a/drivers/firmware/arm_scmi/quirks.c +++ b/drivers/firmware/arm_scmi/quirks.c @@ -258,10 +258,8 @@ static int scmi_quirk_range_parse(struct scmi_quirk *quirk) void scmi_quirks_initialize(void) { struct scmi_quirk *quirk; - int i; - for (i = 0, quirk = scmi_quirks_table[0]; quirk; - i++, quirk = scmi_quirks_table[i]) { + for (unsigned int i = 0; (quirk = scmi_quirks_table[i]); i++) { int ret; ret = scmi_quirk_range_parse(quirk); From 145728fb616a62e5a2642f2efd3829f7c8874958 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 3 Apr 2026 10:41:31 +0200 Subject: [PATCH 047/189] firmware: arm_scmi: Convert to list_for_each_entry() Simplify the loop in scmi_handle_get() by using list_for_each_entry(). Suggested-by: Marek Vasut Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/bccbd4a64ef4619afd5454e9e533073b00aeaba6.1775205358.git.geert+renesas@glider.be Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/driver.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index f7ee1b1495d7..53310beb67a8 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -2552,13 +2552,11 @@ static bool scmi_is_transport_atomic(const struct scmi_handle *handle, */ static struct scmi_handle *scmi_handle_get(struct device *dev) { - struct list_head *p; struct scmi_info *info; struct scmi_handle *handle = NULL; mutex_lock(&scmi_list_mutex); - list_for_each(p, &scmi_list) { - info = list_entry(p, struct scmi_info, node); + list_for_each_entry(info, &scmi_list, node) { if (dev->parent == info->dev) { info->users++; handle = &info->handle; From 12c8e4fe4acd19aa4f85204d07529aef205f967b Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 25 Mar 2026 10:46:19 -0700 Subject: [PATCH 048/189] soc: brcmstb: consolidate initcall functions Merge the separate early_initcall and arch_initcall functions into a single early_initcall. This is possible thanks to commit 6e12db376b60 ("base: soc: Allow early registration of a single SoC device"), which allows soc_device_register() to be called during early_initcall by deferring the actual registration until the soc_bus is ready. Replace static family_id/product_id variables with a dynamically allocated brcmstb_soc_info structure. Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Tested-by: Florian Fainelli Link: https://lore.kernel.org/r/20260325174619.3761964-1-justin.chen@broadcom.com Signed-off-by: Florian Fainelli --- drivers/soc/bcm/brcmstb/common.c | 98 +++++++++++++++++--------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/drivers/soc/bcm/brcmstb/common.c b/drivers/soc/bcm/brcmstb/common.c index 2da79bd42930..7be0374f5943 100644 --- a/drivers/soc/bcm/brcmstb/common.c +++ b/drivers/soc/bcm/brcmstb/common.c @@ -11,18 +11,22 @@ #include #include -static u32 family_id; -static u32 product_id; +struct brcmstb_soc_info { + u32 family_id; + u32 product_id; +}; + +static struct brcmstb_soc_info *soc_info; u32 brcmstb_get_family_id(void) { - return family_id; + return soc_info ? soc_info->family_id : 0; } EXPORT_SYMBOL(brcmstb_get_family_id); u32 brcmstb_get_product_id(void) { - return product_id; + return soc_info ? soc_info->product_id : 0; } EXPORT_SYMBOL(brcmstb_get_product_id); @@ -40,38 +44,11 @@ static const struct of_device_id sun_top_ctrl_match[] = { { } }; -static int __init brcmstb_soc_device_early_init(void) -{ - struct device_node *sun_top_ctrl; - void __iomem *sun_top_ctrl_base; - int ret = 0; - - /* We could be on a multi-platform kernel, don't make this fatal but - * bail out early - */ - sun_top_ctrl = of_find_matching_node(NULL, sun_top_ctrl_match); - if (!sun_top_ctrl) - return ret; - - sun_top_ctrl_base = of_iomap(sun_top_ctrl, 0); - if (!sun_top_ctrl_base) { - ret = -ENODEV; - goto out; - } - - family_id = readl(sun_top_ctrl_base); - product_id = readl(sun_top_ctrl_base + 0x4); - iounmap(sun_top_ctrl_base); -out: - of_node_put(sun_top_ctrl); - return ret; -} -early_initcall(brcmstb_soc_device_early_init); - static int __init brcmstb_soc_device_init(void) { struct soc_device_attribute *soc_dev_attr; struct device_node *sun_top_ctrl; + void __iomem *sun_top_ctrl_base; struct soc_device *soc_dev; int ret = 0; @@ -80,34 +57,61 @@ static int __init brcmstb_soc_device_init(void) */ sun_top_ctrl = of_find_matching_node(NULL, sun_top_ctrl_match); if (!sun_top_ctrl) - return ret; + return 0; + + sun_top_ctrl_base = of_iomap(sun_top_ctrl, 0); + if (!sun_top_ctrl_base) { + ret = -ENODEV; + goto out_put_node; + } + + soc_info = kzalloc(sizeof(*soc_info), GFP_KERNEL); + if (!soc_info) { + ret = -ENOMEM; + goto out_unmap; + } + + soc_info->family_id = readl(sun_top_ctrl_base); + soc_info->product_id = readl(sun_top_ctrl_base + 0x4); soc_dev_attr = kzalloc_obj(*soc_dev_attr); if (!soc_dev_attr) { ret = -ENOMEM; - goto out; + goto out_free_info; } soc_dev_attr->family = kasprintf(GFP_KERNEL, "%x", - family_id >> 28 ? - family_id >> 16 : family_id >> 8); + soc_info->family_id >> 28 ? + soc_info->family_id >> 16 : soc_info->family_id >> 8); soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%x", - product_id >> 28 ? - product_id >> 16 : product_id >> 8); + soc_info->product_id >> 28 ? + soc_info->product_id >> 16 : soc_info->product_id >> 8); soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%c%d", - ((product_id & 0xf0) >> 4) + 'A', - product_id & 0xf); + ((soc_info->product_id & 0xf0) >> 4) + 'A', + soc_info->product_id & 0xf); soc_dev = soc_device_register(soc_dev_attr); if (IS_ERR(soc_dev)) { - kfree(soc_dev_attr->family); - kfree(soc_dev_attr->soc_id); - kfree(soc_dev_attr->revision); - kfree(soc_dev_attr); - ret = -ENOMEM; + ret = PTR_ERR(soc_dev); + goto out_free_attr; } -out: + + iounmap(sun_top_ctrl_base); + of_node_put(sun_top_ctrl); + return 0; + +out_free_attr: + kfree(soc_dev_attr->revision); + kfree(soc_dev_attr->soc_id); + kfree(soc_dev_attr->family); + kfree(soc_dev_attr); +out_free_info: + kfree(soc_info); + soc_info = NULL; +out_unmap: + iounmap(sun_top_ctrl_base); +out_put_node: of_node_put(sun_top_ctrl); return ret; } -arch_initcall(brcmstb_soc_device_init); +early_initcall(brcmstb_soc_device_init); From e8f908b91550dad3274b5ab3bb0e779ca9d80aff Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Mon, 27 Apr 2026 17:10:57 +0800 Subject: [PATCH 049/189] firmware: raspberrypi: Change dependency to ARCH_BCM2835 and COMPILE_TEST The Raspberry Pi firmware driver has no compile dependencies on the BCM2835 mailbox driver. It's just a indirect runtime dependency: the driver only works on a Raspberry Pi. Change the dependency from BCM2835_MBOX to ARCH_BCM2835. Also allow compile tests. This allows drivers that have build time dependencies on this firmware driver to be compile tested as well. More dependencies are added to account for build time dependencies: - depends on MAILBOX for mailbox API (not stubbed) usage - depends on ARM || ARM64 for dsb() usage Also make it built by default if ARCH_BCM2835, since many functions will not work without this firmware driver. Signed-off-by: Chen-Yu Tsai Link: https://lore.kernel.org/r/20260427091058.2669812-1-wenst@chromium.org Signed-off-by: Florian Fainelli --- drivers/firmware/Kconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index bbd2155d8483..bcfda01c2ab8 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -114,7 +114,10 @@ config ISCSI_IBFT config RASPBERRYPI_FIRMWARE tristate "Raspberry Pi Firmware Driver" - depends on BCM2835_MBOX + depends on ARCH_BCM2835 || COMPILE_TEST + depends on ARM || ARM64 + depends on MAILBOX + default ARCH_BCM2835 help This option enables support for communicating with the firmware on the Raspberry Pi. From a390203b7eb4dc67324cbae8e10d7adcaf9b2b4b Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Mon, 4 May 2026 18:53:33 +0300 Subject: [PATCH 050/189] dt-bindings: soc: qcom: qcom,pmic-glink: Document Eliza compatible Document the compatible for the PMIC GLINK interface found on the Qualcomm Eliza SoC. It is fully compatible with the one found on SM8550, so use that as fallback. Acked-by: Rob Herring (Arm) Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20260504-eliza-bindings-pmic-glink-v2-1-d6b5397b7899@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml index ff01d2f3ee5b..411932f6b282 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml @@ -39,6 +39,7 @@ properties: - const: qcom,pmic-glink - items: - enum: + - qcom,eliza-pmic-glink - qcom,milos-pmic-glink - qcom,sm8650-pmic-glink - qcom,sm8750-pmic-glink From 7754a6b6a1424b01de9e981c8bc67b30c084d1c2 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Mon, 4 May 2026 18:56:52 +0300 Subject: [PATCH 051/189] dt-bindings: soc: qcom,aoss-qmp: Document the Eliza Always-On Subsystem side channel Document the Always-On Subsystem (AOSS) side channel found on the Qualcomm Eliza SoC. It is used for communication with other clients, like remoteprocs. Acked-by: Rob Herring (Arm) Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20260504-eliza-bindings-aoss-v2-1-c3628ca79a25@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml index c5c1bac2db01..8496d623c621 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml @@ -25,6 +25,7 @@ properties: compatible: items: - enum: + - qcom,eliza-aoss-qmp - qcom,glymur-aoss-qmp - qcom,kaanapali-aoss-qmp - qcom,milos-aoss-qmp From f2e4a121f432a1608472c4f69ac8045f33830fc4 Mon Sep 17 00:00:00 2001 From: Komal Bajaj Date: Sat, 2 May 2026 21:35:06 +0530 Subject: [PATCH 052/189] dt-bindings: cache: qcom,llcc: Document Shikra LLCC Document the Last Level Cache Controller on Shikra SoC. Signed-off-by: Komal Bajaj Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260502-shikra-llcc-binding-v1-1-1da4b1eff0a8@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/cache/qcom,llcc.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml index 995d57815781..730556184a59 100644 --- a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml +++ b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml @@ -35,6 +35,7 @@ properties: - qcom,sc8280xp-llcc - qcom,sdm670-llcc - qcom,sdm845-llcc + - qcom,shikra-llcc - qcom,sm6350-llcc - qcom,sm7150-llcc - qcom,sm8150-llcc @@ -206,6 +207,7 @@ allOf: enum: - qcom,sc7280-llcc - qcom,sdm670-llcc + - qcom,shikra-llcc then: properties: reg: From 928f6a0b1d88e526d5e8a6734e95ef20705fa98b Mon Sep 17 00:00:00 2001 From: Komal Bajaj Date: Tue, 28 Apr 2026 14:47:46 +0530 Subject: [PATCH 053/189] dt-bindings: arm: qcom,ids: Add SoC ID for Shikra IoT variants Document the IDs used by Shikra SoC IoT variants: - CQ2390M: Shikra Retail with modem - CQ2390S: Shikra Retail without modem - IQ2390S: Shikra Industrial without modem Signed-off-by: Komal Bajaj Reviewed-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260428-shikra-socid-v1-1-6ff16bad5ea2@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- include/dt-bindings/arm/qcom,ids.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/dt-bindings/arm/qcom,ids.h b/include/dt-bindings/arm/qcom,ids.h index 336f7bb7188a..c300ffb4a6a5 100644 --- a/include/dt-bindings/arm/qcom,ids.h +++ b/include/dt-bindings/arm/qcom,ids.h @@ -299,6 +299,9 @@ #define QCOM_ID_QCS615 680 #define QCOM_ID_CQ7790M 731 #define QCOM_ID_CQ7790S 732 +#define QCOM_ID_CQ2390M 756 +#define QCOM_ID_CQ2390S 758 +#define QCOM_ID_IQ2390S 759 #define QCOM_ID_IPQ5200 765 #define QCOM_ID_IPQ5210 766 #define QCOM_ID_QCF2200 767 From b828561684a217e3363c595108f5e8d560b9fb25 Mon Sep 17 00:00:00 2001 From: Komal Bajaj Date: Tue, 28 Apr 2026 14:47:47 +0530 Subject: [PATCH 054/189] soc: qcom: socinfo: Add SoC ID for Shikra IoT variants Add SoC ID for Shikra IoT variants: CQ2390M, CQ2390S and IQ2390S. Signed-off-by: Komal Bajaj Reviewed-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260428-shikra-socid-v1-2-6ff16bad5ea2@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 8ffd903ebddb..396a9c66b240 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -528,6 +528,9 @@ static const struct soc_id soc_id[] = { { qcom_board_id(QCS615) }, { qcom_board_id(CQ7790M) }, { qcom_board_id(CQ7790S) }, + { qcom_board_id(CQ2390M) }, + { qcom_board_id(CQ2390S) }, + { qcom_board_id(IQ2390S) }, { qcom_board_id(IPQ5200) }, { qcom_board_id(IPQ5210) }, { qcom_board_id(QCF2200) }, From 35466ef5db1fbdff49c4142026c4c56514d5ff47 Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Mon, 27 Apr 2026 08:59:01 +0800 Subject: [PATCH 055/189] dt-bindings: qcom: geni-se-qup: Add compatible for SA8797P SoC Document GENI Serial Engine QUP Wrapper Controller on Nord SA8797P SoC which is compatible with SA8255P one. Signed-off-by: Deepti Jaggi Signed-off-by: Shawn Guo Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260427005901.230237-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../soc/qcom/qcom,sa8255p-geni-se-qup.yaml | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,sa8255p-geni-se-qup.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,sa8255p-geni-se-qup.yaml index 352af3426d34..9c38ba59662b 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,sa8255p-geni-se-qup.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,sa8255p-geni-se-qup.yaml @@ -19,7 +19,11 @@ description: properties: compatible: - const: qcom,sa8255p-geni-se-qup + oneOf: + - const: qcom,sa8255p-geni-se-qup + - items: + - const: qcom,sa8797p-geni-se-qup + - const: qcom,sa8255p-geni-se-qup reg: description: QUP wrapper common register address and length. @@ -49,7 +53,11 @@ patternProperties: properties: compatible: - const: qcom,sa8255p-geni-spi + oneOf: + - const: qcom,sa8255p-geni-spi + - items: + - const: qcom,sa8797p-geni-spi + - const: qcom,sa8255p-geni-spi "i2c@[0-9a-f]+$": type: object @@ -58,7 +66,11 @@ patternProperties: properties: compatible: - const: qcom,sa8255p-geni-i2c + oneOf: + - const: qcom,sa8255p-geni-i2c + - items: + - const: qcom,sa8797p-geni-i2c + - const: qcom,sa8255p-geni-i2c "serial@[0-9a-f]+$": type: object @@ -67,9 +79,16 @@ patternProperties: properties: compatible: - enum: - - qcom,sa8255p-geni-uart - - qcom,sa8255p-geni-debug-uart + oneOf: + - enum: + - qcom,sa8255p-geni-uart + - qcom,sa8255p-geni-debug-uart + - items: + - const: qcom,sa8797p-geni-uart + - const: qcom,sa8255p-geni-uart + - items: + - const: qcom,sa8797p-geni-debug-uart + - const: qcom,sa8255p-geni-debug-uart required: - compatible From a1b94131042930114d0257c44362ee16bca18f4a Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Mon, 27 Apr 2026 21:08:00 +0800 Subject: [PATCH 056/189] dt-bindings: firmware: qcom,scm: Document SCM for Nord SoC Document SCM on Qualcomm Nord SoC which is compatible with 'qcom,scm'. Signed-off-by: Shawn Guo Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260427130800.271146-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/firmware/qcom,scm.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/firmware/qcom,scm.yaml b/Documentation/devicetree/bindings/firmware/qcom,scm.yaml index 7918d31f58b4..c9590ecbcd2d 100644 --- a/Documentation/devicetree/bindings/firmware/qcom,scm.yaml +++ b/Documentation/devicetree/bindings/firmware/qcom,scm.yaml @@ -49,6 +49,7 @@ properties: - qcom,scm-msm8994 - qcom,scm-msm8996 - qcom,scm-msm8998 + - qcom,scm-nord - qcom,scm-qcm2290 - qcom,scm-qcs615 - qcom,scm-qcs8300 From 75c9015d0d8b66ef878ac6c4b753175ada3f8aa5 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 7 Apr 2026 15:15:19 -0700 Subject: [PATCH 057/189] soc: qcom: wcnss: fix leak of fw The kzalloc_flex call needs to release it, not just blindly return. Also move kfree up as it is allocated after fw. Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202604060902.awXdPsBh-lkp@intel.com/ Signed-off-by: Rosen Penev Link: https://lore.kernel.org/r/20260407221519.6824-1-rosenp@gmail.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/wcnss_ctrl.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/soc/qcom/wcnss_ctrl.c b/drivers/soc/qcom/wcnss_ctrl.c index ffb31a049d4a..942e11feba65 100644 --- a/drivers/soc/qcom/wcnss_ctrl.c +++ b/drivers/soc/qcom/wcnss_ctrl.c @@ -221,8 +221,10 @@ static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc) left = fw->size; req = kzalloc_flex(*req, fragment, NV_FRAGMENT_SIZE); - if (!req) - return -ENOMEM; + if (!req) { + ret = -ENOMEM; + goto release_fw; + } req->frag_size = NV_FRAGMENT_SIZE; req->hdr.type = WCNSS_DOWNLOAD_NV_REQ; @@ -243,7 +245,7 @@ static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc) ret = rpmsg_send(wcnss->channel, req, req->hdr.len); if (ret < 0) { dev_err(dev, "failed to send smd packet\n"); - goto release_fw; + goto release_req; } /* Increment for next fragment */ @@ -262,9 +264,10 @@ static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc) ret = 0; } +release_req: + kfree(req); release_fw: release_firmware(fw); - kfree(req); return ret; } From 8baf66cc4ca9bb72e97254af62ec8306225bb2f5 Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 8 Apr 2026 15:28:34 +0530 Subject: [PATCH 058/189] dt-bindings: arm: qcom,ids: add SOC IDs for IPQ9650 family Add SoC IDs for Qualcomm's IPQ9650 family. Signed-off-by: Kathiravan Thirumoorthy Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260408-ipq9650_soc_ids-v1-1-e76faac33f77@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- include/dt-bindings/arm/qcom,ids.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/dt-bindings/arm/qcom,ids.h b/include/dt-bindings/arm/qcom,ids.h index c300ffb4a6a5..bc65e233eb93 100644 --- a/include/dt-bindings/arm/qcom,ids.h +++ b/include/dt-bindings/arm/qcom,ids.h @@ -307,6 +307,12 @@ #define QCOM_ID_QCF2200 767 #define QCOM_ID_QCF3200 768 #define QCOM_ID_QCF3210 769 +#define QCOM_ID_IPQ9620 770 +#define QCOM_ID_IPQ9650 771 +#define QCOM_ID_IPQ9610 778 +#define QCOM_ID_IPQ9630 779 +#define QCOM_ID_IPQ9640 780 +#define QCOM_ID_IPQ9670 781 /* * The board type and revision information, used by Qualcomm bootloaders and From 6fe5ea1989ad12adf181eb7872c990f9f32e89a8 Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 8 Apr 2026 15:28:35 +0530 Subject: [PATCH 059/189] soc: qcom: socinfo: add SoC ID for IPQ9650 family Add SoC IDs for Qualcomm's IPQ9650 family. Signed-off-by: Kathiravan Thirumoorthy Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260408-ipq9650_soc_ids-v1-2-e76faac33f77@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 396a9c66b240..00e56cb189cf 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -536,6 +536,12 @@ static const struct soc_id soc_id[] = { { qcom_board_id(QCF2200) }, { qcom_board_id(QCF3200) }, { qcom_board_id(QCF3210) }, + { qcom_board_id(IPQ9620) }, + { qcom_board_id(IPQ9650) }, + { qcom_board_id(IPQ9610) }, + { qcom_board_id(IPQ9630) }, + { qcom_board_id(IPQ9640) }, + { qcom_board_id(IPQ9670) }, }; static const char *socinfo_machine(struct device *dev, unsigned int id) From 79602b750b96c49e776f1fea7e7e1161831768e0 Mon Sep 17 00:00:00 2001 From: Tony Truong Date: Thu, 23 Apr 2026 16:25:41 +0530 Subject: [PATCH 060/189] soc: qcom: smem: Switch partitions to xarray The partitions array is currently statically sized and uses the remote host ID as an index. Future protocol improvements to allow for more than two hosts in a partition will require hostIDs to be bitwise significant integers. This will result in large, sparse host IDs that generally exceed the current static limit. Switch to using xarray to efficiently handle these sparse indices and allow for dynamic growth. Signed-off-by: Tony Truong Tested-by: Shawn Guo # On Nord Tested-by: Konrad Dybcio # Glymur CRD Reviewed-by: Konrad Dybcio Signed-off-by: Pranav Mahesh Phansalkar Link: https://lore.kernel.org/r/20260423-smem-v3-1-582befff289a@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/smem.c | 56 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index d5c94b47f431..afb21a778fe7 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -85,9 +85,6 @@ /* Processor/host identifier for the global partition */ #define SMEM_GLOBAL_HOST 0xfffe -/* Max number of processors/hosts in a system */ -#define SMEM_HOST_COUNT 25 - /** * struct smem_proc_comm - proc_comm communication struct (legacy) * @command: current command to be executed @@ -282,7 +279,7 @@ struct qcom_smem { struct platform_device *socinfo; struct smem_ptable *ptable; struct smem_partition global_partition; - struct smem_partition partitions[SMEM_HOST_COUNT]; + struct xarray partitions; unsigned num_regions; struct smem_region regions[] __counted_by(num_regions); @@ -382,7 +379,7 @@ static struct qcom_smem *__smem = INIT_ERR_PTR(-EPROBE_DEFER); int qcom_smem_bust_hwspin_lock_by_host(unsigned int host) { /* This function is for remote procs, so ignore SMEM_HOST_APPS */ - if (host == SMEM_HOST_APPS || host >= SMEM_HOST_COUNT) + if (host == SMEM_HOST_APPS || !xa_load(&__smem->partitions, host)) return -EINVAL; return hwspin_lock_bust(__smem->hwlock, SMEM_HOST_ID_TO_HWSPINLOCK_ID(host)); @@ -530,8 +527,8 @@ int qcom_smem_alloc(unsigned host, unsigned item, size_t size) if (ret) return ret; - if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { - part = &__smem->partitions[host]; + part = xa_load(&__smem->partitions, host); + if (part) { ret = qcom_smem_alloc_private(__smem, part, item, size); } else if (__smem->global_partition.virt_base) { part = &__smem->global_partition; @@ -697,8 +694,8 @@ void *qcom_smem_get(unsigned host, unsigned item, size_t *size) if (item >= __smem->item_count) return ERR_PTR(-EINVAL); - if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { - part = &__smem->partitions[host]; + part = xa_load(&__smem->partitions, host); + if (part) { ptr = qcom_smem_get_private(__smem, part, item, size); } else if (__smem->global_partition.virt_base) { part = &__smem->global_partition; @@ -730,8 +727,8 @@ int qcom_smem_get_free_space(unsigned host) if (IS_ERR(__smem)) return PTR_ERR(__smem); - if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { - part = &__smem->partitions[host]; + part = xa_load(&__smem->partitions, host); + if (part) { phdr = part->virt_base; ret = le32_to_cpu(phdr->offset_free_cached) - le32_to_cpu(phdr->offset_free_uncached); @@ -774,12 +771,11 @@ phys_addr_t qcom_smem_virt_to_phys(void *p) { struct smem_partition *part; struct smem_region *area; + unsigned long index; u64 offset; u32 i; - for (i = 0; i < SMEM_HOST_COUNT; i++) { - part = &__smem->partitions[i]; - + xa_for_each(&__smem->partitions, index, part) { if (addr_in_range(part->virt_base, part->size, p)) { offset = p - part->virt_base; @@ -1016,16 +1012,20 @@ static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) { struct smem_partition_header *header; + struct smem_partition *part; struct smem_ptable_entry *entry; struct smem_ptable *ptable; u16 remote_host; u16 host0, host1; + int ret; int i; ptable = qcom_smem_get_ptable(smem); if (IS_ERR(ptable)) return PTR_ERR(ptable); + xa_init(&smem->partitions); + for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { entry = &ptable->entry[i]; if (!le32_to_cpu(entry->offset)) @@ -1042,12 +1042,7 @@ qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) else continue; - if (remote_host >= SMEM_HOST_COUNT) { - dev_err(smem->dev, "bad host %u\n", remote_host); - return -EINVAL; - } - - if (smem->partitions[remote_host].virt_base) { + if (xa_load(&smem->partitions, remote_host)) { dev_err(smem->dev, "duplicate host %u\n", remote_host); return -EINVAL; } @@ -1056,11 +1051,20 @@ qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) if (!header) return -EINVAL; - smem->partitions[remote_host].virt_base = (void __iomem *)header; - smem->partitions[remote_host].phys_base = smem->regions[0].aux_base + - le32_to_cpu(entry->offset); - smem->partitions[remote_host].size = le32_to_cpu(entry->size); - smem->partitions[remote_host].cacheline = le32_to_cpu(entry->cacheline); + part = devm_kzalloc(smem->dev, sizeof(struct smem_partition), GFP_KERNEL); + if (!part) + return -ENOMEM; + + part->virt_base = (void __iomem *)header; + part->phys_base = smem->regions[0].aux_base + le32_to_cpu(entry->offset); + part->size = le32_to_cpu(entry->size); + part->cacheline = le32_to_cpu(entry->cacheline); + + ret = xa_insert(&smem->partitions, remote_host, part, GFP_KERNEL); + if (ret) { + dev_err(smem->dev, "fail to insert host %u\n", remote_host); + return ret; + } } return 0; @@ -1229,7 +1233,6 @@ static int qcom_smem_probe(struct platform_device *pdev) return -EINVAL; } - BUILD_BUG_ON(SMEM_HOST_APPS >= SMEM_HOST_COUNT); ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); if (ret < 0 && ret != -ENOENT) return ret; @@ -1249,6 +1252,7 @@ static void qcom_smem_remove(struct platform_device *pdev) { platform_device_unregister(__smem->socinfo); + xa_destroy(&__smem->partitions); /* Set to -EPROBE_DEFER to signal unprobed state */ __smem = ERR_PTR(-EPROBE_DEFER); } From 95e3c6fe707ece757a7294779781958b35b5f3b7 Mon Sep 17 00:00:00 2001 From: Raj Aryan Date: Thu, 7 May 2026 19:27:15 +0530 Subject: [PATCH 061/189] soc: qcom: spmi-pmic: add SUBTYPEs for Glymur/Kaanapali/SM8750 PMICs On Glymur, Kaanapali, and SM8750, PMIC info is not being properly populated in qcom_socinfo. Its shows `unknown` as PMIC subtypes are not updated in the socinfo. root@glymur-crd:/sys/kernel/debug/qcom_socinfo# cat pmic_model unknown (92) root@glymur-crd:/sys/kernel/debug/qcom_socinfo# cat pmic_model_array unknown (92) unknown (93) unknown (98) unknown (98) unknown (97) unknown (97) unknown (96) unknown (96) Update the SUBTYPE info for PMICs present on Glymur,Kaanapali and SM8750 boards, to fix this issue. Also, there are some PMIC subtypes present in the socinfo but not present in the spmi header file, add these entries to keep both definitions aligned. Signed-off-by: Raj Aryan Link: https://lore.kernel.org/r/20260507-fury-v1-1-d24e4bb5b774@qti.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 8 ++++++++ include/soc/qcom/qcom-spmi-pmic.h | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 00e56cb189cf..21e1bc5ca3f7 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -188,7 +188,15 @@ static const char *const pmic_models[] = { [80] = "PM7550", [82] = "PMC8380", [83] = "SMB2360", + [86] = "PM8750B", + [87] = "PMD8028", [91] = "PMIV0108", + [92] = "PMK8850", + [93] = "PMH0101", + [95] = "SMB2370", + [96] = "PMH0104", + [97] = "PMH0110", + [98] = "PMCX0102", }; struct socinfo_params { diff --git a/include/soc/qcom/qcom-spmi-pmic.h b/include/soc/qcom/qcom-spmi-pmic.h index 2cf9e2d8cd55..997fa18d70fe 100644 --- a/include/soc/qcom/qcom-spmi-pmic.h +++ b/include/soc/qcom/qcom-spmi-pmic.h @@ -50,9 +50,22 @@ #define PMR735B_SUBTYPE 0x34 #define PM6350_SUBTYPE 0x36 #define PM4125_SUBTYPE 0x37 +#define PM8010_SUBTYPE 0x41 +#define PM8550VS_SUBTYPE 0x45 +#define PM8550VE_SUBTYPE 0x46 +#define PMR735D_SUBTYPE 0x48 +#define PM8550_SUBTYPE 0x49 +#define PMK8550_SUBTYPE 0x4a #define PMM8650AU_SUBTYPE 0x4e #define PMM8650AU_PSAIL_SUBTYPE 0x4f - +#define PM8750B_SUBTYPE 0x56 +#define PMD8028_SUBTYPE 0x57 +#define PMK8850_SUBTYPE 0x5c +#define PMH0101_SUBTYPE 0x5d +#define SMB2370_SUBTYPE 0x5f +#define PMH0104_SUBTYPE 0x60 +#define PMH0110_SUBTYPE 0x61 +#define PMCX0102_SUBTYPE 0x62 #define PMI8998_FAB_ID_SMIC 0x11 #define PMI8998_FAB_ID_GF 0x30 From 17e48e7e5f18b45fd4a9411090148aae3b74f7f3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 2 Mar 2026 17:29:11 +0100 Subject: [PATCH 062/189] soc: renesas: Convert to of_machine_get_match() Use the of_machine_get_match() helper to avoid accessing of_root directly, which is planned to become private. Signed-off-by: Geert Uytterhoeven Acked-by: Viresh Kumar Link: https://patch.msgid.link/10876b30a8bdb7d1cfcc2f23fb859f2ffea335fe.1772468323.git.geert+renesas@glider.be --- drivers/soc/renesas/renesas-soc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c index 60b09020c935..dedb2a0be586 100644 --- a/drivers/soc/renesas/renesas-soc.c +++ b/drivers/soc/renesas/renesas-soc.c @@ -474,7 +474,7 @@ static int __init renesas_soc_init(void) const char *soc_id; int ret; - match = of_match_node(renesas_socs, of_root); + match = of_machine_get_match(renesas_socs); if (!match) return -ENODEV; From 004361f01625fda9f6d4009d0dc5a59e32b7be7d Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Wed, 25 Mar 2026 11:57:38 +0800 Subject: [PATCH 063/189] soc: mediatek: Use pkt_write function pointer for subsys ID compatibility Switch to pkt_write and reg_write_mask function pointers for register access, enabling compatibility with platforms regardless of subsys ID support. Signed-off-by: Jason-JH Lin Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mtk-mmsys.c | 8 +++++--- drivers/soc/mediatek/mtk-mutex.c | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/soc/mediatek/mtk-mmsys.c b/drivers/soc/mediatek/mtk-mmsys.c index bb4639ca0b8c..2f3e0778bb17 100644 --- a/drivers/soc/mediatek/mtk-mmsys.c +++ b/drivers/soc/mediatek/mtk-mmsys.c @@ -167,9 +167,11 @@ static void mtk_mmsys_update_bits(struct mtk_mmsys *mmsys, u32 offset, u32 mask, u32 tmp; if (mmsys->cmdq_base.size && cmdq_pkt) { - ret = cmdq_pkt_write_mask(cmdq_pkt, mmsys->cmdq_base.subsys, - mmsys->cmdq_base.offset + offset, val, - mask); + ret = mmsys->cmdq_base.pkt_write_mask(cmdq_pkt, + mmsys->cmdq_base.subsys, + mmsys->cmdq_base.pa_base, + mmsys->cmdq_base.offset + offset, + val, mask); if (ret) pr_debug("CMDQ unavailable: using CPU write\n"); else diff --git a/drivers/soc/mediatek/mtk-mutex.c b/drivers/soc/mediatek/mtk-mutex.c index 38179e8cd98f..eb5d381ff5af 100644 --- a/drivers/soc/mediatek/mtk-mutex.c +++ b/drivers/soc/mediatek/mtk-mutex.c @@ -990,6 +990,7 @@ int mtk_mutex_enable_by_cmdq(struct mtk_mutex *mutex, void *pkt) struct mtk_mutex_ctx *mtx = container_of(mutex, struct mtk_mutex_ctx, mutex[mutex->id]); struct cmdq_pkt *cmdq_pkt = (struct cmdq_pkt *)pkt; + dma_addr_t en_addr = mtx->addr + DISP_REG_MUTEX_EN(mutex->id); WARN_ON(&mtx->mutex[mutex->id] != mutex); @@ -998,8 +999,8 @@ int mtk_mutex_enable_by_cmdq(struct mtk_mutex *mutex, void *pkt) return -ENODEV; } - cmdq_pkt_write(cmdq_pkt, mtx->cmdq_reg.subsys, - mtx->addr + DISP_REG_MUTEX_EN(mutex->id), 1); + mtx->cmdq_reg.pkt_write(cmdq_pkt, mtx->cmdq_reg.subsys, en_addr, en_addr, 1); + return 0; } EXPORT_SYMBOL_GPL(mtk_mutex_enable_by_cmdq); From a4656aef98dd6f163c55063c36297d45912737f4 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Wed, 25 Mar 2026 11:57:39 +0800 Subject: [PATCH 064/189] soc: mediatek: mtk-cmdq: Add cmdq_pkt_jump_rel_temp() for removing shift_pa Since shift_pa will be stored into the cmdq_mobx_priv of cmdq_pkt, all the shif_pa parameters in CMDQ helper APIs can be removed. Add cmdq_pkt_jump_rel_temp() for the current users of cmdq_pkt_jump_rel(), and then remove shift_pa after all users have migrated to the new APIs. Signed-off-by: Jason-JH Lin Signed-off-by: AngeloGioacchino Del Regno --- include/linux/soc/mediatek/mtk-cmdq.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/include/linux/soc/mediatek/mtk-cmdq.h b/include/linux/soc/mediatek/mtk-cmdq.h index a06b5a61f337..03bb85462566 100644 --- a/include/linux/soc/mediatek/mtk-cmdq.h +++ b/include/linux/soc/mediatek/mtk-cmdq.h @@ -445,6 +445,24 @@ static inline int cmdq_pkt_jump(struct cmdq_pkt *pkt, dma_addr_t addr, u8 shift_ */ int cmdq_pkt_jump_rel(struct cmdq_pkt *pkt, s32 offset, u8 shift_pa); +/** + * cmdq_pkt_jump_rel_temp() - Temporary wrapper for new CMDQ helper API + * @pkt: the CMDQ packet + * @offset: relative offset of target instruction buffer from current PC. + * @shift_pa: [DEPRECATED] shift bits of physical address in CMDQ instruction. + * This value is got by cmdq_get_shift_pa(). + * + * This function is a temporary wrapper that was introduced only for ease of + * migration of the many users of the CMDQ API located in multiple kernel + * subsystems. + * + * This has to be removed after all users are migrated to the newer CMDQ API. + */ +static inline int cmdq_pkt_jump_rel_temp(struct cmdq_pkt *pkt, s32 offset, u8 shift_pa) +{ + return cmdq_pkt_jump_rel(pkt, offset, shift_pa); +} + /** * cmdq_pkt_eoc() - Append EOC and ask GCE to generate an IRQ at end of execution * @pkt: The CMDQ packet @@ -599,6 +617,12 @@ static inline int cmdq_pkt_jump_rel(struct cmdq_pkt *pkt, s32 offset, u8 shift_p return -EINVAL; } +/* This wrapper has to be removed after all users migrated to jump_rel */ +static inline int cmdq_pkt_jump_rel_temp(struct cmdq_pkt *pkt, s32 offset, u8 shift_pa) +{ + return -EINVAL; +} + static inline int cmdq_pkt_eoc(struct cmdq_pkt *pkt) { return -EINVAL; From 7d462de9f65b002b439b1b168bf3b5579b0de48b Mon Sep 17 00:00:00 2001 From: Luca Leonardo Scorcia Date: Tue, 5 May 2026 22:13:16 +0100 Subject: [PATCH 065/189] soc: mediatek: mtk-mmsys: Restore MT8167 routing masks lost during merge The original patch that was sent to the mailing lists included the values for the route masks, but they got lost during merge: add back the full register masks where missing. Fixes: 060f7875bd23 ("soc: mediatek: mmsys: Add support for MT8167 SoC") Signed-off-by: Luca Leonardo Scorcia Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: AngeloGioacchino Del Regno --- drivers/soc/mediatek/mt8167-mmsys.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/soc/mediatek/mt8167-mmsys.h b/drivers/soc/mediatek/mt8167-mmsys.h index c468926561b4..eef14083c47b 100644 --- a/drivers/soc/mediatek/mt8167-mmsys.h +++ b/drivers/soc/mediatek/mt8167-mmsys.h @@ -10,24 +10,29 @@ #define MT8167_DISP_REG_CONFIG_DISP_RDMA0_SOUT_SEL_IN 0x06c #define MT8167_DITHER_MOUT_EN_RDMA0 0x1 +#define MT8167_DITHER_MOUT_EN_MASK 0x7 + #define MT8167_RDMA0_SOUT_DSI0 0x2 +#define MT8167_RDMA0_SOUT_MASK 0x3 + #define MT8167_DSI0_SEL_IN_RDMA0 0x1 +#define MT8167_DSI0_SEL_IN_MASK 0x3 static const struct mtk_mmsys_routes mt8167_mmsys_routing_table[] = { MMSYS_ROUTE(OVL0, COLOR0, MT8167_DISP_REG_CONFIG_DISP_OVL0_MOUT_EN, OVL0_MOUT_EN_COLOR0, OVL0_MOUT_EN_COLOR0), MMSYS_ROUTE(DITHER0, RDMA0, - MT8167_DISP_REG_CONFIG_DISP_DITHER_MOUT_EN, MT8167_DITHER_MOUT_EN_RDMA0, + MT8167_DISP_REG_CONFIG_DISP_DITHER_MOUT_EN, MT8167_DITHER_MOUT_EN_MASK, MT8167_DITHER_MOUT_EN_RDMA0), MMSYS_ROUTE(OVL0, COLOR0, MT8167_DISP_REG_CONFIG_DISP_COLOR0_SEL_IN, COLOR0_SEL_IN_OVL0, COLOR0_SEL_IN_OVL0), MMSYS_ROUTE(RDMA0, DSI0, - MT8167_DISP_REG_CONFIG_DISP_DSI0_SEL_IN, MT8167_DSI0_SEL_IN_RDMA0, + MT8167_DISP_REG_CONFIG_DISP_DSI0_SEL_IN, MT8167_DSI0_SEL_IN_MASK, MT8167_DSI0_SEL_IN_RDMA0), MMSYS_ROUTE(RDMA0, DSI0, - MT8167_DISP_REG_CONFIG_DISP_RDMA0_SOUT_SEL_IN, MT8167_RDMA0_SOUT_DSI0, + MT8167_DISP_REG_CONFIG_DISP_RDMA0_SOUT_SEL_IN, MT8167_RDMA0_SOUT_MASK, MT8167_RDMA0_SOUT_DSI0), }; From e225c13b15bdc91b7ebb6d8b4474e455dddfe467 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Fri, 10 Apr 2026 12:05:13 +0200 Subject: [PATCH 066/189] soc: qcom: socinfo: Add PMICs that ship with Glymur Add the missing REVID_PERPH_SUBTYPE<->name mappings for PMICs that ship with Glymur. Signed-off-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260410-topic-glymur_pmics-v1-1-26bdbb577000@oss.qualcomm.com [bjorn: Dropped 92,93,96,97,98 as these have already been applied] Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 21e1bc5ca3f7..ef7074930a55 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -190,6 +190,7 @@ static const char *const pmic_models[] = { [83] = "SMB2360", [86] = "PM8750B", [87] = "PMD8028", + [88] = "PMC1020H", [91] = "PMIV0108", [92] = "PMK8850", [93] = "PMH0101", From d0c81a38d06d446d341532700b3a4a43d3b00eb1 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:46 +0100 Subject: [PATCH 067/189] clk: scmi: Fix clock rate rounding While the do_div() helper used for rounding expects its divisor argument to be a 32bits quantity, the currently provided divisor parameter is a 64bit value that, as a consequence, is silently truncated and a possible source of bugs. Fix by using the proper div64_ul helper. Cc: Michael Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Fixes: 7a8655e19bdb ("clk: scmi: Fix the rounding of clock rate") Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-2-cristian.marussi@arm.com Reviewed-by: Brian Masney Signed-off-by: Sudeep Holla --- drivers/clk/clk-scmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c index 6b286ea6f121..b6a12f3bc123 100644 --- a/drivers/clk/clk-scmi.c +++ b/drivers/clk/clk-scmi.c @@ -10,9 +10,9 @@ #include #include #include +#include #include #include -#include #define NOT_ATOMIC false #define ATOMIC true @@ -83,7 +83,7 @@ static int scmi_clk_determine_rate(struct clk_hw *hw, ftmp = req->rate - fmin; ftmp += clk->info->range.step_size - 1; /* to round up */ - do_div(ftmp, clk->info->range.step_size); + ftmp = div64_ul(ftmp, clk->info->range.step_size); req->rate = ftmp * clk->info->range.step_size + fmin; From ecde921eb46022acbdbfff2ad4e4c6e6d0493430 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:47 +0100 Subject: [PATCH 068/189] firmware: arm_scmi: Add clock determine_rate operation Add a clock operation to help determining the effective rate, closest to the required one, that a specific clock can support. Calculation is currently performed kernel side and the logic is taken directly from the SCMI Clock driver: embedding the determinate rate logic in the protocol layer enables simplifications in the SCMI Clock protocol interface and will more easily accommodate further evolutions where such determine_rate logic into is optionally delegated to the platform SCMI server. Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-3-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 42 +++++++++++++++++++++++++++++++ include/linux/scmi_protocol.h | 6 +++++ 2 files changed, 48 insertions(+) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index ab36871650a1..54b55517b759 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -5,6 +5,7 @@ * Copyright (C) 2018-2022 ARM Ltd. */ +#include #include #include #include @@ -624,6 +625,46 @@ static int scmi_clock_rate_set(const struct scmi_protocol_handle *ph, return ret; } +static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, + u32 clk_id, unsigned long *rate) +{ + u64 fmin, fmax, ftmp; + struct scmi_clock_info *clk; + struct clock_info *ci = ph->get_priv(ph); + + if (!rate) + return -EINVAL; + + clk = scmi_clock_domain_lookup(ci, clk_id); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + /* + * If we can't figure out what rate it will be, so just return the + * rate back to the caller. + */ + if (clk->rate_discrete) + return 0; + + fmin = clk->range.min_rate; + fmax = clk->range.max_rate; + if (*rate <= fmin) { + *rate = fmin; + return 0; + } else if (*rate >= fmax) { + *rate = fmax; + return 0; + } + + ftmp = *rate - fmin; + ftmp += clk->range.step_size - 1; /* to round up */ + ftmp = div64_ul(ftmp, clk->range.step_size); + + *rate = ftmp * clk->range.step_size + fmin; + + return 0; +} + static int scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id, enum clk_state state, @@ -936,6 +977,7 @@ static const struct scmi_clk_proto_ops clk_proto_ops = { .info_get = scmi_clock_info_get, .rate_get = scmi_clock_rate_get, .rate_set = scmi_clock_rate_set, + .determine_rate = scmi_clock_determine_rate, .enable = scmi_clock_enable, .disable = scmi_clock_disable, .state_get = scmi_clock_state_get, diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 8d0b106caed8..984117f51695 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -91,6 +91,10 @@ enum scmi_clock_oem_config { * @info_get: get the information of the specified clock * @rate_get: request the current clock rate of a clock * @rate_set: set the clock rate of a clock + * @determine_rate: determine the effective rate that can be supported by a + * clock calculating the closest allowed rate. + * Note that @rate is an input/output parameter used both to + * describe the requested rate and report the closest match * @enable: enables the specified clock * @disable: disables the specified clock * @state_get: get the status of the specified clock @@ -108,6 +112,8 @@ struct scmi_clk_proto_ops { u64 *rate); int (*rate_set)(const struct scmi_protocol_handle *ph, u32 clk_id, u64 rate); + int (*determine_rate)(const struct scmi_protocol_handle *ph, u32 clk_id, + unsigned long *rate); int (*enable)(const struct scmi_protocol_handle *ph, u32 clk_id, bool atomic); int (*disable)(const struct scmi_protocol_handle *ph, u32 clk_id, From af86c99170b771a3c763be38b01dc519501e907b Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:48 +0100 Subject: [PATCH 069/189] clk: scmi: Use new determine_rate clock operation Use the Clock protocol layer determine_rate logic to calculate the closest rate that can be supported by a specific clock. No functional change. Cc: Brian Masney Cc: Michael Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-4-cristian.marussi@arm.com Reviewed-by: Brian Masney Signed-off-by: Sudeep Holla --- drivers/clk/clk-scmi.c | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c index b6a12f3bc123..c223e4ef1dd1 100644 --- a/drivers/clk/clk-scmi.c +++ b/drivers/clk/clk-scmi.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -57,35 +56,17 @@ static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw, static int scmi_clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { - u64 fmin, fmax, ftmp; + int ret; struct scmi_clk *clk = to_scmi_clk(hw); /* - * We can't figure out what rate it will be, so just return the - * rate back to the caller. scmi_clk_recalc_rate() will be called - * after the rate is set and we'll know what rate the clock is + * If we could not get a better rate scmi_clk_recalc_rate() will be + * called after the rate is set and we'll know what rate the clock is * running at then. */ - if (clk->info->rate_discrete) - return 0; - - fmin = clk->info->range.min_rate; - fmax = clk->info->range.max_rate; - if (req->rate <= fmin) { - req->rate = fmin; - - return 0; - } else if (req->rate >= fmax) { - req->rate = fmax; - - return 0; - } - - ftmp = req->rate - fmin; - ftmp += clk->info->range.step_size - 1; /* to round up */ - ftmp = div64_ul(ftmp, clk->info->range.step_size); - - req->rate = ftmp * clk->info->range.step_size + fmin; + ret = scmi_proto_clk_ops->determine_rate(clk->ph, clk->id, &req->rate); + if (ret) + return ret; return 0; } From 0d76f62613cafecb7d326a5a45619024fa7e6e8e Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:49 +0100 Subject: [PATCH 070/189] firmware: arm_scmi: Simplify clock rates exposed interface Introduce a new internal struct scmi_clock_desc so as to be able to hide, in the future, some of the needlessly public fields currently kept inside scmi_clock_info, while keeping exposed only the two new min_rate and max_rate fields for each clock. No functional change. Reviewed-by: Peng Fan Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-5-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 158 ++++++++++++++++-------------- include/linux/scmi_protocol.h | 2 + 2 files changed, 85 insertions(+), 75 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 54b55517b759..512400122b85 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -157,13 +157,27 @@ struct scmi_clock_rate_notify_payld { __le32 rate_high; }; +struct scmi_clock_desc { + u32 id; + bool rate_discrete; + unsigned int num_rates; + u64 rates[SCMI_MAX_NUM_RATES]; +#define RATE_MIN 0 +#define RATE_MAX 1 +#define RATE_STEP 2 + struct scmi_clock_info info; +}; + +#define to_desc(p) (container_of(p, struct scmi_clock_desc, info)) + struct clock_info { int num_clocks; int max_async_req; bool notify_rate_changed_cmd; bool notify_rate_change_requested_cmd; atomic_t cur_async_req; - struct scmi_clock_info *clk; + struct scmi_clock_desc *clkds; +#define CLOCK_INFO(c, i) (&(((c)->clkds + (i))->info)) int (*clock_config_set)(const struct scmi_protocol_handle *ph, u32 clk_id, enum clk_state state, enum scmi_clock_oem_config oem_type, @@ -185,7 +199,7 @@ scmi_clock_domain_lookup(struct clock_info *ci, u32 clk_id) if (clk_id >= ci->num_clocks) return ERR_PTR(-EINVAL); - return ci->clk + clk_id; + return CLOCK_INFO(ci, clk_id); } static int @@ -226,8 +240,7 @@ scmi_clock_protocol_attributes_get(const struct scmi_protocol_handle *ph, struct scmi_clk_ipriv { struct device *dev; - u32 clk_id; - struct scmi_clock_info *clk; + struct scmi_clock_desc *clkd; }; static void iter_clk_possible_parents_prepare_message(void *message, unsigned int desc_index, @@ -236,7 +249,7 @@ static void iter_clk_possible_parents_prepare_message(void *message, unsigned in struct scmi_msg_clock_possible_parents *msg = message; const struct scmi_clk_ipriv *p = priv; - msg->id = cpu_to_le32(p->clk_id); + msg->id = cpu_to_le32(p->clkd->id); /* Set the number of OPPs to be skipped/already read */ msg->skip_parents = cpu_to_le32(desc_index); } @@ -246,7 +259,6 @@ static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st { const struct scmi_msg_resp_clock_possible_parents *r = response; struct scmi_clk_ipriv *p = priv; - struct device *dev = ((struct scmi_clk_ipriv *)p)->dev; u32 flags; flags = le32_to_cpu(r->num_parent_flags); @@ -258,12 +270,13 @@ static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st * assume it's returned+remaining on first call. */ if (!st->max_resources) { - p->clk->num_parents = st->num_returned + st->num_remaining; - p->clk->parents = devm_kcalloc(dev, p->clk->num_parents, - sizeof(*p->clk->parents), - GFP_KERNEL); - if (!p->clk->parents) { - p->clk->num_parents = 0; + p->clkd->info.num_parents = st->num_returned + st->num_remaining; + p->clkd->info.parents = devm_kcalloc(p->dev, + p->clkd->info.num_parents, + sizeof(*p->clkd->info.parents), + GFP_KERNEL); + if (!p->clkd->info.parents) { + p->clkd->info.num_parents = 0; return -ENOMEM; } st->max_resources = st->num_returned + st->num_remaining; @@ -280,29 +293,27 @@ static int iter_clk_possible_parents_process_response(const struct scmi_protocol const struct scmi_msg_resp_clock_possible_parents *r = response; struct scmi_clk_ipriv *p = priv; - u32 *parent = &p->clk->parents[st->desc_index + st->loop_idx]; + u32 *parent = &p->clkd->info.parents[st->desc_index + st->loop_idx]; *parent = le32_to_cpu(r->possible_parents[st->loop_idx]); return 0; } -static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, u32 clk_id, - struct scmi_clock_info *clk) +static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, + u32 clk_id, struct clock_info *cinfo) { struct scmi_iterator_ops ops = { .prepare_message = iter_clk_possible_parents_prepare_message, .update_state = iter_clk_possible_parents_update_state, .process_response = iter_clk_possible_parents_process_response, }; - + struct scmi_clock_desc *clkd = &cinfo->clkds[clk_id]; struct scmi_clk_ipriv ppriv = { - .clk_id = clk_id, - .clk = clk, + .clkd = clkd, .dev = ph->dev, }; void *iter; - int ret; iter = ph->hops->iter_response_init(ph, &ops, 0, CLOCK_POSSIBLE_PARENTS_GET, @@ -311,9 +322,7 @@ static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, u3 if (IS_ERR(iter)) return PTR_ERR(iter); - ret = ph->hops->iter_response_run(iter); - - return ret; + return ph->hops->iter_response_run(iter); } static int @@ -352,7 +361,7 @@ static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph, u32 attributes; struct scmi_xfer *t; struct scmi_msg_resp_clock_attributes *attr; - struct scmi_clock_info *clk = cinfo->clk + clk_id; + struct scmi_clock_info *clk = CLOCK_INFO(cinfo, clk_id); ret = ph->xops->xfer_get_init(ph, CLOCK_ATTRIBUTES, sizeof(clk_id), sizeof(*attr), &t); @@ -394,7 +403,7 @@ static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph, clk->rate_change_requested_notifications = true; if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) { if (SUPPORTS_PARENT_CLOCK(attributes)) - scmi_clock_possible_parents(ph, clk_id, clk); + scmi_clock_possible_parents(ph, clk_id, cinfo); if (SUPPORTS_GET_PERMISSIONS(attributes)) scmi_clock_get_permissions(ph, clk_id, clk); if (SUPPORTS_EXTENDED_CONFIG(attributes)) @@ -424,7 +433,7 @@ static void iter_clk_describe_prepare_message(void *message, struct scmi_msg_clock_describe_rates *msg = message; const struct scmi_clk_ipriv *p = priv; - msg->id = cpu_to_le32(p->clk_id); + msg->id = cpu_to_le32(p->clkd->id); /* Set the number of rates to be skipped/already read */ msg->rate_index = cpu_to_le32(desc_index); } @@ -457,14 +466,15 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, flags = le32_to_cpu(r->num_rates_flags); st->num_remaining = NUM_REMAINING(flags); st->num_returned = NUM_RETURNED(flags); - p->clk->rate_discrete = RATE_DISCRETE(flags); + p->clkd->rate_discrete = RATE_DISCRETE(flags); + p->clkd->info.rate_discrete = p->clkd->rate_discrete; /* Warn about out of spec replies ... */ - if (!p->clk->rate_discrete && + if (!p->clkd->rate_discrete && (st->num_returned != 3 || st->num_remaining != 0)) { dev_warn(p->dev, "Out-of-spec CLOCK_DESCRIBE_RATES reply for %s - returned:%d remaining:%d rx_len:%zd\n", - p->clk->name, st->num_returned, st->num_remaining, + p->clkd->info.name, st->num_returned, st->num_remaining, st->rx_len); SCMI_QUIRK(clock_rates_triplet_out_of_spec, @@ -479,38 +489,19 @@ iter_clk_describe_process_response(const struct scmi_protocol_handle *ph, const void *response, struct scmi_iterator_state *st, void *priv) { - int ret = 0; struct scmi_clk_ipriv *p = priv; const struct scmi_msg_resp_clock_describe_rates *r = response; - if (!p->clk->rate_discrete) { - switch (st->desc_index + st->loop_idx) { - case 0: - p->clk->range.min_rate = RATE_TO_U64(r->rate[0]); - break; - case 1: - p->clk->range.max_rate = RATE_TO_U64(r->rate[1]); - break; - case 2: - p->clk->range.step_size = RATE_TO_U64(r->rate[2]); - break; - default: - ret = -EINVAL; - break; - } - } else { - u64 *rate = &p->clk->list.rates[st->desc_index + st->loop_idx]; + p->clkd->rates[st->desc_index + st->loop_idx] = + RATE_TO_U64(r->rate[st->loop_idx]); + p->clkd->num_rates++; - *rate = RATE_TO_U64(r->rate[st->loop_idx]); - p->clk->list.num_rates++; - } - - return ret; + return 0; } static int scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, - struct scmi_clock_info *clk) + struct clock_info *cinfo) { int ret; void *iter; @@ -519,9 +510,9 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, .update_state = iter_clk_describe_update_state, .process_response = iter_clk_describe_process_response, }; + struct scmi_clock_desc *clkd = &cinfo->clkds[clk_id]; struct scmi_clk_ipriv cpriv = { - .clk_id = clk_id, - .clk = clk, + .clkd = clkd, .dev = ph->dev, }; @@ -536,16 +527,31 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, if (ret) return ret; - if (!clk->rate_discrete) { - dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", - clk->range.min_rate, clk->range.max_rate, - clk->range.step_size); - } else if (clk->list.num_rates) { - sort(clk->list.rates, clk->list.num_rates, - sizeof(clk->list.rates[0]), rate_cmp_func, NULL); - } + /* empty set ? */ + if (!clkd->num_rates) + return 0; - return ret; + if (!clkd->rate_discrete) { + clkd->info.range.min_rate = clkd->rates[RATE_MIN]; + clkd->info.range.max_rate = clkd->rates[RATE_MAX]; + clkd->info.range.step_size = clkd->rates[RATE_STEP]; + clkd->info.max_rate = clkd->rates[RATE_MAX]; + dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", + clkd->rates[RATE_MIN], clkd->rates[RATE_MAX], + clkd->rates[RATE_STEP]); + } else { + unsigned int i; + + sort(clkd->rates, clkd->num_rates, + sizeof(clkd->rates[0]), rate_cmp_func, NULL); + clkd->info.list.num_rates = clkd->num_rates; + for (i = 0; i < clkd->num_rates; i++) + clkd->info.list.rates[i] = clkd->rates[i]; + clkd->info.max_rate = clkd->rates[clkd->num_rates - 1]; + } + clkd->info.min_rate = clkd->rates[RATE_MIN]; + + return 0; } static int @@ -630,6 +636,7 @@ static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, { u64 fmin, fmax, ftmp; struct scmi_clock_info *clk; + struct scmi_clock_desc *clkd; struct clock_info *ci = ph->get_priv(ph); if (!rate) @@ -639,15 +646,17 @@ static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, if (IS_ERR(clk)) return PTR_ERR(clk); + clkd = to_desc(clk); + /* * If we can't figure out what rate it will be, so just return the * rate back to the caller. */ - if (clk->rate_discrete) + if (clkd->rate_discrete) return 0; - fmin = clk->range.min_rate; - fmax = clk->range.max_rate; + fmin = clk->min_rate; + fmax = clk->max_rate; if (*rate <= fmin) { *rate = fmin; return 0; @@ -657,10 +666,10 @@ static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, } ftmp = *rate - fmin; - ftmp += clk->range.step_size - 1; /* to round up */ - ftmp = div64_ul(ftmp, clk->range.step_size); + ftmp += clkd->rates[RATE_STEP] - 1; /* to round up */ + ftmp = div64_ul(ftmp, clkd->rates[RATE_STEP]); - *rate = ftmp * clk->range.step_size + fmin; + *rate = ftmp * clkd->rates[RATE_STEP] + fmin; return 0; } @@ -1122,17 +1131,16 @@ static int scmi_clock_protocol_init(const struct scmi_protocol_handle *ph) if (ret) return ret; - cinfo->clk = devm_kcalloc(ph->dev, cinfo->num_clocks, - sizeof(*cinfo->clk), GFP_KERNEL); - if (!cinfo->clk) + cinfo->clkds = devm_kcalloc(ph->dev, cinfo->num_clocks, + sizeof(*cinfo->clkds), GFP_KERNEL); + if (!cinfo->clkds) return -ENOMEM; for (clkid = 0; clkid < cinfo->num_clocks; clkid++) { - struct scmi_clock_info *clk = cinfo->clk + clkid; - + cinfo->clkds[clkid].id = clkid; ret = scmi_clock_attributes_get(ph, clkid, cinfo); if (!ret) - scmi_clock_describe_rates_get(ph, clkid, clk); + scmi_clock_describe_rates_get(ph, clkid, cinfo); } if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) { diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 984117f51695..f82747adc8eb 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -51,6 +51,8 @@ struct scmi_clock_info { bool rate_ctrl_forbidden; bool parent_ctrl_forbidden; bool extended_config; + u64 min_rate; + u64 max_rate; union { struct { int num_rates; From cdcd2fc94936f78752e8e4829b1dc9962f0c2383 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:50 +0100 Subject: [PATCH 071/189] clk: scmi: Use new simplified per-clock rate properties Use the new min_rate and max_rate unified properties that provide the proper values without having to consider the clock type. Cc: Michael Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Reviewed-by: Peng Fan Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-6-cristian.marussi@arm.com Reviewed-by: Brian Masney Signed-off-by: Sudeep Holla --- drivers/clk/clk-scmi.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c index c223e4ef1dd1..7c562559ad8b 100644 --- a/drivers/clk/clk-scmi.c +++ b/drivers/clk/clk-scmi.c @@ -202,7 +202,6 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk, const struct clk_ops *scmi_ops) { int ret; - unsigned long min_rate, max_rate; struct clk_init_data init = { .flags = CLK_GET_RATE_NOCACHE, @@ -217,20 +216,8 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk, if (ret) return ret; - if (sclk->info->rate_discrete) { - int num_rates = sclk->info->list.num_rates; - - if (num_rates <= 0) - return -EINVAL; - - min_rate = sclk->info->list.rates[0]; - max_rate = sclk->info->list.rates[num_rates - 1]; - } else { - min_rate = sclk->info->range.min_rate; - max_rate = sclk->info->range.max_rate; - } - - clk_hw_set_rate_range(&sclk->hw, min_rate, max_rate); + clk_hw_set_rate_range(&sclk->hw, sclk->info->min_rate, + sclk->info->max_rate); return ret; } From 2e757f71a5ab861478204e2907bb373ccb3ca087 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:51 +0100 Subject: [PATCH 072/189] firmware: arm_scmi: Drop unused clock rate interfaces Only the unified interface exposing min_rate/max_rate is now used. Reviewed-by: Peng Fan Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260508153300.2224715-7-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 9 --------- include/linux/scmi_protocol.h | 12 ------------ 2 files changed, 21 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 512400122b85..467b13a3a18f 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -467,7 +467,6 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, st->num_remaining = NUM_REMAINING(flags); st->num_returned = NUM_RETURNED(flags); p->clkd->rate_discrete = RATE_DISCRETE(flags); - p->clkd->info.rate_discrete = p->clkd->rate_discrete; /* Warn about out of spec replies ... */ if (!p->clkd->rate_discrete && @@ -532,21 +531,13 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, return 0; if (!clkd->rate_discrete) { - clkd->info.range.min_rate = clkd->rates[RATE_MIN]; - clkd->info.range.max_rate = clkd->rates[RATE_MAX]; - clkd->info.range.step_size = clkd->rates[RATE_STEP]; clkd->info.max_rate = clkd->rates[RATE_MAX]; dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", clkd->rates[RATE_MIN], clkd->rates[RATE_MAX], clkd->rates[RATE_STEP]); } else { - unsigned int i; - sort(clkd->rates, clkd->num_rates, sizeof(clkd->rates[0]), rate_cmp_func, NULL); - clkd->info.list.num_rates = clkd->num_rates; - for (i = 0; i < clkd->num_rates; i++) - clkd->info.list.rates[i] = clkd->rates[i]; clkd->info.max_rate = clkd->rates[clkd->num_rates - 1]; } clkd->info.min_rate = clkd->rates[RATE_MIN]; diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index f82747adc8eb..9913b4f097d8 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -44,7 +44,6 @@ struct scmi_base_info { struct scmi_clock_info { char name[SCMI_MAX_STR_SIZE]; unsigned int enable_latency; - bool rate_discrete; bool rate_changed_notifications; bool rate_change_requested_notifications; bool state_ctrl_forbidden; @@ -53,17 +52,6 @@ struct scmi_clock_info { bool extended_config; u64 min_rate; u64 max_rate; - union { - struct { - int num_rates; - u64 rates[SCMI_MAX_NUM_RATES]; - } list; - struct { - u64 min_rate; - u64 max_rate; - u64 step_size; - } range; - }; int num_parents; u32 *parents; }; From 62ba967595e0b2599768f886851ba5e0d4bfb55b Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:52 +0100 Subject: [PATCH 073/189] firmware: arm_scmi: Make clock rates allocation dynamic Leveraging SCMI Clock protocol dynamic discovery capabilities, move away from the static per-clock rates allocation model in favour of a dynamic runtime allocation based on effectively discovered resources. No functional change. Reviewed-by: Peng Fan Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-8-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 19 ++++++++++++++++--- include/linux/scmi_protocol.h | 1 - 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 467b13a3a18f..c9b62edce4fd 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -161,7 +161,7 @@ struct scmi_clock_desc { u32 id; bool rate_discrete; unsigned int num_rates; - u64 rates[SCMI_MAX_NUM_RATES]; + u64 *rates; #define RATE_MIN 0 #define RATE_MAX 1 #define RATE_STEP 2 @@ -480,6 +480,18 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, QUIRK_OUT_OF_SPEC_TRIPLET); } + if (!st->max_resources) { + int num_rates = st->num_returned + st->num_remaining; + + p->clkd->rates = devm_kcalloc(p->dev, num_rates, + sizeof(*p->clkd->rates), GFP_KERNEL); + if (!p->clkd->rates) + return -ENOMEM; + + /* max_resources is used by the iterators to control bounds */ + st->max_resources = st->num_returned + st->num_remaining; + } + return 0; } @@ -493,6 +505,8 @@ iter_clk_describe_process_response(const struct scmi_protocol_handle *ph, p->clkd->rates[st->desc_index + st->loop_idx] = RATE_TO_U64(r->rate[st->loop_idx]); + + /* Count only effectively discovered rates */ p->clkd->num_rates++; return 0; @@ -515,8 +529,7 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, .dev = ph->dev, }; - iter = ph->hops->iter_response_init(ph, &ops, SCMI_MAX_NUM_RATES, - CLOCK_DESCRIBE_RATES, + iter = ph->hops->iter_response_init(ph, &ops, 0, CLOCK_DESCRIBE_RATES, sizeof(struct scmi_msg_clock_describe_rates), &cpriv); if (IS_ERR(iter)) diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 9913b4f097d8..55db9ba8fac3 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -15,7 +15,6 @@ #define SCMI_MAX_STR_SIZE 64 #define SCMI_SHORT_NAME_MAX_SIZE 16 -#define SCMI_MAX_NUM_RATES 16 /** * struct scmi_base_info - version information structure From bda40491e0cedcdf5b25a5e12d21d105cd4033bf Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:53 +0100 Subject: [PATCH 074/189] firmware: arm_scmi: Harden clock parents discovery Fix clock parents enumeration to account only for effectively discovered parents during enumeration, avoiding to trust the total number of parents declared upfront by the platform. Reviewed-by: Peng Fan Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-9-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index c9b62edce4fd..d07cfef243fd 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -270,15 +270,15 @@ static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st * assume it's returned+remaining on first call. */ if (!st->max_resources) { - p->clkd->info.num_parents = st->num_returned + st->num_remaining; - p->clkd->info.parents = devm_kcalloc(p->dev, - p->clkd->info.num_parents, + int num_parents = st->num_returned + st->num_remaining; + + p->clkd->info.parents = devm_kcalloc(p->dev, num_parents, sizeof(*p->clkd->info.parents), GFP_KERNEL); - if (!p->clkd->info.parents) { - p->clkd->info.num_parents = 0; + if (!p->clkd->info.parents) return -ENOMEM; - } + + /* max_resources is used by the iterators to control bounds */ st->max_resources = st->num_returned + st->num_remaining; } @@ -293,9 +293,11 @@ static int iter_clk_possible_parents_process_response(const struct scmi_protocol const struct scmi_msg_resp_clock_possible_parents *r = response; struct scmi_clk_ipriv *p = priv; - u32 *parent = &p->clkd->info.parents[st->desc_index + st->loop_idx]; + p->clkd->info.parents[st->desc_index + st->loop_idx] = + le32_to_cpu(r->possible_parents[st->loop_idx]); - *parent = le32_to_cpu(r->possible_parents[st->loop_idx]); + /* Count only effectively discovered parents */ + p->clkd->info.num_parents++; return 0; } From e99ed72672637662aa0207af0204185203516b28 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:54 +0100 Subject: [PATCH 075/189] firmware: arm_scmi: Refactor iterators internal allocation Use cleanup handlers to manage iterator data structures. No functional change. Reviewed-by: Peng Fan Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-10-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/driver.c | 35 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index 53310beb67a8..143f0ec60aae 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -17,6 +17,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -1789,39 +1790,41 @@ static void *scmi_iterator_init(const struct scmi_protocol_handle *ph, size_t tx_size, void *priv) { int ret; - struct scmi_iterator *i; - i = devm_kzalloc(ph->dev, sizeof(*i), GFP_KERNEL); + struct scmi_iterator *i __free(kfree) = kzalloc(sizeof(*i), GFP_KERNEL); if (!i) return ERR_PTR(-ENOMEM); + if (!ops || !ph) + return ERR_PTR(-EINVAL); + i->ph = ph; i->ops = ops; i->priv = priv; ret = ph->xops->xfer_get_init(ph, msg_id, tx_size, 0, &i->t); - if (ret) { - devm_kfree(ph->dev, i); + if (ret) return ERR_PTR(ret); - } i->state.max_resources = max_resources; i->msg = i->t->tx.buf; i->resp = i->t->rx.buf; - return i; + return no_free_ptr(i); } static int scmi_iterator_run(void *iter) { - int ret = -EINVAL; + int ret; struct scmi_iterator_ops *iops; const struct scmi_protocol_handle *ph; struct scmi_iterator_state *st; - struct scmi_iterator *i = iter; - if (!i || !i->ops || !i->ph) - return ret; + if (!iter) + return -EINVAL; + + /* Take ownership of the iterator */ + struct scmi_iterator *i __free(kfree) = iter; iops = i->ops; ph = i->ph; @@ -1846,12 +1849,12 @@ static int scmi_iterator_run(void *iter) break; } - for (st->loop_idx = 0; st->loop_idx < st->num_returned; - st->loop_idx++) { + for (st->loop_idx = 0; !ret && st->loop_idx < st->num_returned; + st->loop_idx++) ret = iops->process_response(ph, i->resp, st, i->priv); - if (ret) - goto out; - } + + if (ret) + break; st->desc_index += st->num_returned; ph->xops->reset_rx_to_maxsz(ph, i->t); @@ -1861,10 +1864,8 @@ static int scmi_iterator_run(void *iter) */ } while (st->num_returned && st->num_remaining); -out: /* Finalize and destroy iterator */ ph->xops->xfer_put(ph, i->t); - devm_kfree(ph->dev, i); return ret; } From 4848d07ea9fc5e4c2239e10b3eb9fe7e647aaa12 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:55 +0100 Subject: [PATCH 076/189] firmware: arm_scmi: Add bound iterators support SCMI core stack provides some common helpers to handle in a unified way multipart message replies: such iterator-helpers, when run, currently process by default the whole set of discovered resources. Introduce an alternative way to run the initialized iterator on a limited range of resources. Note that the subset of resources that can be chosen is anyway limited by the SCMI protocol specification, since you are only allowed to choose the start-index on a multi-part enumeration NOT the end-index, so that the effective number of returned items by a bound iterators depends really on platform side decisions. Suggested-by: Etienne Carriere Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Tested-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260508153300.2224715-11-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 3 +- drivers/firmware/arm_scmi/driver.c | 58 +++++++++++++++++++-------- drivers/firmware/arm_scmi/protocols.h | 13 +++++- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index d07cfef243fd..8ce889dfc87b 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -505,8 +505,7 @@ iter_clk_describe_process_response(const struct scmi_protocol_handle *ph, struct scmi_clk_ipriv *p = priv; const struct scmi_msg_resp_clock_describe_rates *r = response; - p->clkd->rates[st->desc_index + st->loop_idx] = - RATE_TO_U64(r->rate[st->loop_idx]); + p->clkd->rates[p->clkd->num_rates] = RATE_TO_U64(r->rate[st->loop_idx]); /* Count only effectively discovered rates */ p->clkd->num_rates++; diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index 143f0ec60aae..a59d8f9219ed 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1813,48 +1813,50 @@ static void *scmi_iterator_init(const struct scmi_protocol_handle *ph, return no_free_ptr(i); } -static int scmi_iterator_run(void *iter) +static int __scmi_iterator_run(void *iter, unsigned int *start, unsigned int *end) { int ret; struct scmi_iterator_ops *iops; const struct scmi_protocol_handle *ph; struct scmi_iterator_state *st; + struct scmi_iterator *i; if (!iter) return -EINVAL; - /* Take ownership of the iterator */ - struct scmi_iterator *i __free(kfree) = iter; - + i = iter; iops = i->ops; ph = i->ph; st = &i->state; + /* Reinitialize state for next run */ + st->num_returned = 0; + st->num_remaining = 0; + st->desc_index = start ? *start : 0; + do { iops->prepare_message(i->msg, st->desc_index, i->priv); ret = ph->xops->do_xfer(ph, i->t); if (ret) - break; + return ret; st->rx_len = i->t->rx.len; ret = iops->update_state(st, i->resp, i->priv); if (ret) - break; + return ret; if (st->num_returned > st->max_resources - st->desc_index) { dev_err(ph->dev, "No. of resources can't exceed %d\n", st->max_resources); - ret = -EINVAL; - break; + return -EINVAL; } - for (st->loop_idx = 0; !ret && st->loop_idx < st->num_returned; - st->loop_idx++) + for (st->loop_idx = 0; st->loop_idx < st->num_returned; st->loop_idx++) { ret = iops->process_response(ph, i->resp, st, i->priv); - - if (ret) - break; + if (ret) + return ret; + } st->desc_index += st->num_returned; ph->xops->reset_rx_to_maxsz(ph, i->t); @@ -1862,14 +1864,36 @@ static int scmi_iterator_run(void *iter) * check for both returned and remaining to avoid infinite * loop due to buggy firmware */ - } while (st->num_returned && st->num_remaining); + } while (st->num_returned && st->num_remaining && + (!end || (st->desc_index <= min(*end, st->max_resources - 1)))); - /* Finalize and destroy iterator */ - ph->xops->xfer_put(ph, i->t); + return 0; +} + +static void scmi_iterator_cleanup(void *iter) +{ + struct scmi_iterator *i = iter; + + i->ph->xops->xfer_put(i->ph, i->t); + kfree(i); +} + +static int scmi_iterator_run(void *iter) +{ + int ret; + + ret = __scmi_iterator_run(iter, NULL, NULL); + scmi_iterator_cleanup(iter); return ret; } +static int scmi_iterator_run_bound(void *iter, unsigned int *start, + unsigned int *end) +{ + return __scmi_iterator_run(iter, start, end); +} + struct scmi_msg_get_fc_info { __le32 domain; __le32 message_id; @@ -2048,6 +2072,8 @@ static const struct scmi_proto_helpers_ops helpers_ops = { .get_max_msg_size = scmi_common_get_max_msg_size, .iter_response_init = scmi_iterator_init, .iter_response_run = scmi_iterator_run, + .iter_response_run_bound = scmi_iterator_run_bound, + .iter_response_cleanup = scmi_iterator_cleanup, .protocol_msg_check = scmi_protocol_msg_check, .fastchannel_init = scmi_common_fastchannel_init, .fastchannel_db_ring = scmi_common_fastchannel_db_ring, diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h index f51245aca259..e2ef604c16ef 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -259,7 +259,15 @@ struct scmi_fc_info { * multi-part responses using the custom operations * provided in @ops. * @iter_response_run: A common helper to trigger the run of a previously - * initialized iterator. + * initialized iterator. Note that unbound iterators are + * automatically cleaned up. + * @iter_response_run_bound: A common helper to trigger the run of a previously + * initialized iterator, but only within the + * specified, optional, @start and @end resource + * indexes. Note that these bound-iterators need + * explicit cleanup via @iter_response_bound_cleanup. + * @iter_response_bound_cleanup: A common helper to finally release the iterator + * for bound iterators. * @protocol_msg_check: A common helper to check is a specific protocol message * is supported. * @fastchannel_init: A common helper used to initialize FC descriptors by @@ -276,6 +284,9 @@ struct scmi_proto_helpers_ops { unsigned int max_resources, u8 msg_id, size_t tx_size, void *priv); int (*iter_response_run)(void *iter); + int (*iter_response_run_bound)(void *iter, + unsigned int *start, unsigned int *end); + void (*iter_response_cleanup)(void *iter); int (*protocol_msg_check)(const struct scmi_protocol_handle *ph, u32 message_id, u32 *attributes); void (*fastchannel_init)(const struct scmi_protocol_handle *ph, From ae4a088f13debc1d7bbb6a9b265a31d25b60ddd4 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 8 May 2026 16:32:56 +0100 Subject: [PATCH 077/189] firmware: arm_scmi: Fix bound iterators returning too many items When using a bound-iterator with an upper bound, commands are sent, and responses are received, until the upper bound is reached. However, it is up to the SCMI provider implementation to decide how many rates are returned in response to a single CLOCK_DESCRIBE_RATES command. If the last response contains rates beyond the specified upper bound, they are still passed up for further processing. This may lead to buffer overflows in unprepared callsites. While the imprecise bound handling may have been intentional (it was mentioned in the commit message introducing the code), it is still confusing for users, and may cause hard to debug crashes. Fix this by strictly enforcing the upper bound. Note that this may cause an increase in the number of CLOCK_DESCRIBE_RATES commands issued, as retrieving the last rate may no longer be done inadvertentently, but require its own command. Signed-off-by: Geert Uytterhoeven Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Link: https://patch.msgid.link/20260508153300.2224715-12-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/driver.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index a59d8f9219ed..595ce7b33c61 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1820,6 +1820,7 @@ static int __scmi_iterator_run(void *iter, unsigned int *start, unsigned int *en const struct scmi_protocol_handle *ph; struct scmi_iterator_state *st; struct scmi_iterator *i; + unsigned int n; if (!iter) return -EINVAL; @@ -1852,13 +1853,17 @@ static int __scmi_iterator_run(void *iter, unsigned int *start, unsigned int *en return -EINVAL; } - for (st->loop_idx = 0; st->loop_idx < st->num_returned; st->loop_idx++) { + if (end) + n = min(st->num_returned, *end - st->desc_index + 1); + else + n = st->num_returned; + for (st->loop_idx = 0; st->loop_idx < n; st->loop_idx++) { ret = iops->process_response(ph, i->resp, st, i->priv); if (ret) return ret; } - st->desc_index += st->num_returned; + st->desc_index += n; ph->xops->reset_rx_to_maxsz(ph, i->t); /* * check for both returned and remaining to avoid infinite From 3065e26dac525696bd0ef2fdaff7724b1bd345f9 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 8 May 2026 16:32:57 +0100 Subject: [PATCH 078/189] firmware: arm_scmi: Use proper iter_response_bound_cleanup() name The documentation speaks of the "iter_response_bound_cleanup()" protocol helper, while the actual helper is called "iter_response_cleanup()". Settle on the former name, because the helper is only needed when using bound-iterators. Signed-off-by: Geert Uytterhoeven Signed-off-by: Cristian Marussi Tested-by: Florian Fainelli Link: https://patch.msgid.link/20260508153300.2224715-13-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/driver.c | 6 +++--- drivers/firmware/arm_scmi/protocols.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index 595ce7b33c61..3e0d975ec94c 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -1875,7 +1875,7 @@ static int __scmi_iterator_run(void *iter, unsigned int *start, unsigned int *en return 0; } -static void scmi_iterator_cleanup(void *iter) +static void scmi_iterator_bound_cleanup(void *iter) { struct scmi_iterator *i = iter; @@ -1888,7 +1888,7 @@ static int scmi_iterator_run(void *iter) int ret; ret = __scmi_iterator_run(iter, NULL, NULL); - scmi_iterator_cleanup(iter); + scmi_iterator_bound_cleanup(iter); return ret; } @@ -2078,7 +2078,7 @@ static const struct scmi_proto_helpers_ops helpers_ops = { .iter_response_init = scmi_iterator_init, .iter_response_run = scmi_iterator_run, .iter_response_run_bound = scmi_iterator_run_bound, - .iter_response_cleanup = scmi_iterator_cleanup, + .iter_response_bound_cleanup = scmi_iterator_bound_cleanup, .protocol_msg_check = scmi_protocol_msg_check, .fastchannel_init = scmi_common_fastchannel_init, .fastchannel_db_ring = scmi_common_fastchannel_db_ring, diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h index e2ef604c16ef..15ad5162e37a 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -286,7 +286,7 @@ struct scmi_proto_helpers_ops { int (*iter_response_run)(void *iter); int (*iter_response_run_bound)(void *iter, unsigned int *start, unsigned int *end); - void (*iter_response_cleanup)(void *iter); + void (*iter_response_bound_cleanup)(void *iter); int (*protocol_msg_check)(const struct scmi_protocol_handle *ph, u32 message_id, u32 *attributes); void (*fastchannel_init)(const struct scmi_protocol_handle *ph, From 26d04d592a47890d409d1bf77b9cbf5920f2efc8 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:58 +0100 Subject: [PATCH 079/189] firmware: arm_scmi: Use bound iterators to minimize discovered rates Clock rates are guaranteed to be returned in ascending order for SCMI clock protocol versions greater than 1.0: in such a case, use bounded iterators to minimize the number of message exchanges needed to discover min and max rate. Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260508153300.2224715-14-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 90 +++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 8ce889dfc87b..955bb9565ce3 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -160,6 +160,7 @@ struct scmi_clock_rate_notify_payld { struct scmi_clock_desc { u32 id; bool rate_discrete; + unsigned int tot_rates; unsigned int num_rates; u64 *rates; #define RATE_MIN 0 @@ -483,15 +484,16 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, } if (!st->max_resources) { - int num_rates = st->num_returned + st->num_remaining; + unsigned int tot_rates = st->num_returned + st->num_remaining; - p->clkd->rates = devm_kcalloc(p->dev, num_rates, + p->clkd->rates = devm_kcalloc(p->dev, tot_rates, sizeof(*p->clkd->rates), GFP_KERNEL); if (!p->clkd->rates) return -ENOMEM; /* max_resources is used by the iterators to control bounds */ - st->max_resources = st->num_returned + st->num_remaining; + p->clkd->tot_rates = tot_rates; + st->max_resources = tot_rates; } return 0; @@ -514,8 +516,8 @@ iter_clk_describe_process_response(const struct scmi_protocol_handle *ph, } static int -scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, - struct clock_info *cinfo) +scmi_clock_describe_rates_get_full(const struct scmi_protocol_handle *ph, + struct scmi_clock_desc *clkd) { int ret; void *iter; @@ -524,7 +526,6 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, .update_state = iter_clk_describe_update_state, .process_response = iter_clk_describe_process_response, }; - struct scmi_clock_desc *clkd = &cinfo->clkds[clk_id]; struct scmi_clk_ipriv cpriv = { .clkd = clkd, .dev = ph->dev, @@ -544,17 +545,88 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, if (!clkd->num_rates) return 0; + if (clkd->rate_discrete) + sort(clkd->rates, clkd->num_rates, + sizeof(clkd->rates[0]), rate_cmp_func, NULL); + + return 0; +} + +static int +scmi_clock_describe_rates_get_lazy(const struct scmi_protocol_handle *ph, + struct scmi_clock_desc *clkd) +{ + struct scmi_iterator_ops ops = { + .prepare_message = iter_clk_describe_prepare_message, + .update_state = iter_clk_describe_update_state, + .process_response = iter_clk_describe_process_response, + }; + struct scmi_clk_ipriv cpriv = { + .clkd = clkd, + .dev = ph->dev, + }; + unsigned int first, last; + void *iter; + int ret; + + iter = ph->hops->iter_response_init(ph, &ops, 0, CLOCK_DESCRIBE_RATES, + sizeof(struct scmi_msg_clock_describe_rates), + &cpriv); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + /* Try to grab a triplet, so that in case is NON-discrete we are done */ + first = 0; + last = 2; + ret = ph->hops->iter_response_run_bound(iter, &first, &last); + if (ret) + goto out; + + /* If discrete grab the last value, which should be the max */ + if (clkd->rate_discrete && clkd->tot_rates > 3) { + first = clkd->tot_rates - 1; + last = clkd->tot_rates - 1; + ret = ph->hops->iter_response_run_bound(iter, &first, &last); + } + +out: + ph->hops->iter_response_bound_cleanup(iter); + + return ret; +} + +static int +scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, + u32 clk_id, struct clock_info *cinfo) +{ + struct scmi_clock_desc *clkd = &cinfo->clkds[clk_id]; + int ret; + + /* + * Since only after SCMI Clock v1.0 the returned rates are guaranteed to + * be discovered in ascending order, lazy enumeration cannot be use for + * SCMI Clock v1.0 protocol. + */ + if (PROTOCOL_REV_MAJOR(ph->version) > 0x1) + ret = scmi_clock_describe_rates_get_lazy(ph, clkd); + else + ret = scmi_clock_describe_rates_get_full(ph, clkd); + + if (ret) + return ret; + + clkd->info.min_rate = clkd->rates[RATE_MIN]; if (!clkd->rate_discrete) { clkd->info.max_rate = clkd->rates[RATE_MAX]; dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", clkd->rates[RATE_MIN], clkd->rates[RATE_MAX], clkd->rates[RATE_STEP]); } else { - sort(clkd->rates, clkd->num_rates, - sizeof(clkd->rates[0]), rate_cmp_func, NULL); clkd->info.max_rate = clkd->rates[clkd->num_rates - 1]; + dev_dbg(ph->dev, "Clock:%s Num_Rates:%u -> Min %llu Max %llu\n", + clkd->info.name, clkd->tot_rates, + clkd->info.min_rate, clkd->info.max_rate); } - clkd->info.min_rate = clkd->rates[RATE_MIN]; return 0; } From 4a07036d615976354ac806017f23ea800f1fc489 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 8 May 2026 16:32:59 +0100 Subject: [PATCH 080/189] firmware: arm_scmi: Fix OOB in scmi_clock_describe_rates_get_lazy() Lazy discovery of discrete rates works as follows: A. Grab the first three rates, B. Grab the last rate, if there are more than three rates. It is up to the SCMI provider implementation to decide how many rates are returned in response to a single CLOCK_DESCRIBE_RATES command. Each rate received is stored in the scmi_clock_rates.rates[] array, and .num_rates is updated accordingly. When more than 3 rates have been received after step A, the last rate may have been received already, and stored in scmi_clock_rates.rates[] (which has space for scmi_clock_desc.tot_rates entries). Hence grabbing the last rate again will store it a second time, beyond the end of the array. Fix this by only grabbing the last rate when we don't already have it. Signed-off-by: Geert Uytterhoeven Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260508153300.2224715-15-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 955bb9565ce3..ab8c65ed785a 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -582,8 +582,11 @@ scmi_clock_describe_rates_get_lazy(const struct scmi_protocol_handle *ph, if (ret) goto out; - /* If discrete grab the last value, which should be the max */ - if (clkd->rate_discrete && clkd->tot_rates > 3) { + /* + * If discrete and we don't already have it, grab the last value, which + * should be the max + */ + if (clkd->rate_discrete && clkd->tot_rates > clkd->num_rates) { first = clkd->tot_rates - 1; last = clkd->tot_rates - 1; ret = ph->hops->iter_response_run_bound(iter, &first, &last); From d2488ff1a257342111e1be1348d52e8b4ecfaa36 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:33:00 +0100 Subject: [PATCH 081/189] firmware: arm_scmi: Introduce all_rates_get clock operation Add a clock operation to get the whole set of rates available to a specific clock: when needed this request could transparently trigger a full rate discovery enumeration if this specific clock-rates were previously only lazily enumerated. Reviewed-by: Peng Fan Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260508153300.2224715-16-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/clock.c | 83 +++++++++++++++++++++---------- include/linux/scmi_protocol.h | 9 ++++ 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index ab8c65ed785a..42e666a628c7 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -159,10 +159,8 @@ struct scmi_clock_rate_notify_payld { struct scmi_clock_desc { u32 id; - bool rate_discrete; unsigned int tot_rates; - unsigned int num_rates; - u64 *rates; + struct scmi_clock_rates r; #define RATE_MIN 0 #define RATE_MAX 1 #define RATE_STEP 2 @@ -469,10 +467,10 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, flags = le32_to_cpu(r->num_rates_flags); st->num_remaining = NUM_REMAINING(flags); st->num_returned = NUM_RETURNED(flags); - p->clkd->rate_discrete = RATE_DISCRETE(flags); + p->clkd->r.rate_discrete = RATE_DISCRETE(flags); /* Warn about out of spec replies ... */ - if (!p->clkd->rate_discrete && + if (!p->clkd->r.rate_discrete && (st->num_returned != 3 || st->num_remaining != 0)) { dev_warn(p->dev, "Out-of-spec CLOCK_DESCRIBE_RATES reply for %s - returned:%d remaining:%d rx_len:%zd\n", @@ -486,9 +484,9 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, if (!st->max_resources) { unsigned int tot_rates = st->num_returned + st->num_remaining; - p->clkd->rates = devm_kcalloc(p->dev, tot_rates, - sizeof(*p->clkd->rates), GFP_KERNEL); - if (!p->clkd->rates) + p->clkd->r.rates = devm_kcalloc(p->dev, tot_rates, + sizeof(*p->clkd->r.rates), GFP_KERNEL); + if (!p->clkd->r.rates) return -ENOMEM; /* max_resources is used by the iterators to control bounds */ @@ -507,10 +505,10 @@ iter_clk_describe_process_response(const struct scmi_protocol_handle *ph, struct scmi_clk_ipriv *p = priv; const struct scmi_msg_resp_clock_describe_rates *r = response; - p->clkd->rates[p->clkd->num_rates] = RATE_TO_U64(r->rate[st->loop_idx]); + p->clkd->r.rates[p->clkd->r.num_rates] = RATE_TO_U64(r->rate[st->loop_idx]); /* Count only effectively discovered rates */ - p->clkd->num_rates++; + p->clkd->r.num_rates++; return 0; } @@ -531,7 +529,13 @@ scmi_clock_describe_rates_get_full(const struct scmi_protocol_handle *ph, .dev = ph->dev, }; - iter = ph->hops->iter_response_init(ph, &ops, 0, CLOCK_DESCRIBE_RATES, + /* + * Using tot_rates as max_resources parameter here so as to trigger + * the dynamic allocation only when strictly needed: when trying a + * full enumeration after a lazy one tot_rates will be non-zero. + */ + iter = ph->hops->iter_response_init(ph, &ops, clkd->tot_rates, + CLOCK_DESCRIBE_RATES, sizeof(struct scmi_msg_clock_describe_rates), &cpriv); if (IS_ERR(iter)) @@ -542,12 +546,12 @@ scmi_clock_describe_rates_get_full(const struct scmi_protocol_handle *ph, return ret; /* empty set ? */ - if (!clkd->num_rates) + if (!clkd->r.num_rates) return 0; - if (clkd->rate_discrete) - sort(clkd->rates, clkd->num_rates, - sizeof(clkd->rates[0]), rate_cmp_func, NULL); + if (clkd->r.rate_discrete && PROTOCOL_REV_MAJOR(ph->version) == 0x1) + sort(clkd->r.rates, clkd->r.num_rates, + sizeof(clkd->r.rates[0]), rate_cmp_func, NULL); return 0; } @@ -586,7 +590,7 @@ scmi_clock_describe_rates_get_lazy(const struct scmi_protocol_handle *ph, * If discrete and we don't already have it, grab the last value, which * should be the max */ - if (clkd->rate_discrete && clkd->tot_rates > clkd->num_rates) { + if (clkd->r.rate_discrete && clkd->tot_rates > clkd->r.num_rates) { first = clkd->tot_rates - 1; last = clkd->tot_rates - 1; ret = ph->hops->iter_response_run_bound(iter, &first, &last); @@ -618,14 +622,14 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, if (ret) return ret; - clkd->info.min_rate = clkd->rates[RATE_MIN]; - if (!clkd->rate_discrete) { - clkd->info.max_rate = clkd->rates[RATE_MAX]; + clkd->info.min_rate = clkd->r.rates[RATE_MIN]; + if (!clkd->r.rate_discrete) { + clkd->info.max_rate = clkd->r.rates[RATE_MAX]; dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", - clkd->rates[RATE_MIN], clkd->rates[RATE_MAX], - clkd->rates[RATE_STEP]); + clkd->r.rates[RATE_MIN], clkd->r.rates[RATE_MAX], + clkd->r.rates[RATE_STEP]); } else { - clkd->info.max_rate = clkd->rates[clkd->num_rates - 1]; + clkd->info.max_rate = clkd->r.rates[clkd->r.num_rates - 1]; dev_dbg(ph->dev, "Clock:%s Num_Rates:%u -> Min %llu Max %llu\n", clkd->info.name, clkd->tot_rates, clkd->info.min_rate, clkd->info.max_rate); @@ -732,7 +736,7 @@ static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, * If we can't figure out what rate it will be, so just return the * rate back to the caller. */ - if (clkd->rate_discrete) + if (clkd->r.rate_discrete) return 0; fmin = clk->min_rate; @@ -746,14 +750,40 @@ static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, } ftmp = *rate - fmin; - ftmp += clkd->rates[RATE_STEP] - 1; /* to round up */ - ftmp = div64_ul(ftmp, clkd->rates[RATE_STEP]); + ftmp += clkd->r.rates[RATE_STEP] - 1; /* to round up */ + ftmp = div64_ul(ftmp, clkd->r.rates[RATE_STEP]); - *rate = ftmp * clkd->rates[RATE_STEP] + fmin; + *rate = ftmp * clkd->r.rates[RATE_STEP] + fmin; return 0; } +static const struct scmi_clock_rates * +scmi_clock_all_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + struct clock_info *ci = ph->get_priv(ph); + struct scmi_clock_desc *clkd; + struct scmi_clock_info *clk; + + clk = scmi_clock_domain_lookup(ci, clk_id); + if (IS_ERR(clk) || !clk->name[0]) + return NULL; + + clkd = to_desc(clk); + /* Needs full enumeration ? */ + if (clkd->r.rate_discrete && clkd->tot_rates != clkd->r.num_rates) { + int ret; + + /* rates[] is already allocated BUT we need to re-enumerate */ + clkd->r.num_rates = 0; + ret = scmi_clock_describe_rates_get_full(ph, clkd); + if (ret) + return NULL; + } + + return &clkd->r; +} + static int scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id, enum clk_state state, @@ -1067,6 +1097,7 @@ static const struct scmi_clk_proto_ops clk_proto_ops = { .rate_get = scmi_clock_rate_get, .rate_set = scmi_clock_rate_set, .determine_rate = scmi_clock_determine_rate, + .all_rates_get = scmi_clock_all_rates_get, .enable = scmi_clock_enable, .disable = scmi_clock_disable, .state_get = scmi_clock_state_get, diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 55db9ba8fac3..5ab73b1ab9aa 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -40,6 +40,12 @@ struct scmi_base_info { char sub_vendor_id[SCMI_SHORT_NAME_MAX_SIZE]; }; +struct scmi_clock_rates { + bool rate_discrete; + unsigned int num_rates; + u64 *rates; +}; + struct scmi_clock_info { char name[SCMI_MAX_STR_SIZE]; unsigned int enable_latency; @@ -84,6 +90,7 @@ enum scmi_clock_oem_config { * clock calculating the closest allowed rate. * Note that @rate is an input/output parameter used both to * describe the requested rate and report the closest match + * @all_rates_get: get the list of all available rates for the specified clock. * @enable: enables the specified clock * @disable: disables the specified clock * @state_get: get the status of the specified clock @@ -103,6 +110,8 @@ struct scmi_clk_proto_ops { u64 rate); int (*determine_rate)(const struct scmi_protocol_handle *ph, u32 clk_id, unsigned long *rate); + const struct scmi_clock_rates __must_check *(*all_rates_get) + (const struct scmi_protocol_handle *ph, u32 clk_id); int (*enable)(const struct scmi_protocol_handle *ph, u32 clk_id, bool atomic); int (*disable)(const struct scmi_protocol_handle *ph, u32 clk_id, From cb4a414bacbfdcb837048146b8979032d87e4961 Mon Sep 17 00:00:00 2001 From: Francisco Munoz Ruiz Date: Tue, 7 Apr 2026 14:51:47 -0700 Subject: [PATCH 082/189] dt-bindings: cache: qcom,llcc: Document Hawi SoC Add documentation for the Last Level Cache Controller (LLCC) bindings to support Hawi SoC where the System Cache Table (SCT) is programmed by firmware outside of Linux. Introduce a property that specifies the base address of the shared memory region from which the driver should read SCT descriptors provided by firmware. Signed-off-by: Francisco Munoz Ruiz Reviewed-by: Konrad Dybcio Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260407-external_llcc_changes2set-v2-1-b5017ce2020b@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/cache/qcom,llcc.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml index 730556184a59..1fc5411fe948 100644 --- a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml +++ b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml @@ -21,6 +21,7 @@ properties: compatible: enum: - qcom,glymur-llcc + - qcom,hawi-llcc - qcom,ipq5424-llcc - qcom,kaanapali-llcc - qcom,qcs615-llcc @@ -58,6 +59,11 @@ properties: interrupts: maxItems: 1 + memory-region: + maxItems: 1 + description: handle to a reserved-memory node used for firmware-populated + SLC/SCT shared memory. + nvmem-cells: items: - description: Reference to an nvmem node for multi channel DDR @@ -320,6 +326,7 @@ allOf: contains: enum: - qcom,kaanapali-llcc + - qcom,hawi-llcc - qcom,sm8450-llcc - qcom,sm8550-llcc - qcom,sm8650-llcc @@ -342,6 +349,18 @@ allOf: - const: llcc3_base - const: llcc_broadcast_base - const: llcc_broadcast_and_base + - if: + properties: + compatible: + contains: + enum: + - qcom,hawi-llcc + then: + required: + - memory-region + else: + properties: + memory-region: false additionalProperties: false From ac23106a9b9a0f6ed90002ae3d6fe7c14e97e6bf Mon Sep 17 00:00:00 2001 From: Francisco Munoz Ruiz Date: Tue, 7 Apr 2026 14:51:48 -0700 Subject: [PATCH 083/189] soc: qcom: llcc-qcom: get SCT descriptors from fw-populated memory Retrieve System Cache Table (SCT) descriptors from a shared memory region populated by firmware. SCT initialization and programming are performed entirely by firmware outside of Linux. The LLCC driver only consumes the pre-initialized descriptor data and does not configure SCT itself. Support this mechanism for future SoCs that provide SCT programming via firmware. Signed-off-by: Francisco Munoz Ruiz Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260407-external_llcc_changes2set-v2-2-b5017ce2020b@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/llcc-qcom.c | 273 +++++++++++++++++++++++++---- include/linux/soc/qcom/llcc-qcom.h | 8 +- 2 files changed, 242 insertions(+), 39 deletions(-) diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index 0161ceec8842..f8cd35b205eb 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -5,7 +5,6 @@ */ #include -#include #include #include #include @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +76,12 @@ #define LLCC_VERSION_4_1_0_0 0x04010000 #define LLCC_VERSION_6_0_0_0 0X06000000 +#define SLC_SCT_MEM_LAYOUT_VERSION1 1 /* SCT Memory layout version */ +#define SLC_SCT_DONE 0x00534354444f4e45 /* SCT programming OK */ +#define SLC_SCT_FAIL 0x005343544641494c /* SCT programming failed */ +#define SLC_SCT_NAME_LEN 15 +#define SLC_SCT_SLICE_ACT_ON_BOOT BIT(25) + /** * struct llcc_slice_config - Data associated with the llcc slice * @usecase_id: Unique id for the client's use case @@ -143,6 +149,87 @@ struct llcc_slice_config { u32 parent_slice_id; }; +/* + * struct slc_sct_error - Represents SCT error + * @code: FW code status + * @param: Holds the SCT programming error + */ +struct slc_sct_error { + __le64 code; + __le64 param; +} __packed; + +/* + * struct slc_sct_status - SCT programming status + * @program_status: Indicates programming success or failure + * @version: SCT mem layout version + * @error: Error enum and its param + */ +struct slc_sct_status { + __le64 program_status; + /* Use the lower 8 bits */ + __le64 version; + struct slc_sct_error error; +} __packed; + +/* + * struct slc_sct_details - SCT details + * @revision: revision of the SCT table + * @name: name of the SCT table + */ +struct slc_sct_details { + u8 revision; + char name[SLC_SCT_NAME_LEN]; +} __packed; + +/* + * struct tcm_mem_info - SC TCM Shared memory details + * @is_present: is TCM region present + * @offset: offset of TCM shared memory details + */ +struct slc_tcm_mem_info { + __le32 is_present; + __le32 offset; +} __packed; + +/* + * struct slc_sct_slice_desc - Slice descriptor definition used in shmem + * @slice_id: SCID of the slice + * @usecase_id: Usecase ID of the slice + * @slice_properties: + * slice_size: Contains the slice descriptor size - 20 bit wide + * rsvd: Reserved space - 4 bit wide + * flags: Flags for descriptors - 3 bit wide + * MPAM SCID: Bit 24 + * Activate on boot: Bit 25 + * Non-HLOS SCID: Bit 26 + * HWMutex: Ensures only one processor (CPU or MCU) at a time can + * access the LLCC hardware resources - 5 bit wide + */ +struct slc_sct_slice_desc { + __le16 slice_id; + __le16 usecase_id; + __le32 slice_properties; +} __packed; + +/* + * struct slc_sct_mem - Shared memory structure + * @sct_status: Status of SCT programming + * @sct_details: Sct revision and name details + * @tcm_mem_info: TCM shared memory presence & offset info + * @slice_descs_count: Number of slice desc present in SCT + * @scid_max: Maximum no. of SCIDs supported + * @slice_descs: Array of SCT slice desc + */ +struct slc_sct_mem { + struct slc_sct_status sct_status; + struct slc_sct_details sct_details; + struct slc_tcm_mem_info tcm_mem_info; + __le32 slice_descs_count; + __le32 scid_max; + struct slc_sct_slice_desc slice_descs[] __counted_by_le(slice_descs_count); +} __packed; + struct qcom_llcc_config { const struct llcc_slice_config *sct_data; const u32 *reg_offset; @@ -4141,6 +4228,15 @@ static const u32 llcc_v6_reg_offset[] = { [LLCC_TRP_WRS_CACHEABLE_EN] = 0x00042088, }; +static const struct qcom_llcc_config hawi_sct_cfg[] = { + { + .sct_data = NULL, + .size = 0, + .reg_offset = llcc_v6_reg_offset, + .edac_reg_offset = &llcc_v6_edac_reg_offset, + }, +}; + static const struct qcom_llcc_config kaanapali_cfg[] = { { .sct_data = kaanapali_data, @@ -4397,6 +4493,11 @@ static const struct qcom_llcc_config x1e80100_cfg[] = { }, }; +static const struct qcom_sct_config hawi_sct_cfgs = { + .llcc_config = hawi_sct_cfg, + .num_config = ARRAY_SIZE(hawi_sct_cfg), +}; + static const struct qcom_sct_config kaanapali_cfgs = { .llcc_config = kaanapali_cfg, .num_config = ARRAY_SIZE(kaanapali_cfg), @@ -4533,23 +4634,20 @@ static struct llcc_drv_data *drv_data = (void *) -EPROBE_DEFER; */ struct llcc_slice_desc *llcc_slice_getd(u32 uid) { - const struct llcc_slice_config *cfg; - u32 sz, i; - if (IS_ERR(drv_data)) return ERR_CAST(drv_data); - cfg = drv_data->cfg; - sz = drv_data->cfg_size; - - for (i = 0; cfg && i < sz; i++, cfg++) - if (cfg->usecase_id == uid) - break; - - if (i == sz) + if (IS_ERR_OR_NULL(drv_data->desc)) return ERR_PTR(-ENODEV); - return &drv_data->desc[i]; + for (u32 i = 0; i < drv_data->cfg_size; i++) { + if (uid == drv_data->desc[i].uid) + return &drv_data->desc[i]; + } + + dev_err(drv_data->dev, "Failed to get slice desc for uid: %u\n", uid); + + return ERR_PTR(-EINVAL); } EXPORT_SYMBOL_GPL(llcc_slice_getd); @@ -5029,6 +5127,12 @@ static int qcom_llcc_cfg_program(struct platform_device *pdev, sz = drv_data->cfg_size; llcc_table = drv_data->cfg; + for (i = 0; i < sz; i++) { + drv_data->desc[i].uid = llcc_table[i].usecase_id; + drv_data->desc[i].slice_id = llcc_table[i].slice_id; + drv_data->desc[i].slice_size = llcc_table[i].max_cap; + } + if (drv_data->version >= LLCC_VERSION_6_0_0_0) { for (i = 0; i < sz; i++) { ret = _qcom_llcc_cfg_program_v6(&llcc_table[i], cfg); @@ -5064,6 +5168,101 @@ static int qcom_llcc_get_cfg_index(struct platform_device *pdev, u8 *cfg_index, return ret; } +static int qcom_llcc_verify_fw_config(struct device *dev, + const struct slc_sct_mem *slc_mem) +{ + u64 program_status; + + program_status = le64_to_cpu(slc_mem->sct_status.program_status); + + if (program_status == SLC_SCT_DONE) { + u32 desc_count = le32_to_cpu(slc_mem->slice_descs_count); + u32 scid_max = le32_to_cpu(slc_mem->scid_max); + + if (desc_count > scid_max) { + dev_err(dev, "Descriptor count above max limit (%u > %u)\n", + desc_count, scid_max); + return -EINVAL; + } + + u8 revision = slc_mem->sct_details.revision; + char name_buf[SLC_SCT_NAME_LEN]; + + memcpy(name_buf, slc_mem->sct_details.name, + SLC_SCT_NAME_LEN - 1); + name_buf[SLC_SCT_NAME_LEN - 1] = '\0'; + + dev_dbg(dev, "SCT init: desc_count=%u, rev=%u, name=%s\n", + desc_count, revision, name_buf); + + return 0; + } else if (program_status == SLC_SCT_FAIL) { + u8 version = (u8)(le64_to_cpu(slc_mem->sct_status.version)); + u64 code = le64_to_cpu(slc_mem->sct_status.error.code); + u64 param = le64_to_cpu(slc_mem->sct_status.error.param); + + if (version == SLC_SCT_MEM_LAYOUT_VERSION1) { + dev_err(dev, "SCT init failed: code = %llu, param = %llu, version = 0x%x\n", + code, param, version); + } else { + dev_err(dev, "Found unsupported version %u\n", version); + } + } else { + dev_err(dev, "Unknown SCT Initialization error\n"); + } + + return -EINVAL; +} + +static int qcom_llcc_get_fw_config(struct platform_device *pdev) +{ + const struct slc_sct_mem *slc_mem = NULL; + const struct slc_sct_slice_desc *memslice; + struct device *dev = &pdev->dev; + u32 slice_properties; + struct resource res; + u32 i, sz; + int ret; + + ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res); + if (ret) { + dev_err(dev, "Unable to locate DT /reserved-memory resource\n"); + return ret; + } + + slc_mem = devm_memremap(dev, res.start, resource_size(&res), MEMREMAP_WB); + if (!slc_mem) { + dev_err(dev, "Failed to memremap SLC shared memory\n"); + return -ENOMEM; + } + + ret = qcom_llcc_verify_fw_config(dev, slc_mem); + if (ret) + return ret; + + sz = le32_to_cpu(slc_mem->slice_descs_count); + + drv_data->desc = devm_kcalloc(dev, sz, sizeof(struct llcc_slice_desc), + GFP_KERNEL); + if (!drv_data->desc) + return -ENOMEM; + + for (i = 0; i < sz; i++) { + memslice = &slc_mem->slice_descs[i]; + drv_data->desc[i].slice_id = le16_to_cpu(memslice->slice_id); + drv_data->desc[i].uid = le16_to_cpu(memslice->usecase_id); + slice_properties = le32_to_cpu(memslice->slice_properties); + /* Set refcount to 1 if FW already activated this descriptor */ + if (FIELD_GET(SLC_SCT_SLICE_ACT_ON_BOOT, slice_properties)) + refcount_set(&drv_data->desc[i].refcount, 1); + } + + drv_data->cfg = NULL; + drv_data->cfg_size = sz; + + return 0; +} + static void qcom_llcc_remove(struct platform_device *pdev) { /* Set the global pointer to a error code to avoid referencing it */ @@ -5096,8 +5295,6 @@ static int qcom_llcc_probe(struct platform_device *pdev) struct platform_device *llcc_edac; const struct qcom_sct_config *cfgs; const struct qcom_llcc_config *cfg; - const struct llcc_slice_config *llcc_cfg; - u32 sz; u8 cfg_index; u32 version; struct regmap *regmap; @@ -5190,32 +5387,31 @@ static int qcom_llcc_probe(struct platform_device *pdev) } } - llcc_cfg = cfg->sct_data; - sz = cfg->size; - drv_data->desc = devm_kcalloc(dev, sz, sizeof(struct llcc_slice_desc), GFP_KERNEL); - if (!drv_data->desc) { - ret = -ENOMEM; - goto err; - } - - for (i = 0; i < sz; i++) { - drv_data->desc[i].slice_id = llcc_cfg[i].slice_id; - drv_data->desc[i].slice_size = llcc_cfg[i].max_cap; - refcount_set(&drv_data->desc[i].refcount, 0); - } - - drv_data->cfg = llcc_cfg; - drv_data->cfg_size = sz; - drv_data->edac_reg_offset = cfg->edac_reg_offset; - drv_data->ecc_irq_configured = cfg->irq_configured; mutex_init(&drv_data->lock); - platform_set_drvdata(pdev, drv_data); + if (!cfg->size) { + ret = qcom_llcc_get_fw_config(pdev); + if (ret) + goto err; + } else { + drv_data->cfg = cfg->sct_data; + drv_data->cfg_size = cfg->size; + drv_data->desc = devm_kcalloc(dev, cfg->size, + sizeof(struct llcc_slice_desc), GFP_KERNEL); - ret = qcom_llcc_cfg_program(pdev, cfg); - if (ret) - goto err; + if (!drv_data->desc) { + ret = -ENOMEM; + goto err; + } + + ret = qcom_llcc_cfg_program(pdev, cfg); + if (ret) + goto err; + } drv_data->ecc_irq = platform_get_irq_optional(pdev, 0); + drv_data->edac_reg_offset = cfg->edac_reg_offset; + drv_data->ecc_irq_configured = cfg->irq_configured; + drv_data->dev = dev; /* * On some platforms, the access to EDAC registers will be locked by @@ -5231,6 +5427,8 @@ static int qcom_llcc_probe(struct platform_device *pdev) dev_err(dev, "Failed to register llcc edac driver\n"); } + platform_set_drvdata(pdev, drv_data); + return 0; err: drv_data = ERR_PTR(-ENODEV); @@ -5239,6 +5437,7 @@ static int qcom_llcc_probe(struct platform_device *pdev) static const struct of_device_id qcom_llcc_of_match[] = { { .compatible = "qcom,glymur-llcc", .data = &glymur_cfgs }, + { .compatible = "qcom,hawi-llcc", .data = &hawi_sct_cfgs }, { .compatible = "qcom,ipq5424-llcc", .data = &ipq5424_cfgs}, { .compatible = "qcom,kaanapali-llcc", .data = &kaanapali_cfgs}, { .compatible = "qcom,qcs615-llcc", .data = &qcs615_cfgs}, diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h index 227125d84318..b5e917154998 100644 --- a/include/linux/soc/qcom/llcc-qcom.h +++ b/include/linux/soc/qcom/llcc-qcom.h @@ -90,11 +90,13 @@ /** * struct llcc_slice_desc - Cache slice descriptor * @slice_id: llcc slice id + * @uid: Unique ID associated with the llcc device * @slice_size: Size allocated for the llcc slice * @refcount: Atomic counter to track activate/deactivate calls */ struct llcc_slice_desc { u32 slice_id; + u32 uid; size_t slice_size; refcount_t refcount; }; @@ -147,6 +149,7 @@ struct llcc_edac_reg_offset { /** * struct llcc_drv_data - Data associated with the llcc driver + * @dev: device back-pointer for this llcc instance * @regmaps: regmaps associated with the llcc device * @bcast_regmap: regmap associated with llcc broadcast OR offset * @bcast_and_regmap: regmap associated with llcc broadcast AND offset @@ -157,10 +160,11 @@ struct llcc_edac_reg_offset { * @num_banks: Number of llcc banks * @ecc_irq: interrupt for llcc cache error detection and reporting * @ecc_irq_configured: 'True' if firmware has already configured the irq propagation - * @desc: Array pointer of pre-allocated LLCC slice descriptors * @version: Indicates the LLCC version + * @desc: Array pointer of pre-allocated LLCC slice descriptors */ struct llcc_drv_data { + struct device *dev; struct regmap **regmaps; struct regmap *bcast_regmap; struct regmap *bcast_and_regmap; @@ -183,7 +187,7 @@ struct llcc_drv_data { struct llcc_slice_desc *llcc_slice_getd(u32 uid); /** - * llcc_slice_putd - llcc slice descritpor + * llcc_slice_putd - llcc slice descriptor * @desc: Pointer to llcc slice descriptor */ void llcc_slice_putd(struct llcc_slice_desc *desc); From 06a84c2d3ca4b9b54724e79717c2cfd9a59947d9 Mon Sep 17 00:00:00 2001 From: Francisco Munoz Ruiz Date: Tue, 7 Apr 2026 14:51:49 -0700 Subject: [PATCH 084/189] soc: qcom: llcc-qcom: Capitalize LLCC/EDAC in comments and diagnostics Capitalize occurrences of the acronym "LLCC" and "EDAC" in comments and diagnostic text to improve consistency and readability. Signed-off-by: Francisco Munoz Ruiz Reviewed-by: Konrad Dybcio Reviewed-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260407-external_llcc_changes2set-v2-3-b5017ce2020b@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/llcc-qcom.c | 32 +++++++++++------------ include/linux/soc/qcom/llcc-qcom.h | 42 +++++++++++++++--------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index f8cd35b205eb..dcc08f63e020 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -83,9 +83,9 @@ #define SLC_SCT_SLICE_ACT_ON_BOOT BIT(25) /** - * struct llcc_slice_config - Data associated with the llcc slice + * struct llcc_slice_config - Data associated with the LLCC slice * @usecase_id: Unique id for the client's use case - * @slice_id: llcc slice id for each client + * @slice_id: LLCC slice id for each client * @max_cap: The maximum capacity of the cache slice provided in KB * @priority: Priority of the client used to select victim line for replacement * @fixed_size: Boolean indicating if the slice has a fixed capacity @@ -99,7 +99,7 @@ * slice: normal or TCM(Tightly Coupled Memory) * @probe_target_ways: Determines what ways to probe for access hit. When * configured to 1 only bonus and reserved ways are probed. - * When configured to 0 all ways in llcc are probed. + * When configured to 0 all ways in LLCC are probed. * @dis_cap_alloc: Disable capacity based allocation for a client * @retain_on_pc: If this bit is set and client has maintained active vote * then the ways assigned to this client are not flushed on power @@ -4626,10 +4626,10 @@ static const struct qcom_sct_config x1e80100_cfgs = { static struct llcc_drv_data *drv_data = (void *) -EPROBE_DEFER; /** - * llcc_slice_getd - get llcc slice descriptor + * llcc_slice_getd - get LLCC slice descriptor * @uid: usecase_id for the client * - * A pointer to llcc slice descriptor will be returned on success + * A pointer to LLCC slice descriptor will be returned on success * and error pointer is returned on failure */ struct llcc_slice_desc *llcc_slice_getd(u32 uid) @@ -4652,8 +4652,8 @@ struct llcc_slice_desc *llcc_slice_getd(u32 uid) EXPORT_SYMBOL_GPL(llcc_slice_getd); /** - * llcc_slice_putd - llcc slice descriptor - * @desc: Pointer to llcc slice descriptor + * llcc_slice_putd - LLCC slice descriptor + * @desc: Pointer to LLCC slice descriptor */ void llcc_slice_putd(struct llcc_slice_desc *desc) { @@ -4716,8 +4716,8 @@ static int llcc_update_act_ctrl(u32 sid, } /** - * llcc_slice_activate - Activate the llcc slice - * @desc: Pointer to llcc slice descriptor + * llcc_slice_activate - Activate the LLCC slice + * @desc: Pointer to LLCC slice descriptor * * A value of zero will be returned on success and a negative errno will * be returned in error cases @@ -4752,8 +4752,8 @@ int llcc_slice_activate(struct llcc_slice_desc *desc) EXPORT_SYMBOL_GPL(llcc_slice_activate); /** - * llcc_slice_deactivate - Deactivate the llcc slice - * @desc: Pointer to llcc slice descriptor + * llcc_slice_deactivate - Deactivate the LLCC slice + * @desc: Pointer to LLCC slice descriptor * * A value of zero will be returned on success and a negative errno will * be returned in error cases @@ -4789,7 +4789,7 @@ EXPORT_SYMBOL_GPL(llcc_slice_deactivate); /** * llcc_get_slice_id - return the slice id - * @desc: Pointer to llcc slice descriptor + * @desc: Pointer to LLCC slice descriptor */ int llcc_get_slice_id(struct llcc_slice_desc *desc) { @@ -4802,7 +4802,7 @@ EXPORT_SYMBOL_GPL(llcc_get_slice_id); /** * llcc_get_slice_size - return the slice id - * @desc: Pointer to llcc slice descriptor + * @desc: Pointer to LLCC slice descriptor */ size_t llcc_get_slice_size(struct llcc_slice_desc *desc) { @@ -4836,9 +4836,9 @@ static int _qcom_llcc_cfg_program(const struct llcc_slice_config *config, /* * LLCC instances can vary for each target. * The SW writes to broadcast register which gets propagated - * to each llcc instance (llcc0,.. llccN). + * to each LLCC instance (llcc0,.. llccN). * Since the size of the memory is divided equally amongst the - * llcc instances, we need to configure the max cap accordingly. + * LLCC instances, we need to configure the max cap accordingly. */ max_cap_cacheline = max_cap_cacheline / drv_data->num_banks; max_cap_cacheline >>= CACHE_LINE_SIZE_SHIFT; @@ -5424,7 +5424,7 @@ static int qcom_llcc_probe(struct platform_device *pdev) "qcom_llcc_edac", -1, drv_data, sizeof(*drv_data)); if (IS_ERR(llcc_edac)) - dev_err(dev, "Failed to register llcc edac driver\n"); + dev_err(dev, "Failed to register LLCC EDAC driver\n"); } platform_set_drvdata(pdev, drv_data); diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h index b5e917154998..f3ed63e475ab 100644 --- a/include/linux/soc/qcom/llcc-qcom.h +++ b/include/linux/soc/qcom/llcc-qcom.h @@ -89,9 +89,9 @@ /** * struct llcc_slice_desc - Cache slice descriptor - * @slice_id: llcc slice id - * @uid: Unique ID associated with the llcc device - * @slice_size: Size allocated for the llcc slice + * @slice_id: LLCC slice id + * @uid: Unique ID associated with the LLCC device + * @slice_size: Size allocated for the LLCC slice * @refcount: Atomic counter to track activate/deactivate calls */ struct llcc_slice_desc { @@ -102,7 +102,7 @@ struct llcc_slice_desc { }; /** - * struct llcc_edac_reg_data - llcc edac registers data for each error type + * struct llcc_edac_reg_data - LLCC EDAC registers data for each error type * @name: Name of the error * @reg_cnt: Number of registers * @count_mask: Mask value to get the error count @@ -148,17 +148,17 @@ struct llcc_edac_reg_offset { }; /** - * struct llcc_drv_data - Data associated with the llcc driver - * @dev: device back-pointer for this llcc instance - * @regmaps: regmaps associated with the llcc device - * @bcast_regmap: regmap associated with llcc broadcast OR offset - * @bcast_and_regmap: regmap associated with llcc broadcast AND offset + * struct llcc_drv_data - Data associated with the LLCC driver + * @dev: device back-pointer for this LLCC instance + * @regmaps: regmaps associated with the LLCC device + * @bcast_regmap: regmap associated with LLCC broadcast OR offset + * @bcast_and_regmap: regmap associated with LLCC broadcast AND offset * @cfg: pointer to the data structure for slice configuration * @edac_reg_offset: Offset of the LLCC EDAC registers * @lock: mutex associated with each slice * @cfg_size: size of the config data table - * @num_banks: Number of llcc banks - * @ecc_irq: interrupt for llcc cache error detection and reporting + * @num_banks: Number of LLCC banks + * @ecc_irq: interrupt for LLCC cache error detection and reporting * @ecc_irq_configured: 'True' if firmware has already configured the irq propagation * @version: Indicates the LLCC version * @desc: Array pointer of pre-allocated LLCC slice descriptors @@ -181,38 +181,38 @@ struct llcc_drv_data { #if IS_ENABLED(CONFIG_QCOM_LLCC) /** - * llcc_slice_getd - get llcc slice descriptor + * llcc_slice_getd - get LLCC slice descriptor * @uid: usecase_id of the client */ struct llcc_slice_desc *llcc_slice_getd(u32 uid); /** - * llcc_slice_putd - llcc slice descriptor - * @desc: Pointer to llcc slice descriptor + * llcc_slice_putd - LLCC slice descriptor + * @desc: Pointer to LLCC slice descriptor */ void llcc_slice_putd(struct llcc_slice_desc *desc); /** * llcc_get_slice_id - get slice id - * @desc: Pointer to llcc slice descriptor + * @desc: Pointer to LLCC slice descriptor */ int llcc_get_slice_id(struct llcc_slice_desc *desc); /** - * llcc_get_slice_size - llcc slice size - * @desc: Pointer to llcc slice descriptor + * llcc_get_slice_size - LLCC slice size + * @desc: Pointer to LLCC slice descriptor */ size_t llcc_get_slice_size(struct llcc_slice_desc *desc); /** - * llcc_slice_activate - Activate the llcc slice - * @desc: Pointer to llcc slice descriptor + * llcc_slice_activate - Activate the LLCC slice + * @desc: Pointer to LLCC slice descriptor */ int llcc_slice_activate(struct llcc_slice_desc *desc); /** - * llcc_slice_deactivate - Deactivate the llcc slice - * @desc: Pointer to llcc slice descriptor + * llcc_slice_deactivate - Deactivate the LLCC slice + * @desc: Pointer to LLCC slice descriptor */ int llcc_slice_deactivate(struct llcc_slice_desc *desc); From 35246d44bbea86fd7717c93faba4fcde9733a3be Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Wed, 1 Apr 2026 18:08:25 +0530 Subject: [PATCH 085/189] dt-bindings: firmware: qcom,scm: Document SCM on Hawi SoC Document SCM compatible for the Qualcomm Hawi SoC. Signed-off-by: Mukesh Ojha Acked-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20260401123825.589452-1-mukesh.ojha@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/firmware/qcom,scm.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/firmware/qcom,scm.yaml b/Documentation/devicetree/bindings/firmware/qcom,scm.yaml index c9590ecbcd2d..25f62bacbc91 100644 --- a/Documentation/devicetree/bindings/firmware/qcom,scm.yaml +++ b/Documentation/devicetree/bindings/firmware/qcom,scm.yaml @@ -25,6 +25,7 @@ properties: - qcom,scm-apq8084 - qcom,scm-eliza - qcom,scm-glymur + - qcom,scm-hawi - qcom,scm-ipq4019 - qcom,scm-ipq5018 - qcom,scm-ipq5210 @@ -209,6 +210,7 @@ allOf: contains: enum: - qcom,scm-eliza + - qcom,scm-hawi - qcom,scm-kaanapali - qcom,scm-milos - qcom,scm-sm8450 From 676f67060adc0ca8311b392134b3e0831f75fa91 Mon Sep 17 00:00:00 2001 From: Fenglin Wu Date: Sun, 19 Apr 2026 19:37:37 -0700 Subject: [PATCH 086/189] dt-bindings: soc: qcom: qcom,pmic-glink: Add Hawi compatible string Hawi is a mobile platform that is compatible with Kaanapali platform with respect to pmic-glink support. Add the Hawi compatible string with Kaanapali as a fallback. Signed-off-by: Fenglin Wu Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260419-hawi-pmic-glink-v1-1-a26908c468fc@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml index 411932f6b282..2db4288a8a54 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,pmic-glink.yaml @@ -32,6 +32,11 @@ properties: - qcom,sm8450-pmic-glink - qcom,sm8550-pmic-glink - const: qcom,pmic-glink + - items: + - enum: + - qcom,hawi-pmic-glink + - const: qcom,kaanapali-pmic-glink + - const: qcom,pmic-glink - items: - enum: - qcom,sm7325-pmic-glink From c5f5156ed4cb11ddaa35c49e2ca8ead5a6dff1e7 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 27 Apr 2026 23:46:09 +0530 Subject: [PATCH 087/189] dt-bindings: soc: qcom,aoss-qmp: Document the Hawi AOSS side channel Document the Always-on Subsystem side channel on Qualcomm Hawi SoC. Signed-off-by: Mukesh Ojha Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260427181609.3648384-1-mukesh.ojha@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml index 8496d623c621..8eaa04431d74 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,aoss-qmp.yaml @@ -27,6 +27,7 @@ properties: - enum: - qcom,eliza-aoss-qmp - qcom,glymur-aoss-qmp + - qcom,hawi-aoss-qmp - qcom,kaanapali-aoss-qmp - qcom,milos-aoss-qmp - qcom,qcs615-aoss-qmp From b758be0232407c760e46466625a7ed6e32c78978 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Wed, 6 May 2026 16:32:26 +0530 Subject: [PATCH 088/189] soc: qcom: pd-mapper: Add support for Hawi SoC Hawi uses the same protection domain layout as Kaanapali, so reuse the kaanapali_domains table. Also add the missing adsp_ois_pd entry (OIS protection domain, instance_id 74) to kaanapali_domains, which is required by both Kaanapali and Hawi. Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260506110226.2256249-1-mukesh.ojha@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom_pd_mapper.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/soc/qcom/qcom_pd_mapper.c b/drivers/soc/qcom/qcom_pd_mapper.c index 7bb14c20ab5d..b99718e25f2f 100644 --- a/drivers/soc/qcom/qcom_pd_mapper.c +++ b/drivers/soc/qcom/qcom_pd_mapper.c @@ -266,6 +266,12 @@ static const struct qcom_pdm_domain_data adsp_charger_pd = { .services = { NULL }, }; +static const struct qcom_pdm_domain_data adsp_ois_pd = { + .domain = "msm/adsp/ois_pd", + .instance_id = 74, + .services = { NULL, }, +}; + static const struct qcom_pdm_domain_data adsp_root_pd = { .domain = "msm/adsp/root_pd", .instance_id = 74, @@ -370,6 +376,7 @@ static const struct qcom_pdm_domain_data *glymur_domains[] = { static const struct qcom_pdm_domain_data *kaanapali_domains[] = { &adsp_audio_pd, + &adsp_ois_pd, &adsp_root_pd, &adsp_sensor_pd, &cdsp_root_pd, @@ -581,6 +588,7 @@ static const struct of_device_id qcom_pdm_domains[] __maybe_unused = { { .compatible = "qcom,eliza", .data = sm8550_domains, }, { .compatible = "qcom,apq8096", .data = msm8996_domains, }, { .compatible = "qcom,glymur", .data = glymur_domains, }, + { .compatible = "qcom,hawi", .data = kaanapali_domains, }, { .compatible = "qcom,kaanapali", .data = kaanapali_domains, }, { .compatible = "qcom,mahua", .data = glymur_domains, }, { .compatible = "qcom,milos", .data = sm8550_domains, }, From 667d05a09db3da8b3dfbe0bc1dafae2d7887176b Mon Sep 17 00:00:00 2001 From: Harrison Vanderbyl Date: Tue, 12 May 2026 00:40:53 +1000 Subject: [PATCH 089/189] firmware: qcom: scm: Allow QSEECOM on Surface Pro 12in Add the Surface Pro 12in to the QSEECOM allowlist so that the Qualcomm Secure Execution Environment interface is available on this device. Signed-off-by: Harrison Vanderbyl Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/92171ad5e7851e6758dd205246b4289f32e12655.1778498477.git.harrison.vanderbyl@gmail.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 9b06a69d3a6d..6b601a4b89db 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -2306,6 +2306,7 @@ static const struct of_device_id qcom_scm_qseecom_allowlist[] __maybe_unused = { { .compatible = "lenovo,yoga-slim7x" }, { .compatible = "medion,sprchrgd14s1" }, { .compatible = "microsoft,arcata", }, + { .compatible = "microsoft,surface-pro-12in", }, { .compatible = "microsoft,blackrock" }, { .compatible = "microsoft,denali", }, { .compatible = "microsoft,romulus13", }, From 4eda0dac7a2807c0bb628a1931b4f67d945c3758 Mon Sep 17 00:00:00 2001 From: Ethan Tidmore Date: Thu, 7 May 2026 20:57:16 -0500 Subject: [PATCH 090/189] memory: tegra: Fix possible null pointer dereference The function tegra114_emc_find_timing() has the possibility of returning null and it's return value 'timing' is dereferenced before it is checked for null. Place dereference after null pointer check. Detected by Smatch: drivers/memory/tegra/tegra114-emc.c:520 tegra114_emc_prepare_timing_change() warn: variable dereferenced before check 'timing' (see line 515) Fixes: dce208b5405f4 ("memory: tegra: Add Tegra114 EMC driver") Signed-off-by: Ethan Tidmore Acked-by: Thierry Reding Link: https://patch.msgid.link/20260508015716.652347-1-ethantidmore06@gmail.com Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202605100506.wJFmI6IM-lkp@intel.com/ Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra114-emc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/memory/tegra/tegra114-emc.c b/drivers/memory/tegra/tegra114-emc.c index be6c699cb656..fc3bf1a28630 100644 --- a/drivers/memory/tegra/tegra114-emc.c +++ b/drivers/memory/tegra/tegra114-emc.c @@ -512,14 +512,16 @@ static int tegra114_emc_prepare_timing_change(struct tegra_emc *emc, enum emc_dll_change dll_change; unsigned int pre_wait = 0; u32 val, mask; - bool next_dll_enabled = !(timing->emc_mode_1 & 0x1); bool last_dll_enabled = !(last->emc_mode_1 & 0x1); bool update = false; + bool next_dll_enabled; unsigned int i; if (!timing) return -ENOENT; + next_dll_enabled = !(timing->emc_mode_1 & 0x1); + if (next_dll_enabled == last_dll_enabled) dll_change = DLL_CHANGE_NONE; else if (next_dll_enabled) From b79d9b5747d961516c35ef4d5e91efa579fd3e9a Mon Sep 17 00:00:00 2001 From: Stepan Ionichev Date: Wed, 13 May 2026 14:09:00 +0500 Subject: [PATCH 091/189] clk: scpi: Unregister child clock providers on remove SCPI clock providers are registered for each child node in scpi_clk_add(), but scpi_clocks_remove() unregisters the parent node on each iteration. of_clk_del_provider() matches providers by the node used at registration time, so passing the parent node leaves the child providers registered. This leaks the provider allocations and the node references held by the clock provider core. Pass the child node to of_clk_del_provider() so the remove path matches the probe path. Fixes: cd52c2a4b5c4 ("clk: add support for clocks provided by SCP(System Control Processor)") Signed-off-by: Stepan Ionichev Link: https://patch.msgid.link/20260513090900.5323-1-sozdayvek@gmail.com (sudeep.holla: Updated commit title and message a bit) Signed-off-by: Sudeep Holla --- drivers/clk/clk-scpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index 7806569cd0d5..24cee7c9fda6 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -258,7 +258,7 @@ static void scpi_clocks_remove(struct platform_device *pdev) } for_each_available_child_of_node(np, child) - of_clk_del_provider(np); + of_clk_del_provider(child); } static int scpi_clocks_probe(struct platform_device *pdev) From 67de69ef2a780efd71b222652a048ee6aaf45e42 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 4 May 2026 21:00:05 +0200 Subject: [PATCH 092/189] memory: tegra114-emc: Do not print error on icc_node_create() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit icc_node_create() is alloc-like function, so no need to print error messages on its failure. Dropping one label makes the code a bit simpler. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260504190004.747509-3-krzysztof.kozlowski@oss.qualcomm.com --- drivers/memory/tegra/tegra114-emc.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/memory/tegra/tegra114-emc.c b/drivers/memory/tegra/tegra114-emc.c index fc3bf1a28630..559ce47694a5 100644 --- a/drivers/memory/tegra/tegra114-emc.c +++ b/drivers/memory/tegra/tegra114-emc.c @@ -1204,10 +1204,8 @@ static int tegra114_emc_interconnect_init(struct tegra_emc *emc) /* create External Memory Controller node */ node = icc_node_create(TEGRA_ICC_EMC); - if (IS_ERR(node)) { - err = PTR_ERR(node); - goto err_msg; - } + if (IS_ERR(node)) + return PTR_ERR(node); node->name = "External Memory Controller"; icc_node_add(node, &emc->provider); @@ -1235,7 +1233,6 @@ static int tegra114_emc_interconnect_init(struct tegra_emc *emc) remove_nodes: icc_nodes_remove(&emc->provider); -err_msg: dev_err(emc->dev, "failed to initialize ICC: %d\n", err); return err; From 0a783a6686c96847d74874dbea38fa0e6b1f6c12 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 4 May 2026 21:00:06 +0200 Subject: [PATCH 093/189] memory: tegra114-emc: Simplify tegra114_emc_interconnect_init() error message Use dev_err_probe() in tegra114_emc_interconnect_init() to make code a bit simpler. It's preferred form of printing error messages during probe, even if actual call cannot return EPROBE_DEFER. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260504190004.747509-4-krzysztof.kozlowski@oss.qualcomm.com --- drivers/memory/tegra/tegra114-emc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/memory/tegra/tegra114-emc.c b/drivers/memory/tegra/tegra114-emc.c index 559ce47694a5..e50d71842d3a 100644 --- a/drivers/memory/tegra/tegra114-emc.c +++ b/drivers/memory/tegra/tegra114-emc.c @@ -1233,9 +1233,8 @@ static int tegra114_emc_interconnect_init(struct tegra_emc *emc) remove_nodes: icc_nodes_remove(&emc->provider); - dev_err(emc->dev, "failed to initialize ICC: %d\n", err); - return err; + return dev_err_probe(emc->dev, err, "failed to initialize ICC"); } static void devm_tegra114_emc_unset_callback(void *data) From b920352cfd2b0fcd1249ff006618c939b64fc8f7 Mon Sep 17 00:00:00 2001 From: Siddharth Vadapalli Date: Fri, 1 May 2026 18:10:54 +0530 Subject: [PATCH 094/189] soc: ti: k3-ringacc: Fix access mode for k3_ringacc_ring_pop_tail_io/proxy k3_ringacc_ring_pop_tail_io() and k3_ringacc_ring_pop_tail_proxy() incorrectly use K3_RINGACC_ACCESS_MODE_POP_HEAD instead of K3_RINGACC_ACCESS_MODE_POP_TAIL. This will result in ring elements being popped in the reverse order of that which the caller expects. Fix this. Fixes: 3277e8aa2504 ("soc: ti: k3: add navss ringacc driver") Cc: stable@vger.kernel.org Signed-off-by: Siddharth Vadapalli Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260501124129.362192-1-s-vadapalli@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/k3-ringacc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/soc/ti/k3-ringacc.c b/drivers/soc/ti/k3-ringacc.c index 7602b8a909b0..e2ca380812d2 100644 --- a/drivers/soc/ti/k3-ringacc.c +++ b/drivers/soc/ti/k3-ringacc.c @@ -1012,7 +1012,7 @@ static int k3_ringacc_ring_pop_head_proxy(struct k3_ring *ring, void *elem) static int k3_ringacc_ring_pop_tail_proxy(struct k3_ring *ring, void *elem) { return k3_ringacc_ring_access_proxy(ring, elem, - K3_RINGACC_ACCESS_MODE_POP_HEAD); + K3_RINGACC_ACCESS_MODE_POP_TAIL); } static int k3_ringacc_ring_access_io(struct k3_ring *ring, void *elem, @@ -1083,7 +1083,7 @@ static int k3_ringacc_ring_pop_io(struct k3_ring *ring, void *elem) static int k3_ringacc_ring_pop_tail_io(struct k3_ring *ring, void *elem) { return k3_ringacc_ring_access_io(ring, elem, - K3_RINGACC_ACCESS_MODE_POP_HEAD); + K3_RINGACC_ACCESS_MODE_POP_TAIL); } /* From 10a1969353b20caa50c320717e054601631c0d3e Mon Sep 17 00:00:00 2001 From: Md Shofiqul Islam Date: Wed, 6 May 2026 18:41:14 +0300 Subject: [PATCH 095/189] soc: ti: knav_qmss_queue: Implement resource cleanup in remove() Implement the TODO in knav_queue_remove() by stopping PDSPs and freeing queue regions and queue ranges before disabling runtime PM, mirroring the cleanup performed in the probe error path. Set device_ready to false before cleanup to prevent any further use of the device during teardown. This ensures resources are released on driver unbind and avoids leaking queue/region state. Suggested-by: Nishanth Menon Signed-off-by: Md Shofiqul Islam Link: https://lore.kernel.org/linux-arm-kernel/20260506154114.2288-1-shofiqtest@gmail.com/ Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss_queue.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index 86d7a9c9ae01..a0644578789f 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -1873,7 +1873,12 @@ static int knav_queue_probe(struct platform_device *pdev) static void knav_queue_remove(struct platform_device *pdev) { - /* TODO: Free resources */ + struct knav_device *kdev = platform_get_drvdata(pdev); + + device_ready = false; + knav_queue_stop_pdsps(kdev); + knav_queue_free_regions(kdev); + knav_free_queue_ranges(kdev); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); } From cc7e8f21b9f0c229d68cf19a837cba82b5ac2d87 Mon Sep 17 00:00:00 2001 From: Yeoreum Yun Date: Fri, 8 May 2026 18:54:15 +0100 Subject: [PATCH 096/189] Revert "firmware: arm_ffa: Change initcall level of ffa_init() to rootfs_initcall" This reverts commit 0e0546eabcd6c19765a8dbf5b5db3723e7b0ea75, which was added to address ordering issues with the IMA LSM initialisation where the TPM would not be fully ready by the time IMA wanted it. This has been resolved within IMA by retrying setup during late_initcall_sync if the TPM is not available at first. Signed-off-by: Yeoreum Yun Link: https://patch.msgid.link/20260508-b4-ffa_plat_dev-v1-1-c5a30f8cf7b8@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index b9f17fda7243..8e1a070bbf83 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -2164,7 +2164,7 @@ static int __init ffa_init(void) kfree(drv_info); return ret; } -rootfs_initcall(ffa_init); +module_init(ffa_init); static void __exit ffa_exit(void) { From e659fc8e537c7a21d5d693d6f30d8852f2fa8d91 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 8 May 2026 18:54:16 +0100 Subject: [PATCH 097/189] firmware: arm_ffa: Register core as a platform driver Move the FF-A core bring-up and teardown paths into platform driver probe and remove callbacks, and register a synthetic arm-ffa platform device to bind the driver. This makes the FF-A core lifetime follow the driver model while keeping the device creation internal to the FF-A core. Use normal platform driver registration so the probe path has standard driver-core semantics. The synthetic platform device is a temporary bridge until ACPI and devicetree describe the FF-A core device or object. Once those firmware description paths are defined, the internal platform device creation can be dropped and the driver can bind to the firmware-described device directly. Since the transport selection now happens from the platform probe path, drop the __init annotation from ffa_transport_init(). Reviewed-by: Yeoreum Yun Link: https://patch.msgid.link/20260508-b4-ffa_plat_dev-v1-2-c5a30f8cf7b8@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/common.h | 4 +-- drivers/firmware/arm_ffa/driver.c | 55 +++++++++++++++++++++++++++---- drivers/firmware/arm_ffa/smccc.c | 2 +- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/drivers/firmware/arm_ffa/common.h b/drivers/firmware/arm_ffa/common.h index 9c6425a81d0d..5cdf4bd222c6 100644 --- a/drivers/firmware/arm_ffa/common.h +++ b/drivers/firmware/arm_ffa/common.h @@ -18,9 +18,9 @@ bool ffa_device_is_valid(struct ffa_device *ffa_dev); void ffa_device_match_uuid(struct ffa_device *ffa_dev, const uuid_t *uuid); #ifdef CONFIG_ARM_FFA_SMCCC -int __init ffa_transport_init(ffa_fn **invoke_ffa_fn); +int ffa_transport_init(ffa_fn **invoke_ffa_fn); #else -static inline int __init ffa_transport_init(ffa_fn **invoke_ffa_fn) +static inline int ffa_transport_init(ffa_fn **invoke_ffa_fn) { return -EOPNOTSUPP; } diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 8e1a070bbf83..dd74f6c79749 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #define FFA_DRIVER_VERSION FFA_VERSION_1_2 #define FFA_MIN_VERSION FFA_VERSION_1_0 +#define FFA_PLATFORM_NAME "arm-ffa" #define SENDER_ID_MASK GENMASK(31, 16) #define RECEIVER_ID_MASK GENMASK(15, 0) @@ -115,6 +117,7 @@ struct ffa_drv_info { }; static struct ffa_drv_info *drv_info; +static struct platform_device *ffa_pdev; /* * The driver must be able to support all the versions from the earliest @@ -2086,7 +2089,7 @@ static void ffa_notifications_setup(void) ffa_notifications_cleanup(); } -static int __init ffa_init(void) +static int ffa_probe(struct platform_device *pdev) { int ret; u32 buf_sz; @@ -2099,6 +2102,7 @@ static int __init ffa_init(void) drv_info = kzalloc_obj(*drv_info); if (!drv_info) return -ENOMEM; + platform_set_drvdata(pdev, drv_info); ret = ffa_version_check(&drv_info->version); if (ret) @@ -2161,19 +2165,56 @@ static int __init ffa_init(void) free_pages_exact(drv_info->tx_buffer, rxtx_bufsz); free_pages_exact(drv_info->rx_buffer, rxtx_bufsz); free_drv_info: + platform_set_drvdata(pdev, NULL); kfree(drv_info); + drv_info = NULL; + return ret; +} + +static void ffa_remove(struct platform_device *pdev) +{ + struct ffa_drv_info *info = platform_get_drvdata(pdev); + + ffa_notifications_cleanup(); + ffa_partitions_cleanup(); + ffa_rxtx_unmap(); + free_pages_exact(info->tx_buffer, info->rxtx_bufsz); + free_pages_exact(info->rx_buffer, info->rxtx_bufsz); + kfree(info); + platform_set_drvdata(pdev, NULL); + drv_info = NULL; +} + +static struct platform_driver ffa_driver = { + .probe = ffa_probe, + .remove = ffa_remove, + .driver = { + .name = FFA_PLATFORM_NAME, + }, +}; + +static int __init ffa_init(void) +{ + int ret; + + ffa_pdev = platform_device_register_simple(FFA_PLATFORM_NAME, + PLATFORM_DEVID_NONE, + NULL, 0); + if (IS_ERR(ffa_pdev)) + return PTR_ERR(ffa_pdev); + + ret = platform_driver_register(&ffa_driver); + if (ret) + platform_device_unregister(ffa_pdev); + return ret; } module_init(ffa_init); static void __exit ffa_exit(void) { - ffa_notifications_cleanup(); - ffa_partitions_cleanup(); - ffa_rxtx_unmap(); - free_pages_exact(drv_info->tx_buffer, drv_info->rxtx_bufsz); - free_pages_exact(drv_info->rx_buffer, drv_info->rxtx_bufsz); - kfree(drv_info); + platform_device_unregister(ffa_pdev); + platform_driver_unregister(&ffa_driver); } module_exit(ffa_exit); diff --git a/drivers/firmware/arm_ffa/smccc.c b/drivers/firmware/arm_ffa/smccc.c index 4d85bfff0a4e..e6125dd9f58f 100644 --- a/drivers/firmware/arm_ffa/smccc.c +++ b/drivers/firmware/arm_ffa/smccc.c @@ -17,7 +17,7 @@ static void __arm_ffa_fn_hvc(ffa_value_t args, ffa_value_t *res) arm_smccc_1_2_hvc(&args, res); } -int __init ffa_transport_init(ffa_fn **invoke_ffa_fn) +int ffa_transport_init(ffa_fn **invoke_ffa_fn) { enum arm_smccc_conduit conduit; From 7fe2ec9fb8e9a78dad8d6b1e551cb4d126e36f1e Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 8 May 2026 18:54:17 +0100 Subject: [PATCH 098/189] firmware: arm_ffa: Set the core device as FF-A device parent Pass a parent device into ffa_device_register() and use the synthetic arm-ffa platform device as the parent for each registered FF-A device. This keeps the enumerated FF-A partition devices anchored below the FF-A core device in the driver model, matching the platform-driver conversion of the core transport. Suggested-by: Yeoreum Yun Reviewed-by: Yeoreum Yun Link: https://patch.msgid.link/20260508-b4-ffa_plat_dev-v1-3-c5a30f8cf7b8@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/bus.c | 3 ++- drivers/firmware/arm_ffa/driver.c | 5 +++-- include/linux/arm_ffa.h | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/arm_ffa/bus.c b/drivers/firmware/arm_ffa/bus.c index 601c3418e0d9..4df5c3b3fe43 100644 --- a/drivers/firmware/arm_ffa/bus.c +++ b/drivers/firmware/arm_ffa/bus.c @@ -192,7 +192,7 @@ bool ffa_device_is_valid(struct ffa_device *ffa_dev) struct ffa_device * ffa_device_register(const struct ffa_partition_info *part_info, - const struct ffa_ops *ops) + const struct ffa_ops *ops, struct device *parent) { int id, ret; struct device *dev; @@ -212,6 +212,7 @@ ffa_device_register(const struct ffa_partition_info *part_info, } dev = &ffa_dev->dev; + dev->parent = parent; dev->bus = &ffa_bus_type; dev->release = ffa_release_device; dev->dma_mask = &dev->coherent_dma_mask; diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index dd74f6c79749..4bb6b1384978 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1738,7 +1738,7 @@ static int ffa_setup_host_partition(int vm_id) int ret; buf.id = vm_id; - ffa_dev = ffa_device_register(&buf, &ffa_drv_ops); + ffa_dev = ffa_device_register(&buf, &ffa_drv_ops, &ffa_pdev->dev); if (!ffa_dev) { pr_err("%s: failed to register host partition ID 0x%x\n", __func__, vm_id); @@ -1813,7 +1813,8 @@ static int ffa_setup_partitions(void) * provides UUID here for each partition as part of the * discovery API and the same is passed. */ - ffa_dev = ffa_device_register(tpbuf, &ffa_drv_ops); + ffa_dev = ffa_device_register(tpbuf, &ffa_drv_ops, + &ffa_pdev->dev); if (!ffa_dev) { pr_err("%s: failed to register partition ID 0x%x\n", __func__, tpbuf->id); diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h index 81e603839c4a..17eca3dfc59e 100644 --- a/include/linux/arm_ffa.h +++ b/include/linux/arm_ffa.h @@ -173,7 +173,7 @@ struct ffa_partition_info; #if IS_REACHABLE(CONFIG_ARM_FFA_TRANSPORT) struct ffa_device * ffa_device_register(const struct ffa_partition_info *part_info, - const struct ffa_ops *ops); + const struct ffa_ops *ops, struct device *parent); void ffa_device_unregister(struct ffa_device *ffa_dev); int ffa_driver_register(struct ffa_driver *driver, struct module *owner, const char *mod_name); @@ -184,7 +184,7 @@ bool ffa_device_is_valid(struct ffa_device *ffa_dev); #else static inline struct ffa_device * ffa_device_register(const struct ffa_partition_info *part_info, - const struct ffa_ops *ops) + const struct ffa_ops *ops, struct device *parent) { return NULL; } From 3acc80a78e45246a87061cbdd46775baa132de83 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 8 May 2026 18:54:18 +0100 Subject: [PATCH 099/189] firmware: arm_ffa: Defer probe until pKVM is initialized When protected KVM is enabled, the kernel includes a pKVM FF-A proxy that sits in front of the normal FF-A driver. The proxy has to perform its own FF-A version negotiation and setup first, so that it can mediate subsequent FF-A traffic correctly. Defer FF-A core probing until pKVM has completed initialization. This keeps the normal driver from negotiating the FF-A version or performing other transport setup before the pKVM proxy is ready, and lets the driver model retry probing once the protected KVM state required by the FF-A transport is available. Suggested-by: Yeoreum Yun Reviewed-by: Yeoreum Yun Link: https://patch.msgid.link/20260508-b4-ffa_plat_dev-v1-4-c5a30f8cf7b8@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 4bb6b1384978..fb97b01c282a 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -43,6 +43,8 @@ #include #include +#include + #include "common.h" #define FFA_DRIVER_VERSION FFA_VERSION_1_2 @@ -2096,6 +2098,10 @@ static int ffa_probe(struct platform_device *pdev) u32 buf_sz; size_t rxtx_bufsz = SZ_4K; + if (IS_BUILTIN(CONFIG_ARM_FFA_TRANSPORT) && + is_protected_kvm_enabled() && !is_pkvm_initialized()) + return -EPROBE_DEFER; + ret = ffa_transport_init(&invoke_ffa_fn); if (ret) return ret; From 577bc2354726fe96969a10821bcdc61a6c0aeeb8 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 5 May 2026 12:26:36 +0200 Subject: [PATCH 100/189] soc: aspeed: Move MODULE_DEVICE_TABLE next to the table itself By convention MODULE_DEVICE_TABLE() immediately follows the ID table it exports, because this is easier to read and verify. It also makes more sense since #ifdef for ACPI or OF could hide both of them. Most of the privers already have this correctly placed, so adjust the missing ones. No functional impact. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Andrew Jeffery --- drivers/soc/aspeed/aspeed-lpc-ctrl.c | 2 +- drivers/soc/aspeed/aspeed-lpc-snoop.c | 2 +- drivers/soc/aspeed/aspeed-p2a-ctrl.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/soc/aspeed/aspeed-lpc-ctrl.c b/drivers/soc/aspeed/aspeed-lpc-ctrl.c index b7dbb12bd095..4d8574cac703 100644 --- a/drivers/soc/aspeed/aspeed-lpc-ctrl.c +++ b/drivers/soc/aspeed/aspeed-lpc-ctrl.c @@ -338,6 +338,7 @@ static const struct of_device_id aspeed_lpc_ctrl_match[] = { { .compatible = "aspeed,ast2600-lpc-ctrl" }, { }, }; +MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match); static struct platform_driver aspeed_lpc_ctrl_driver = { .driver = { @@ -350,7 +351,6 @@ static struct platform_driver aspeed_lpc_ctrl_driver = { module_platform_driver(aspeed_lpc_ctrl_driver); -MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Cyril Bur "); MODULE_DESCRIPTION("Control for ASPEED LPC HOST to BMC mappings"); diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c index b03310c0830d..28e491fffc7d 100644 --- a/drivers/soc/aspeed/aspeed-lpc-snoop.c +++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c @@ -366,6 +366,7 @@ static const struct of_device_id aspeed_lpc_snoop_match[] = { .data = &ast2500_model_data }, { }, }; +MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); static struct platform_driver aspeed_lpc_snoop_driver = { .driver = { @@ -378,7 +379,6 @@ static struct platform_driver aspeed_lpc_snoop_driver = { module_platform_driver(aspeed_lpc_snoop_driver); -MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Robert Lippert "); MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality"); diff --git a/drivers/soc/aspeed/aspeed-p2a-ctrl.c b/drivers/soc/aspeed/aspeed-p2a-ctrl.c index d88abc5f195b..b6b7b80f842a 100644 --- a/drivers/soc/aspeed/aspeed-p2a-ctrl.c +++ b/drivers/soc/aspeed/aspeed-p2a-ctrl.c @@ -416,6 +416,7 @@ static const struct of_device_id aspeed_p2a_ctrl_match[] = { .data = &ast2500_model_data }, { }, }; +MODULE_DEVICE_TABLE(of, aspeed_p2a_ctrl_match); static struct platform_driver aspeed_p2a_ctrl_driver = { .driver = { @@ -428,7 +429,6 @@ static struct platform_driver aspeed_p2a_ctrl_driver = { module_platform_driver(aspeed_p2a_ctrl_driver); -MODULE_DEVICE_TABLE(of, aspeed_p2a_ctrl_match); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Patrick Venture "); MODULE_DESCRIPTION("Control for aspeed 2400/2500 P2A VGA HOST to BMC mappings"); From b3990b52de516431df2b0751ace9349e96e512c0 Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Mon, 13 Apr 2026 09:37:09 +0000 Subject: [PATCH 101/189] soc: qcom: socinfo: Add PMIV0102 & PMIV0104 PMICs Add the PMIV0102 and PMIV0104 to the pmic_models array. Signed-off-by: Alexander Koskovich Link: https://lore.kernel.org/r/20260413-add-pmic-ids-v1-1-1f40b8773ef8@pm.me Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index ef7074930a55..e30fd133a12b 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -191,6 +191,8 @@ static const char *const pmic_models[] = { [86] = "PM8750B", [87] = "PMD8028", [88] = "PMC1020H", + [89] = "PMIV0104", + [90] = "PMIV0102", [91] = "PMIV0108", [92] = "PMK8850", [93] = "PMH0101", From a92d3fdb44c2b9d2b8dc463e287030e03ea00699 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sun, 19 Apr 2026 21:15:23 +0800 Subject: [PATCH 102/189] soc: qcom: socinfo: Add PMIC PMAU0102 Add PMAU0102 found on Nord boards to pmic_models array. Signed-off-by: Shawn Guo Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260419131523.1232835-1-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index e30fd133a12b..b7d02a5c625d 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -196,6 +196,7 @@ static const char *const pmic_models[] = { [91] = "PMIV0108", [92] = "PMK8850", [93] = "PMH0101", + [94] = "PMAU0102", [95] = "SMB2370", [96] = "PMH0104", [97] = "PMH0110", From 5073d284650a585dc2450d682ae0c0052b253ae4 Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Sun, 12 Apr 2026 15:42:40 +0000 Subject: [PATCH 103/189] dt-bindings: arm: qcom,ids: Add SoC ID for SM7750 Document the ID for SM7750, an Eliza SoC variant that can be found on the Nothing Phone (4a) Pro. Signed-off-by: Alexander Koskovich Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260412-sm7550-id-v1-1-958a673ff791@pm.me Signed-off-by: Bjorn Andersson --- include/dt-bindings/arm/qcom,ids.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/arm/qcom,ids.h b/include/dt-bindings/arm/qcom,ids.h index bc65e233eb93..7f73f315f0ec 100644 --- a/include/dt-bindings/arm/qcom,ids.h +++ b/include/dt-bindings/arm/qcom,ids.h @@ -290,6 +290,7 @@ #define QCOM_ID_IPQ5424 651 #define QCOM_ID_QCM6690 657 #define QCOM_ID_QCS6690 658 +#define QCOM_ID_SM7750 659 #define QCOM_ID_SM8850 660 #define QCOM_ID_IPQ5404 671 #define QCOM_ID_QCS9100 667 From d8eae83cefbc251f090b074b533616684e7b60cf Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Sun, 12 Apr 2026 15:42:44 +0000 Subject: [PATCH 104/189] soc: qcom: socinfo: Add SoC ID for SM7750 Recognize the SM7750 SoC which is an Eliza SoC variant. Signed-off-by: Alexander Koskovich Reviewed-by: Krzysztof Kozlowski Reviewed-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20260412-sm7550-id-v1-2-958a673ff791@pm.me Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index b7d02a5c625d..52c91de0e42c 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -531,6 +531,7 @@ static const struct soc_id soc_id[] = { { qcom_board_id(IPQ5424) }, { qcom_board_id(QCM6690) }, { qcom_board_id(QCS6690) }, + { qcom_board_id(SM7750) }, { qcom_board_id(SM8850) }, { qcom_board_id(IPQ5404) }, { qcom_board_id(QCS9100) }, From 0670a10b7c22bef36d9e1ca5a75b832db4508a1b Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Mon, 27 Apr 2026 08:35:30 +0800 Subject: [PATCH 105/189] dt-bindings: arm: qcom,ids: Add SoC ID for Nord SA8797P Nord is a SoC family from Qualcomm designed as the next generation of Lemans series. SA8797P is the automotive variant of Nord, where platform resources such as clocks, regulators, interconnects, etc. are managed by firmware through SCMI. Add SoC ID for Nord SA8797P. Signed-off-by: Deepti Jaggi Reviewed-by: Krzysztof Kozlowski Signed-off-by: Shawn Guo Link: https://lore.kernel.org/r/20260427003531.229671-2-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- include/dt-bindings/arm/qcom,ids.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/dt-bindings/arm/qcom,ids.h b/include/dt-bindings/arm/qcom,ids.h index 7f73f315f0ec..1af73c0ad41c 100644 --- a/include/dt-bindings/arm/qcom,ids.h +++ b/include/dt-bindings/arm/qcom,ids.h @@ -298,6 +298,7 @@ #define QCOM_ID_QCS8275 675 #define QCOM_ID_QCS9075 676 #define QCOM_ID_QCS615 680 +#define QCOM_ID_SA8797P 690 #define QCOM_ID_CQ7790M 731 #define QCOM_ID_CQ7790S 732 #define QCOM_ID_CQ2390M 756 From b0bc160c311a0e7163c00a7e66116d28ce9771df Mon Sep 17 00:00:00 2001 From: Deepti Jaggi Date: Mon, 27 Apr 2026 08:35:31 +0800 Subject: [PATCH 106/189] soc: qcom: socinfo: Add SoC ID for Nord SA8797P Add Nord SA8797P SoC ID to socinfo driver. Signed-off-by: Deepti Jaggi Reviewed-by: Krzysztof Kozlowski Signed-off-by: Shawn Guo Link: https://lore.kernel.org/r/20260427003531.229671-3-shengchao.guo@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/socinfo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 52c91de0e42c..af418adad7aa 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -539,6 +539,7 @@ static const struct soc_id soc_id[] = { { qcom_board_id(QCS8275) }, { qcom_board_id(QCS9075) }, { qcom_board_id(QCS615) }, + { qcom_board_id(SA8797P) }, { qcom_board_id(CQ7790M) }, { qcom_board_id(CQ7790S) }, { qcom_board_id(CQ2390M) }, From 01b9cae706161a39452a2cce0f281d4369344c51 Mon Sep 17 00:00:00 2001 From: Jamie Nguyen Date: Mon, 18 May 2026 13:31:16 -0700 Subject: [PATCH 107/189] firmware: arm_ffa: Honor partition info descriptor size FFA_PARTITION_INFO_GET_REGS reports the size of each partition information descriptor in x2[63:48]. However, __ffa_partition_info_get_regs() walks the returned register payload with a hardcoded 24-byte stride (regs += 3), even though the size is already read into buf_sz. That works for the FF-A v1.1/v1.2 24-byte descriptor layout, where each descriptor consumes three registers. Newer FF-A revisions can extend the descriptor while keeping the existing fields at the front. For example, a 48-byte descriptor consumes six registers, so advancing by only three registers desynchronises the parser and can make it read subsequent entries from the middle of a descriptor. Use the advertised descriptor size to derive the register stride. Validate that the size is register-aligned, large enough for the fields parsed by the driver, and that the requested number of descriptors fits in the returned x3..x17 register window. The driver still copies only the fields it understands, but now skips over any trailing descriptor fields correctly. Fixes: ba85c644ac8d ("firmware: arm_ffa: Add support for FFA_PARTITION_INFO_GET_REGS") Suggested-by: Sudeep Holla Signed-off-by: Jamie Nguyen Link: https://patch.msgid.link/20260518203116.42624-1-jamien@nvidia.com (sudeep.holla: Minor rewordng of the commit message and subject) Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index fb97b01c282a..54984e1b9741 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -329,11 +329,9 @@ __ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, #define PART_INFO_EXEC_CXT_MASK GENMASK(31, 16) #define PART_INFO_PROPS_MASK GENMASK(63, 32) #define FFA_PART_INFO_GET_REGS_FIRST_REG 3 -#define FFA_PART_INFO_GET_REGS_REGS_PER_DESC 3 -#define FFA_PART_INFO_GET_REGS_MAX_DESC \ - (((sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0)) - \ - FFA_PART_INFO_GET_REGS_FIRST_REG) / \ - FFA_PART_INFO_GET_REGS_REGS_PER_DESC) +#define FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC 3 +#define FFA_PART_INFO_GET_REGS_NUM_REGS \ + (sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0)) #define PART_INFO_ID(x) ((u16)(FIELD_GET(PART_INFO_ID_MASK, (x)))) #define PART_INFO_EXEC_CXT(x) ((u16)(FIELD_GET(PART_INFO_EXEC_CXT_MASK, (x)))) #define PART_INFO_PROPERTIES(x) ((u32)(FIELD_GET(PART_INFO_PROPS_MASK, (x)))) @@ -347,7 +345,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, do { __le64 *regs; - int idx, nr_desc, buf_idx; + int idx, nr_desc, buf_idx, regs_per_desc, max_desc; invoke_ffa_fn((ffa_value_t){ .a0 = FFA_PARTITION_INFO_GET_REGS, @@ -370,8 +368,18 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, if (cur_idx < start_idx || cur_idx >= count) return -EINVAL; + buf_sz = PARTITION_INFO_SZ(partition_info.a2); + if (buf_sz % sizeof(*regs)) + return -EINVAL; + + regs_per_desc = buf_sz / sizeof(*regs); + if (regs_per_desc < FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC) + return -EINVAL; + nr_desc = cur_idx - start_idx + 1; - if (nr_desc > FFA_PART_INFO_GET_REGS_MAX_DESC) + max_desc = (FFA_PART_INFO_GET_REGS_NUM_REGS - + FFA_PART_INFO_GET_REGS_FIRST_REG) / regs_per_desc; + if (nr_desc > max_desc) return -EINVAL; buf_idx = buf - buffer; @@ -379,9 +387,6 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, return -EINVAL; tag = UUID_INFO_TAG(partition_info.a2); - buf_sz = PARTITION_INFO_SZ(partition_info.a2); - if (buf_sz > sizeof(*buffer)) - buf_sz = sizeof(*buffer); regs = (void *)&partition_info.a3; for (idx = 0; idx < nr_desc; idx++, buf++) { @@ -400,7 +405,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, buf->exec_ctxt = PART_INFO_EXEC_CXT(val); buf->properties = PART_INFO_PROPERTIES(val); uuid_copy(&buf->uuid, &uuid_regs.uuid); - regs += 3; + regs += regs_per_desc; } start_idx = cur_idx + 1; From 70492cfce2a4d41e87bf46989028a90f4bc6b38f Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Tue, 2 Sep 2025 18:20:53 +0100 Subject: [PATCH 108/189] firmware: smccc: Fix Arm SMCCC SOC_ID name call Commit 5f9c23abc477 ("firmware: smccc: Support optional Arm SMCCC SOC_ID name") introduced the SOC_ID name string call, which reports a human readable string describing the SoC, as returned by firmware. The SMCCC spec v1.6 describes this feature as AArch64 only, since we rely on 8 characters to be transmitted per register. Consequently the SMCCC call must use the AArch64 calling convention, which requires bit 30 of the FID to be set. The spec is a bit confusing here, since it mentions that in the parameter description ("2: SoC name (optionally implemented for SMC64 calls, ..."), but still prints the FID explicitly as 0x80000002. But as this FID is using the SMC32 calling convention (correct for the other two calls), it will not match what any SMCCC conformant firmware is expecting, so any call would return NOT_SUPPORTED. Add a 64-bit version of the ARCH_SOC_ID FID macro, and use that for the SoC name version of the call to fix the issue. Fixes: 5f9c23abc477 ("firmware: smccc: Support optional Arm SMCCC SOC_ID name") Signed-off-by: Andre Przywara Link: https://patch.msgid.link/20250902172053.304911-1-andre.przywara@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/smccc/soc_id.c | 2 +- include/linux/arm-smccc.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/smccc/soc_id.c b/drivers/firmware/smccc/soc_id.c index 2f7475e66b3c..a909d5e6dee5 100644 --- a/drivers/firmware/smccc/soc_id.c +++ b/drivers/firmware/smccc/soc_id.c @@ -60,7 +60,7 @@ static char __init *smccc_soc_name_init(void) * to the ARM_SMCCC_ARCH_SOC_ID function. Fetch it if * available. */ - args.a0 = ARM_SMCCC_ARCH_SOC_ID; + args.a0 = ARM_SMCCC_ARCH_SOC_ID64; args.a1 = 2; /* SOC_ID name */ arm_smccc_1_2_invoke(&args, &res); diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h index 50b47eba7d01..976c5f8001ff 100644 --- a/include/linux/arm-smccc.h +++ b/include/linux/arm-smccc.h @@ -90,6 +90,11 @@ ARM_SMCCC_SMC_32, \ 0, 2) +#define ARM_SMCCC_ARCH_SOC_ID64 \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_64, \ + 0, 2) + #define ARM_SMCCC_ARCH_WORKAROUND_1 \ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ ARM_SMCCC_SMC_32, \ From f6fe7c3c007df18afd289ff2d98c692fae9ab085 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Sun, 17 May 2026 20:02:40 +0100 Subject: [PATCH 109/189] firmware: arm_scmi: Read sensor config as 32-bit value The SENSOR_CONFIG_GET response contains a 32-bit sensor_config field, and the xfer is initialized with a 4-byte RX buffer. Reading it with get_unaligned_le64() can consume bytes past the returned payload. Use get_unaligned_le32() to match the protocol layout and the allocated response size. Fixes: 7b83c5f41088 ("firmware: arm_scmi: Add SCMI v3.0 sensor configuration support") Link: https://patch.msgid.link/20260517-scmi_fixes-v1-1-d86daec4defd@kernel.org Reviewed-by: Cristian Marussi Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c index 882d55f987d2..836c294a9f42 100644 --- a/drivers/firmware/arm_scmi/sensors.c +++ b/drivers/firmware/arm_scmi/sensors.c @@ -793,7 +793,7 @@ static int scmi_sensor_config_get(const struct scmi_protocol_handle *ph, if (!ret) { struct scmi_sensor_info *s = si->sensors + sensor_id; - *sensor_config = get_unaligned_le64(t->rx.buf); + *sensor_config = get_unaligned_le32(t->rx.buf); s->sensor_config = *sensor_config; } From 56e7e64cdd0e7209a58c8ec66028d63387402919 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Sun, 17 May 2026 20:02:41 +0100 Subject: [PATCH 110/189] firmware: arm_scmi: Validate BASE_ERROR_EVENT payload size BASE_ERROR_EVENT carries a variable number of message reports, with the count encoded in error_status. The notification parser used that count without checking whether the received payload contained all reported entries. Reject truncated payloads before copying the report array. Link: https://patch.msgid.link/20260517-scmi_fixes-v1-2-d86daec4defd@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/base.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c index 4df2620e3c5d..a9cea09355ff 100644 --- a/drivers/firmware/arm_scmi/base.c +++ b/drivers/firmware/arm_scmi/base.c @@ -325,6 +325,8 @@ static void *scmi_base_fill_custom_report(const struct scmi_protocol_handle *ph, void *report, u32 *src_id) { int i; + u32 error_status; + size_t expected_sz; const struct scmi_base_error_notify_payld *p = payld; struct scmi_base_error_report *r = report; @@ -338,10 +340,19 @@ static void *scmi_base_fill_custom_report(const struct scmi_protocol_handle *ph, if (evt_id != SCMI_EVENT_BASE_ERROR_EVENT || sizeof(*p) < payld_sz) return NULL; + expected_sz = offsetof(typeof(*p), msg_reports); + if (payld_sz < expected_sz) + return NULL; + r->timestamp = timestamp; r->agent_id = le32_to_cpu(p->agent_id); - r->fatal = IS_FATAL_ERROR(le32_to_cpu(p->error_status)); - r->cmd_count = ERROR_CMD_COUNT(le32_to_cpu(p->error_status)); + error_status = le32_to_cpu(p->error_status); + r->fatal = IS_FATAL_ERROR(error_status); + r->cmd_count = ERROR_CMD_COUNT(error_status); + expected_sz += r->cmd_count * sizeof(p->msg_reports[0]); + if (payld_sz < expected_sz) + return NULL; + for (i = 0; i < r->cmd_count; i++) r->reports[i] = le64_to_cpu(p->msg_reports[i]); *src_id = 0; From 32bc5496b48174dbca1f187f710955ee4d9527a1 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Sun, 17 May 2026 20:02:42 +0100 Subject: [PATCH 111/189] firmware: arm_scmi: Validate SENSOR_UPDATE payload size SENSOR_UPDATE carries one or more sensor readings after the fixed notification header. The parser derives the expected reading count from the sensor description, but it did not verify that the received payload contains those entries before parsing them. Reject truncated update notifications before reading the variable array. Link: https://patch.msgid.link/20260517-scmi_fixes-v1-3-d86daec4defd@kernel.org Reviewed-by: Cristian Marussi Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/sensors.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c index 836c294a9f42..b14bb1146356 100644 --- a/drivers/firmware/arm_scmi/sensors.c +++ b/drivers/firmware/arm_scmi/sensors.c @@ -1072,12 +1072,15 @@ scmi_sensor_fill_custom_report(const struct scmi_protocol_handle *ph, case SCMI_EVENT_SENSOR_UPDATE: { int i; + size_t expected_sz; struct scmi_sensor_info *s; const struct scmi_sensor_update_notify_payld *p = payld; struct scmi_sensor_update_report *r = report; struct sensors_info *sinfo = ph->get_priv(ph); - /* payld_sz is variable for this event */ + if (payld_sz < sizeof(*p)) + break; + r->sensor_id = le32_to_cpu(p->sensor_id); if (r->sensor_id >= sinfo->num_sensors) break; @@ -1091,6 +1094,11 @@ scmi_sensor_fill_custom_report(const struct scmi_protocol_handle *ph, * readings defined for this sensor or 1 for scalar sensors. */ r->readings_count = s->num_axis ?: 1; + expected_sz = sizeof(*p) + r->readings_count * + sizeof(p->readings[0]); + if (payld_sz < expected_sz) + break; + for (i = 0; i < r->readings_count; i++) scmi_parse_sensor_readings(&r->readings[i], &p->readings[i]); From fcca603c6a09c7fd041d9004f63421d3a4014db5 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Sun, 17 May 2026 20:02:43 +0100 Subject: [PATCH 112/189] firmware: arm_scmi: Validate Powercap domains before state access Powercap protocol v2 keeps local enable and last-cap state per domain. The v2 enable helpers and disabled-domain cap_set path indexed that state before checking that the supplied domain id was valid. Validate the domain before touching the per-domain state. Link: https://patch.msgid.link/20260517-scmi_fixes-v1-4-d86daec4defd@kernel.org Reviewed-by: Cristian Marussi Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/powercap.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c index ab9733f4458b..27e3c805e927 100644 --- a/drivers/firmware/arm_scmi/powercap.c +++ b/drivers/firmware/arm_scmi/powercap.c @@ -453,10 +453,14 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph, return -EINVAL; /* Just log the last set request if acting on a disabled domain */ - if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && - !pi->states[domain_id].enabled) { - pi->states[domain_id].last_pcap = power_cap; - return 0; + if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) { + if (!scmi_powercap_dom_info_get(ph, domain_id)) + return -EINVAL; + + if (!pi->states[domain_id].enabled) { + pi->states[domain_id].last_pcap = power_cap; + return 0; + } } return __scmi_powercap_cap_set(ph, pi, domain_id, @@ -637,6 +641,9 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph, if (PROTOCOL_REV_MAJOR(ph->version) < 0x2) return -EINVAL; + if (!scmi_powercap_dom_info_get(ph, domain_id)) + return -EINVAL; + if (enable == pi->states[domain_id].enabled) return 0; @@ -678,6 +685,9 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph, if (PROTOCOL_REV_MAJOR(ph->version) < 0x2) return 0; + if (!scmi_powercap_dom_info_get(ph, domain_id)) + return -EINVAL; + /* * Report always real platform state; platform could have ignored * a previous disable request. Default true on any error. From 17d9064987ca4e0b05b670da48ca93edb0796810 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Tue, 7 Apr 2026 16:29:31 +0100 Subject: [PATCH 113/189] dt-bindings: soc: microchip: document irqmux on pic64gx Being practically identical to PolarFire SoC, pic64gx has a irqmux that's entirely compatible with that on mpfs. Acked-by: Krzysztof Kozlowski Signed-off-by: Conor Dooley --- .../bindings/soc/microchip/microchip,mpfs-irqmux.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/soc/microchip/microchip,mpfs-irqmux.yaml b/Documentation/devicetree/bindings/soc/microchip/microchip,mpfs-irqmux.yaml index 51164772724f..419b32e2df93 100644 --- a/Documentation/devicetree/bindings/soc/microchip/microchip,mpfs-irqmux.yaml +++ b/Documentation/devicetree/bindings/soc/microchip/microchip,mpfs-irqmux.yaml @@ -26,7 +26,11 @@ description: | properties: compatible: - const: microchip,mpfs-irqmux + oneOf: + - items: + - const: microchip,pic64gx-irqmux + - const: microchip,mpfs-irqmux + - const: microchip,mpfs-irqmux reg: maxItems: 1 From 8e2501e1494c8d27f62eb8d36bba5af465f48962 Mon Sep 17 00:00:00 2001 From: Dominique Belhachemi Date: Thu, 30 Apr 2026 03:52:59 +0000 Subject: [PATCH 114/189] cache: sifive_ccache: Add StarFive JH7110 SoC support This cache controller is also used on the StarFive JH7110 SoC. It does not have the data-uncorrectable ECC quirk that JH7100 has, so only QUIRK_NONSTANDARD_CACHE_OPS is set. Signed-off-by: Dominique Belhachemi Signed-off-by: Conor Dooley --- drivers/cache/sifive_ccache.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/cache/sifive_ccache.c b/drivers/cache/sifive_ccache.c index a86800b123b9..2acb8bdf06d5 100644 --- a/drivers/cache/sifive_ccache.c +++ b/drivers/cache/sifive_ccache.c @@ -124,6 +124,8 @@ static const struct of_device_id sifive_ccache_ids[] = { { .compatible = "sifive,fu740-c000-ccache" }, { .compatible = "starfive,jh7100-ccache", .data = (void *)(QUIRK_NONSTANDARD_CACHE_OPS | QUIRK_BROKEN_DATA_UNCORR) }, + { .compatible = "starfive,jh7110-ccache", + .data = (void *)(QUIRK_NONSTANDARD_CACHE_OPS) }, { .compatible = "sifive,ccache0" }, { /* end of table */ } }; From f9ef3f66f4b18078e464b7606f9497e4dbeb9905 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 15 May 2026 11:59:15 +0200 Subject: [PATCH 115/189] firmware: arm_scmi: Fix OOB in scmi_power_name_get() scmi_power_name_get() does not validate the domain number passed by the external caller, which may lead to an out-of-bounds access. Fix this by returning "unknown" for invalid domains, like scmi_reset_name_get() does. Fixes: 76a6550990e296a7 ("firmware: arm_scmi: add initial support for power protocol") Signed-off-by: Geert Uytterhoeven Reviewed-by: Cristian Marussi Link: https://patch.msgid.link/75caae28bdffb55199a0bc6cac5df112a966c608.1778838987.git.geert+renesas@glider.be Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/power.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c index bb5062ab8280..28ef63a4ecc2 100644 --- a/drivers/firmware/arm_scmi/power.c +++ b/drivers/firmware/arm_scmi/power.c @@ -204,8 +204,12 @@ scmi_power_name_get(const struct scmi_protocol_handle *ph, u32 domain) { struct scmi_power_info *pi = ph->get_priv(ph); - struct power_dom_info *dom = pi->dom_info + domain; + struct power_dom_info *dom; + if (domain >= pi->num_domains) + return "unknown"; + + dom = pi->dom_info + domain; return dom->name; } From 9dfae7d2edb4422c2c11fb6f0198691ec58e1d79 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Sun, 10 May 2026 17:05:24 +0100 Subject: [PATCH 116/189] firmware: arm_scmi: Add transport instance handles SCMI transport drivers are initialized first and then the control is passed to the SCMI core stack: some of these transports are dependent also on some external subsytem which will have to be initialized upfront, before the transport driver itself can be deemed operational. Transport drivers like virtio or optee need a way to defer the core SCMI probing till they are fully initialized and operational and also a way to pass back the device reference to be used as a supplier while building the devlink relations. SCMI transport drivers can be probed multiple times when used in a multiple instance configuration but the capability to carry-on with multiple probes depends on the support provided by the underlying transport driver. This change will also allow for the removal of the frowned-upon trick of registering a platform driver only after the end of the transport drivers porbe to avoid explicit probe deferrals. Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260510160527.3537474-2-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/common.h | 52 +++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 07a127dec031..9de8e4eb719a 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -462,6 +463,28 @@ struct scmi_transport_core_operations { const struct scmi_message_operations *msg; }; +/** + * struct scmi_transport_handle - Transport instance handle + * @supplier_get: A helper to retrieve the device descriptor, identifying the + * transport driver serving this SCMI instance, which will be + * used as a supplier for the core SCMI driver: returning an + * error here causes the probe sequence to be interrupted and + * return that same error code, so that each transport can decide + * which policy to implement by choosing an appropriate error. + * @supplier_put: A helper to signal that the specified transport supplier is + * no more being used and it is made available again. + * + * Note that these helpers are needed and provided only by those transports + * whose initialization relies on some other subsystem and whose relations to + * the core SCMI driver is not tracked by firmware descriptions. + */ +struct scmi_transport_handle { + struct device __must_check *(*supplier_get) + (const struct scmi_transport_handle *th); + int (*supplier_put)(const struct scmi_transport_handle *th, + struct device *dev); +}; + /** * struct scmi_transport - A structure representing a configured transport * @@ -470,35 +493,52 @@ struct scmi_transport_core_operations { * @desc: Transport descriptor * @core_ops: A pointer to a pointer used by the core SCMI stack to make the * core transport operations accessible to the transports. + * @th: An optional pointer to the transport handle */ struct scmi_transport { struct device *supplier; struct scmi_desc desc; struct scmi_transport_core_operations **core_ops; + const struct scmi_transport_handle *th; }; #define DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\ static void __tag##_dev_free(void *data) \ { \ struct platform_device *spdev = data; \ + struct scmi_transport *strans; \ + \ + strans = dev_get_platdata(&spdev->dev); \ + if (strans && strans->th) \ + strans->th->supplier_put(strans->th, strans->supplier); \ \ platform_device_unregister(spdev); \ } \ \ static int __tag##_probe(struct platform_device *pdev) \ { \ - struct device *dev = &pdev->dev; \ + struct device *dev = &pdev->dev, *supplier; \ struct platform_device *spdev; \ struct scmi_transport strans; \ int ret; \ \ + supplier = dev; \ + strans.th = device_get_match_data(dev); \ + if (strans.th) { \ + supplier = strans.th->supplier_get(strans.th); \ + if (IS_ERR(supplier)) \ + return PTR_ERR(supplier); \ + } \ + \ spdev = platform_device_alloc("arm-scmi", PLATFORM_DEVID_AUTO); \ - if (!spdev) \ - return -ENOMEM; \ + if (!spdev) { \ + ret = -ENOMEM; \ + goto err_mem; \ + } \ \ device_set_of_node_from_dev(&spdev->dev, dev); \ \ - strans.supplier = dev; \ + strans.supplier = supplier; \ memcpy(&strans.desc, &(__desc), sizeof(strans.desc)); \ strans.core_ops = &(__core_ops); \ \ @@ -515,6 +555,10 @@ static int __tag##_probe(struct platform_device *pdev) \ \ err: \ platform_device_put(spdev); \ +err_mem: \ + if (strans.th) \ + strans.th->supplier_put(strans.th, supplier); \ + \ return ret; \ } \ \ From ab6eb28a47d48c0c9edd885eca60fa5a39cc80b7 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Sun, 10 May 2026 17:05:25 +0100 Subject: [PATCH 117/189] firmware: arm_scmi: Add a generic transport supplier Add the capability to define a common generic transport supplier which embeds the logic needed to support one single unique instance of transport supplier. Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260510160527.3537474-3-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/common.h | 111 +++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 9de8e4eb719a..b9723c105fc1 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -502,6 +502,117 @@ struct scmi_transport { const struct scmi_transport_handle *th; }; +/** + * struct scmi_transport_supplier - Transport descriptor + * @mtx: A mutex to protect @available + * @available: A reference to an initialized transport device, when available. + * This reference is implicitly used to track the status of the + * supplier and it can cycle through the following 3 states: + * 1. NOT_READY - PTR_ERR(-EPROBE_DEFER): no supplier available; + * this is the transport initial state. + * 2. AVAILABLE - : a transport supplier has been + * initialized and it is available, ready to use. + * 3. BUSY _ PTR_ERR(-EBUSY): transport supplier is currently in use. + * @th: An embedded transport handle object that embeds the helpers + * implementing the above mentioned logic + * + * Note that this transport driver enforces single instance probing. + */ +struct scmi_transport_supplier { + /* Protect @available */ + struct mutex mtx; + struct device *available; + const struct scmi_transport_handle th; +}; + +#define to_sup(t) container_of(t, struct scmi_transport_supplier, th) + +/** + * scmi_transport_supplier_put - A helper to dispose of a supplier + * @th: A reference to the transport handle to use + * @supplier: A reference to the device supplier to manage, cannot be NULL + * or ERR_PTR. + * + * Note that putting a supplier will have different effect based on the + * current state of scmi_transport_supplier.available: + * - NOT_READY/BUSY: @supplier will be set as the new available device: this + * can be used to made available a supplier OR stop using one. + * - AVAILABLE: if the @supplier we are disposing of matches the currently + * available one, roll back to NOT_READY state. + * Any other attempt to override an available supplier with a + * new one is rejected, effectively enforcing one single supplier. + * + * Return: 0 on Success, errno otherwise. + */ +static inline int +scmi_transport_supplier_put(const struct scmi_transport_handle *th, + struct device *supplier) +{ + struct scmi_transport_supplier *sup = to_sup(th); + + /* Nothing to do when the provided supplier was never real */ + if (IS_ERR_OR_NULL(supplier)) + return 0; + + guard(mutex)(&sup->mtx); + switch (PTR_ERR_OR_ZERO(sup->available)) { + case -EPROBE_DEFER: + case -EBUSY: + sup->available = supplier; + break; + case 0: + /* Putting a supplier when in the AVAILABLE state causes a + * transition back to the NOT_READY state, BUT only if the + * supplier we are disposing of was exactly the device that was + * previously made readily available. + */ + if (supplier != sup->available) + return -EINVAL; + sup->available = ERR_PTR(-EPROBE_DEFER); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * scmi_transport_supplier_get - A helper to get hold of a supplier + * @th: A reference to the transport handle to use + * + * Note that, trying to get a supplier device can return: + * - a ready to use supplier device, (subsequently made unavailable) + * - PTR_ERR(-EPROBE_DEFER): no supplier is available + * - PTR_ERR(-BUSY): supplier was already taken by a previous get + * + * This allows the probe to defer and wait when a possible device can + * be reasonably expected to appear. + * + * Return: a usable supplier device on Success or PTR_ERR on Failure. + */ +static inline struct device * +scmi_transport_supplier_get(const struct scmi_transport_handle *th) +{ + struct scmi_transport_supplier *sup = to_sup(th); + struct device *supplier; + + guard(mutex)(&sup->mtx); + supplier = sup->available; + if (!IS_ERR(sup->available)) + sup->available = ERR_PTR(-EBUSY); + + return supplier; +} + +#define DEFINE_SCMI_TRANSPORT_SUPPLIER(__supplier) \ +struct scmi_transport_supplier __supplier = { \ + .mtx = __MUTEX_INITIALIZER(__supplier.mtx), \ + .available = INIT_ERR_PTR(-EPROBE_DEFER), \ + .th.supplier_get = scmi_transport_supplier_get, \ + .th.supplier_put = scmi_transport_supplier_put, \ +} + #define DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\ static void __tag##_dev_free(void *data) \ { \ From c08051901a55f8574968b606f960a70415be303c Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Sun, 10 May 2026 17:05:26 +0100 Subject: [PATCH 118/189] firmware: arm_scmi: virtio: Rework transport probe sequence Use the new per-instance transport handles helpers to synchronize and optionally defer the core SCMI driver probe up until the transport driver has completely been initialized and it is fully operational as a supplier. Introduce proper module init/exit routines while removing the ugly trick of registering a driver from within the probe sequence of another one, just to avoid to have to deal with probe deferrals. Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260510160527.3537474-4-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/transports/virtio.c | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/drivers/firmware/arm_scmi/transports/virtio.c b/drivers/firmware/arm_scmi/transports/virtio.c index 326c4a93e44b..3282d8271839 100644 --- a/drivers/firmware/arm_scmi/transports/virtio.c +++ b/drivers/firmware/arm_scmi/transports/virtio.c @@ -4,7 +4,7 @@ * (SCMI). * * Copyright (C) 2020-2022 OpenSynergy. - * Copyright (C) 2021-2024 ARM Ltd. + * Copyright (C) 2021-2026 ARM Ltd. */ /** @@ -116,6 +116,8 @@ static struct scmi_transport_core_operations *core; /* Only one SCMI VirtIO device can possibly exist */ static struct virtio_device *scmi_vdev; +static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_virtio_supplier); + static void scmi_vio_channel_ready(struct scmi_vio_channel *vioch, struct scmi_chan_info *cinfo) { @@ -394,6 +396,10 @@ static bool virtio_chan_available(struct device_node *of_node, int idx) return false; } + dev_dbg(&scmi_vdev->dev, "%s Channel %sAVAILABLE on SCMI Virtio device.\n", + idx == VIRTIO_SCMI_VQ_TX ? "TX" : "RX", + (vioch && !vioch->cinfo) ? "" : "NOT "); + return vioch && !vioch->cinfo; } @@ -410,7 +416,7 @@ static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, int i; if (!scmi_vdev) - return -EPROBE_DEFER; + return -EINVAL; vioch = &((struct scmi_vio_channel *)scmi_vdev->priv)[index]; @@ -460,6 +466,9 @@ static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, scmi_vio_channel_ready(vioch, cinfo); + dev_dbg(&scmi_vdev->dev, "%s Channel SETUP on SCMI Virtio device.\n", + tx ? "TX" : "RX"); + return 0; } @@ -801,7 +810,7 @@ static struct scmi_desc scmi_virtio_desc = { }; static const struct of_device_id scmi_of_match[] = { - { .compatible = "arm,scmi-virtio" }, + { .compatible = "arm,scmi-virtio", .data = &scmi_virtio_supplier.th}, { /* Sentinel */ }, }; @@ -864,33 +873,33 @@ static int scmi_vio_probe(struct virtio_device *vdev) sz = MSG_TOKEN_MAX; } channels[i].max_msg = sz; + dev_dbg(dev, "VQ%d initialized with max_msg: %d\n", i, sz); } vdev->priv = channels; - /* Ensure initialized scmi_vdev is visible */ smp_store_mb(scmi_vdev, vdev); /* Set device ready */ virtio_device_ready(vdev); - ret = platform_driver_register(&scmi_virtio_driver); + ret = scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev); if (ret) { + virtio_reset_device(vdev); vdev->priv = NULL; vdev->config->del_vqs(vdev); /* Ensure NULLified scmi_vdev is visible */ smp_store_mb(scmi_vdev, NULL); - return ret; } + dev_dbg(dev, "Probed and initialized SCMI Virtio device.\n"); + return 0; } static void scmi_vio_remove(struct virtio_device *vdev) { - platform_driver_unregister(&scmi_virtio_driver); - /* * Once we get here, virtio_chan_free() will have already been called by * the SCMI core for any existing channel and, as a consequence, all the @@ -900,8 +909,10 @@ static void scmi_vio_remove(struct virtio_device *vdev) */ virtio_reset_device(vdev); vdev->config->del_vqs(vdev); + /* Ensure scmi_vdev is visible as NULL */ smp_store_mb(scmi_vdev, NULL); + scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev); } static int scmi_vio_validate(struct virtio_device *vdev) @@ -936,7 +947,30 @@ static struct virtio_driver virtio_scmi_driver = { .validate = scmi_vio_validate, }; -module_virtio_driver(virtio_scmi_driver); +static int __init scmi_transport_virtio_init(void) +{ + int ret; + + ret = register_virtio_driver(&virtio_scmi_driver); + if (ret) + return ret; + + ret = platform_driver_register(&scmi_virtio_driver); + if (ret) { + unregister_virtio_driver(&virtio_scmi_driver); + return ret; + } + + return ret; +} +module_init(scmi_transport_virtio_init); + +static void __exit scmi_transport_virtio_exit(void) +{ + platform_driver_unregister(&scmi_virtio_driver); + unregister_virtio_driver(&virtio_scmi_driver); +} +module_exit(scmi_transport_virtio_exit); MODULE_AUTHOR("Igor Skalkin "); MODULE_AUTHOR("Peter Hilber "); From 524abd2fa6907ebe2762342be339afcc5b227dc4 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Sun, 10 May 2026 17:05:27 +0100 Subject: [PATCH 119/189] firmware: arm_scmi: optee: Rework transport probe sequence Use the new per-instance transport handles helpers to synchronize and optionally defer the core SCMI driver probe up until the transport driver has completely been initialized and it is fully operational as a supplier. Introduce proper module init/exit routines while removing the ugly trick of registering a driver from within the probe sequence of another one, just to avoid to have to deal with probe deferrals. Signed-off-by: Cristian Marussi Link: https://patch.msgid.link/20260510160527.3537474-5-cristian.marussi@arm.com Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scmi/transports/optee.c | 46 +++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/drivers/firmware/arm_scmi/transports/optee.c b/drivers/firmware/arm_scmi/transports/optee.c index 07ae18d5279d..dbe32e141748 100644 --- a/drivers/firmware/arm_scmi/transports/optee.c +++ b/drivers/firmware/arm_scmi/transports/optee.c @@ -154,6 +154,8 @@ static struct scmi_transport_core_operations *core; /* There can be only 1 SCMI service in OP-TEE we connect to */ static struct scmi_optee_agent *scmi_optee_private; +static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_optee_supplier); + /* Open a session toward SCMI OP-TEE service with REE_KERNEL identity */ static int open_session(struct scmi_optee_agent *agent, u32 *tee_session) { @@ -522,7 +524,7 @@ static struct scmi_desc scmi_optee_desc = { }; static const struct of_device_id scmi_of_match[] = { - { .compatible = "linaro,scmi-optee" }, + { .compatible = "linaro,scmi-optee", .data = &scmi_optee_supplier.th}, { /* Sentinel */ }, }; @@ -561,18 +563,20 @@ static int scmi_optee_service_probe(struct tee_client_device *scmi_pta) if (ret) goto err; - /* Ensure agent resources are all visible before scmi_optee_private is */ + /* Ensure initialized scmi_optee_private is visible */ smp_mb(); scmi_optee_private = agent; - ret = platform_driver_register(&scmi_optee_driver); - if (ret) { - scmi_optee_private = NULL; - goto err; - } + ret = scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev); + if (ret) + goto err_put; return 0; +err_put: + /* Ensure cleared reference is visible before resources are released */ + smp_store_mb(scmi_optee_private, NULL); + err: tee_client_close_context(tee_ctx); @@ -586,13 +590,12 @@ static void scmi_optee_service_remove(struct tee_client_device *scmi_pta) if (!scmi_optee_private) return; - platform_driver_unregister(&scmi_optee_driver); - if (!list_empty(&scmi_optee_private->channel_list)) return; /* Ensure cleared reference is visible before resources are released */ smp_store_mb(scmi_optee_private, NULL); + scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev); tee_client_close_context(agent->tee_ctx); } @@ -616,7 +619,30 @@ static struct tee_client_driver scmi_optee_service_driver = { }, }; -module_tee_client_driver(scmi_optee_service_driver); +static int __init scmi_transport_optee_init(void) +{ + int ret; + + ret = tee_client_driver_register(&scmi_optee_service_driver); + if (ret) + return ret; + + ret = platform_driver_register(&scmi_optee_driver); + if (ret) { + tee_client_driver_unregister(&scmi_optee_service_driver); + return ret; + } + + return ret; +} +module_init(scmi_transport_optee_init); + +static void __exit scmi_transport_optee_exit(void) +{ + platform_driver_unregister(&scmi_optee_driver); + tee_client_driver_unregister(&scmi_optee_service_driver); +} +module_exit(scmi_transport_optee_exit); MODULE_AUTHOR("Etienne Carriere "); MODULE_DESCRIPTION("SCMI OPTEE Transport driver"); From 6487b12a875a5e3cc2f99ff7eba1112fe3f72483 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 13 May 2026 14:11:02 +0300 Subject: [PATCH 120/189] dt-bindings: cache: qcom,llcc: Document Eliza LLCC block Document the Last Level Cache Controller on Eliza SoC. Eliza LLCC has 2 base register regions and an additional AND, OR broadcast region, total 4 register regions. Signed-off-by: Abel Vesa Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20260513-eliza-llcc-v2-1-27381ae833d5@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/cache/qcom,llcc.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml index 1fc5411fe948..34e3a2d78592 100644 --- a/Documentation/devicetree/bindings/cache/qcom,llcc.yaml +++ b/Documentation/devicetree/bindings/cache/qcom,llcc.yaml @@ -20,6 +20,7 @@ description: | properties: compatible: enum: + - qcom,eliza-llcc - qcom,glymur-llcc - qcom,hawi-llcc - qcom,ipq5424-llcc @@ -362,6 +363,27 @@ allOf: properties: memory-region: false + - if: + properties: + compatible: + contains: + enum: + - qcom,eliza-llcc + then: + properties: + reg: + items: + - description: LLCC0 base register region + - description: LLCC2 base register region + - description: LLCC broadcast OR register region + - description: LLCC broadcast AND register region + reg-names: + items: + - const: llcc0_base + - const: llcc2_base + - const: llcc_broadcast_base + - const: llcc_broadcast_and_base + additionalProperties: false examples: From 0a4d53ae2cb68cbfe3c69e14d8cfc0acc7c37bda Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 13 May 2026 14:11:03 +0300 Subject: [PATCH 121/189] soc: qcom: llcc-qcom: Add support for Eliza Eliza uses a 4-region LLCC register layout made up of two per-bank base register regions together with the broadcast OR and AND regions. So add this SoC specific configuration and its compatible string. Reviewed-by: Konrad Dybcio Signed-off-by: Abel Vesa Link: https://lore.kernel.org/r/20260513-eliza-llcc-v2-2-27381ae833d5@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/llcc-qcom.c | 180 +++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index dcc08f63e020..1359c7b2d41b 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -268,6 +268,171 @@ enum llcc_reg_offset { LLCC_TRP_WRS_CACHEABLE_EN, }; +static const struct llcc_slice_config eliza_data[] = { + { + .usecase_id = LLCC_CPUSS, + .slice_id = 1, + .max_cap = 896, + .bonus_ways = 0xfff, + .activate_on_init = true, + .write_scid_en = true, + .stale_en = true, + }, + { + .usecase_id = LLCC_MDMHPFX, + .slice_id = 24, + .max_cap = 1024, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xfff, + }, + { + .usecase_id = LLCC_VIDSC0, + .slice_id = 2, + .max_cap = 128, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xfff, + }, + { + .usecase_id = LLCC_MDMHPGRW, + .slice_id = 25, + .max_cap = 1024, + .priority = 5, + .bonus_ways = 0xfff, + }, + { + .usecase_id = LLCC_GPUHTW, + .slice_id = 11, + .max_cap = 256, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + }, + { + .usecase_id = LLCC_GPU, + .slice_id = 9, + .max_cap = 896, + .priority = 1, + .bonus_ways = 0xfff, + .write_scid_cacheable_en = true, + }, + { + .usecase_id = LLCC_MMUHWT, + .slice_id = 18, + .max_cap = 256, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + .activate_on_init = true, + }, + { + .usecase_id = LLCC_MDMPNG, + .slice_id = 27, + .max_cap = 256, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xfff, + }, + { + .usecase_id = LLCC_MODPE, + .slice_id = 29, + .max_cap = 256, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xf00, + .alloc_oneway_en = true, + }, + { + .usecase_id = LLCC_WRCACHE, + .slice_id = 31, + .max_cap = 256, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + .activate_on_init = true, + }, + { + .usecase_id = LLCC_LCPDARE, + .slice_id = 30, + .max_cap = 128, + .priority = 5, + .fixed_size = true, + .bonus_ways = 0xfff, + .activate_on_init = true, + .alloc_oneway_en = true, + }, + { + .usecase_id = LLCC_ISLAND1, + .slice_id = 12, + .max_cap = 1280, + .priority = 7, + .fixed_size = true, + .res_ways = 0x3ff, + }, + { + .usecase_id = LLCC_CAMOFE, + .slice_id = 33, + .max_cap = 1024, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + .stale_en = true, + .parent_slice_id = 13, + }, + { + .usecase_id = LLCC_CAMRTIP, + .slice_id = 13, + .max_cap = 1024, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + .stale_en = true, + .parent_slice_id = 13, + }, + { + .usecase_id = LLCC_CAMSRTIP, + .slice_id = 14, + .max_cap = 512, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + .stale_en = true, + .parent_slice_id = 13, + }, + { + .usecase_id = LLCC_CAMRTRF, + .slice_id = 7, + .max_cap = 1024, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + .stale_en = true, + .parent_slice_id = 13, + }, + { + .usecase_id = LLCC_CAMSRTRF, + .slice_id = 21, + .max_cap = 1024, + .priority = 1, + .fixed_size = true, + .bonus_ways = 0xfff, + .stale_en = true, + .parent_slice_id = 13, + }, + { + .usecase_id = LLCC_CPUSSMPAM, + .slice_id = 6, + .max_cap = 512, + .priority = 0, + .fixed_size = true, + .bonus_ways = 0xfff, + .activate_on_init = true, + .write_scid_en = true, + .stale_en = true, + }, +}; + static const struct llcc_slice_config glymur_data[] = { { .usecase_id = LLCC_CPUSS, @@ -4237,6 +4402,15 @@ static const struct qcom_llcc_config hawi_sct_cfg[] = { }, }; +static const struct qcom_llcc_config eliza_cfg[] = { + { + .sct_data = eliza_data, + .size = ARRAY_SIZE(eliza_data), + .reg_offset = llcc_v6_reg_offset, + .edac_reg_offset = &llcc_v6_edac_reg_offset, + }, +}; + static const struct qcom_llcc_config kaanapali_cfg[] = { { .sct_data = kaanapali_data, @@ -4498,6 +4672,11 @@ static const struct qcom_sct_config hawi_sct_cfgs = { .num_config = ARRAY_SIZE(hawi_sct_cfg), }; +static const struct qcom_sct_config eliza_cfgs = { + .llcc_config = eliza_cfg, + .num_config = ARRAY_SIZE(eliza_cfg), +}; + static const struct qcom_sct_config kaanapali_cfgs = { .llcc_config = kaanapali_cfg, .num_config = ARRAY_SIZE(kaanapali_cfg), @@ -5436,6 +5615,7 @@ static int qcom_llcc_probe(struct platform_device *pdev) } static const struct of_device_id qcom_llcc_of_match[] = { + { .compatible = "qcom,eliza-llcc", .data = &eliza_cfgs }, { .compatible = "qcom,glymur-llcc", .data = &glymur_cfgs }, { .compatible = "qcom,hawi-llcc", .data = &hawi_sct_cfgs }, { .compatible = "qcom,ipq5424-llcc", .data = &ipq5424_cfgs}, From db0ec1ad723187a3428145b05d4a230d5b6f7e32 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 20 May 2026 09:00:30 +0300 Subject: [PATCH 122/189] soc: qcom: llcc-qcom: Fix NULL vs IS_ERR() bug in qcom_llcc_get_fw_config() The devm_memremap() function doesn't return NULL, it returns error pointers. Fix the error checking to match. Fixes: ac23106a9b9a ("soc: qcom: llcc-qcom: get SCT descriptors from fw-populated memory") Signed-off-by: Dan Carpenter Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/ag1N_rAHEQ1YJsa7@stanley.mountain Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/llcc-qcom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index 1359c7b2d41b..8948b5fd42d2 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -5410,9 +5410,9 @@ static int qcom_llcc_get_fw_config(struct platform_device *pdev) } slc_mem = devm_memremap(dev, res.start, resource_size(&res), MEMREMAP_WB); - if (!slc_mem) { + if (IS_ERR(slc_mem)) { dev_err(dev, "Failed to memremap SLC shared memory\n"); - return -ENOMEM; + return PTR_ERR(slc_mem); } ret = qcom_llcc_verify_fw_config(dev, slc_mem); From e231930279b038ddd3274a4819b4bc2bf0dbab37 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:32 +0530 Subject: [PATCH 123/189] soc: qcom: geni-se: Refactor geni_icc_get() and make qup-memory ICC path optional The "qup-memory" interconnect path is optional and may not be defined in all device trees. Unroll the loop-based ICC path initialization to allow specific error handling for each path type. The "qup-core" and "qup-config" paths remain mandatory and will fail probe if missing, while "qup-memory" is now handled as optional and skipped when not present in the device tree. Co-developed-by: Konrad Dybcio Signed-off-by: Konrad Dybcio Signed-off-by: Praveen Talari Reviewed-by: Mukesh Kumar Savaliya [...] Tested-by: Mattijs Korpershoek Link: https://lore.kernel.org/r/20260227061544.1785978-2-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom-geni-se.c | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index cd1779b6a91a..b6167b968ef6 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -899,30 +899,32 @@ EXPORT_SYMBOL_GPL(geni_se_rx_dma_unprep); int geni_icc_get(struct geni_se *se, const char *icc_ddr) { - int i, err; - const char *icc_names[] = {"qup-core", "qup-config", icc_ddr}; + struct geni_icc_path *icc_paths = se->icc_paths; if (has_acpi_companion(se->dev)) return 0; - for (i = 0; i < ARRAY_SIZE(se->icc_paths); i++) { - if (!icc_names[i]) - continue; + icc_paths[GENI_TO_CORE].path = devm_of_icc_get(se->dev, "qup-core"); + if (IS_ERR(icc_paths[GENI_TO_CORE].path)) + return dev_err_probe(se->dev, PTR_ERR(icc_paths[GENI_TO_CORE].path), + "Failed to get 'qup-core' ICC path\n"); - se->icc_paths[i].path = devm_of_icc_get(se->dev, icc_names[i]); - if (IS_ERR(se->icc_paths[i].path)) - goto err; + icc_paths[CPU_TO_GENI].path = devm_of_icc_get(se->dev, "qup-config"); + if (IS_ERR(icc_paths[CPU_TO_GENI].path)) + return dev_err_probe(se->dev, PTR_ERR(icc_paths[CPU_TO_GENI].path), + "Failed to get 'qup-config' ICC path\n"); + + /* The DDR path is optional, depending on protocol and hw capabilities */ + icc_paths[GENI_TO_DDR].path = devm_of_icc_get(se->dev, "qup-memory"); + if (IS_ERR(icc_paths[GENI_TO_DDR].path)) { + if (PTR_ERR(icc_paths[GENI_TO_DDR].path) == -ENODATA) + icc_paths[GENI_TO_DDR].path = NULL; + else + return dev_err_probe(se->dev, PTR_ERR(icc_paths[GENI_TO_DDR].path), + "Failed to get 'qup-memory' ICC path\n"); } return 0; - -err: - err = PTR_ERR(se->icc_paths[i].path); - if (err != -EPROBE_DEFER) - dev_err_ratelimited(se->dev, "Failed to get ICC path '%s': %d\n", - icc_names[i], err); - return err; - } EXPORT_SYMBOL_GPL(geni_icc_get); From f746fedb892872cbcee698696303609f0ea76fd7 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:33 +0530 Subject: [PATCH 124/189] soc: qcom: geni-se: Add geni_icc_set_bw_ab() function Add a new function geni_icc_set_bw_ab() that allows callers to set average bandwidth values for all ICC (Interconnect) paths in a single call. This function takes separate parameters for core, config, and DDR average bandwidth values and applies them to the respective ICC paths. This provides a more convenient API for drivers that need to configure specific average bandwidth values. Co-developed-by: Konrad Dybcio Signed-off-by: Konrad Dybcio Signed-off-by: Praveen Talari Tested-by: Mattijs Korpershoek Link: https://lore.kernel.org/r/20260227061544.1785978-3-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom-geni-se.c | 22 ++++++++++++++++++++++ include/linux/soc/qcom/geni-se.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index b6167b968ef6..b0542f836453 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -946,6 +946,28 @@ int geni_icc_set_bw(struct geni_se *se) } EXPORT_SYMBOL_GPL(geni_icc_set_bw); +/** + * geni_icc_set_bw_ab() - Set average bandwidth for all ICC paths and apply + * @se: Pointer to the concerned serial engine. + * @core_ab: Average bandwidth in kBps for GENI_TO_CORE path. + * @cfg_ab: Average bandwidth in kBps for CPU_TO_GENI path. + * @ddr_ab: Average bandwidth in kBps for GENI_TO_DDR path. + * + * Sets bandwidth values for all ICC paths and applies them. DDR path is + * optional and only set if it exists. + * + * Return: 0 on success, negative error code on failure. + */ +int geni_icc_set_bw_ab(struct geni_se *se, u32 core_ab, u32 cfg_ab, u32 ddr_ab) +{ + se->icc_paths[GENI_TO_CORE].avg_bw = core_ab; + se->icc_paths[CPU_TO_GENI].avg_bw = cfg_ab; + se->icc_paths[GENI_TO_DDR].avg_bw = ddr_ab; + + return geni_icc_set_bw(se); +} +EXPORT_SYMBOL_GPL(geni_icc_set_bw_ab); + void geni_icc_set_tag(struct geni_se *se, u32 tag) { int i; diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index 0a984e2579fe..980aabea2157 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -528,6 +528,7 @@ void geni_se_rx_dma_unprep(struct geni_se *se, dma_addr_t iova, size_t len); int geni_icc_get(struct geni_se *se, const char *icc_ddr); int geni_icc_set_bw(struct geni_se *se); +int geni_icc_set_bw_ab(struct geni_se *se, u32 core_ab, u32 cfg_ab, u32 ddr_ab); void geni_icc_set_tag(struct geni_se *se, u32 tag); int geni_icc_enable(struct geni_se *se); From 5b8a39dcf909a4723a58e424ae1b0595d6f5032d Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:34 +0530 Subject: [PATCH 125/189] soc: qcom: geni-se: Introduce helper API for resource initialization The GENI Serial Engine drivers (I2C, SPI, and SERIAL) currently duplicate code for initializing shared resources such as clocks and interconnect paths. Introduce a new helper API, geni_se_resources_init(), to centralize this initialization logic, improving modularity and simplifying the probe function. Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Tested-by: Mattijs Korpershoek Link: https://lore.kernel.org/r/20260227061544.1785978-4-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom-geni-se.c | 47 ++++++++++++++++++++++++++++++++ include/linux/soc/qcom/geni-se.h | 6 ++++ 2 files changed, 53 insertions(+) diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index b0542f836453..75e722cd1a94 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -19,6 +19,7 @@ #include #include #include +#include #include /** @@ -1012,6 +1013,52 @@ int geni_icc_disable(struct geni_se *se) } EXPORT_SYMBOL_GPL(geni_icc_disable); +/** + * geni_se_resources_init() - Initialize resources for a GENI SE device. + * @se: Pointer to the geni_se structure representing the GENI SE device. + * + * This function initializes various resources required by the GENI Serial Engine + * (SE) device, including clock resources (core and SE clocks), interconnect + * paths for communication. + * It retrieves optional and mandatory clock resources, adds an OF-based + * operating performance point (OPP) table, and sets up interconnect paths + * with default bandwidths. The function also sets a flag (`has_opp`) to + * indicate whether OPP support is available for the device. + * + * Return: 0 on success, or a negative errno on failure. + */ +int geni_se_resources_init(struct geni_se *se) +{ + int ret; + + se->core_clk = devm_clk_get_optional(se->dev, "core"); + if (IS_ERR(se->core_clk)) + return dev_err_probe(se->dev, PTR_ERR(se->core_clk), + "Failed to get optional core clk\n"); + + se->clk = devm_clk_get(se->dev, "se"); + if (IS_ERR(se->clk) && !has_acpi_companion(se->dev)) + return dev_err_probe(se->dev, PTR_ERR(se->clk), + "Failed to get SE clk\n"); + + ret = devm_pm_opp_set_clkname(se->dev, "se"); + if (ret) + return ret; + + ret = devm_pm_opp_of_add_table(se->dev); + if (ret && ret != -ENODEV) + return dev_err_probe(se->dev, ret, "Failed to add OPP table\n"); + + se->has_opp = (ret == 0); + + ret = geni_icc_get(se, "qup-memory"); + if (ret) + return ret; + + return geni_icc_set_bw_ab(se, GENI_DEFAULT_BW, GENI_DEFAULT_BW, GENI_DEFAULT_BW); +} +EXPORT_SYMBOL_GPL(geni_se_resources_init); + /** * geni_find_protocol_fw() - Locate and validate SE firmware for a protocol. * @dev: Pointer to the device structure. diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index 980aabea2157..c182dd0f0bde 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -60,18 +60,22 @@ struct geni_icc_path { * @dev: Pointer to the Serial Engine device * @wrapper: Pointer to the parent QUP Wrapper core * @clk: Handle to the core serial engine clock + * @core_clk: Auxiliary clock, which may be required by a protocol * @num_clk_levels: Number of valid clock levels in clk_perf_tbl * @clk_perf_tbl: Table of clock frequency input to serial engine clock * @icc_paths: Array of ICC paths for SE + * @has_opp: Indicates if OPP is supported */ struct geni_se { void __iomem *base; struct device *dev; struct geni_wrapper *wrapper; struct clk *clk; + struct clk *core_clk; unsigned int num_clk_levels; unsigned long *clk_perf_tbl; struct geni_icc_path icc_paths[3]; + bool has_opp; }; /* Common SE registers */ @@ -535,6 +539,8 @@ int geni_icc_enable(struct geni_se *se); int geni_icc_disable(struct geni_se *se); +int geni_se_resources_init(struct geni_se *se); + int geni_load_se_firmware(struct geni_se *se, enum geni_se_protocol_type protocol); #endif #endif From e5f54cda9d75b21baa0e07616b5d662b55e7ff76 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:35 +0530 Subject: [PATCH 126/189] soc: qcom: geni-se: Handle core clk in geni_se_clks_off() and geni_se_clks_on() Currently, core clk is handled individually in protocol drivers like the I2C driver. Move this clock management to the common clock APIs (geni_se_clks_on/off) that are already present in the common GENI SE driver to maintain consistency across all protocol drivers. Core clk is now properly managed alongside the other clocks (se->clk and wrapper clocks) in the fundamental clock control functions, eliminating the need for individual protocol drivers to handle this clock separately. Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Tested-by: Mattijs Korpershoek Link: https://lore.kernel.org/r/20260227061544.1785978-5-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom-geni-se.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index 75e722cd1a94..2e41595ff912 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -583,6 +583,7 @@ static void geni_se_clks_off(struct geni_se *se) clk_disable_unprepare(se->clk); clk_bulk_disable_unprepare(wrapper->num_clks, wrapper->clks); + clk_disable_unprepare(se->core_clk); } /** @@ -619,7 +620,18 @@ static int geni_se_clks_on(struct geni_se *se) ret = clk_prepare_enable(se->clk); if (ret) - clk_bulk_disable_unprepare(wrapper->num_clks, wrapper->clks); + goto err_bulk_clks; + + ret = clk_prepare_enable(se->core_clk); + if (ret) + goto err_se_clk; + + return 0; + +err_se_clk: + clk_disable_unprepare(se->clk); +err_bulk_clks: + clk_bulk_disable_unprepare(wrapper->num_clks, wrapper->clks); return ret; } From 8f4ce470ff8c0d7d820d319f64f05ed9a239a240 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:36 +0530 Subject: [PATCH 127/189] soc: qcom: geni-se: Add resources activation/deactivation helpers The GENI SE protocol drivers (I2C, SPI, UART) implement similar resource activation/deactivation sequences independently, leading to code duplication. Introduce geni_se_resources_activate()/geni_se_resources_deactivate() to power on/off resources.The activate function enables ICC, clocks, and TLMM whereas the deactivate function disables resources in reverse order including OPP rate reset, clocks, ICC and TLMM. Signed-off-by: Praveen Talari Reviewed-by: Konrad Dybcio Tested-by: Mattijs Korpershoek Link: https://lore.kernel.org/r/20260227061544.1785978-6-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom-geni-se.c | 70 ++++++++++++++++++++++++++++++++ include/linux/soc/qcom/geni-se.h | 4 ++ 2 files changed, 74 insertions(+) diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index 2e41595ff912..6e58568701b0 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -1025,6 +1025,76 @@ int geni_icc_disable(struct geni_se *se) } EXPORT_SYMBOL_GPL(geni_icc_disable); +/** + * geni_se_resources_deactivate() - Deactivate GENI SE device resources + * @se: Pointer to the geni_se structure + * + * Deactivates device resources for power saving: OPP rate to 0, pin control + * to sleep state, turns off clocks, and disables interconnect. Skips ACPI devices. + * + * Return: 0 on success, negative error code on failure + */ +int geni_se_resources_deactivate(struct geni_se *se) +{ + int ret; + + if (has_acpi_companion(se->dev)) + return 0; + + if (se->has_opp) + dev_pm_opp_set_rate(se->dev, 0); + + ret = pinctrl_pm_select_sleep_state(se->dev); + if (ret) + return ret; + + geni_se_clks_off(se); + + return geni_icc_disable(se); +} +EXPORT_SYMBOL_GPL(geni_se_resources_deactivate); + +/** + * geni_se_resources_activate() - Activate GENI SE device resources + * @se: Pointer to the geni_se structure + * + * Activates device resources for operation: enables interconnect, prepares clocks, + * and sets pin control to default state. Includes error cleanup. Skips ACPI devices. + * + * Unlike geni_se_resources_deactivate(), this function doesn't alter the + * connected genpds' performance states, which must be additionally handled. + * + * Return: 0 on success, negative error code on failure + */ +int geni_se_resources_activate(struct geni_se *se) +{ + int ret; + + if (has_acpi_companion(se->dev)) + return 0; + + ret = geni_icc_enable(se); + if (ret) + return ret; + + ret = geni_se_clks_on(se); + if (ret) + goto out_icc_disable; + + ret = pinctrl_pm_select_default_state(se->dev); + if (ret) { + geni_se_clks_off(se); + goto out_icc_disable; + } + + return 0; + +out_icc_disable: + geni_icc_disable(se); + return ret; +} +EXPORT_SYMBOL_GPL(geni_se_resources_activate); + /** * geni_se_resources_init() - Initialize resources for a GENI SE device. * @se: Pointer to the geni_se structure representing the GENI SE device. diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index c182dd0f0bde..36a68149345c 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -541,6 +541,10 @@ int geni_icc_disable(struct geni_se *se); int geni_se_resources_init(struct geni_se *se); +int geni_se_resources_activate(struct geni_se *se); + +int geni_se_resources_deactivate(struct geni_se *se); + int geni_load_se_firmware(struct geni_se *se, enum geni_se_protocol_type protocol); #endif #endif From f1a325d2812797166c744ff7ffb2ccbafbac20ad Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:37 +0530 Subject: [PATCH 128/189] soc: qcom: geni-se: Introduce helper API for attaching power domains The GENI Serial Engine drivers (I2C, SPI, and SERIAL) currently handle the attachment of power domains. This often leads to duplicated code logic across different driver probe functions. Introduce a new helper API, geni_se_domain_attach(), to centralize the logic for attaching "power" and "perf" domains to the GENI SE device. Signed-off-by: Praveen Talari Reviewed-by: Konrad Dybcio Tested-by: Mattijs Korpershoek Link: https://lore.kernel.org/r/20260227061544.1785978-7-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom-geni-se.c | 31 +++++++++++++++++++++++++++++++ include/linux/soc/qcom/geni-se.h | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index 6e58568701b0..13ad3a51b58c 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -1095,6 +1096,36 @@ int geni_se_resources_activate(struct geni_se *se) } EXPORT_SYMBOL_GPL(geni_se_resources_activate); +/** + * geni_se_domain_attach() - Attach power domains to a GENI SE device. + * @se: Pointer to the geni_se structure representing the GENI SE device. + * + * This function attaches the power domains ("power" and "perf") required + * in the SCMI auto-VM environment to the GENI Serial Engine device. It + * initializes se->pd_list with the attached domains. + * + * Return: 0 on success, or a negative error code on failure. + */ +int geni_se_domain_attach(struct geni_se *se) +{ + struct dev_pm_domain_attach_data pd_data = { + .pd_flags = PD_FLAG_DEV_LINK_ON, + .pd_names = (const char*[]) { "power", "perf" }, + .num_pd_names = 2, + }; + int ret; + + ret = devm_pm_domain_attach_list(se->dev, + &pd_data, &se->pd_list); + if (ret == 0) + return -ENODEV; + else if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(geni_se_domain_attach); + /** * geni_se_resources_init() - Initialize resources for a GENI SE device. * @se: Pointer to the geni_se structure representing the GENI SE device. diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index 36a68149345c..5f75159c5531 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -64,6 +64,7 @@ struct geni_icc_path { * @num_clk_levels: Number of valid clock levels in clk_perf_tbl * @clk_perf_tbl: Table of clock frequency input to serial engine clock * @icc_paths: Array of ICC paths for SE + * @pd_list: Power domain list for managing power domains * @has_opp: Indicates if OPP is supported */ struct geni_se { @@ -75,6 +76,7 @@ struct geni_se { unsigned int num_clk_levels; unsigned long *clk_perf_tbl; struct geni_icc_path icc_paths[3]; + struct dev_pm_domain_list *pd_list; bool has_opp; }; @@ -546,5 +548,7 @@ int geni_se_resources_activate(struct geni_se *se); int geni_se_resources_deactivate(struct geni_se *se); int geni_load_se_firmware(struct geni_se *se, enum geni_se_protocol_type protocol); + +int geni_se_domain_attach(struct geni_se *se); #endif #endif From c012e28e9a4a84b0fe7bf9ef7db334a9a4f31687 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:38 +0530 Subject: [PATCH 129/189] soc: qcom: geni-se: Introduce helper APIs for performance control The GENI Serial Engine (SE) drivers (I2C, SPI, and SERIAL) currently manage performance levels and operating points directly. This resulting in code duplication across drivers. such as configuring a specific level or find and apply an OPP based on a clock frequency. Introduce two new helper APIs, geni_se_set_perf_level() and geni_se_set_perf_opp(), addresses this issue by providing a streamlined method for the GENI Serial Engine (SE) drivers to find and set the OPP based on the desired performance level, thereby eliminating redundancy. Signed-off-by: Praveen Talari Reviewed-by: Konrad Dybcio Tested-by: Mattijs Korpershoek Link: https://lore.kernel.org/r/20260227061544.1785978-8-praveen.talari@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/qcom-geni-se.c | 50 ++++++++++++++++++++++++++++++++ include/linux/soc/qcom/geni-se.h | 4 +++ 2 files changed, 54 insertions(+) diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index 13ad3a51b58c..15636a8dc907 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -282,6 +282,12 @@ struct se_fw_hdr { #define geni_setbits32(_addr, _v) writel(readl(_addr) | (_v), _addr) #define geni_clrbits32(_addr, _v) writel(readl(_addr) & ~(_v), _addr) +enum domain_idx { + DOMAIN_IDX_POWER, + DOMAIN_IDX_PERF, + DOMAIN_IDX_MAX +}; + /** * geni_se_get_qup_hw_version() - Read the QUP wrapper Hardware version * @se: Pointer to the corresponding serial engine. @@ -1096,6 +1102,50 @@ int geni_se_resources_activate(struct geni_se *se) } EXPORT_SYMBOL_GPL(geni_se_resources_activate); +/** + * geni_se_set_perf_level() - Set performance level for GENI SE. + * @se: Pointer to the struct geni_se instance. + * @level: The desired performance level. + * + * Sets the performance level by directly calling dev_pm_opp_set_level + * on the performance device associated with the SE. + * + * Return: 0 on success, or a negative error code on failure. + */ +int geni_se_set_perf_level(struct geni_se *se, unsigned long level) +{ + return dev_pm_opp_set_level(se->pd_list->pd_devs[DOMAIN_IDX_PERF], level); +} +EXPORT_SYMBOL_GPL(geni_se_set_perf_level); + +/** + * geni_se_set_perf_opp() - Set performance OPP for GENI SE by frequency. + * @se: Pointer to the struct geni_se instance. + * @clk_freq: The requested clock frequency. + * + * Finds the nearest operating performance point (OPP) for the given + * clock frequency and applies it to the SE's performance device. + * + * Return: 0 on success, or a negative error code on failure. + */ +int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq) +{ + struct device *perf_dev = se->pd_list->pd_devs[DOMAIN_IDX_PERF]; + struct dev_pm_opp *opp; + int ret; + + opp = dev_pm_opp_find_freq_floor(perf_dev, &clk_freq); + if (IS_ERR(opp)) { + dev_err(se->dev, "failed to find opp for freq %lu\n", clk_freq); + return PTR_ERR(opp); + } + + ret = dev_pm_opp_set_opp(perf_dev, opp); + dev_pm_opp_put(opp); + return ret; +} +EXPORT_SYMBOL_GPL(geni_se_set_perf_opp); + /** * geni_se_domain_attach() - Attach power domains to a GENI SE device. * @se: Pointer to the geni_se structure representing the GENI SE device. diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index 5f75159c5531..c5e6ab85df09 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -550,5 +550,9 @@ int geni_se_resources_deactivate(struct geni_se *se); int geni_load_se_firmware(struct geni_se *se, enum geni_se_protocol_type protocol); int geni_se_domain_attach(struct geni_se *se); + +int geni_se_set_perf_level(struct geni_se *se, unsigned long level); + +int geni_se_set_perf_opp(struct geni_se *se, unsigned long clk_freq); #endif #endif From a76c24eb1f10366f128c621f876c20a13ae53489 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:13 -0500 Subject: [PATCH 130/189] soc: ti: knav_qmss: Remove remaining redundant ENOMEM printks Commit 168d2fb78055 ("soc: ti: knav_qmss: Remove ENOMEM printks") removed redundant dev_err() calls after allocation failures in knav_queue_setup_regions, knav_queue_init_qmgrs and knav_queue_init_pdsps, but missed three further instances in knav_pool_create, knav_queue_setup_region and knav_setup_queue_range. Remove the missed instances. Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-2-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss_queue.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index a0644578789f..c5669a371312 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -769,10 +769,8 @@ void *knav_pool_create(const char *name, return ERR_PTR(-ENODEV); pool = devm_kzalloc(kdev->dev, sizeof(*pool), GFP_KERNEL); - if (!pool) { - dev_err(kdev->dev, "out of memory allocating pool\n"); + if (!pool) return ERR_PTR(-ENOMEM); - } for_each_region(kdev, reg_itr) { if (reg_itr->id != region_id) @@ -1025,10 +1023,8 @@ static void knav_queue_setup_region(struct knav_device *kdev, region->dma_end = region->dma_start + size; pool = devm_kzalloc(kdev->dev, sizeof(*pool), GFP_KERNEL); - if (!pool) { - dev_err(kdev->dev, "out of memory allocating dummy pool\n"); + if (!pool) goto fail; - } pool->num_desc = 0; pool->region_offset = region->num_desc; list_add(&pool->region_inst, ®ion->pools); @@ -1211,10 +1207,8 @@ static int knav_setup_queue_range(struct knav_device *kdev, int ret, i; range = devm_kzalloc(dev, sizeof(*range), GFP_KERNEL); - if (!range) { - dev_err(dev, "out of memory allocating range\n"); + if (!range) return -ENOMEM; - } range->kdev = kdev; range->name = knav_queue_find_name(node); From 7db27a04fb8a33a7f9904961ee6f3c75f65d1118 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:14 -0500 Subject: [PATCH 131/189] soc: ti: knav_qmss: Rename global kdev to knav_qdev to fix -Wshadow Building with W=2 (clang, LLVM=1) produces 22 -Wshadow warnings in knav_qmss_queue.c because the file-scoped singleton 'kdev' is shadowed by a parameter of the same name in 21 internal functions and one local variable, e.g.: knav_qmss_queue.c:194:49: warning: declaration shadows a variable in the global scope [-Wshadow] 194 | knav_queue_match_id_to_inst(struct knav_device *kdev, unsigned id) Rename the global singleton from kdev to knav_qdev rather than renaming all ~21 function parameters, as this requires fewer changes and leaves function signatures, struct field accesses, and header macros in knav_qmss.h untouched. Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-3-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss_queue.c | 94 ++++++++++++++++---------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index c5669a371312..52a30abb2caf 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -25,7 +25,7 @@ #include "knav_qmss.h" -static struct knav_device *kdev; +static struct knav_device *knav_qdev; static DEFINE_MUTEX(knav_dev_lock); #define knav_dev_lock_held() \ lockdep_is_held(&knav_dev_lock) @@ -205,10 +205,10 @@ knav_queue_match_id_to_inst(struct knav_device *kdev, unsigned id) static inline struct knav_queue_inst *knav_queue_find_by_id(int id) { - if (kdev->base_id <= id && - kdev->base_id + kdev->num_queues > id) { - id -= kdev->base_id; - return knav_queue_match_id_to_inst(kdev, id); + if (knav_qdev->base_id <= id && + knav_qdev->base_id + knav_qdev->num_queues > id) { + id -= knav_qdev->base_id; + return knav_queue_match_id_to_inst(knav_qdev, id); } return NULL; } @@ -296,7 +296,7 @@ static struct knav_queue *knav_queue_open_by_type(const char *name, mutex_lock(&knav_dev_lock); - for_each_instance(idx, inst, kdev) { + for_each_instance(idx, inst, knav_qdev) { if (knav_queue_is_reserved(inst)) continue; if (!knav_queue_match_type(inst, type)) @@ -469,9 +469,9 @@ static int knav_queue_debug_show(struct seq_file *s, void *v) mutex_lock(&knav_dev_lock); seq_printf(s, "%s: %u-%u\n", - dev_name(kdev->dev), kdev->base_id, - kdev->base_id + kdev->num_queues - 1); - for_each_instance(idx, inst, kdev) + dev_name(knav_qdev->dev), knav_qdev->base_id, + knav_qdev->base_id + knav_qdev->num_queues - 1); + for_each_instance(idx, inst, knav_qdev) knav_queue_debug_show_instance(s, inst); mutex_unlock(&knav_dev_lock); @@ -762,17 +762,17 @@ void *knav_pool_create(const char *name, unsigned last_offset; int ret; - if (!kdev) + if (!knav_qdev) return ERR_PTR(-EPROBE_DEFER); - if (!kdev->dev) + if (!knav_qdev->dev) return ERR_PTR(-ENODEV); - pool = devm_kzalloc(kdev->dev, sizeof(*pool), GFP_KERNEL); + pool = devm_kzalloc(knav_qdev->dev, sizeof(*pool), GFP_KERNEL); if (!pool) return ERR_PTR(-ENOMEM); - for_each_region(kdev, reg_itr) { + for_each_region(knav_qdev, reg_itr) { if (reg_itr->id != region_id) continue; region = reg_itr; @@ -780,14 +780,14 @@ void *knav_pool_create(const char *name, } if (!region) { - dev_err(kdev->dev, "region-id(%d) not found\n", region_id); + dev_err(knav_qdev->dev, "region-id(%d) not found\n", region_id); ret = -EINVAL; goto err; } pool->queue = knav_queue_open(name, KNAV_QUEUE_GP, 0); if (IS_ERR(pool->queue)) { - dev_err(kdev->dev, + dev_err(knav_qdev->dev, "failed to open queue for pool(%s), error %ld\n", name, PTR_ERR(pool->queue)); ret = PTR_ERR(pool->queue); @@ -795,13 +795,13 @@ void *knav_pool_create(const char *name, } pool->name = kstrndup(name, KNAV_NAME_SIZE - 1, GFP_KERNEL); - pool->kdev = kdev; - pool->dev = kdev->dev; + pool->kdev = knav_qdev; + pool->dev = knav_qdev->dev; mutex_lock(&knav_dev_lock); if (num_desc > (region->num_desc - region->used_desc)) { - dev_err(kdev->dev, "out of descs in region(%d) for pool(%s)\n", + dev_err(knav_qdev->dev, "out of descs in region(%d) for pool(%s)\n", region_id, name); ret = -ENOMEM; goto err_unlock; @@ -827,10 +827,10 @@ void *knav_pool_create(const char *name, pool->num_desc = num_desc; pool->region_offset = last_offset; region->used_desc += num_desc; - list_add_tail(&pool->list, &kdev->pools); + list_add_tail(&pool->list, &knav_qdev->pools); list_add_tail(&pool->region_inst, node); } else { - dev_err(kdev->dev, "pool(%s) create failed: fragmented desc pool in region(%d)\n", + dev_err(knav_qdev->dev, "pool(%s) create failed: fragmented desc pool in region(%d)\n", name, region_id); ret = -ENOMEM; goto err_unlock; @@ -844,7 +844,7 @@ void *knav_pool_create(const char *name, mutex_unlock(&knav_dev_lock); err: kfree(pool->name); - devm_kfree(kdev->dev, pool); + devm_kfree(knav_qdev->dev, pool); return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(knav_pool_create); @@ -872,7 +872,7 @@ void knav_pool_destroy(void *ph) mutex_unlock(&knav_dev_lock); kfree(pool->name); - devm_kfree(kdev->dev, pool); + devm_kfree(knav_qdev->dev, pool); } EXPORT_SYMBOL_GPL(knav_pool_destroy); @@ -1683,7 +1683,7 @@ static inline struct knav_qmgr_info *knav_find_qmgr(unsigned id) { struct knav_qmgr_info *qmgr; - for_each_qmgr(kdev, qmgr) { + for_each_qmgr(knav_qdev, qmgr) { if ((id >= qmgr->start_queue) && (id < qmgr->start_queue + qmgr->num_queues)) return qmgr; @@ -1775,22 +1775,22 @@ static int knav_queue_probe(struct platform_device *pdev) return -ENODEV; } - kdev = devm_kzalloc(dev, sizeof(struct knav_device), GFP_KERNEL); - if (!kdev) { + knav_qdev = devm_kzalloc(dev, sizeof(struct knav_device), GFP_KERNEL); + if (!knav_qdev) { dev_err(dev, "memory allocation failed\n"); return -ENOMEM; } if (device_get_match_data(dev)) - kdev->version = QMSS_66AK2G; + knav_qdev->version = QMSS_66AK2G; - platform_set_drvdata(pdev, kdev); - kdev->dev = dev; - INIT_LIST_HEAD(&kdev->queue_ranges); - INIT_LIST_HEAD(&kdev->qmgrs); - INIT_LIST_HEAD(&kdev->pools); - INIT_LIST_HEAD(&kdev->regions); - INIT_LIST_HEAD(&kdev->pdsps); + platform_set_drvdata(pdev, knav_qdev); + knav_qdev->dev = dev; + INIT_LIST_HEAD(&knav_qdev->queue_ranges); + INIT_LIST_HEAD(&knav_qdev->qmgrs); + INIT_LIST_HEAD(&knav_qdev->pools); + INIT_LIST_HEAD(&knav_qdev->regions); + INIT_LIST_HEAD(&knav_qdev->pdsps); pm_runtime_enable(&pdev->dev); ret = pm_runtime_resume_and_get(&pdev->dev); @@ -1805,31 +1805,31 @@ static int knav_queue_probe(struct platform_device *pdev) ret = -ENODEV; goto err; } - kdev->base_id = temp[0]; - kdev->num_queues = temp[1]; + knav_qdev->base_id = temp[0]; + knav_qdev->num_queues = temp[1]; /* Initialize queue managers using device tree configuration */ - ret = knav_queue_init_qmgrs(kdev, node); + ret = knav_queue_init_qmgrs(knav_qdev, node); if (ret) goto err; /* get pdsp configuration values from device tree */ - ret = knav_queue_setup_pdsps(kdev, node); + ret = knav_queue_setup_pdsps(knav_qdev, node); if (ret) goto err; /* get usable queue range values from device tree */ - ret = knav_setup_queue_pools(kdev, node); + ret = knav_setup_queue_pools(knav_qdev, node); if (ret) goto err; - ret = knav_get_link_ram(kdev, "linkram0", &kdev->link_rams[0]); + ret = knav_get_link_ram(knav_qdev, "linkram0", &knav_qdev->link_rams[0]); if (ret) { - dev_err(kdev->dev, "could not setup linking ram\n"); + dev_err(knav_qdev->dev, "could not setup linking ram\n"); goto err; } - ret = knav_get_link_ram(kdev, "linkram1", &kdev->link_rams[1]); + ret = knav_get_link_ram(knav_qdev, "linkram1", &knav_qdev->link_rams[1]); if (ret) { /* * nothing really, we have one linking ram already, so we just @@ -1837,15 +1837,15 @@ static int knav_queue_probe(struct platform_device *pdev) */ } - ret = knav_queue_setup_link_ram(kdev); + ret = knav_queue_setup_link_ram(knav_qdev); if (ret) goto err; - ret = knav_queue_setup_regions(kdev, node); + ret = knav_queue_setup_regions(knav_qdev, node); if (ret) goto err; - ret = knav_queue_init_queues(kdev); + ret = knav_queue_init_queues(knav_qdev); if (ret < 0) { dev_err(dev, "hwqueue initialization failed\n"); goto err; @@ -1857,9 +1857,9 @@ static int knav_queue_probe(struct platform_device *pdev) return 0; err: - knav_queue_stop_pdsps(kdev); - knav_queue_free_regions(kdev); - knav_free_queue_ranges(kdev); + knav_queue_stop_pdsps(knav_qdev); + knav_queue_free_regions(knav_qdev); + knav_free_queue_ranges(knav_qdev); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return ret; From b6542cf0f4d062133f6e05de03382080ad941c67 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:15 -0500 Subject: [PATCH 132/189] soc: ti: knav_qmss: Inline lockdep condition in for_each_handle_rcu knav_dev_lock_held() is a single-use wrapper around lockdep_is_held(&knav_dev_lock), used only as the lockdep condition in for_each_handle_rcu. When CONFIG_PROVE_RCU_LIST is disabled, list_for_each_entry_rcu() elides the condition argument entirely, causing clang to report the macro as unused with W=2: knav_qmss_queue.c:30:9: warning: macro is not used [-Wunused-macros] 30 | #define knav_dev_lock_held() \ Remove the intermediate macro and open-code lockdep_is_held() directly in the for_each_handle_rcu definition. Reviewed-by: Andrew Davis Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-4-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss_queue.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index 52a30abb2caf..68c55134ad6c 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -27,9 +27,6 @@ static struct knav_device *knav_qdev; static DEFINE_MUTEX(knav_dev_lock); -#define knav_dev_lock_held() \ - lockdep_is_held(&knav_dev_lock) - /* Queue manager register indices in DTS */ #define KNAV_QUEUE_PEEK_REG_INDEX 0 #define KNAV_QUEUE_STATUS_REG_INDEX 1 @@ -58,7 +55,7 @@ static DEFINE_MUTEX(knav_dev_lock); #define for_each_handle_rcu(qh, inst) \ list_for_each_entry_rcu(qh, &inst->handles, list, \ - knav_dev_lock_held()) + lockdep_is_held(&knav_dev_lock)) #define for_each_instance(idx, inst, kdev) \ for (idx = 0, inst = kdev->instances; \ From 7650590dbf12a3d4a3d302cd33ac31270e39c43c Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:16 -0500 Subject: [PATCH 133/189] soc: ti: knav_qmss: Fix kernel-doc Return: tags Fix functions that use inline 'Returns ...' instead of 'Return:' kernel-doc comments, producing warnings with W=2: knav_qmss_queue.c:524: No description found for return value of 'knav_queue_open' Tested-by: Randy Dunlap Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260512170623.3174416-5-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss_queue.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index 68c55134ad6c..7e3c05554254 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -517,7 +517,7 @@ static int knav_queue_flush(struct knav_queue *qh) * Subsequent attempts to open a shared queue should * also have this flag. * - * Returns a handle to the open hardware queue if successful. Use IS_ERR() + * Return: handle to the open hardware queue on success. Use IS_ERR() * to check the returned value for error codes. */ void *knav_queue_open(const char *name, unsigned id, @@ -573,7 +573,7 @@ EXPORT_SYMBOL_GPL(knav_queue_close); * @cmd: - control commands * @arg: - command argument * - * Returns 0 on success, errno otherwise. + * Return: 0 on success, errno otherwise. */ int knav_queue_device_control(void *qhandle, enum knav_queue_ctrl_cmd cmd, unsigned long arg) @@ -625,7 +625,7 @@ EXPORT_SYMBOL_GPL(knav_queue_device_control); * @size: - size of data to push * @flags: - can be used to pass additional information * - * Returns 0 on success, errno otherwise. + * Return: 0 on success, errno otherwise. */ int knav_queue_push(void *qhandle, dma_addr_t dma, unsigned size, unsigned flags) @@ -646,7 +646,7 @@ EXPORT_SYMBOL_GPL(knav_queue_push); * @qhandle: - hardware queue handle * @size: - (optional) size of the data pop'ed. * - * Returns a DMA address on success, 0 on failure. + * Return: DMA address on success, 0 on failure. */ dma_addr_t knav_queue_pop(void *qhandle, unsigned *size) { @@ -747,8 +747,8 @@ EXPORT_SYMBOL_GPL(knav_pool_desc_dma_to_virt); * @region_id: - QMSS region id from which the descriptors are to be * allocated. * - * Returns a pool handle on success. - * Use IS_ERR_OR_NULL() to identify error values on return. + * Return: pool handle on success. Use IS_ERR_OR_NULL() to identify + * error values on return. */ void *knav_pool_create(const char *name, int num_desc, int region_id) @@ -878,7 +878,7 @@ EXPORT_SYMBOL_GPL(knav_pool_destroy); * knav_pool_desc_get() - Get a descriptor from the pool * @ph: - pool handle * - * Returns descriptor from the pool. + * Return: descriptor from the pool on success, error pointer otherwise. */ void *knav_pool_desc_get(void *ph) { @@ -917,7 +917,7 @@ EXPORT_SYMBOL_GPL(knav_pool_desc_put); * @dma: - DMA address return pointer * @dma_sz: - adjusted return pointer * - * Returns 0 on success, errno otherwise. + * Return: 0 on success, errno otherwise. */ int knav_pool_desc_map(void *ph, void *desc, unsigned size, dma_addr_t *dma, unsigned *dma_sz) @@ -942,7 +942,7 @@ EXPORT_SYMBOL_GPL(knav_pool_desc_map); * @dma: - DMA address of descriptor to unmap * @dma_sz: - size of descriptor to unmap * - * Returns descriptor address on success, Use IS_ERR_OR_NULL() to identify + * Return: descriptor address on success. Use IS_ERR_OR_NULL() to identify * error values on return. */ void *knav_pool_desc_unmap(void *ph, dma_addr_t dma, unsigned dma_sz) @@ -962,7 +962,8 @@ EXPORT_SYMBOL_GPL(knav_pool_desc_unmap); /** * knav_pool_count() - Get the number of descriptors in pool. * @ph: - pool handle - * Returns number of elements in the pool. + * + * Return: number of elements in the pool. */ int knav_pool_count(void *ph) { From a5f624c4fee6d7b51f7bc4bd6e213c220b610ec3 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:17 -0500 Subject: [PATCH 134/189] soc: ti: knav_qmss: Use %pe to print PTR_ERR() Coccinelle (scripts/coccinelle/misc/ptr_err_to_pe.cocci) flags the dev_err() call in knav_pool_create(): knav_qmss_queue.c:789:9-16: WARNING: Consider using %pe to print PTR_ERR() Replace the %ld / PTR_ERR() pair with %pe and pass the error pointer directly to also print the symbolic error name. Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-6-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss_queue.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index 7e3c05554254..8af88a143159 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -785,8 +785,8 @@ void *knav_pool_create(const char *name, pool->queue = knav_queue_open(name, KNAV_QUEUE_GP, 0); if (IS_ERR(pool->queue)) { dev_err(knav_qdev->dev, - "failed to open queue for pool(%s), error %ld\n", - name, PTR_ERR(pool->queue)); + "failed to open queue for pool(%s), error %pe\n", + name, pool->queue); ret = PTR_ERR(pool->queue); goto err; } From cb1a271a4f20faae9a10891bfae2b543778d2b4a Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:18 -0500 Subject: [PATCH 135/189] soc: ti: knav_qmss: Fix __iomem annotations and __be32 type Fix several address-space and type annotation issues reported by sparse: - Change pdsp->command from 'void __iomem *' to 'u32 __iomem *' to match the other union members (acc_command, qos_command); adjust the offset in knav_queue_load_pdsp() from +0x18 to +0x6 to preserve the 24-byte offset. - Fix knav_queue_pdsp_wait() declaration: correct the parameter annotation from 'u32 * __iomem' (pointer-in-iomem-space) to 'u32 __iomem *' (pointer-to-iomem); use 'unsigned int' for the timeout parameter instead of bare 'unsigned'; fix the continuation- line alignment. - Use IOMEM_ERR_PTR() in knav_queue_map_reg() instead of ERR_PTR() when returning an error as void __iomem *. - Annotate the firmware data array as 'const __be32 *' instead of 'u32 *', as be32_to_cpu() requires __be32 input. Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-7-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss.h | 2 +- drivers/soc/ti/knav_qmss_queue.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/soc/ti/knav_qmss.h b/drivers/soc/ti/knav_qmss.h index 9325e8ce2e25..037dc1b36645 100644 --- a/drivers/soc/ti/knav_qmss.h +++ b/drivers/soc/ti/knav_qmss.h @@ -123,7 +123,7 @@ struct knav_pdsp_info { const char *name; struct knav_reg_pdsp_regs __iomem *regs; union { - void __iomem *command; + u32 __iomem *command; struct knav_reg_acc_command __iomem *acc_command; u32 __iomem *qos_command; }; diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index 8af88a143159..7410b63af0e6 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -477,8 +477,8 @@ static int knav_queue_debug_show(struct seq_file *s, void *v) DEFINE_SHOW_ATTRIBUTE(knav_queue_debug); -static inline int knav_queue_pdsp_wait(u32 * __iomem addr, unsigned timeout, - u32 flags) +static inline int knav_queue_pdsp_wait(u32 __iomem *addr, unsigned int timeout, + u32 flags) { unsigned long end; u32 val = 0; @@ -1368,7 +1368,7 @@ static void __iomem *knav_queue_map_reg(struct knav_device *kdev, if (ret) { dev_err(kdev->dev, "Can't translate of node(%pOFn) address for index(%d)\n", node, index); - return ERR_PTR(ret); + return IOMEM_ERR_PTR(ret); } regs = devm_ioremap_resource(kdev->dev, &res); @@ -1556,7 +1556,7 @@ static int knav_queue_load_pdsp(struct knav_device *kdev, int i, ret, fwlen; const struct firmware *fw; bool found = false; - u32 *fwdata; + const __be32 *fwdata; for (i = 0; i < ARRAY_SIZE(knav_acc_firmwares); i++) { if (knav_acc_firmwares[i]) { @@ -1578,9 +1578,9 @@ static int knav_queue_load_pdsp(struct knav_device *kdev, dev_info(kdev->dev, "firmware file %s downloaded for PDSP\n", knav_acc_firmwares[i]); - writel_relaxed(pdsp->id + 1, pdsp->command + 0x18); + writel_relaxed(pdsp->id + 1, pdsp->command + 0x6); /* download the firmware */ - fwdata = (u32 *)fw->data; + fwdata = (const __be32 *)fw->data; fwlen = (fw->size + sizeof(u32) - 1) / sizeof(u32); for (i = 0; i < fwlen; i++) writel_relaxed(be32_to_cpu(fwdata[i]), pdsp->iram + i); From ebc1a345bf1166b22035f0cfbf877b9d0e80ba4e Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:19 -0500 Subject: [PATCH 136/189] soc: ti: knav_qmss_acc: Fix kernel-doc Return: tag Fix knav_init_acc_range() use of 'Return ...' instead of 'Return:' kernel-doc comment, which produces a warning with W=2: knav_qmss_acc.c:473: No description found for return value of 'knav_init_acc_range' Tested-by: Randy Dunlap Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260512170623.3174416-8-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_qmss_acc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/ti/knav_qmss_acc.c b/drivers/soc/ti/knav_qmss_acc.c index 269b4e75ae40..1f8b5acdd462 100644 --- a/drivers/soc/ti/knav_qmss_acc.c +++ b/drivers/soc/ti/knav_qmss_acc.c @@ -466,7 +466,7 @@ static const struct knav_range_ops knav_acc_range_ops = { * @node: device node * @range: qmms range information * - * Return 0 on success or error + * Return: 0 on success, errno otherwise. */ int knav_init_acc_range(struct knav_device *kdev, struct device_node *node, From 2b47cc255a952d3bd7ec3c0f38fdd81ccde3cb58 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:20 -0500 Subject: [PATCH 137/189] soc: ti: knav_dma: Remove unused DMA_PRIO_MASK macro DMA_PRIO_MASK (GENMASK(3, 0)) is defined alongside the other priority macros but is never referenced in the code. tx_priority and rx_priority are only ever assigned DMA_PRIO_DEFAULT (0) and are never sourced from device tree or user-controlled input, so no out-of-range value is possible. W=2 builds report: knav_dma.c:32:9: warning: macro is not used [-Wunused-macros] 32 | #define DMA_PRIO_MASK GENMASK(3, 0) Remove the dead macro. Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-9-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_dma.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/soc/ti/knav_dma.c b/drivers/soc/ti/knav_dma.c index e5f5e3142fc4..462d181ca564 100644 --- a/drivers/soc/ti/knav_dma.c +++ b/drivers/soc/ti/knav_dma.c @@ -29,7 +29,6 @@ #define DMA_TX_FILT_EINFO BIT(30) #define DMA_TX_PRIO_SHIFT 0 #define DMA_RX_PRIO_SHIFT 16 -#define DMA_PRIO_MASK GENMASK(3, 0) #define DMA_PRIO_DEFAULT 0 #define DMA_RX_TIMEOUT_DEFAULT 17500 /* cycles */ #define DMA_RX_TIMEOUT_MASK GENMASK(16, 0) From fe5272e13e34025a9bb10cc452805e5085492a92 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:21 -0500 Subject: [PATCH 138/189] soc: ti: knav_dma: Remove dead check on unsigned args.args[0] smatch warns: knav_dma.c:390 of_channel_match_helper() warn: unsigned 'args.args[0]' is never less than zero. of_phandle_args.args[] is uint32_t, so the 'args.args[0] < 0' check is always false. of_parse_phandle_with_fixed_args() already handles errors by returning a non-zero code, which is checked immediately above. Remove the dead check. Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-10-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_dma.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/soc/ti/knav_dma.c b/drivers/soc/ti/knav_dma.c index 462d181ca564..7ba6fd58e9ce 100644 --- a/drivers/soc/ti/knav_dma.c +++ b/drivers/soc/ti/knav_dma.c @@ -387,11 +387,6 @@ static int of_channel_match_helper(struct device_node *np, const char *name, return -ENODEV; } - if (args.args[0] < 0) { - dev_err(kdev->dev, "Missing args for %s\n", name); - return -ENODEV; - } - return args.args[0]; } From 81f2c17bc0596f619616a2e85fab142c8316de85 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:22 -0500 Subject: [PATCH 139/189] soc: ti: knav_dma: Use IOMEM_ERR_PTR() in pktdma_get_regs() pktdma_get_regs() returns a void __iomem * but uses ERR_PTR() on the error path, causing sparse to warn about an address space mismatch. Replace ERR_PTR() with IOMEM_ERR_PTR() to resolve the warning cleanly. Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-11-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/knav_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/ti/knav_dma.c b/drivers/soc/ti/knav_dma.c index 7ba6fd58e9ce..96df3982e47b 100644 --- a/drivers/soc/ti/knav_dma.c +++ b/drivers/soc/ti/knav_dma.c @@ -520,7 +520,7 @@ static void __iomem *pktdma_get_regs(struct knav_dma_device *dma, if (ret) { dev_err(dev, "Can't translate of node(%pOFn) address for index(%d)\n", node, index); - return ERR_PTR(ret); + return IOMEM_ERR_PTR(ret); } regs = devm_ioremap_resource(kdev->dev, &res); From 33050d81b828c7d699ad0ad0c7489a198c527019 Mon Sep 17 00:00:00 2001 From: Nishanth Menon Date: Tue, 12 May 2026 12:06:23 -0500 Subject: [PATCH 140/189] soc: ti: k3-ringacc: Use str_enabled_disabled() helper Coccinelle (scripts/coccinelle/api/string_choices.cocci) flags an opportunity to replace the open-coded ternary expression in the probe dev_info() call: k3-ringacc.c:1439:3-32: opportunity for str_enabled_disabled(ringacc->dma_ring_reset_quirk) Replace the ternary with str_enabled_disabled() and add the required include for . Reviewed-by: Sai Sree Kartheek Adivi Reviewed-by: Hari Prasath Gujulan Elango Link: https://patch.msgid.link/20260512170623.3174416-12-nm@ti.com Signed-off-by: Nishanth Menon --- drivers/soc/ti/k3-ringacc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/soc/ti/k3-ringacc.c b/drivers/soc/ti/k3-ringacc.c index e2ca380812d2..5966db4327b1 100644 --- a/drivers/soc/ti/k3-ringacc.c +++ b/drivers/soc/ti/k3-ringacc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -1436,7 +1437,7 @@ static int k3_ringacc_init(struct platform_device *pdev, ringacc->rm_gp_range->desc[0].num, ringacc->tisci_dev_id); dev_info(dev, "dma-ring-reset-quirk: %s\n", - ringacc->dma_ring_reset_quirk ? "enabled" : "disabled"); + str_enabled_disabled(ringacc->dma_ring_reset_quirk)); dev_info(dev, "RA Proxy rev. %08x, num_proxies:%u\n", readl(&ringacc->proxy_gcfg->revision), ringacc->num_proxies); From 83c1d176fbac3c3ffbeaddbc37e11e083f19cebc Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 20 May 2026 13:16:31 +0200 Subject: [PATCH 141/189] memory: omap-gpmc: Silence W=1 kerneldoc warnings Recent W=1 builds print kerneldoc warnings, even though a correct kerneldoc is there: Warning: drivers/memory/omap-gpmc.c:441 Excess function parameter 'cs' description in 'get_gpmc_timing_reg' Drop additional inline comments for arguments to fix that. They are anyway not that useful. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260520111630.89365-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/omap-gpmc.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/memory/omap-gpmc.c b/drivers/memory/omap-gpmc.c index d9e13c1f9b13..958d2b0ea54a 100644 --- a/drivers/memory/omap-gpmc.c +++ b/drivers/memory/omap-gpmc.c @@ -432,13 +432,9 @@ static unsigned int gpmc_clk_ticks_to_ns(unsigned int ticks, int cs, * */ static int get_gpmc_timing_reg( - /* timing specifiers */ int cs, int reg, int st_bit, int end_bit, int max, const char *name, const enum gpmc_clk_domain cd, - /* value transform */ - int shift, - /* format specifiers */ - bool raw, bool noval) + int shift, bool raw, bool noval) { u32 l; int nr_bits; From a6954060adc9d956a99f909f46bf9fb0348c4fa2 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 15 May 2026 12:52:01 +0100 Subject: [PATCH 142/189] dt-bindings: memory: renesas,rzg3e-xspi: Add RZ/T2H and RZ/N2H support Document xSPI controller found on the Renesas RZ/T2H and RZ/N2H SoCs. The xSPI IP on these SoCs is identical to that found on the RZ/G3E SoC. The RZ/G3E HW manual (Rev.1.15) references bridge channel 1 and its bits, however the hardware actually supports only a single bridge channel (channel 0), matching the RZ/T2H design. The references to channel 1 and its configuration bits will be corrected in a future revision of the HW manual. Update clock/reset constraints to handle the SoC differences. Signed-off-by: Lad Prabhakar Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260515115202.1515577-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- .../renesas,rzg3e-xspi.yaml | 60 +++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/Documentation/devicetree/bindings/memory-controllers/renesas,rzg3e-xspi.yaml b/Documentation/devicetree/bindings/memory-controllers/renesas,rzg3e-xspi.yaml index 7a84f5bb7284..cdeca4c795f3 100644 --- a/Documentation/devicetree/bindings/memory-controllers/renesas,rzg3e-xspi.yaml +++ b/Documentation/devicetree/bindings/memory-controllers/renesas,rzg3e-xspi.yaml @@ -30,6 +30,8 @@ properties: - enum: - renesas,r9a09g056-xspi # RZ/V2N - renesas,r9a09g057-xspi # RZ/V2H(P) + - renesas,r9a09g077-xspi # RZ/T2H + - renesas,r9a09g087-xspi # RZ/N2H - const: renesas,r9a09g047-xspi reg: @@ -53,28 +55,38 @@ properties: - const: err_pulse clocks: - items: - - description: AHB clock - - description: AXI clock - - description: SPI clock - - description: Double speed SPI clock + oneOf: + - items: + - description: AHB clock + - description: AXI clock + - description: SPI clock + - description: Double speed SPI clock + - items: + - description: AHB clock + - description: SPI clock clock-names: - items: - - const: ahb - - const: axi - - const: spi - - const: spix2 + oneOf: + - items: + - const: ahb + - const: axi + - const: spi + - const: spix2 + - items: + - const: ahb + - const: spi power-domains: maxItems: 1 resets: + minItems: 1 items: - description: Hardware reset - description: AXI reset reset-names: + minItems: 1 items: - const: hresetn - const: aresetn @@ -109,6 +121,34 @@ required: - '#address-cells' - '#size-cells' +if: + properties: + compatible: + contains: + enum: + - renesas,r9a09g077-xspi + - renesas,r9a09g087-xspi +then: + properties: + clocks: + maxItems: 2 + clock-names: + maxItems: 2 + resets: + maxItems: 1 + reset-names: + maxItems: 1 +else: + properties: + clocks: + minItems: 4 + clock-names: + minItems: 4 + resets: + minItems: 2 + reset-names: + minItems: 2 + unevaluatedProperties: false examples: From 3fcf9f334d272989b57acc7b94d4eac717206118 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 15 May 2026 12:52:02 +0100 Subject: [PATCH 143/189] memory: renesas-rpc-if: Fix duplicate device name on multi-instance platforms On platforms with multiple xSPI instances, the driver fails to probe additional instances due to duplicate sysfs entries: [ 86.878242] sysfs: cannot create duplicate filename '/bus/platform/devices/rpc-if-spi' This occurs because platform_device_alloc() uses pdev->id for the device ID, which may be PLATFORM_DEVID_NONE (-1) for multiple instances, causing all instances to attempt registration with the same name. Fix this by using PLATFORM_DEVID_AUTO instead, which automatically assigns unique IDs to each device instance, allowing multiple xSPI controllers to coexist without naming conflicts. Signed-off-by: Lad Prabhakar Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/20260515115202.1515577-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/renesas-rpc-if.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index 0fb568456164..3755956ae906 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -1034,7 +1034,7 @@ static int rpcif_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(rpc->spi_clk), "cannot get enabled spi clk\n"); - vdev = platform_device_alloc(name, pdev->id); + vdev = platform_device_alloc(name, PLATFORM_DEVID_AUTO); if (!vdev) return -ENOMEM; vdev->dev.parent = dev; From 61192938a5870ac36edae81e4775b680dcf02c61 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sun, 1 Mar 2026 17:49:39 +0300 Subject: [PATCH 144/189] bus: sunxi-rsb: Always check register address validity The register address was already validated for read operations in regmap_sunxi_rsb_reg_read before being truncated to a u8. Write operations have the same set of possible addresses, and the address is being truncated from u32 to u8 here as well, so the same check is needed. Signed-off-by: Samuel Holland Signed-off-by: Andrey Skvortsov Fixes: d787dcdb9c8f ("bus: sunxi-rsb: Add driver for Allwinner Reduced Serial Bus") Reviewed-by: Chen-Yu Tsai Reviewed-by: Jernej Skrabec Link: https://patch.msgid.link/20260301144939.1832806-1-andrej.skvortzov@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/bus/sunxi-rsb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/bus/sunxi-rsb.c b/drivers/bus/sunxi-rsb.c index b4f2c64ac181..daf0ea563e47 100644 --- a/drivers/bus/sunxi-rsb.c +++ b/drivers/bus/sunxi-rsb.c @@ -445,6 +445,9 @@ static int regmap_sunxi_rsb_reg_write(void *context, unsigned int reg, struct sunxi_rsb_ctx *ctx = context; struct sunxi_rsb_device *rdev = ctx->rdev; + if (reg > 0xff) + return -EINVAL; + return sunxi_rsb_write(rdev->rsb, rdev->rtaddr, reg, &val, ctx->size); } From eaad5f2cb0e3bbbd62c08543fb93ebcfe87c1051 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 4 May 2026 16:43:27 +0200 Subject: [PATCH 145/189] soc: renesas: Identify R-Car R8A779MD M3Le SoC Add support for identifying the R-Car M3Le (R8A779MD) SoC. The Renesas R-Car R8A779MD M3Le SoC is a variant of the already supported R-Car M3-N SoC with reduced peripherals. Enable support for the M3Le SoC through already existing ARCH_R8A77965 configuration symbol. PRR reads 0x67c05501. Reviewed-by: Geert Uytterhoeven Signed-off-by: Marek Vasut Link: https://patch.msgid.link/20260504144534.43745-6-marek.vasut+renesas@mailbox.org Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/renesas-soc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c index dedb2a0be586..18acca73064c 100644 --- a/drivers/soc/renesas/renesas-soc.c +++ b/drivers/soc/renesas/renesas-soc.c @@ -361,6 +361,7 @@ static const struct of_device_id renesas_socs[] __initconst __maybe_unused = { { .compatible = "renesas,r8a77965", .data = &soc_rcar_m3_n }, { .compatible = "renesas,r8a779m4", .data = &soc_rcar_m3_n }, { .compatible = "renesas,r8a779m5", .data = &soc_rcar_m3_n }, + { .compatible = "renesas,r8a779md", .data = &soc_rcar_m3_n }, #endif #ifdef CONFIG_ARCH_R8A77970 { .compatible = "renesas,r8a77970", .data = &soc_rcar_v3m }, From 8ebebccf1579f6ce92bde3ddbb13df12c080f647 Mon Sep 17 00:00:00 2001 From: Ronak Jain Date: Wed, 20 May 2026 02:36:53 -0700 Subject: [PATCH 146/189] Documentation: ABI: add sysfs interface for ZynqMP CSU registers Document the new sysfs interface that exposes Configuration Security Unit (CSU) registers through the zynqmp-firmware driver. The interface is available under: /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/ The CSU registers are discovered at boot time using the PM_QUERY_DATA firmware API. The following registers are currently supported: - multiboot (CSU_MULTI_BOOT) - idcode (CSU_IDCODE, read-only) - pcap-status (CSU_PCAP_STATUS, read-only) Read operations use the existing IOCTL_READ_REG firmware interface, while write operations use IOCTL_MASK_WRITE_REG. Access control is enforced by the firmware. Write attempts to read-only registers are rejected by firmware even though the sysfs file permissions allow writes. Document the ABI entry accordingly. Signed-off-by: Ronak Jain Signed-off-by: Michal Simek Link: https://lore.kernel.org/r/20260520093654.3303917-2-ronak.jain@amd.com --- .../ABI/stable/sysfs-driver-firmware-zynqmp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp index c3fec3c835af..ac8c2314deee 100644 --- a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp +++ b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp @@ -254,3 +254,36 @@ Description: The expected result is 500. Users: Xilinx + +What: /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/* +Date: May 2026 +KernelVersion: 7.2 +Contact: "Ronak Jain" +Description: + Read/Write CSU (Configuration Security Unit) registers. + + This interface provides dynamic access to CSU registers that are + discovered from the firmware at boot time using PM_QUERY_DATA API. + + The supported registers are: + + - multiboot: CSU_MULTI_BOOT register + - idcode: CSU_IDCODE register (read-only) + - pcap-status: CSU_PCAP_STATUS register (read-only) + + Read operations use the existing IOCTL_READ_REG API. + Write operations use the existing IOCTL_MASK_WRITE_REG API. + + The firmware enforces access control - read-only registers will reject + write attempts even though the sysfs permissions show write access. + + Usage for reading:: + + # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot + # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/idcode + + Usage for writing (mask and value are in hexadecimal):: + + # echo 0xFFFFFFF 0x0 > /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot + +Users: Xilinx/AMD From 47d7bca76dd4f36ba0525d761f247c76ec9e4b17 Mon Sep 17 00:00:00 2001 From: Ronak Jain Date: Wed, 20 May 2026 02:36:54 -0700 Subject: [PATCH 147/189] firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface Add support for dynamically discovering and exposing Configuration Security Unit (CSU) registers through sysfs. Leverage the existing PM_QUERY_DATA API to discover available registers at runtime, making the interface flexible and maintainable. Key features: - Dynamic register discovery using PM_QUERY_DATA API * PM_QID_GET_NODE_COUNT: Query number of available registers * PM_QID_GET_NODE_NAME: Query register names by index - Automatic sysfs attribute creation under csu_registers/ group - Read operations via existing IOCTL_READ_REG API - Write operations via existing IOCTL_MASK_WRITE_REG API The sysfs interface is created at: /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/ Currently supported registers include: - multiboot (CSU_MULTI_BOOT) - idcode (CSU_IDCODE, read-only) - pcap-status (CSU_PCAP_STATUS, read-only) The dynamic discovery approach allows firmware to control which registers are exposed without requiring kernel changes, improving maintainability and security. The firmware does not currently expose per-register access mode information, so the kernel cannot distinguish read-only registers from read-write ones at discovery time. All discovered registers are therefore created with sysfs mode 0644, and the firmware is responsible for rejecting writes to registers it treats as read-only (for example idcode and pcap-status); that error is propagated back to userspace from the store callback. If a per-register access-mode query is added to the firmware in the future, sysfs permissions can be tightened to match. CSU register discovery is an optional feature: on firmware that lacks support for PM_QID_GET_NODE_COUNT or PM_QID_GET_NODE_NAME, the probe returns gracefully without exposing any sysfs entries. To keep the memory footprint minimal on that path, partial devm allocations made during discovery are explicitly released on failure so that no memory lingers until device unbind when the feature is unavailable. Signed-off-by: Ronak Jain Signed-off-by: Michal Simek Link: https://lore.kernel.org/r/20260520093654.3303917-3-ronak.jain@amd.com --- MAINTAINERS | 10 + drivers/firmware/xilinx/Makefile | 2 +- drivers/firmware/xilinx/zynqmp-csu-reg.c | 258 +++++++++++++++++++++++ drivers/firmware/xilinx/zynqmp-csu-reg.h | 18 ++ drivers/firmware/xilinx/zynqmp.c | 6 + include/linux/firmware/xlnx-zynqmp.h | 4 +- 6 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.c create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.h diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..d46789a093d9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29237,6 +29237,16 @@ F: drivers/dma/xilinx/xdma.c F: include/linux/dma/amd_xdma.h F: include/linux/platform_data/amd_xdma.h +XILINX ZYNQMP CSU REGISTER DRIVER +M: Senthil Nathan Thangaraj +R: Michal Simek +R: Ronak Jain +L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) +S: Maintained +F: Documentation/ABI/stable/sysfs-driver-firmware-zynqmp +F: drivers/firmware/xilinx/zynqmp-csu-reg.c +F: drivers/firmware/xilinx/zynqmp-csu-reg.h + XILINX ZYNQMP DPDMA DRIVER M: Laurent Pinchart L: dmaengine@vger.kernel.org diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile index 8db0e66b6b7e..6203f41daaa6 100644 --- a/drivers/firmware/xilinx/Makefile +++ b/drivers/firmware/xilinx/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for Xilinx firmwares -obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o +obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o zynqmp-csu-reg.o obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.c b/drivers/firmware/xilinx/zynqmp-csu-reg.c new file mode 100644 index 000000000000..6e11a9b019f7 --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-csu-reg.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Zynq MPSoC CSU Register Access + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + * + * Michal Simek + * Ronak Jain + */ + +#include +#include +#include +#include + +#include "zynqmp-csu-reg.h" + +/* Node ID for CSU module in firmware */ +#define CSU_NODE_ID 0 + +/* Maximum number of CSU registers supported */ +#define MAX_CSU_REGS 50 + +/* Size of register name returned by firmware (3 u32 words = 12 bytes) */ +#define CSU_REG_NAME_LEN 12 + +/** + * struct zynqmp_csu_reg - CSU register information + * @id: Register index from firmware + * @name: Register name + * @attr: Device attribute for sysfs + */ +struct zynqmp_csu_reg { + u32 id; + char name[CSU_REG_NAME_LEN]; + struct device_attribute attr; +}; + +/** + * struct zynqmp_csu_data - Per-device CSU data + * @csu_regs: Array of CSU registers + * @csu_attr_group: Attribute group for sysfs + */ +struct zynqmp_csu_data { + struct zynqmp_csu_reg *csu_regs; + struct attribute_group csu_attr_group; +}; + +/** + * zynqmp_pm_get_node_count() - Get number of supported nodes via QUERY_DATA + * + * Return: Number of nodes on success, or negative error code + */ +static int zynqmp_pm_get_node_count(void) +{ + struct zynqmp_pm_query_data qdata = {0}; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + qdata.qid = PM_QID_GET_NODE_COUNT; + + ret = zynqmp_pm_query_data(qdata, ret_payload); + if (ret) + return ret; + + return ret_payload[1]; +} + +/** + * zynqmp_pm_get_node_name() - Get node name via QUERY_DATA + * @index: Register index + * @name: Buffer to store register name + * + * Return: 0 on success, error code otherwise + */ +static int zynqmp_pm_get_node_name(u32 index, char *name) +{ + struct zynqmp_pm_query_data qdata = {0}; + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + qdata.qid = PM_QID_GET_NODE_NAME; + qdata.arg1 = index; + + ret = zynqmp_pm_query_data(qdata, ret_payload); + if (ret) + return ret; + + memcpy(name, &ret_payload[1], CSU_REG_NAME_LEN); + name[CSU_REG_NAME_LEN - 1] = '\0'; + + return 0; +} + +/** + * zynqmp_csu_reg_show() - Generic show function for all registers + * @dev: Device pointer + * @attr: Device attribute + * @buf: Output buffer + * + * Return: Number of bytes written to buffer, or error code + */ +static ssize_t zynqmp_csu_reg_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct zynqmp_csu_reg *reg; + u32 value; + int ret; + + /* Use container_of to get register directly */ + reg = container_of(attr, struct zynqmp_csu_reg, attr); + + ret = zynqmp_pm_sec_read_reg(CSU_NODE_ID, reg->id, &value); + if (ret) + return ret; + + return sysfs_emit(buf, "0x%08x\n", value); +} + +/** + * zynqmp_csu_reg_store() - Generic store function for writable registers + * @dev: Device pointer + * @attr: Device attribute + * @buf: Input buffer + * @count: Buffer size + * + * Format: "mask value" - both mask and value required + * Example: echo "0xFFFFFFFF 0x12345678" > register + * + * Return: count on success, error code otherwise + */ +static ssize_t zynqmp_csu_reg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct zynqmp_csu_reg *reg; + u32 mask, value; + int ret; + + reg = container_of(attr, struct zynqmp_csu_reg, attr); + + if (sscanf(buf, "%x %x", &mask, &value) != 2) + return -EINVAL; + + ret = zynqmp_pm_sec_mask_write_reg(CSU_NODE_ID, reg->id, mask, value); + if (ret) + return ret; + + return count; +} + +/** + * zynqmp_csu_discover_registers() - Discover CSU registers from firmware + * @pdev: Platform device pointer + * + * This function uses PM_QUERY_DATA to discover all available CSU registers + * and creates sysfs group under /sys/devices/platform/firmware:zynqmp-firmware/ + * + * Return: 0 on success, error code otherwise + */ +int zynqmp_csu_discover_registers(struct platform_device *pdev) +{ + struct zynqmp_csu_data *csu_data; + struct attribute **attrs; + int count, ret, i; + + ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_COUNT); + if (ret) { + dev_dbg(&pdev->dev, "CSU register discovery not supported by current firmware\n"); + return 0; + } + + ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_NAME); + if (ret) { + dev_dbg(&pdev->dev, "CSU register name query not supported by current firmware\n"); + return 0; + } + + count = zynqmp_pm_get_node_count(); + if (count < 0) + return count; + if (count == 0) { + dev_dbg(&pdev->dev, "No nodes available from firmware\n"); + return 0; + } + + /* Validate count to prevent excessive memory allocation */ + if (count > MAX_CSU_REGS) { + dev_err(&pdev->dev, "Register count %d exceeds maximum %d\n", + count, MAX_CSU_REGS); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "Discovered %d nodes from firmware\n", count); + + csu_data = devm_kzalloc(&pdev->dev, sizeof(*csu_data), GFP_KERNEL); + if (!csu_data) + return -ENOMEM; + + csu_data->csu_regs = devm_kcalloc(&pdev->dev, count, sizeof(*csu_data->csu_regs), + GFP_KERNEL); + if (!csu_data->csu_regs) { + devm_kfree(&pdev->dev, csu_data); + return -ENOMEM; + } + + attrs = devm_kcalloc(&pdev->dev, count + 1, sizeof(*attrs), GFP_KERNEL); + if (!attrs) { + devm_kfree(&pdev->dev, csu_data->csu_regs); + devm_kfree(&pdev->dev, csu_data); + return -ENOMEM; + } + + for (i = 0; i < count; i++) { + struct zynqmp_csu_reg *reg = &csu_data->csu_regs[i]; + struct device_attribute *dev_attr = ®->attr; + + reg->id = i; + + ret = zynqmp_pm_get_node_name(i, reg->name); + if (ret) { + dev_warn(&pdev->dev, "Failed to get name for register %d\n", i); + snprintf(reg->name, sizeof(reg->name), "csu_reg_%d", i); + } + + /* + * The firmware does not expose per-register access mode via + * PM_QUERY_DATA today, so the kernel cannot tell read-only + * registers from read-write ones at discovery time. Expose + * every register as 0644 and rely on the firmware to reject + * IOCTL_MASK_WRITE_REG on read-only registers; the error is + * propagated back to userspace from the store callback. + */ + sysfs_attr_init(&dev_attr->attr); + dev_attr->attr.name = reg->name; + dev_attr->attr.mode = 0644; + dev_attr->show = zynqmp_csu_reg_show; + dev_attr->store = zynqmp_csu_reg_store; + + attrs[i] = &dev_attr->attr; + + dev_dbg(&pdev->dev, "Register %d: id=%d name=%s\n", i, reg->id, reg->name); + } + + csu_data->csu_attr_group.name = "csu_registers"; + csu_data->csu_attr_group.attrs = attrs; + + ret = devm_device_add_group(&pdev->dev, &csu_data->csu_attr_group); + if (ret) { + devm_kfree(&pdev->dev, attrs); + devm_kfree(&pdev->dev, csu_data->csu_regs); + devm_kfree(&pdev->dev, csu_data); + } + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_csu_discover_registers); diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.h b/drivers/firmware/xilinx/zynqmp-csu-reg.h new file mode 100644 index 000000000000..b12415db3496 --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-csu-reg.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Xilinx Zynq MPSoC CSU Register Access + * + * Copyright (C) 2026 Advanced Micro Devices, Inc. + * + * Michal Simek + * Ronak Jain + */ + +#ifndef __ZYNQMP_CSU_REG_H__ +#define __ZYNQMP_CSU_REG_H__ + +#include + +int zynqmp_csu_discover_registers(struct platform_device *pdev); + +#endif /* __ZYNQMP_CSU_REG_H__ */ diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index fbe8510f4927..b549d07f7497 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -27,6 +27,7 @@ #include #include +#include "zynqmp-csu-reg.h" #include "zynqmp-debug.h" /* Max HashMap Order for PM API feature check (1<<7 = 128) */ @@ -2120,6 +2121,11 @@ static int zynqmp_firmware_probe(struct platform_device *pdev) dev_err_probe(&pdev->dev, PTR_ERR(em_dev), "EM register fail with error\n"); } + /* Discover CSU registers dynamically */ + ret = zynqmp_csu_discover_registers(pdev); + if (ret) + dev_warn(&pdev->dev, "CSU register discovery failed: %d\n", ret); + return of_platform_populate(dev->of_node, NULL, NULL, dev); } diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h index d70dcd462b44..a4b293eb96ce 100644 --- a/include/linux/firmware/xlnx-zynqmp.h +++ b/include/linux/firmware/xlnx-zynqmp.h @@ -3,7 +3,7 @@ * Xilinx Zynq MPSoC Firmware layer * * Copyright (C) 2014-2021 Xilinx - * Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc. + * Copyright (C) 2022 - 2026 Advanced Micro Devices, Inc. * * Michal Simek * Davorin Mista @@ -262,6 +262,8 @@ enum pm_query_id { PM_QID_CLOCK_GET_NUM_CLOCKS = 12, PM_QID_CLOCK_GET_MAX_DIVISOR = 13, PM_QID_PINCTRL_GET_ATTRIBUTES = 15, + PM_QID_GET_NODE_NAME = 16, + PM_QID_GET_NODE_COUNT = 17, }; enum rpu_oper_mode { From e9f06763b9b88cb1338ef8a3f113b0fb94d714dd Mon Sep 17 00:00:00 2001 From: "Thomas Richard (TI)" Date: Tue, 19 May 2026 17:06:56 +0200 Subject: [PATCH 148/189] firmware: ti_sci: Add BOARDCFG_MANAGED mode support In BOARDCFG_MANAGED mode, the low power mode configuration is done statically for the DM via the boardcfg. Constraints are not supported, and prepare_sleep() is not needed. Reviewed-by: Dhruva Gole Reviewed-by: Kendall Willis Signed-off-by: Thomas Richard (TI) Link: https://patch.msgid.link/20260519-ti-sci-jacinto-s2r-restore-irq-v9-1-c550a8ae0f31@bootlin.com Signed-off-by: Nishanth Menon --- drivers/firmware/ti_sci.c | 10 +++++++--- drivers/firmware/ti_sci.h | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index e027a2bd8f26..a8dcdbe874b6 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -3770,8 +3770,11 @@ static int ti_sci_prepare_system_suspend(struct ti_sci_info *info) return ti_sci_cmd_prepare_sleep(&info->handle, TISCI_MSG_VALUE_SLEEP_MODE_DM_MANAGED, 0, 0, 0); + } else if (info->fw_caps & MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED) { + /* Nothing to do in the BOARDCFG_MANAGED mode */ + return 0; } else { - /* DM Managed is not supported by the firmware. */ + /* DM Managed and BoardCfg Managed are not supported by the firmware. */ dev_err(info->dev, "Suspend to memory is not supported by the firmware\n"); return -EOPNOTSUPP; } @@ -4009,12 +4012,13 @@ static int ti_sci_probe(struct platform_device *pdev) } ti_sci_msg_cmd_query_fw_caps(&info->handle, &info->fw_caps); - dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s\n", + dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s%s\n", info->fw_caps & MSG_FLAG_CAPS_GENERIC ? "Generic" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_PARTIAL_IO ? " Partial-IO" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED ? " DM-Managed" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "", - info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION ? " IO-Isolation" : "" + info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION ? " IO-Isolation" : "", + info->fw_caps & MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED ? " BoardConfig-Managed" : "" ); ti_sci_setup_ops(info); diff --git a/drivers/firmware/ti_sci.h b/drivers/firmware/ti_sci.h index 4616127e33ff..d5e06769b01c 100644 --- a/drivers/firmware/ti_sci.h +++ b/drivers/firmware/ti_sci.h @@ -150,6 +150,8 @@ struct ti_sci_msg_req_reboot { * MSG_FLAG_CAPS_LPM_DM_MANAGED: LPM can be managed by DM * MSG_FLAG_CAPS_LPM_ABORT: Abort entry to LPM * MSG_FLAG_CAPS_IO_ISOLATION: IO Isolation support + * MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED: LPM config done statically + * for the DM via boardcfg * * Response to a generic message with message type TI_SCI_MSG_QUERY_FW_CAPS * providing currently available SOC/firmware capabilities. SoC that don't @@ -162,6 +164,7 @@ struct ti_sci_msg_resp_query_fw_caps { #define MSG_FLAG_CAPS_LPM_DM_MANAGED TI_SCI_MSG_FLAG(5) #define MSG_FLAG_CAPS_LPM_ABORT TI_SCI_MSG_FLAG(9) #define MSG_FLAG_CAPS_IO_ISOLATION TI_SCI_MSG_FLAG(7) +#define MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED TI_SCI_MSG_FLAG(12) #define MSG_MASK_CAPS_LPM GENMASK_ULL(4, 1) u64 fw_caps; } __packed; From 703b341fdd875c2d9c30a835021689e5a430a39a Mon Sep 17 00:00:00 2001 From: "Thomas Richard (TI)" Date: Tue, 19 May 2026 17:06:57 +0200 Subject: [PATCH 149/189] firmware: ti_sci: Add support for restoring IRQs during resume Some DM-Firmware are not able to restore the IRQ context after a suspend-resume. The IRQ_CONTEXT_LOST firmware capability has been introduced to identify this characteristic. In this case the responsibility is delegated to the ti_sci driver, which maintains an internal list of all requested IRQs. This list is updated on each set()/free() operation, and all IRQs are restored during the resume_noirq() phase. Reviewed-by: Dhruva Gole Reviewed-by: Kendall Willis Signed-off-by: Thomas Richard (TI) Link: https://patch.msgid.link/20260519-ti-sci-jacinto-s2r-restore-irq-v9-2-c550a8ae0f31@bootlin.com Signed-off-by: Nishanth Menon --- drivers/firmware/ti_sci.c | 190 +++++++++++++++++++++++++++++++++++--- drivers/firmware/ti_sci.h | 3 + 2 files changed, 181 insertions(+), 12 deletions(-) diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index a8dcdbe874b6..f6bffeae30f5 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -12,11 +12,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -87,6 +89,16 @@ struct ti_sci_desc { int max_msg_size; }; +/** + * struct ti_sci_irq - Description of allocated irqs + * @node: Link to hash table + * @desc: Description of the irq + */ +struct ti_sci_irq { + struct hlist_node node; + struct ti_sci_msg_req_manage_irq desc; +}; + /** * struct ti_sci_info - Structure representing a TI SCI instance * @dev: Device pointer @@ -101,6 +113,8 @@ struct ti_sci_desc { * @chan_rx: Receive mailbox channel * @minfo: Message info * @node: list head + * @irqs: List of allocated irqs + * @irq_lock: Protection for irq hash list * @host_id: Host ID * @fw_caps: FW/SoC low power capabilities * @users: Number of users of this instance @@ -118,6 +132,8 @@ struct ti_sci_info { struct mbox_chan *chan_rx; struct ti_sci_xfers_info minfo; struct list_head node; + DECLARE_HASHTABLE(irqs, 8); + struct mutex irq_lock; u8 host_id; u64 fw_caps; /* protected by ti_sci_list_mutex */ @@ -2299,6 +2315,32 @@ static int ti_sci_manage_irq(const struct ti_sci_handle *handle, return ret; } +/** + * ti_sci_irq_hash() - Helper API to compute irq hash for the hash table. + * @irq: irq to hash + * + * Return: the computed hash value. + */ +static int ti_sci_irq_hash(struct ti_sci_msg_req_manage_irq *irq) +{ + return irq->src_id ^ irq->src_index; +} + +/** + * ti_sci_irq_equal() - Helper API to compare two irqs (generic headers are not + * compared) + * @irq_a: irq_a to compare + * @irq_b: irq_b to compare + * + * Return: true if the two irqs are equal, else false. + */ +static bool ti_sci_irq_equal(struct ti_sci_msg_req_manage_irq *irq_a, + struct ti_sci_msg_req_manage_irq *irq_b) +{ + return !memcmp(&irq_a->valid_params, &irq_b->valid_params, + sizeof(*irq_a) - sizeof(irq_a->hdr)); +} + /** * ti_sci_set_irq() - Helper api to configure the irq route between the * requested source and destination @@ -2322,15 +2364,53 @@ static int ti_sci_set_irq(const struct ti_sci_handle *handle, u32 valid_params, u16 dst_host_irq, u16 ia_id, u16 vint, u16 global_event, u8 vint_status_bit, u8 s_host) { + struct ti_sci_info *info = handle_to_ti_sci_info(handle); + struct ti_sci_msg_req_manage_irq *desc; + struct ti_sci_irq *irq; + int ret; + + /* Lock for set_irq() and free_irq() to keep the IRQ hash list consistent */ + guard(mutex)(&info->irq_lock); + pr_debug("%s: IRQ set with valid_params = 0x%x from src = %d, index = %d, to dst = %d, irq = %d,via ia_id = %d, vint = %d, global event = %d,status_bit = %d\n", __func__, valid_params, src_id, src_index, dst_id, dst_host_irq, ia_id, vint, global_event, vint_status_bit); - return ti_sci_manage_irq(handle, valid_params, src_id, src_index, - dst_id, dst_host_irq, ia_id, vint, - global_event, vint_status_bit, s_host, - TI_SCI_MSG_SET_IRQ); + ret = ti_sci_manage_irq(handle, valid_params, src_id, src_index, + dst_id, dst_host_irq, ia_id, vint, + global_event, vint_status_bit, s_host, + TI_SCI_MSG_SET_IRQ); + + if (ret || !(info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST)) + goto end; + + irq = kzalloc_obj(*irq, GFP_KERNEL); + if (!irq) { + ti_sci_manage_irq(handle, valid_params, src_id, src_index, + dst_id, dst_host_irq, ia_id, vint, + global_event, vint_status_bit, s_host, + TI_SCI_MSG_FREE_IRQ); + ret = -ENOMEM; + goto end; + } + + desc = &irq->desc; + desc->valid_params = valid_params; + desc->src_id = src_id; + desc->src_index = src_index; + desc->dst_id = dst_id; + desc->dst_host_irq = dst_host_irq; + desc->ia_id = ia_id; + desc->vint = vint; + desc->global_event = global_event; + desc->vint_status_bit = vint_status_bit; + desc->secondary_host = s_host; + + hash_add(info->irqs, &irq->node, ti_sci_irq_hash(desc)); + +end: + return ret; } /** @@ -2356,15 +2436,53 @@ static int ti_sci_free_irq(const struct ti_sci_handle *handle, u32 valid_params, u16 dst_host_irq, u16 ia_id, u16 vint, u16 global_event, u8 vint_status_bit, u8 s_host) { + struct ti_sci_info *info = handle_to_ti_sci_info(handle); + struct ti_sci_msg_req_manage_irq irq_desc; + struct device *dev = info->dev; + struct ti_sci_irq *this_irq; + struct hlist_node *tmp_node; + int ret; + + guard(mutex)(&info->irq_lock); + pr_debug("%s: IRQ release with valid_params = 0x%x from src = %d, index = %d, to dst = %d, irq = %d,via ia_id = %d, vint = %d, global event = %d,status_bit = %d\n", __func__, valid_params, src_id, src_index, dst_id, dst_host_irq, ia_id, vint, global_event, vint_status_bit); - return ti_sci_manage_irq(handle, valid_params, src_id, src_index, - dst_id, dst_host_irq, ia_id, vint, - global_event, vint_status_bit, s_host, - TI_SCI_MSG_FREE_IRQ); + ret = ti_sci_manage_irq(handle, valid_params, src_id, src_index, + dst_id, dst_host_irq, ia_id, vint, + global_event, vint_status_bit, s_host, + TI_SCI_MSG_FREE_IRQ); + + if (ret || !(info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST)) + goto end; + + irq_desc.valid_params = valid_params; + irq_desc.src_id = src_id; + irq_desc.src_index = src_index; + irq_desc.dst_id = dst_id; + irq_desc.dst_host_irq = dst_host_irq; + irq_desc.ia_id = ia_id; + irq_desc.vint = vint; + irq_desc.global_event = global_event; + irq_desc.vint_status_bit = vint_status_bit; + irq_desc.secondary_host = s_host; + + hash_for_each_possible_safe(info->irqs, this_irq, tmp_node, node, + ti_sci_irq_hash(&irq_desc)) { + if (ti_sci_irq_equal(&irq_desc, &this_irq->desc)) { + hlist_del(&this_irq->node); + kfree(this_irq); + goto end; + } + } + + dev_warn(dev, "%s: should not be here, IRQ was not found in hash list\n", + __func__); + +end: + return ret; } /** @@ -3845,7 +3963,10 @@ static int ti_sci_suspend_noirq(struct device *dev) static int ti_sci_resume_noirq(struct device *dev) { struct ti_sci_info *info = dev_get_drvdata(dev); - int ret = 0; + struct ti_sci_msg_req_manage_irq *irq_desc; + struct ti_sci_irq *irq; + struct hlist_node *tmp_node; + int ret = 0, err = 0, i; u32 source; u64 time; u8 pin; @@ -3857,13 +3978,50 @@ static int ti_sci_resume_noirq(struct device *dev) return ret; } + switch (pm_suspend_target_state) { + case PM_SUSPEND_MEM: + if (info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST) { + hash_for_each_safe(info->irqs, i, tmp_node, irq, node) { + irq_desc = &irq->desc; + ret = ti_sci_manage_irq(&info->handle, + irq_desc->valid_params, + irq_desc->src_id, + irq_desc->src_index, + irq_desc->dst_id, + irq_desc->dst_host_irq, + irq_desc->ia_id, + irq_desc->vint, + irq_desc->global_event, + irq_desc->vint_status_bit, + irq_desc->secondary_host, + TI_SCI_MSG_SET_IRQ); + if (ret) { + dev_err(dev, "failed to restore IRQ with valid_params = 0x%x from src = %d, index = %d, to dst = %d, irq = %d, via ia_id = %d, vint = %d, global event = %d,status_bit = %d\n", + irq_desc->valid_params, + irq_desc->src_id, + irq_desc->src_index, + irq_desc->dst_id, + irq_desc->dst_host_irq, + irq_desc->ia_id, + irq_desc->vint, + irq_desc->global_event, + irq_desc->vint_status_bit); + err = ret; + } + } + } + break; + default: + break; + } + ret = ti_sci_msg_cmd_lpm_wake_reason(&info->handle, &source, &time, &pin, &mode); /* Do not fail to resume on error as the wake reason is not critical */ if (!ret) dev_info(dev, "ti_sci: wakeup source:0x%x, pin:0x%x, mode:0x%x\n", source, pin, mode); - return 0; + return err; } static void ti_sci_pm_complete(struct device *dev) @@ -4012,13 +4170,14 @@ static int ti_sci_probe(struct platform_device *pdev) } ti_sci_msg_cmd_query_fw_caps(&info->handle, &info->fw_caps); - dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s%s\n", + dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s%s%s\n", info->fw_caps & MSG_FLAG_CAPS_GENERIC ? "Generic" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_PARTIAL_IO ? " Partial-IO" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED ? " DM-Managed" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "", info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION ? " IO-Isolation" : "", - info->fw_caps & MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED ? " BoardConfig-Managed" : "" + info->fw_caps & MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED ? " BoardConfig-Managed" : "", + info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST ? " IRQ-Context-Lost" : "" ); ti_sci_setup_ops(info); @@ -4051,6 +4210,13 @@ static int ti_sci_probe(struct platform_device *pdev) list_add_tail(&info->node, &ti_sci_list); mutex_unlock(&ti_sci_list_mutex); + ret = devm_mutex_init(dev, &info->irq_lock); + if (ret) + goto out; + + if (info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST) + hash_init(info->irqs); + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); if (ret) { dev_err(dev, "platform_populate failed %pe\n", ERR_PTR(ret)); diff --git a/drivers/firmware/ti_sci.h b/drivers/firmware/ti_sci.h index d5e06769b01c..ad69c765d614 100644 --- a/drivers/firmware/ti_sci.h +++ b/drivers/firmware/ti_sci.h @@ -152,6 +152,8 @@ struct ti_sci_msg_req_reboot { * MSG_FLAG_CAPS_IO_ISOLATION: IO Isolation support * MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED: LPM config done statically * for the DM via boardcfg + * MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST: DM is not able to restore IRQ + * context * * Response to a generic message with message type TI_SCI_MSG_QUERY_FW_CAPS * providing currently available SOC/firmware capabilities. SoC that don't @@ -165,6 +167,7 @@ struct ti_sci_msg_resp_query_fw_caps { #define MSG_FLAG_CAPS_LPM_ABORT TI_SCI_MSG_FLAG(9) #define MSG_FLAG_CAPS_IO_ISOLATION TI_SCI_MSG_FLAG(7) #define MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED TI_SCI_MSG_FLAG(12) +#define MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST TI_SCI_MSG_FLAG(14) #define MSG_MASK_CAPS_LPM GENMASK_ULL(4, 1) u64 fw_caps; } __packed; From 3c5912373340cb79db6585f0fcd8107198d4933d Mon Sep 17 00:00:00 2001 From: "Thomas Richard (TI)" Date: Tue, 19 May 2026 17:06:58 +0200 Subject: [PATCH 150/189] clk: keystone: sci-clk: Add restore_context() operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the restore_context() operation to restore the clock rate and the clock parent state. The clock rate is saved in sci_clk struct during set_rate() and recalc_rate() operations. The parent index is saved in sci_clk struct during set_parent() operation. During clock registration, the core retrieves each clock’s parent using get_parent() operation to ensure the internal clock tree reflects the actual hardware state, including any configurations made by the bootloader. So we also save the parent index in get_parent(). Reviewed-by: Dhruva Gole Reviewed-by: Kendall Willis Acked-by: Stephen Boyd Reviewed-by: Brian Masney Signed-off-by: Thomas Richard (TI) Link: https://patch.msgid.link/20260519-ti-sci-jacinto-s2r-restore-irq-v9-3-c550a8ae0f31@bootlin.com Signed-off-by: Nishanth Menon --- drivers/clk/keystone/sci-clk.c | 45 ++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/drivers/clk/keystone/sci-clk.c b/drivers/clk/keystone/sci-clk.c index 9d5071223f4c..7c0f7b3e89e0 100644 --- a/drivers/clk/keystone/sci-clk.c +++ b/drivers/clk/keystone/sci-clk.c @@ -47,6 +47,8 @@ struct sci_clk_provider { * @node: Link for handling clocks probed via DT * @cached_req: Cached requested freq for determine rate calls * @cached_res: Cached result freq for determine rate calls + * @parent_id: Parent index for this clock + * @rate: Clock rate */ struct sci_clk { struct clk_hw hw; @@ -58,6 +60,8 @@ struct sci_clk { struct list_head node; unsigned long cached_req; unsigned long cached_res; + int parent_id; + unsigned long rate; }; #define to_sci_clk(_hw) container_of(_hw, struct sci_clk, hw) @@ -150,6 +154,8 @@ static unsigned long sci_clk_recalc_rate(struct clk_hw *hw, return 0; } + clk->rate = freq; + return freq; } @@ -210,10 +216,15 @@ static int sci_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct sci_clk *clk = to_sci_clk(hw); + int ret; - return clk->provider->ops->set_freq(clk->provider->sci, clk->dev_id, - clk->clk_id, rate / 10 * 9, rate, - rate / 10 * 11); + ret = clk->provider->ops->set_freq(clk->provider->sci, clk->dev_id, + clk->clk_id, rate / 10 * 9, rate, + rate / 10 * 11); + if (!ret) + clk->rate = rate; + + return ret; } /** @@ -234,12 +245,13 @@ static u8 sci_clk_get_parent(struct clk_hw *hw) dev_err(clk->provider->dev, "get-parent failed for dev=%d, clk=%d, ret=%d\n", clk->dev_id, clk->clk_id, ret); + clk->parent_id = ret; return 0; } - parent_id = parent_id - clk->clk_id - 1; + clk->parent_id = parent_id - clk->clk_id - 1; - return (u8)parent_id; + return (u8)clk->parent_id; } /** @@ -252,12 +264,28 @@ static u8 sci_clk_get_parent(struct clk_hw *hw) static int sci_clk_set_parent(struct clk_hw *hw, u8 index) { struct sci_clk *clk = to_sci_clk(hw); + int ret; clk->cached_req = 0; - return clk->provider->ops->set_parent(clk->provider->sci, clk->dev_id, - clk->clk_id, - index + 1 + clk->clk_id); + ret = clk->provider->ops->set_parent(clk->provider->sci, clk->dev_id, + clk->clk_id, + index + 1 + clk->clk_id); + if (!ret) + clk->parent_id = index; + + return ret; +} + +static void sci_clk_restore_context(struct clk_hw *hw) +{ + struct sci_clk *clk = to_sci_clk(hw); + + if (clk->num_parents > 1 && clk->parent_id >= 0) + sci_clk_set_parent(hw, (u8)clk->parent_id); + + if (clk->rate) + sci_clk_set_rate(hw, clk->rate, 0); } static const struct clk_ops sci_clk_ops = { @@ -269,6 +297,7 @@ static const struct clk_ops sci_clk_ops = { .set_rate = sci_clk_set_rate, .get_parent = sci_clk_get_parent, .set_parent = sci_clk_set_parent, + .restore_context = sci_clk_restore_context, }; /** From dc51f820bd32415445655aa6e3af9011b9e3e0f3 Mon Sep 17 00:00:00 2001 From: "Thomas Richard (TI)" Date: Tue, 19 May 2026 17:06:59 +0200 Subject: [PATCH 151/189] firmware: ti_sci: Add support for restoring clock context during resume Some DM-Firmware are not able to restore the clock rates and the clock parents after a suspend-resume. The CLK_CONTEXT_LOST firmware capability has been introduced to identify this characteristic. In this case the responsibility is therefore delegated to the ti_sci driver, which uses clk_restore_context() to trigger the context_restore() operation for all registered clocks, including those managed by the sci-clk. The sci-clk driver implements the context_restore() operation to ensure rates and clock parents are correctly restored. Reviewed-by: Dhruva Gole Reviewed-by: Kendall Willis Signed-off-by: Thomas Richard (TI) Link: https://patch.msgid.link/20260519-ti-sci-jacinto-s2r-restore-irq-v9-4-c550a8ae0f31@bootlin.com Signed-off-by: Nishanth Menon --- drivers/firmware/ti_sci.c | 9 +++++++-- drivers/firmware/ti_sci.h | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index f6bffeae30f5..590a464403c5 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -9,6 +9,7 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ #include +#include #include #include #include @@ -4010,6 +4011,9 @@ static int ti_sci_resume_noirq(struct device *dev) } } } + + if (info->fw_caps & MSG_FLAG_CAPS_LPM_CLK_CONTEXT_LOST) + clk_restore_context(); break; default: break; @@ -4170,14 +4174,15 @@ static int ti_sci_probe(struct platform_device *pdev) } ti_sci_msg_cmd_query_fw_caps(&info->handle, &info->fw_caps); - dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s%s%s\n", + dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s%s%s%s\n", info->fw_caps & MSG_FLAG_CAPS_GENERIC ? "Generic" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_PARTIAL_IO ? " Partial-IO" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED ? " DM-Managed" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "", info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION ? " IO-Isolation" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED ? " BoardConfig-Managed" : "", - info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST ? " IRQ-Context-Lost" : "" + info->fw_caps & MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST ? " IRQ-Context-Lost" : "", + info->fw_caps & MSG_FLAG_CAPS_LPM_CLK_CONTEXT_LOST ? " Clk-Context-Lost" : "" ); ti_sci_setup_ops(info); diff --git a/drivers/firmware/ti_sci.h b/drivers/firmware/ti_sci.h index ad69c765d614..8fccbcd1c9a2 100644 --- a/drivers/firmware/ti_sci.h +++ b/drivers/firmware/ti_sci.h @@ -154,6 +154,8 @@ struct ti_sci_msg_req_reboot { * for the DM via boardcfg * MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST: DM is not able to restore IRQ * context + * MSG_FLAG_CAPS_LPM_CLK_CONTEXT_LOST: DM is not able to restore + * Clock context * * Response to a generic message with message type TI_SCI_MSG_QUERY_FW_CAPS * providing currently available SOC/firmware capabilities. SoC that don't @@ -168,6 +170,7 @@ struct ti_sci_msg_resp_query_fw_caps { #define MSG_FLAG_CAPS_IO_ISOLATION TI_SCI_MSG_FLAG(7) #define MSG_FLAG_CAPS_LPM_BOARDCFG_MANAGED TI_SCI_MSG_FLAG(12) #define MSG_FLAG_CAPS_LPM_IRQ_CONTEXT_LOST TI_SCI_MSG_FLAG(14) +#define MSG_FLAG_CAPS_LPM_CLK_CONTEXT_LOST TI_SCI_MSG_FLAG(15) #define MSG_MASK_CAPS_LPM GENMASK_ULL(4, 1) u64 fw_caps; } __packed; From 18706ea68fc4344049bf693b702cb311a7c27ca7 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 26 May 2026 11:36:49 +0100 Subject: [PATCH 152/189] firmware: arm_ffa: Treat missing FF-A feature on a platform as a probe miss When FF-A initialisation is driven from a platform device probe, systems that do not implement FF-A can return -EOPNOTSUPP from the early transport or version discovery paths. Driver core treats that as a matched probe failure and prints: | arm-ffa arm-ffa: probe with driver arm-ffa failed with error -95 That is noisy for a firmware interface that can be absent on otherwise valid systems. Driver core already treats -ENODEV and -ENXIO as quiet rejected matches, so translate only the early unsupported discovery cases to -ENODEV. Keep later setup failures unchanged so real FF-A initialisation problems are still reported as probe failures. Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/all/20260523001148.GA1319283@ax162 Reviewed-by: Yeoreum Yun Tested-by: Nathan Chancellor Link: https://patch.msgid.link/20260526103649.5684-1-sudeep.holla@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 54984e1b9741..0f468362c288 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -2109,7 +2109,7 @@ static int ffa_probe(struct platform_device *pdev) ret = ffa_transport_init(&invoke_ffa_fn); if (ret) - return ret; + return ret == -EOPNOTSUPP ? -ENODEV : ret; drv_info = kzalloc_obj(*drv_info); if (!drv_info) @@ -2117,8 +2117,11 @@ static int ffa_probe(struct platform_device *pdev) platform_set_drvdata(pdev, drv_info); ret = ffa_version_check(&drv_info->version); - if (ret) + if (ret) { + if (ret == -EOPNOTSUPP) + ret = -ENODEV; goto free_drv_info; + } if (ffa_id_get(&drv_info->vm_id)) { pr_err("failed to obtain VM id for self\n"); From c3ce114d81e39cd83f7bf9c5c46020bb7695b017 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Mon, 18 May 2026 18:13:04 +0530 Subject: [PATCH 153/189] memory: tegra264: Skip clients without bpmp_id or type Some MC clients are present in tegra264_mc_clients[] only for fault-log naming and have no .bpmp_id or .type assigned. Skip forwarding bandwidth requests to BPMP for such clients in tegra264_mc_icc_set(). Signed-off-by: Sumit Gupta Link: https://patch.msgid.link/20260518124306.2071481-2-sumitg@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra264.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/memory/tegra/tegra264.c b/drivers/memory/tegra/tegra264.c index e43ef14da1ee..e62890841725 100644 --- a/drivers/memory/tegra/tegra264.c +++ b/drivers/memory/tegra/tegra264.c @@ -262,6 +262,10 @@ static int tegra264_mc_icc_set(struct icc_node *src, struct icc_node *dst) return -ENOENT; } + /* Skip forwarding bw requests to BPMP from clients without bpmp_id/type. */ + if (pclient->type == TEGRA_ICC_NONE || !pclient->bpmp_id) + return 0; + if (pclient->type == TEGRA_ICC_NISO) bwmgr_req.bwmgr_calc_set_req.niso_bw = src->avg_bw; else From 16868fd4b7ecf241b74123e1bb23fd08d05a2e09 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Mon, 18 May 2026 18:13:05 +0530 Subject: [PATCH 154/189] dt-bindings: memory: tegra264: Add full set of MC client IDs Add the complete set of TEGRA264_MEMORY_CLIENT_* IDs exposed by the Tegra264 MC. Signed-off-by: Sumit Gupta Link: https://patch.msgid.link/20260518124306.2071481-3-sumitg@nvidia.com Signed-off-by: Krzysztof Kozlowski --- include/dt-bindings/memory/nvidia,tegra264.h | 287 +++++++++++++++++++ 1 file changed, 287 insertions(+) diff --git a/include/dt-bindings/memory/nvidia,tegra264.h b/include/dt-bindings/memory/nvidia,tegra264.h index 521405c01f84..c65403a76413 100644 --- a/include/dt-bindings/memory/nvidia,tegra264.h +++ b/include/dt-bindings/memory/nvidia,tegra264.h @@ -58,24 +58,108 @@ * memory client IDs */ +/* PTW read client mapped to SOC SMMU0 */ +#define TEGRA264_MEMORY_CLIENT_PTCR 0x00 /* HOST1X read client */ #define TEGRA264_MEMORY_CLIENT_HOST1XR 0x16 +#define TEGRA264_MEMORY_CLIENT_MPCORER 0x27 +/* Platform security (PSC) Read clients */ +#define TEGRA264_MEMORY_CLIENT_PSCR 0x33 +/* PSC Write clients */ +#define TEGRA264_MEMORY_CLIENT_PSCW 0x34 +/* ISP0 Read client */ +#define TEGRA264_MEMORY_CLIENT_ISP0R 0x37 +#define TEGRA264_MEMORY_CLIENT_MPCOREW 0x39 +/* ISP0 Write client */ +#define TEGRA264_MEMORY_CLIENT_ISP0W 0x44 +/* ISP1 Write client */ +#define TEGRA264_MEMORY_CLIENT_ISP1W 0x45 +/* ISP FALCON Read client */ +#define TEGRA264_MEMORY_CLIENT_ISPFALCONR 0x47 +/* ISP FALCON Write client */ +#define TEGRA264_MEMORY_CLIENT_ISPFALCONW 0x4f +/* MGBE2 Read mccif */ +#define TEGRA264_MEMORY_CLIENT_MGBE2R 0x5c +#define TEGRA264_MEMORY_CLIENT_OFAR2MC 0x5d +#define TEGRA264_MEMORY_CLIENT_OFAW2MC 0x5e +/* MGBE2 Write mccif */ +#define TEGRA264_MEMORY_CLIENT_MGBE2W 0x5f +/* MGBE3 Read mccif */ +#define TEGRA264_MEMORY_CLIENT_MGBE3R 0x61 +/* MGBE3 Write mccif */ +#define TEGRA264_MEMORY_CLIENT_MGBE3W 0x65 +/* SEU1 Memory Read Client */ +#define TEGRA264_MEMORY_CLIENT_SEU1RD 0x68 +/* SEU1 Memory Write Client */ +#define TEGRA264_MEMORY_CLIENT_SEU1WR 0x69 /* VIC read client */ #define TEGRA264_MEMORY_CLIENT_VICR 0x6c /* VIC Write client */ #define TEGRA264_MEMORY_CLIENT_VICW 0x6d /* VI R5 Write client */ #define TEGRA264_MEMORY_CLIENT_VIW 0x72 +/* QSPI Read Client */ +#define TEGRA264_MEMORY_CLIENT_XSPI0R 0x75 +/* QSPI Write Client */ +#define TEGRA264_MEMORY_CLIENT_XSPI0W 0x76 #define TEGRA264_MEMORY_CLIENT_NVDECSRD2MC 0x78 #define TEGRA264_MEMORY_CLIENT_NVDECSWR2MC 0x79 /* Audio processor(APE) Read client */ #define TEGRA264_MEMORY_CLIENT_APER 0x7a /* Audio processor(APE) Write client */ #define TEGRA264_MEMORY_CLIENT_APEW 0x7b +/* SEU0 read client */ +#define TEGRA264_MEMORY_CLIENT_SER 0x80 +/* SEU0 write client */ +#define TEGRA264_MEMORY_CLIENT_SEW 0x81 +/* AXI AP and DFD/Coresight1-AUX0/1 Read clients both share the same interface on MSS */ +#define TEGRA264_MEMORY_CLIENT_AXIAPR 0x82 +/* AXI AP and DFD/Coresight1-AUX0/1 Write clients both share the same interface on MSS */ +#define TEGRA264_MEMORY_CLIENT_AXIAPW 0x83 +/* ETR or DFD/Coresight0 Read Client */ +#define TEGRA264_MEMORY_CLIENT_ETRR 0x84 +/* ETR or DFD/Coresight0 Write Client */ +#define TEGRA264_MEMORY_CLIENT_ETRW 0x85 +/* Security(tsec) Read client */ +#define TEGRA264_MEMORY_CLIENT_TSECR 0x86 +/* Security(tsec) Write client */ +#define TEGRA264_MEMORY_CLIENT_TSECW 0x87 +/* BPMP read client */ +#define TEGRA264_MEMORY_CLIENT_BPMPR 0x93 +/* BPMP write client */ +#define TEGRA264_MEMORY_CLIENT_BPMPW 0x94 +/* AON Read Client */ +#define TEGRA264_MEMORY_CLIENT_AONR 0x97 +/* AON write client */ +#define TEGRA264_MEMORY_CLIENT_AONW 0x98 +/* GPCDMA debug Read client */ +#define TEGRA264_MEMORY_CLIENT_GPCDMAR 0x99 +/* GPCDMA debug Write client */ +#define TEGRA264_MEMORY_CLIENT_GPCDMAW 0x9a /* Audio DMA Read client */ #define TEGRA264_MEMORY_CLIENT_APEDMAR 0x9f /* Audio DMA Write client */ #define TEGRA264_MEMORY_CLIENT_APEDMAW 0xa0 +/* mss internal memqual MIU0 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU0R 0xa6 +/* mss internal memqual MIU0 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU0W 0xa7 +/* mss internal memqual MIU1 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU1R 0xa8 +/* mss internal memqual MIU1 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU1W 0xa9 +/* mss internal memqual MIU2 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU2R 0xae +/* mss internal memqual MIU2 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU2W 0xaf +/* mss internal memqual MIU3 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU3R 0xb0 +/* mss internal memqual MIU3 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU3W 0xb1 +/* mss internal memqual MIU4 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU4R 0xb2 +/* mss internal memqual MIU4 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU4W 0xb3 #define TEGRA264_MEMORY_CLIENT_GPUR02MC 0xb6 #define TEGRA264_MEMORY_CLIENT_GPUW02MC 0xb7 /* VI Falcon Read client */ @@ -86,6 +170,8 @@ #define TEGRA264_MEMORY_CLIENT_RCER 0xd2 /* Write client of RCE */ #define TEGRA264_MEMORY_CLIENT_RCEW 0xd3 +#define TEGRA264_MEMORY_CLIENT_NVENC1SRD2MC 0xd6 +#define TEGRA264_MEMORY_CLIENT_NVENC1SWR2MC 0xd7 /* PCIE0/MSI Write clients */ #define TEGRA264_MEMORY_CLIENT_PCIE0W 0xd9 /* PCIE1/RPX4 Read clients */ @@ -108,16 +194,140 @@ #define TEGRA264_MEMORY_CLIENT_PCIE5R 0xe2 /* PCIE5/DMX4 Write clients */ #define TEGRA264_MEMORY_CLIENT_PCIE5W 0xe3 +/* mss internal memqual MIU5 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU5R 0xfc +/* mss internal memqual MIU5 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU5W 0xfd +/* mss internal memqual MIU6 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU6W 0xff +#define TEGRA264_MEMORY_CLIENT_RISTR 0x100 +#define TEGRA264_MEMORY_CLIENT_RISTW 0x101 +/* OESP (Pluton) Read client */ +#define TEGRA264_MEMORY_CLIENT_OESPR 0x102 +/* OESP (Pluton) Write client */ +#define TEGRA264_MEMORY_CLIENT_OESPW 0x103 +/* mss internal memqual MIU7 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU7W 0x105 +/* mss internal memqual MIU8 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU8R 0x106 +/* mss internal memqual MIU8 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU8W 0x107 +/* mss internal memqual MIU9 reads */ +#define TEGRA264_MEMORY_CLIENT_MIU9R 0x108 +/* mss internal memqual MIU9 writes */ +#define TEGRA264_MEMORY_CLIENT_MIU9W 0x109 +/* HWPM Write Interface */ +#define TEGRA264_MEMORY_CLIENT_PMA0AWR 0x122 +#define TEGRA264_MEMORY_CLIENT_NVJPG1SRD2MC 0x123 +#define TEGRA264_MEMORY_CLIENT_NVJPG1SWR2MC 0x124 +/* CTW read client mapped to SMMU0 */ +#define TEGRA264_MEMORY_CLIENT_SMMU0CTWR 0x12e +/* CMDQV read client mapped to SMMU0 */ +#define TEGRA264_MEMORY_CLIENT_SMMU0CMDQVR 0x12f +/* CMDQV write client mapped to SMMU0 */ +#define TEGRA264_MEMORY_CLIENT_SMMU0CMDQVW 0x130 +/* EVNTQ write client mapped to SMMU0 */ +#define TEGRA264_MEMORY_CLIENT_SMMU0EVNTQW 0x131 +/* PTW read client mapped to SMMU1 */ +#define TEGRA264_MEMORY_CLIENT_SMMU1PTWR 0x132 +/* CTW read client mapped to SMMU1 */ +#define TEGRA264_MEMORY_CLIENT_SMMU1CTWR 0x134 +/* CMDQV read client mapped to SMMU1 */ +#define TEGRA264_MEMORY_CLIENT_SMMU1CMDQVR 0x135 +/* CMDQV write client mapped to SMMU1 */ +#define TEGRA264_MEMORY_CLIENT_SMMU1CMDQVW 0x136 +/* EVNTQ write client mapped to SMMU1 */ +#define TEGRA264_MEMORY_CLIENT_SMMU1EVNTQW 0x137 +/* PTW read client mapped to SMMU2 */ +#define TEGRA264_MEMORY_CLIENT_SMMU2PTWR 0x138 +/* CTW read client mapped to SMMU2 */ +#define TEGRA264_MEMORY_CLIENT_SMMU2CTWR 0x13a +/* CMDQV read client mapped to SMMU2 */ +#define TEGRA264_MEMORY_CLIENT_SMMU2CMDQVR 0x13b +/* CMDQV write client mapped to SMMU2 */ +#define TEGRA264_MEMORY_CLIENT_SMMU2CMDQVW 0x13c +/* EVNTQ write client mapped to SMMU2 */ +#define TEGRA264_MEMORY_CLIENT_SMMU2EVNTQW 0x13d +/* CMDQ read client mapped to SMMU0 */ +#define TEGRA264_MEMORY_CLIENT_SMMU0CMDQR 0x144 +/* CMDQ read client mapped to SMMU1 */ +#define TEGRA264_MEMORY_CLIENT_SMMU1CMDQR 0x145 +/* CMDQ read client mapped to SMMU2 */ +#define TEGRA264_MEMORY_CLIENT_SMMU2CMDQR 0x146 +/* Audio processor1(APE1) Read client */ +#define TEGRA264_MEMORY_CLIENT_APE1R 0x150 +/* Audio processor1(APE1) Write client */ +#define TEGRA264_MEMORY_CLIENT_APE1W 0x151 /* UFS Read client */ #define TEGRA264_MEMORY_CLIENT_UFSR 0x15c /* UFS write client */ #define TEGRA264_MEMORY_CLIENT_UFSW 0x15d +/* XUSB HOST Read Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEVR 0x166 +/* XUSB HOST Write Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEVW 0x167 +/* XUSB SS0 Read Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV1R 0x168 +/* XUSB SS1 Write Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV2W 0x169 +/* XUSB SS2 Read Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV3R 0x16a +/* XUSB SS2 Write Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV3W 0x16b +/* XUSB SS3 Read Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV4R 0x16c +/* XUSB SS3 Write Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV4W 0x16d +/* XUSB DEV Read Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV5R 0x16e +/* XUSB DEV Write Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV5W 0x16f +/* DCE Read client */ +#define TEGRA264_MEMORY_CLIENT_DCER 0x17a +/* DCE Write client */ +#define TEGRA264_MEMORY_CLIENT_DCEW 0x17b /* HDA Read client */ #define TEGRA264_MEMORY_CLIENT_HDAR 0x17c /* HDA Write client */ #define TEGRA264_MEMORY_CLIENT_HDAW 0x17d +/* DISPNISO read client */ +#define TEGRA264_MEMORY_CLIENT_DISPNISOR 0x17e +/* DISPNISO write client */ +#define TEGRA264_MEMORY_CLIENT_DISPNISOW 0x17f +/* XUSB SS0 Write Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV1W 0x180 +/* XUSB SS1 Read Client */ +#define TEGRA264_MEMORY_CLIENT_XUSB_DEV2R 0x181 /* Disp ISO Read Client */ #define TEGRA264_MEMORY_CLIENT_DISPR 0x182 +/* MSSSEQ Read Client */ +#define TEGRA264_MEMORY_CLIENT_MSSSEQR 0x185 +/* MSSSEQ Write Client */ +#define TEGRA264_MEMORY_CLIENT_MSSSEQW 0x186 +/* PTW read client mapped to SMMU3 */ +#define TEGRA264_MEMORY_CLIENT_SMMU3PTWR 0x18b +/* CTW read client mapped to SMMU3 */ +#define TEGRA264_MEMORY_CLIENT_SMMU3CTWR 0x18d +/* CMDQV read client mapped to SMMU3 */ +#define TEGRA264_MEMORY_CLIENT_SMMU3CMDQVR 0x18e +/* CMDQV write client mapped to SMMU3 */ +#define TEGRA264_MEMORY_CLIENT_SMMU3CMDQVW 0x18f +/* EVNTQ write client mapped to SMMU3 */ +#define TEGRA264_MEMORY_CLIENT_SMMU3EVNTQW 0x190 +/* CMDQ read client mapped to SMMU3 */ +#define TEGRA264_MEMORY_CLIENT_SMMU3CMDQR 0x191 +/* PTW read client mapped to SMMU4 */ +#define TEGRA264_MEMORY_CLIENT_SMMU4PTWR 0x192 +/* CTW read client mapped to SMMU4 */ +#define TEGRA264_MEMORY_CLIENT_SMMU4CTWR 0x194 +/* CMDQV read client mapped to SMMU4 */ +#define TEGRA264_MEMORY_CLIENT_SMMU4CMDQVR 0x195 +/* CMDQV write client mapped to SMMU4 */ +#define TEGRA264_MEMORY_CLIENT_SMMU4CMDQVW 0x196 +/* EVNTQ write client mapped to SMMU4 */ +#define TEGRA264_MEMORY_CLIENT_SMMU4EVNTQW 0x197 +/* CMDQ read client mapped to SMMU4 */ +#define TEGRA264_MEMORY_CLIENT_SMMU4CMDQR 0x198 /* MGBE0 Read mccif */ #define TEGRA264_MEMORY_CLIENT_MGBE0R 0x1a2 /* MGBE0 Write mccif */ @@ -128,9 +338,86 @@ #define TEGRA264_MEMORY_CLIENT_MGBE1W 0x1a5 /* VI1 R5 Write client */ #define TEGRA264_MEMORY_CLIENT_VI1W 0x1a6 +/* VI Falcon1 Read client */ +#define TEGRA264_MEMORY_CLIENT_VIFALCON1R 0x1a7 +/* VI Falcon1 Write client */ +#define TEGRA264_MEMORY_CLIENT_VIFALCON1W 0x1a8 +/* ISP FALCON1 Read client */ +#define TEGRA264_MEMORY_CLIENT_ISPFALCON1R 0x1a9 +/* ISP FALCON1 Write client */ +#define TEGRA264_MEMORY_CLIENT_ISPFALCON1W 0x1aa +/* Read Client of RCE1 */ +#define TEGRA264_MEMORY_CLIENT_RCE1R 0x1ab +/* Write client of RCE1 */ +#define TEGRA264_MEMORY_CLIENT_RCE1W 0x1ac +/* SEU2 Read client */ +#define TEGRA264_MEMORY_CLIENT_SEU2R 0x1ad +/* SEU2 Write client */ +#define TEGRA264_MEMORY_CLIENT_SEU2W 0x1ae +/* SEU3 Read client */ +#define TEGRA264_MEMORY_CLIENT_SEU3R 0x1af +/* SEU3 Write client */ +#define TEGRA264_MEMORY_CLIENT_SEU3W 0x1b0 +/* PVA0 Falcon Read mccif */ +#define TEGRA264_MEMORY_CLIENT_PVA0R 0x1b1 +/* PVA0 Falcon Write mccif */ +#define TEGRA264_MEMORY_CLIENT_PVA0W 0x1b2 +/* PVA1 Read mccif */ +#define TEGRA264_MEMORY_CLIENT_PVA1R 0x1b3 +/* PVA1 Write mccif */ +#define TEGRA264_MEMORY_CLIENT_PVA1W 0x1b4 +/* PVA2 Read mccif */ +#define TEGRA264_MEMORY_CLIENT_PVA2R 0x1b5 +/* PVA2 Write mccif */ +#define TEGRA264_MEMORY_CLIENT_PVA2W 0x1b6 +/* ISP3 Write client */ +#define TEGRA264_MEMORY_CLIENT_ISP3W 0x1b7 +/* ISP2 Read Client */ +#define TEGRA264_MEMORY_CLIENT_ISP2R 0x1b8 +/* ISP2 Write Client */ +#define TEGRA264_MEMORY_CLIENT_ISP2W 0x1b9 +/* EQOS Read mccif */ +#define TEGRA264_MEMORY_CLIENT_EQOSR 0x1bc +/* EQOS Write mccif */ +#define TEGRA264_MEMORY_CLIENT_EQOSW 0x1bd +/* FSI0 Read mccif */ +#define TEGRA264_MEMORY_CLIENT_FSI0R 0x1be +/* FSI0 Write mccif */ +#define TEGRA264_MEMORY_CLIENT_FSI0W 0x1bf +/* FSI1 Read mccif */ +#define TEGRA264_MEMORY_CLIENT_FSI1R 0x1c0 +/* FSI1 Write mccif */ +#define TEGRA264_MEMORY_CLIENT_FSI1W 0x1c1 /* SDMMC0 Read mccif */ #define TEGRA264_MEMORY_CLIENT_SDMMC0R 0x1c2 /* SDMMC0 Write mccif */ #define TEGRA264_MEMORY_CLIENT_SDMMC0W 0x1c3 +/* Strongbox (SB) read client */ +#define TEGRA264_MEMORY_CLIENT_SBR 0x1c6 +/* Strongbox (SB) write client */ +#define TEGRA264_MEMORY_CLIENT_SBW 0x1c7 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU0R 0x1c8 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU0W 0x1c9 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU1R 0x1ca +#define TEGRA264_MEMORY_CLIENT_HSS_MIU1W 0x1cb +#define TEGRA264_MEMORY_CLIENT_HSS_MIU2R 0x1cc +#define TEGRA264_MEMORY_CLIENT_HSS_MIU2W 0x1cd +#define TEGRA264_MEMORY_CLIENT_HSS_MIU3R 0x1ce +#define TEGRA264_MEMORY_CLIENT_HSS_MIU3W 0x1cf +#define TEGRA264_MEMORY_CLIENT_HSS_MIU4R 0x1d0 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU4W 0x1d1 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU5R 0x1d2 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU5W 0x1d3 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU6R 0x1d4 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU6W 0x1d5 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU7R 0x1d6 +#define TEGRA264_MEMORY_CLIENT_HSS_MIU7W 0x1d7 +#define TEGRA264_MEMORY_CLIENT_GMMUR2MC 0x1d8 +#define TEGRA264_MEMORY_CLIENT_UCFELAR 0x1d9 +#define TEGRA264_MEMORY_CLIENT_UCFELAW 0x1da +#define TEGRA264_MEMORY_CLIENT_SLCR 0x1db +#define TEGRA264_MEMORY_CLIENT_SLCW 0x1dc +#define TEGRA264_MEMORY_CLIENT_REMOTER 0x1dd +#define TEGRA264_MEMORY_CLIENT_REMOTEW 0x1de #endif /* DT_BINDINGS_MEMORY_NVIDIA_TEGRA264_H */ From d530ed059dac445dc5b81d564adf1952b5c4adba Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Mon, 18 May 2026 18:13:06 +0530 Subject: [PATCH 155/189] memory: tegra264: Add full set of MC clients Extend the tegra264_mc_clients table to cover the full set of memory clients exposed by the SoC. The client name is used for MC fault reporting. Clients managed by the BPMP bandwidth manager additionally carry their bpmp_id and type. Entries in tegra264_mc_clients[] are sorted to match the order of the override and security register offsets used in previous SoCs. Signed-off-by: Sumit Gupta Link: https://patch.msgid.link/20260518124306.2071481-4-sumitg@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra264.c | 564 ++++++++++++++++++++++++++++++-- 1 file changed, 532 insertions(+), 32 deletions(-) diff --git a/drivers/memory/tegra/tegra264.c b/drivers/memory/tegra/tegra264.c index e62890841725..b8d6c4ce4836 100644 --- a/drivers/memory/tegra/tegra264.c +++ b/drivers/memory/tegra/tegra264.c @@ -21,45 +21,62 @@ */ static const struct tegra_mc_client tegra264_mc_clients[] = { { - .id = TEGRA264_MEMORY_CLIENT_HDAR, - .name = "hdar", - .bpmp_id = TEGRA264_BWMGR_HDA, - .type = TEGRA_ICC_ISO_AUDIO, + .id = TEGRA264_MEMORY_CLIENT_PTCR, + .name = "ptcr", }, { - .id = TEGRA264_MEMORY_CLIENT_HDAW, - .name = "hdaw", - .bpmp_id = TEGRA264_BWMGR_HDA, - .type = TEGRA_ICC_ISO_AUDIO, + .id = TEGRA264_MEMORY_CLIENT_HOST1XR, + .name = "host1xr", }, { - .id = TEGRA264_MEMORY_CLIENT_MGBE0R, - .name = "mgbe0r", - .bpmp_id = TEGRA264_BWMGR_EQOS, - .type = TEGRA_ICC_NISO, + .id = TEGRA264_MEMORY_CLIENT_MPCORER, + .name = "mpcorer", }, { - .id = TEGRA264_MEMORY_CLIENT_MGBE0W, - .name = "mgbe0w", - .bpmp_id = TEGRA264_BWMGR_EQOS, - .type = TEGRA_ICC_NISO, + .id = TEGRA264_MEMORY_CLIENT_PSCR, + .name = "pscr", }, { - .id = TEGRA264_MEMORY_CLIENT_MGBE1R, - .name = "mgbe1r", - .bpmp_id = TEGRA264_BWMGR_EQOS, - .type = TEGRA_ICC_NISO, + .id = TEGRA264_MEMORY_CLIENT_PSCW, + .name = "pscw", }, { - .id = TEGRA264_MEMORY_CLIENT_MGBE1W, - .name = "mgbe1w", - .bpmp_id = TEGRA264_BWMGR_EQOS, - .type = TEGRA_ICC_NISO, + .id = TEGRA264_MEMORY_CLIENT_ISP0R, + .name = "isp0r", }, { - .id = TEGRA264_MEMORY_CLIENT_SDMMC0R, - .name = "sdmmc0r", - .bpmp_id = TEGRA264_BWMGR_SDMMC_1, - .type = TEGRA_ICC_NISO, + .id = TEGRA264_MEMORY_CLIENT_MPCOREW, + .name = "mpcorew", }, { - .id = TEGRA264_MEMORY_CLIENT_SDMMC0W, - .name = "sdmmc0w", - .bpmp_id = TEGRA264_BWMGR_SDMMC_1, - .type = TEGRA_ICC_NISO, + .id = TEGRA264_MEMORY_CLIENT_ISP0W, + .name = "isp0w", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISP1W, + .name = "isp1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISPFALCONR, + .name = "ispfalconr", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISPFALCONW, + .name = "ispfalconw", + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE2R, + .name = "mgbe2r", + }, { + .id = TEGRA264_MEMORY_CLIENT_OFAR2MC, + .name = "ofar2mc", + }, { + .id = TEGRA264_MEMORY_CLIENT_OFAW2MC, + .name = "ofaw2mc", + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE2W, + .name = "mgbe2w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE3R, + .name = "mgbe3r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE3W, + .name = "mgbe3w", + }, { + .id = TEGRA264_MEMORY_CLIENT_SEU1RD, + .name = "seu1rd", + }, { + .id = TEGRA264_MEMORY_CLIENT_SEU1WR, + .name = "seu1wr", }, { .id = TEGRA264_MEMORY_CLIENT_VICR, .name = "vicr", @@ -70,6 +87,15 @@ static const struct tegra_mc_client tegra264_mc_clients[] = { .name = "vicw", .bpmp_id = TEGRA264_BWMGR_VIC, .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_VIW, + .name = "viw", + }, { + .id = TEGRA264_MEMORY_CLIENT_XSPI0R, + .name = "xspi0r", + }, { + .id = TEGRA264_MEMORY_CLIENT_XSPI0W, + .name = "xspi0w", }, { .id = TEGRA264_MEMORY_CLIENT_APER, .name = "aper", @@ -80,6 +106,48 @@ static const struct tegra_mc_client tegra264_mc_clients[] = { .name = "apew", .bpmp_id = TEGRA264_BWMGR_APE, .type = TEGRA_ICC_ISO_AUDIO, + }, { + .id = TEGRA264_MEMORY_CLIENT_SER, + .name = "ser", + }, { + .id = TEGRA264_MEMORY_CLIENT_SEW, + .name = "sew", + }, { + .id = TEGRA264_MEMORY_CLIENT_AXIAPR, + .name = "axiapr", + }, { + .id = TEGRA264_MEMORY_CLIENT_AXIAPW, + .name = "axiapw", + }, { + .id = TEGRA264_MEMORY_CLIENT_ETRR, + .name = "etrr", + }, { + .id = TEGRA264_MEMORY_CLIENT_ETRW, + .name = "etrw", + }, { + .id = TEGRA264_MEMORY_CLIENT_TSECR, + .name = "tsecr", + }, { + .id = TEGRA264_MEMORY_CLIENT_TSECW, + .name = "tsecw", + }, { + .id = TEGRA264_MEMORY_CLIENT_BPMPR, + .name = "bpmpr", + }, { + .id = TEGRA264_MEMORY_CLIENT_BPMPW, + .name = "bpmpw", + }, { + .id = TEGRA264_MEMORY_CLIENT_AONR, + .name = "aonr", + }, { + .id = TEGRA264_MEMORY_CLIENT_AONW, + .name = "aonw", + }, { + .id = TEGRA264_MEMORY_CLIENT_GPCDMAR, + .name = "gpcdmar", + }, { + .id = TEGRA264_MEMORY_CLIENT_GPCDMAW, + .name = "gpcdmaw", }, { .id = TEGRA264_MEMORY_CLIENT_APEDMAR, .name = "apedmar", @@ -90,6 +158,36 @@ static const struct tegra_mc_client tegra264_mc_clients[] = { .name = "apedmaw", .bpmp_id = TEGRA264_BWMGR_APEDMA, .type = TEGRA_ICC_ISO_AUDIO, + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU0R, + .name = "miu0r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU0W, + .name = "miu0w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU1R, + .name = "miu1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU1W, + .name = "miu1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU2R, + .name = "miu2r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU2W, + .name = "miu2w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU3R, + .name = "miu3r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU3W, + .name = "miu3w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU4R, + .name = "miu4r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU4W, + .name = "miu4w", }, { .id = TEGRA264_MEMORY_CLIENT_VIFALCONR, .name = "vifalconr", @@ -110,6 +208,12 @@ static const struct tegra_mc_client tegra264_mc_clients[] = { .name = "rcew", .bpmp_id = TEGRA264_BWMGR_RCE, .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_NVENC1SRD2MC, + .name = "nvenc1srd2mc", + }, { + .id = TEGRA264_MEMORY_CLIENT_NVENC1SWR2MC, + .name = "nvenc1swr2mc", }, { .id = TEGRA264_MEMORY_CLIENT_PCIE0W, .name = "pcie0w", @@ -185,6 +289,402 @@ static const struct tegra_mc_client tegra264_mc_clients[] = { .name = "nvdecswr2mc", .bpmp_id = TEGRA264_BWMGR_NVDEC, .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU5R, + .name = "miu5r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU5W, + .name = "miu5w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU6W, + .name = "miu6w", + }, { + .id = TEGRA264_MEMORY_CLIENT_RISTR, + .name = "ristr", + }, { + .id = TEGRA264_MEMORY_CLIENT_RISTW, + .name = "ristw", + }, { + .id = TEGRA264_MEMORY_CLIENT_OESPR, + .name = "oespr", + }, { + .id = TEGRA264_MEMORY_CLIENT_OESPW, + .name = "oespw", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU7W, + .name = "miu7w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU8R, + .name = "miu8r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU8W, + .name = "miu8w", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU9R, + .name = "miu9r", + }, { + .id = TEGRA264_MEMORY_CLIENT_MIU9W, + .name = "miu9w", + }, { + .id = TEGRA264_MEMORY_CLIENT_PMA0AWR, + .name = "pma0awr", + }, { + .id = TEGRA264_MEMORY_CLIENT_NVJPG1SRD2MC, + .name = "nvjpg1srd2mc", + }, { + .id = TEGRA264_MEMORY_CLIENT_NVJPG1SWR2MC, + .name = "nvjpg1swr2mc", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU0CTWR, + .name = "smmu0ctwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU0CMDQVR, + .name = "smmu0cmdqvr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU0CMDQVW, + .name = "smmu0cmdqvw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU0EVNTQW, + .name = "smmu0evntqw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU1PTWR, + .name = "smmu1ptwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU1CTWR, + .name = "smmu1ctwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU1CMDQVR, + .name = "smmu1cmdqvr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU1CMDQVW, + .name = "smmu1cmdqvw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU1EVNTQW, + .name = "smmu1evntqw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU2PTWR, + .name = "smmu2ptwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU2CTWR, + .name = "smmu2ctwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU2CMDQVR, + .name = "smmu2cmdqvr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU2CMDQVW, + .name = "smmu2cmdqvw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU2EVNTQW, + .name = "smmu2evntqw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU0CMDQR, + .name = "smmu0cmdqr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU1CMDQR, + .name = "smmu1cmdqr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU2CMDQR, + .name = "smmu2cmdqr", + }, { + .id = TEGRA264_MEMORY_CLIENT_APE1R, + .name = "ape1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_APE1W, + .name = "ape1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_UFSR, + .name = "ufsr", + }, { + .id = TEGRA264_MEMORY_CLIENT_UFSW, + .name = "ufsw", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEVR, + .name = "xusb_devr", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEVW, + .name = "xusb_devw", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV1R, + .name = "xusb_dev1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV2W, + .name = "xusb_dev2w", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV3R, + .name = "xusb_dev3r", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV3W, + .name = "xusb_dev3w", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV4R, + .name = "xusb_dev4r", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV4W, + .name = "xusb_dev4w", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV5R, + .name = "xusb_dev5r", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV5W, + .name = "xusb_dev5w", + }, { + .id = TEGRA264_MEMORY_CLIENT_DCER, + .name = "dcer", + }, { + .id = TEGRA264_MEMORY_CLIENT_DCEW, + .name = "dcew", + }, { + .id = TEGRA264_MEMORY_CLIENT_HDAR, + .name = "hdar", + .bpmp_id = TEGRA264_BWMGR_HDA, + .type = TEGRA_ICC_ISO_AUDIO, + }, { + .id = TEGRA264_MEMORY_CLIENT_HDAW, + .name = "hdaw", + .bpmp_id = TEGRA264_BWMGR_HDA, + .type = TEGRA_ICC_ISO_AUDIO, + }, { + .id = TEGRA264_MEMORY_CLIENT_DISPNISOR, + .name = "dispnisor", + }, { + .id = TEGRA264_MEMORY_CLIENT_DISPNISOW, + .name = "dispnisow", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV1W, + .name = "xusb_dev1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_XUSB_DEV2R, + .name = "xusb_dev2r", + }, { + .id = TEGRA264_MEMORY_CLIENT_DISPR, + .name = "dispr", + .bpmp_id = TEGRA264_BWMGR_DISPLAY, + .type = TEGRA_ICC_ISO_DISPLAY, + }, { + .id = TEGRA264_MEMORY_CLIENT_MSSSEQR, + .name = "mssseqr", + }, { + .id = TEGRA264_MEMORY_CLIENT_MSSSEQW, + .name = "mssseqw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU3PTWR, + .name = "smmu3ptwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU3CTWR, + .name = "smmu3ctwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU3CMDQVR, + .name = "smmu3cmdqvr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU3CMDQVW, + .name = "smmu3cmdqvw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU3EVNTQW, + .name = "smmu3evntqw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU3CMDQR, + .name = "smmu3cmdqr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU4PTWR, + .name = "smmu4ptwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU4CTWR, + .name = "smmu4ctwr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU4CMDQVR, + .name = "smmu4cmdqvr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU4CMDQVW, + .name = "smmu4cmdqvw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU4EVNTQW, + .name = "smmu4evntqw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SMMU4CMDQR, + .name = "smmu4cmdqr", + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE0R, + .name = "mgbe0r", + .bpmp_id = TEGRA264_BWMGR_EQOS, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE0W, + .name = "mgbe0w", + .bpmp_id = TEGRA264_BWMGR_EQOS, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE1R, + .name = "mgbe1r", + .bpmp_id = TEGRA264_BWMGR_EQOS, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_MGBE1W, + .name = "mgbe1w", + .bpmp_id = TEGRA264_BWMGR_EQOS, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_VI1W, + .name = "vi1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_VIFALCON1R, + .name = "vifalcon1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_VIFALCON1W, + .name = "vifalcon1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISPFALCON1R, + .name = "ispfalcon1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISPFALCON1W, + .name = "ispfalcon1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_RCE1R, + .name = "rce1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_RCE1W, + .name = "rce1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_SEU2R, + .name = "seu2r", + }, { + .id = TEGRA264_MEMORY_CLIENT_SEU2W, + .name = "seu2w", + }, { + .id = TEGRA264_MEMORY_CLIENT_SEU3R, + .name = "seu3r", + }, { + .id = TEGRA264_MEMORY_CLIENT_SEU3W, + .name = "seu3w", + }, { + .id = TEGRA264_MEMORY_CLIENT_PVA0R, + .name = "pva0r", + }, { + .id = TEGRA264_MEMORY_CLIENT_PVA0W, + .name = "pva0w", + }, { + .id = TEGRA264_MEMORY_CLIENT_PVA1R, + .name = "pva1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_PVA1W, + .name = "pva1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_PVA2R, + .name = "pva2r", + }, { + .id = TEGRA264_MEMORY_CLIENT_PVA2W, + .name = "pva2w", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISP3W, + .name = "isp3w", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISP2R, + .name = "isp2r", + }, { + .id = TEGRA264_MEMORY_CLIENT_ISP2W, + .name = "isp2w", + }, { + .id = TEGRA264_MEMORY_CLIENT_EQOSR, + .name = "eqosr", + }, { + .id = TEGRA264_MEMORY_CLIENT_EQOSW, + .name = "eqosw", + }, { + .id = TEGRA264_MEMORY_CLIENT_FSI0R, + .name = "fsi0r", + }, { + .id = TEGRA264_MEMORY_CLIENT_FSI0W, + .name = "fsi0w", + }, { + .id = TEGRA264_MEMORY_CLIENT_FSI1R, + .name = "fsi1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_FSI1W, + .name = "fsi1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_SDMMC0R, + .name = "sdmmc0r", + .bpmp_id = TEGRA264_BWMGR_SDMMC_1, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_SDMMC0W, + .name = "sdmmc0w", + .bpmp_id = TEGRA264_BWMGR_SDMMC_1, + .type = TEGRA_ICC_NISO, + }, { + .id = TEGRA264_MEMORY_CLIENT_SBR, + .name = "sbr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SBW, + .name = "sbw", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU0R, + .name = "hss_miu0r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU0W, + .name = "hss_miu0w", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU1R, + .name = "hss_miu1r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU1W, + .name = "hss_miu1w", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU2R, + .name = "hss_miu2r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU2W, + .name = "hss_miu2w", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU3R, + .name = "hss_miu3r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU3W, + .name = "hss_miu3w", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU4R, + .name = "hss_miu4r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU4W, + .name = "hss_miu4w", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU5R, + .name = "hss_miu5r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU5W, + .name = "hss_miu5w", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU6R, + .name = "hss_miu6r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU6W, + .name = "hss_miu6w", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU7R, + .name = "hss_miu7r", + }, { + .id = TEGRA264_MEMORY_CLIENT_HSS_MIU7W, + .name = "hss_miu7w", + }, { + .id = TEGRA264_MEMORY_CLIENT_GMMUR2MC, + .name = "gmmur2mc", + }, { + .id = TEGRA264_MEMORY_CLIENT_UCFELAR, + .name = "ucfelar", + }, { + .id = TEGRA264_MEMORY_CLIENT_UCFELAW, + .name = "ucfelaw", + }, { + .id = TEGRA264_MEMORY_CLIENT_SLCR, + .name = "slcr", + }, { + .id = TEGRA264_MEMORY_CLIENT_SLCW, + .name = "slcw", + }, { + .id = TEGRA264_MEMORY_CLIENT_REMOTER, + .name = "remoter", + }, { + .id = TEGRA264_MEMORY_CLIENT_REMOTEW, + .name = "remotew", }, }; From 912f0e6f274d5e1e0bfb241ae826f5522e0d49e8 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 16 Jan 2026 20:18:36 +0100 Subject: [PATCH 156/189] MAINTAINERS: Move Peter De Schrijver to CREDITS Peter sadly passed away a while back. Paul did a much better job at finding the right words to mourn this loss than I ever could, so I will leave this link here: https://lore.kernel.org/lkml/alpine.DEB.2.21.999.2407240345480.11116@utopia.booyaka.com/T/#u Co-developed-by: Paul Walmsley Signed-off-by: Paul Walmsley Co-developed-by: Aaro Koskinen Signed-off-by: Aaro Koskinen Co-developed-by: Geert Uytterhoeven Signed-off-by: Geert Uytterhoeven Signed-off-by: Thierry Reding --- CREDITS | 10 ++++++++++ MAINTAINERS | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CREDITS b/CREDITS index 17962bdd6dbd..29bafdc47866 100644 --- a/CREDITS +++ b/CREDITS @@ -3668,7 +3668,17 @@ D: Macintosh IDE Driver N: Peter De Schrijver E: stud11@cc4.kuleuven.ac.be +E: p2@mind.be +E: peter.de-schrijver@nokia.com +E: pdeschrijver@nvidia.com +E: p2@psychaos.be +D: Apollo Domain workstations +D: Ariadne and Hydra Amiga Ethernet drivers +D: IBM PS/2, Microchannel, and Token Ring support D: Mitsumi CD-ROM driver patches March version +D: TWL4030 power management and audio codec driver +D: OMAP power management +D: NVIDIA Tegra clock and BPMP drivers, among many other things S: Molenbaan 29 S: B2240 Zandhoven S: Belgium diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..1ce2f83b6e68 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26194,7 +26194,6 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux.git N: [^a-z]tegra TEGRA CLOCK DRIVER -M: Peter De Schrijver M: Prashant Gaikwad S: Supported F: drivers/clk/tegra/ From 18c620ff671004a356b174e5f6d494fa449ce57c Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 17 Apr 2026 15:05:10 +0200 Subject: [PATCH 157/189] Documentation: ABI: Take over as contact for sysfs-driver-tegra-fuse Peter sadly passed away a while ago, so I'll be taking over as contact for this ABI documentation. Suggested-by: Geert Uytterhoeven Signed-off-by: Thierry Reding --- Documentation/ABI/testing/sysfs-driver-tegra-fuse | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-driver-tegra-fuse b/Documentation/ABI/testing/sysfs-driver-tegra-fuse index b8936fad2ccf..47d5513100f6 100644 --- a/Documentation/ABI/testing/sysfs-driver-tegra-fuse +++ b/Documentation/ABI/testing/sysfs-driver-tegra-fuse @@ -1,6 +1,6 @@ What: /sys/devices/*//fuse Date: February 2014 -Contact: Peter De Schrijver +Contact: Thierry Reding Description: read-only access to the efuses on Tegra20, Tegra30, Tegra114 and Tegra124 SoC's from NVIDIA. The efuses contain write once data programmed at the factory. The data is laid out in 32bit From 64e609f9299d457a88dec0bce68b42a434a78502 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Feb 2025 16:25:29 +0100 Subject: [PATCH 158/189] ata: ahci_tegra: Explicitly specify PMC instance to use Currently the kernel relies on a global variable to reference the PMC context. Use an explicit lookup for the PMC and pass that to the public PMC APIs. Acked-by: Damien Le Moal Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/ata/ahci_tegra.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/ata/ahci_tegra.c b/drivers/ata/ahci_tegra.c index 44584eed6374..554f05e09f98 100644 --- a/drivers/ata/ahci_tegra.c +++ b/drivers/ata/ahci_tegra.c @@ -175,6 +175,7 @@ struct tegra_ahci_priv { struct reset_control *sata_cold_rst; /* Needs special handling, cannot use ahci_platform */ struct clk *sata_clk; + struct tegra_pmc *pmc; struct regulator_bulk_data *supplies; const struct tegra_ahci_soc *soc; }; @@ -246,9 +247,10 @@ static int tegra_ahci_power_on(struct ahci_host_priv *hpriv) return ret; if (!tegra->pdev->dev.pm_domain) { - ret = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_SATA, - tegra->sata_clk, - tegra->sata_rst); + ret = tegra_pmc_powergate_sequence_power_up(tegra->pmc, + TEGRA_POWERGATE_SATA, + tegra->sata_clk, + tegra->sata_rst); if (ret) goto disable_regulators; } @@ -269,7 +271,7 @@ static int tegra_ahci_power_on(struct ahci_host_priv *hpriv) clk_disable_unprepare(tegra->sata_clk); if (!tegra->pdev->dev.pm_domain) - tegra_powergate_power_off(TEGRA_POWERGATE_SATA); + tegra_pmc_powergate_power_off(tegra->pmc, TEGRA_POWERGATE_SATA); disable_regulators: regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); @@ -289,7 +291,7 @@ static void tegra_ahci_power_off(struct ahci_host_priv *hpriv) clk_disable_unprepare(tegra->sata_clk); if (!tegra->pdev->dev.pm_domain) - tegra_powergate_power_off(TEGRA_POWERGATE_SATA); + tegra_pmc_powergate_power_off(tegra->pmc, TEGRA_POWERGATE_SATA); regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); } @@ -571,6 +573,11 @@ static int tegra_ahci_probe(struct platform_device *pdev) return PTR_ERR(tegra->sata_clk); } + tegra->pmc = devm_tegra_pmc_get(&pdev->dev); + if (IS_ERR(tegra->pmc)) + return dev_err_probe(&pdev->dev, PTR_ERR(tegra->pmc), + "failed to get PMC\n"); + tegra->supplies = devm_kcalloc(&pdev->dev, tegra->soc->num_supplies, sizeof(*tegra->supplies), GFP_KERNEL); From 3ef27f1ad5ed0e36c2dac82876ef7dde33a588f2 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Feb 2025 16:27:02 +0100 Subject: [PATCH 159/189] drm/nouveau: tegra: Explicitly specify PMC instance to use Currently the kernel relies on a global variable to reference the PMC context. Use an explicit lookup for the PMC and pass that to the public PMC APIs. Acked-by: Danilo Krummrich Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/nouveau/include/nvkm/core/tegra.h | 2 ++ drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/tegra.h b/drivers/gpu/drm/nouveau/include/nvkm/core/tegra.h index 57bc542780bb..6aaa30ef167f 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/tegra.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/tegra.h @@ -18,6 +18,8 @@ struct nvkm_device_tegra { struct regulator *vdd; + struct tegra_pmc *pmc; + struct { /* * Protects accesses to mm from subsystems diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c index 46bb55a1f565..3c8d0878891a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/tegra.c @@ -54,7 +54,8 @@ nvkm_device_tegra_power_up(struct nvkm_device_tegra *tdev) reset_control_assert(tdev->rst); udelay(10); - ret = tegra_powergate_remove_clamping(TEGRA_POWERGATE_3D); + ret = tegra_pmc_powergate_remove_clamping(tdev->pmc, + TEGRA_POWERGATE_3D); if (ret) goto err_clamp; udelay(10); @@ -307,6 +308,12 @@ nvkm_device_tegra_new(const struct nvkm_device_tegra_func *func, goto free; } + tdev->pmc = devm_tegra_pmc_get(&pdev->dev); + if (IS_ERR(tdev->pmc)) { + ret = PTR_ERR(tdev->pmc); + goto free; + } + /** * The IOMMU bit defines the upper limit of the GPU-addressable space. */ From e68c4244136b8e9c2a9fc3aaa4d748be83afddef Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Feb 2025 16:28:38 +0100 Subject: [PATCH 160/189] drm/tegra: Explicitly specify PMC instance to use Currently the kernel relies on a global variable to reference the PMC context. Use an explicit lookup for the PMC and pass that to the public PMC APIs. Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 14 ++++++++++---- drivers/gpu/drm/tegra/dc.h | 1 + drivers/gpu/drm/tegra/gr3d.c | 9 ++++++++- drivers/gpu/drm/tegra/sor.c | 16 ++++++++++++---- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 06370b7e0e56..e6099f5aced7 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -2780,7 +2780,7 @@ static int tegra_dc_runtime_suspend(struct host1x_client *client) } if (dc->soc->has_powergate) - tegra_powergate_power_off(dc->powergate); + tegra_pmc_powergate_power_off(dc->pmc, dc->powergate); clk_disable_unprepare(dc->clk); pm_runtime_put_sync(dev); @@ -2801,8 +2801,9 @@ static int tegra_dc_runtime_resume(struct host1x_client *client) } if (dc->soc->has_powergate) { - err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk, - dc->rst); + err = tegra_pmc_powergate_sequence_power_up(dc->pmc, + dc->powergate, + dc->clk, dc->rst); if (err < 0) { dev_err(dev, "failed to power partition: %d\n", err); goto put_rpm; @@ -3231,12 +3232,17 @@ static int tegra_dc_probe(struct platform_device *pdev) clk_disable_unprepare(dc->clk); if (dc->soc->has_powergate) { + dc->pmc = devm_tegra_pmc_get(dc->dev); + if (IS_ERR(dc->pmc)) + return dev_err_probe(dc->dev, PTR_ERR(dc->pmc), + "failed to get PMC\n"); + if (dc->pipe == 0) dc->powergate = TEGRA_POWERGATE_DIS; else dc->powergate = TEGRA_POWERGATE_DISB; - tegra_powergate_power_off(dc->powergate); + tegra_pmc_powergate_power_off(dc->pmc, dc->powergate); } err = tegra_dc_init_opp_table(dc); diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index 0559fa6b1bf7..10b86250e4ca 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -94,6 +94,7 @@ struct tegra_dc { int irq; struct tegra_output *rgb; + struct tegra_pmc *pmc; struct tegra_dc_stats stats; struct list_head list; diff --git a/drivers/gpu/drm/tegra/gr3d.c b/drivers/gpu/drm/tegra/gr3d.c index 42e9656ab80c..debf66fa4eeb 100644 --- a/drivers/gpu/drm/tegra/gr3d.c +++ b/drivers/gpu/drm/tegra/gr3d.c @@ -46,6 +46,7 @@ struct gr3d { unsigned int nclocks; struct reset_control_bulk_data resets[RST_GR3D_MAX]; unsigned int nresets; + struct tegra_pmc *pmc; struct dev_pm_domain_list *pd_list; DECLARE_BITMAP(addr_regs, GR3D_NUM_REGS); @@ -353,7 +354,8 @@ static int gr3d_power_up_legacy_domain(struct device *dev, const char *name, if (err) { dev_err(dev, "failed to acquire %s reset: %d\n", name, err); } else { - err = tegra_powergate_sequence_power_up(id, clk, reset); + err = tegra_pmc_powergate_sequence_power_up(gr3d->pmc, id, + clk, reset); reset_control_release(reset); } @@ -385,6 +387,11 @@ static int gr3d_init_power(struct device *dev, struct gr3d *gr3d) if (err != -ENOENT) return err; + gr3d->pmc = devm_tegra_pmc_get(dev); + if (IS_ERR(gr3d->pmc)) + return dev_err_probe(dev, PTR_ERR(gr3d->pmc), + "failed to get PMC\n"); + /* * Older device-trees don't use GENPD. In this case we should * toggle power domain manually. diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index de8b2dfc4984..a76095838133 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -422,6 +422,8 @@ struct tegra_sor { struct clk *clk_dp; struct clk *clk; + struct tegra_pmc *pmc; + u8 xbar_cfg[5]; struct drm_dp_link link; @@ -2237,7 +2239,7 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) if (err < 0) dev_err(sor->dev, "failed to power down SOR: %d\n", err); - err = tegra_io_pad_power_disable(sor->pad); + err = tegra_pmc_io_pad_power_disable(sor->pmc, sor->pad); if (err < 0) dev_err(sor->dev, "failed to power off I/O pad: %d\n", err); @@ -2277,7 +2279,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) div = clk_get_rate(sor->clk) / 1000000 * 4; - err = tegra_io_pad_power_enable(sor->pad); + err = tegra_pmc_io_pad_power_enable(sor->pmc, sor->pad); if (err < 0) dev_err(sor->dev, "failed to power on I/O pad: %d\n", err); @@ -2701,7 +2703,7 @@ static void tegra_sor_dp_disable(struct drm_encoder *encoder) if (err < 0) dev_err(sor->dev, "failed to power down SOR: %d\n", err); - err = tegra_io_pad_power_disable(sor->pad); + err = tegra_pmc_io_pad_power_disable(sor->pmc, sor->pad); if (err < 0) dev_err(sor->dev, "failed to power off I/O pad: %d\n", err); @@ -2743,7 +2745,7 @@ static void tegra_sor_dp_enable(struct drm_encoder *encoder) if (err < 0) dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); - err = tegra_io_pad_power_enable(sor->pad); + err = tegra_pmc_io_pad_power_enable(sor->pmc, sor->pad); if (err < 0) dev_err(sor->dev, "failed to power on LVDS rail: %d\n", err); @@ -3730,6 +3732,12 @@ static int tegra_sor_probe(struct platform_device *pdev) sor->num_settings = sor->soc->num_settings; + sor->pmc = devm_tegra_pmc_get(&pdev->dev); + if (IS_ERR(sor->pmc)) { + err = PTR_ERR(sor->pmc); + goto put_aux; + } + np = of_parse_phandle(pdev->dev.of_node, "nvidia,dpaux", 0); if (np) { sor->aux = drm_dp_aux_find_by_of_node(np); From c4d22e0d4458b79c716c64b57bee4022cead2b5e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Feb 2025 16:29:35 +0100 Subject: [PATCH 161/189] media: vde: Explicitly specify PMC instance to use Currently the kernel relies on a global variable to reference the PMC context. Use an explicit lookup for the PMC and pass that to the public PMC APIs. Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/media/platform/nvidia/tegra-vde/vde.c | 15 +++++++++++---- drivers/media/platform/nvidia/tegra-vde/vde.h | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/nvidia/tegra-vde/vde.c b/drivers/media/platform/nvidia/tegra-vde/vde.c index 2b3898828304..c3097085ad9d 100644 --- a/drivers/media/platform/nvidia/tegra-vde/vde.c +++ b/drivers/media/platform/nvidia/tegra-vde/vde.c @@ -161,7 +161,8 @@ static __maybe_unused int tegra_vde_runtime_suspend(struct device *dev) int err; if (!dev->pm_domain) { - err = tegra_powergate_power_off(TEGRA_POWERGATE_VDEC); + err = tegra_pmc_powergate_power_off(vde->pmc, + TEGRA_POWERGATE_VDEC); if (err) { dev_err(dev, "Failed to power down HW: %d\n", err); return err; @@ -193,15 +194,16 @@ static __maybe_unused int tegra_vde_runtime_resume(struct device *dev) } if (!dev->pm_domain) { - err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_VDEC, - vde->clk, vde->rst); + err = tegra_pmc_powergate_sequence_power_up(vde->pmc, + TEGRA_POWERGATE_VDEC, + vde->clk, vde->rst); if (err) { dev_err(dev, "Failed to power up HW : %d\n", err); goto release_reset; } } else { /* - * tegra_powergate_sequence_power_up() leaves clocks enabled, + * tegra_pmc_powergate_sequence_power_up() leaves clocks enabled, * while GENPD not. */ err = clk_prepare_enable(vde->clk); @@ -293,6 +295,11 @@ static int tegra_vde_probe(struct platform_device *pdev) return err; } + vde->pmc = devm_tegra_pmc_get(dev); + if (IS_ERR(vde->pmc)) + return dev_err_probe(dev, PTR_ERR(vde->pmc), + "failed to get PMC\n"); + irq = platform_get_irq_byname(pdev, "sync-token"); if (irq < 0) return irq; diff --git a/drivers/media/platform/nvidia/tegra-vde/vde.h b/drivers/media/platform/nvidia/tegra-vde/vde.h index b2890484b7c3..abac0221d6e4 100644 --- a/drivers/media/platform/nvidia/tegra-vde/vde.h +++ b/drivers/media/platform/nvidia/tegra-vde/vde.h @@ -107,6 +107,7 @@ struct tegra_vde { struct list_head map_list; struct reset_control *rst; struct reset_control *rst_mc; + struct tegra_pmc *pmc; struct gen_pool *iram_pool; struct completion decode_completion; struct clk *clk; From 5f726aeb505c9e5dde87530c78a11b88a44b9edd Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Feb 2025 16:30:24 +0100 Subject: [PATCH 162/189] PCI: tegra: Explicitly specify PMC instance to use Currently the kernel relies on a global variable to reference the PMC context. Use an explicit lookup for the PMC and pass that to the public PMC APIs. Acked-by: Manivannan Sadhasivam Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/pci/controller/pci-tegra.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/pci-tegra.c b/drivers/pci/controller/pci-tegra.c index 512309763d1f..229c69534476 100644 --- a/drivers/pci/controller/pci-tegra.c +++ b/drivers/pci/controller/pci-tegra.c @@ -340,6 +340,8 @@ struct tegra_pcie { struct reset_control *afi_rst; struct reset_control *pcie_xrst; + struct tegra_pmc *pmc; + bool legacy_phy; struct phy *phy; @@ -1165,7 +1167,7 @@ static void tegra_pcie_power_off(struct tegra_pcie *pcie) clk_disable_unprepare(pcie->afi_clk); if (!dev->pm_domain) - tegra_powergate_power_off(TEGRA_POWERGATE_PCIE); + tegra_pmc_powergate_power_off(pcie->pmc, TEGRA_POWERGATE_PCIE); err = regulator_bulk_disable(pcie->num_supplies, pcie->supplies); if (err < 0) @@ -1183,7 +1185,7 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie) reset_control_assert(pcie->pex_rst); if (!dev->pm_domain) - tegra_powergate_power_off(TEGRA_POWERGATE_PCIE); + tegra_pmc_powergate_power_off(pcie->pmc, TEGRA_POWERGATE_PCIE); /* enable regulators */ err = regulator_bulk_enable(pcie->num_supplies, pcie->supplies); @@ -1191,12 +1193,14 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie) dev_err(dev, "failed to enable regulators: %d\n", err); if (!dev->pm_domain) { - err = tegra_powergate_power_on(TEGRA_POWERGATE_PCIE); + err = tegra_pmc_powergate_power_on(pcie->pmc, + TEGRA_POWERGATE_PCIE); if (err) { dev_err(dev, "failed to power ungate: %d\n", err); goto regulator_disable; } - err = tegra_powergate_remove_clamping(TEGRA_POWERGATE_PCIE); + err = tegra_pmc_powergate_remove_clamping(pcie->pmc, + TEGRA_POWERGATE_PCIE); if (err) { dev_err(dev, "failed to remove clamp: %d\n", err); goto powergate; @@ -1234,7 +1238,7 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie) clk_disable_unprepare(pcie->afi_clk); powergate: if (!dev->pm_domain) - tegra_powergate_power_off(TEGRA_POWERGATE_PCIE); + tegra_pmc_powergate_power_off(pcie->pmc, TEGRA_POWERGATE_PCIE); regulator_disable: regulator_bulk_disable(pcie->num_supplies, pcie->supplies); @@ -1432,6 +1436,11 @@ static int tegra_pcie_get_resources(struct tegra_pcie *pcie) return err; } + pcie->pmc = devm_tegra_pmc_get(dev); + if (IS_ERR(pcie->pmc)) + return dev_err_probe(dev, PTR_ERR(pcie->pmc), + "failed to get PMC\n"); + if (soc->program_uphy) { err = tegra_pcie_phys_get(pcie); if (err < 0) { From 258cff9857918615897bd5b57c588dc13c0058b6 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Feb 2025 16:31:14 +0100 Subject: [PATCH 163/189] usb: xhci: tegra: Explicitly specify PMC instance to use Currently the kernel relies on a global variable to reference the PMC context. Use an explicit lookup for the PMC and pass that to the public PMC APIs. Acked-by: Mathias Nyman Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/usb/host/xhci-tegra.c | 38 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index d2214d309e96..122b711929ef 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -292,6 +292,7 @@ struct tegra_xusb { struct reset_control *host_rst; struct reset_control *ss_rst; + struct tegra_pmc *pmc; struct device *genpd_dev_host; struct device *genpd_dev_ss; bool use_genpd; @@ -1188,20 +1189,23 @@ static int tegra_xusb_unpowergate_partitions(struct tegra_xusb *tegra) return rc; } } else { - rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA, - tegra->ss_clk, - tegra->ss_rst); + rc = tegra_pmc_powergate_sequence_power_up(tegra->pmc, + TEGRA_POWERGATE_XUSBA, + tegra->ss_clk, + tegra->ss_rst); if (rc < 0) { dev_err(dev, "failed to enable XUSB SS partition\n"); return rc; } - rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, - tegra->host_clk, - tegra->host_rst); + rc = tegra_pmc_powergate_sequence_power_up(tegra->pmc, + TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); if (rc < 0) { dev_err(dev, "failed to enable XUSB Host partition\n"); - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + tegra_pmc_powergate_power_off(tegra->pmc, + TEGRA_POWERGATE_XUSBA); return rc; } } @@ -1228,18 +1232,21 @@ static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra) return rc; } } else { - rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); + rc = tegra_pmc_powergate_power_off(tegra->pmc, + TEGRA_POWERGATE_XUSBC); if (rc < 0) { dev_err(dev, "failed to disable XUSB Host partition\n"); return rc; } - rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + rc = tegra_pmc_powergate_power_off(tegra->pmc, + TEGRA_POWERGATE_XUSBA); if (rc < 0) { dev_err(dev, "failed to disable XUSB SS partition\n"); - tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, - tegra->host_clk, - tegra->host_rst); + tegra_pmc_powergate_sequence_power_up(tegra->pmc, + TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); return rc; } } @@ -1733,6 +1740,13 @@ static int tegra_xusb_probe(struct platform_device *pdev) err); goto put_padctl; } + + tegra->pmc = devm_tegra_pmc_get(&pdev->dev); + if (IS_ERR(tegra->pmc)) { + err = dev_err_probe(&pdev->dev, PTR_ERR(tegra->pmc), + "failed to get PMC\n"); + goto put_padctl; + } } else { err = tegra_xusb_powerdomain_init(&pdev->dev, tegra); if (err) From 8a3571618c2e3f339b5b6fee5841143face58a2b Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Thu, 14 May 2026 11:18:31 +0530 Subject: [PATCH 164/189] soc/tegra: fuse: Register nvmem lookups at probe Register nvmem lookups in tegra_fuse_probe(), after the nvmem device has been registered, since they can only be used after the nvmem device is registered. Signed-off-by: Kartik Rajput Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/fuse-tegra.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index 291484595dcf..071cd9620634 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -182,10 +182,6 @@ static int tegra_fuse_probe(struct platform_device *pdev) } fuse->soc->init(fuse); - - err = tegra_fuse_add_lookups(fuse); - if (err) - return dev_err_probe(&pdev->dev, err, "failed to add FUSE lookups\n"); } fuse->clk = devm_clk_get_optional(&pdev->dev, "fuse"); @@ -231,6 +227,10 @@ static int tegra_fuse_probe(struct platform_device *pdev) return err; } + err = tegra_fuse_add_lookups(fuse); + if (err) + return dev_err_probe(&pdev->dev, err, "failed to add FUSE lookups\n"); + fuse->rst = devm_reset_control_get_optional(&pdev->dev, "fuse"); if (IS_ERR(fuse->rst)) return dev_err_probe(&pdev->dev, PTR_ERR(fuse->rst), "failed to get FUSE reset\n"); @@ -473,7 +473,7 @@ static int __init tegra_init_fuse(void) const struct of_device_id *match; struct device_node *np; struct resource regs; - int err; + int err = 0; tegra_init_apbmisc(); @@ -566,10 +566,6 @@ static int __init tegra_init_fuse(void) tegra_fuse_print_sku_info(&tegra_sku_info); - err = tegra_fuse_add_lookups(fuse); - if (err) - pr_err("failed to add FUSE lookups\n"); - return err; } early_initcall(tegra_init_fuse); From 55ee4d297e3929beac865714b681a4f25759868f Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sat, 25 Apr 2026 20:03:14 -0400 Subject: [PATCH 165/189] firmware: tegra: Make TEGRA_IVC a hidden Kconfig symbol kconfiglint reports: K002: config TEGRA_BPMP selects visible symbol TEGRA_IVC which has dependencies TEGRA_IVC was originally introduced in commit ca791d7f4256 ("firmware: tegra: Add IVC library") as a user-visible bool with a prompt ("Tegra IVC protocol"). At that time, TEGRA_BPMP depended on TEGRA_IVC, requiring users to manually enable it. Recently, commit 78eb18020a88 ("firmware: tegra: Fix IVC dependency problems") recognized that TEGRA_IVC is library code that should be activated via `select` rather than user selection. That commit changed TEGRA_BPMP from `depends on TEGRA_IVC` to `select TEGRA_IVC`, and restricted TEGRA_IVC's prompt to only appear under COMPILE_TEST (`bool "Tegra IVC protocol" if COMPILE_TEST`). The commit message explicitly states: "The IVC code is library code that other drivers need to select if they need that library." However, the `if COMPILE_TEST` qualifier still leaves TEGRA_IVC as a technically visible symbol, triggering K002 when TEGRA_BPMP selects it. Since TEGRA_IVC depends on ARCH_TEGRA, it cannot be independently enabled under COMPILE_TEST without ARCH_TEGRA anyway, limiting the value of the standalone COMPILE_TEST path. TEGRA_BPMP itself provides adequate COMPILE_TEST coverage for the IVC library through its own dependency chain. Complete the transition to a pure library symbol by removing the prompt entirely, making TEGRA_IVC a hidden bool activated only via select from TEGRA_BPMP. This is consistent with the intent expressed in 78eb18020a88. Assisted-by: Claude:claude-opus-4-6 kconfiglint Signed-off-by: Sasha Levin Signed-off-by: Thierry Reding --- drivers/firmware/tegra/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/firmware/tegra/Kconfig b/drivers/firmware/tegra/Kconfig index 91f2320c0d0f..3a9162706439 100644 --- a/drivers/firmware/tegra/Kconfig +++ b/drivers/firmware/tegra/Kconfig @@ -2,7 +2,7 @@ menu "Tegra firmware driver" config TEGRA_IVC - bool "Tegra IVC protocol" if COMPILE_TEST + bool depends on ARCH_TEGRA help IVC (Inter-VM Communication) protocol is part of the IPC From 8b8ee2e56f951ccf41d98eebe73195cea487cd48 Mon Sep 17 00:00:00 2001 From: Kartik Rajput Date: Thu, 14 May 2026 11:00:41 +0530 Subject: [PATCH 166/189] soc/tegra: Use ARM SMCCC to get chip ID, revision, and platform info Tegra410 and Tegra241 deprecate the HIDREV register. The recommended method is to use ARM SMCCC to retrieve the chip ID, major and minor revisions, and platform information. Prefer ARM SMCCC when the platform supports it; fall back to HIDREV otherwise. Behavior on older Tegra SoCs that do not support ARM SMCCC remains unchanged. Signed-off-by: Kartik Rajput Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/tegra-apbmisc.c | 40 +++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/drivers/soc/tegra/fuse/tegra-apbmisc.c b/drivers/soc/tegra/fuse/tegra-apbmisc.c index 0ce94fdc536f..87ae63a7e52d 100644 --- a/drivers/soc/tegra/fuse/tegra-apbmisc.c +++ b/drivers/soc/tegra/fuse/tegra-apbmisc.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -27,6 +28,11 @@ #define PMC_STRAPPING_OPT_A_RAM_CODE_MASK_SHORT \ (0x3 << PMC_STRAPPING_OPT_A_RAM_CODE_SHIFT) +#define TEGRA_SMCCC_PLATFORM(x) (((x) >> 8) & 0xff) +#define TEGRA_SMCCC_CHIP_ID(x) (((x) >> 4) & 0xff) +#define TEGRA_SMCCC_MAJOR_REV(x) ((x) & 0xf) +#define TEGRA_SMCCC_MINOR_REV(x) ((x) & 0xf) + static void __iomem *apbmisc_base; static bool long_ram_code; static u32 strapping; @@ -34,28 +40,56 @@ static u32 chipid; u32 tegra_read_chipid(void) { - WARN(!chipid, "Tegra APB MISC not yet available\n"); + WARN(!apbmisc_base, "Tegra APB MISC not yet available\n"); + + if (!chipid) + chipid = readl_relaxed(apbmisc_base + 4); return chipid; } u8 tegra_get_chip_id(void) { +#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) + s32 soc_id = arm_smccc_get_soc_id_version(); + + if (soc_id >= 0) + return TEGRA_SMCCC_CHIP_ID(soc_id); +#endif return (tegra_read_chipid() >> 8) & 0xff; } u8 tegra_get_major_rev(void) { +#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) + s32 soc_id = arm_smccc_get_soc_id_version(); + + if (soc_id >= 0) + return TEGRA_SMCCC_MAJOR_REV(soc_id); +#endif return (tegra_read_chipid() >> 4) & 0xf; } u8 tegra_get_minor_rev(void) { +#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) + s32 revision = arm_smccc_get_soc_id_revision(); + + if (revision >= 0) + return TEGRA_SMCCC_MINOR_REV(revision); +#endif return (tegra_read_chipid() >> 16) & 0xf; + } u8 tegra_get_platform(void) { +#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) + s32 revision = arm_smccc_get_soc_id_revision(); + + if (revision >= 0) + return TEGRA_SMCCC_PLATFORM(revision); +#endif return (tegra_read_chipid() >> 20) & 0xf; } @@ -170,9 +204,7 @@ static void tegra_init_apbmisc_resources(struct resource *apbmisc, void __iomem *strapping_base; apbmisc_base = ioremap(apbmisc->start, resource_size(apbmisc)); - if (apbmisc_base) - chipid = readl_relaxed(apbmisc_base + 4); - else + if (!apbmisc_base) pr_err("failed to map APBMISC registers\n"); strapping_base = ioremap(straps->start, resource_size(straps)); From 2e9b940f07b518c8a4c69122c5857cae7d2bbae7 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 19 May 2026 09:56:18 +0200 Subject: [PATCH 167/189] dt-bindings: soc: renesas: mfis: Add R-Car V4H/V4M support The above SoCs have only 12 mailboxes and do not have an extra register space for mailboxes. Everything is contained in the common register set. In addition to adding these SoCs, the other entries get updated to enforce 2 register spaces and their specific number of interrupts. Signed-off-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Acked-by: Conor Dooley Link: https://patch.msgid.link/20260519075620.4128-2-wsa+renesas@sang-engineering.com Signed-off-by: Geert Uytterhoeven --- .../soc/renesas/renesas,r8a78000-mfis.yaml | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/soc/renesas/renesas,r8a78000-mfis.yaml b/Documentation/devicetree/bindings/soc/renesas/renesas,r8a78000-mfis.yaml index eef8c0a59e9c..72b9fa67d1f7 100644 --- a/Documentation/devicetree/bindings/soc/renesas/renesas,r8a78000-mfis.yaml +++ b/Documentation/devicetree/bindings/soc/renesas/renesas,r8a78000-mfis.yaml @@ -19,25 +19,29 @@ description: properties: compatible: enum: + - renesas,r8a779g0-mfis # R-Car V4H + - renesas,r8a779h0-mfis # R-Car V4M - renesas,r8a78000-mfis # R-Car X5H (AP<->AP, with PRR) - renesas,r8a78000-mfis-scp # R-Car X5H (AP<->SCP, without PRR) reg: + minItems: 1 maxItems: 2 reg-names: + minItems: 1 items: - const: common - const: mboxes interrupts: - minItems: 32 + minItems: 12 maxItems: 128 description: The interrupts raised by the remote doorbells. interrupt-names: - minItems: 32 + minItems: 12 maxItems: 128 description: An interrupt name is constructed with the prefix 'ch'. Then, the @@ -56,6 +60,26 @@ properties: . allOf: + - if: + properties: + compatible: + contains: + enum: + - renesas,r8a779g0-mfis + - renesas,r8a779h0-mfis + then: + properties: + reg: + maxItems: 1 + reg-names: + maxItems: 1 + interrupts: + maxItems: 12 + interrupt-names: + maxItems: 12 + items: + pattern: "^ch[0-9]+e$" + - if: properties: compatible: @@ -63,6 +87,10 @@ allOf: const: renesas,r8a78000-mfis then: properties: + reg: + minItems: 2 + reg-names: + minItems: 2 interrupts: minItems: 128 interrupt-names: @@ -77,9 +105,15 @@ allOf: const: renesas,r8a78000-mfis-scp then: properties: + reg: + minItems: 2 + reg-names: + minItems: 2 interrupts: + minItems: 32 maxItems: 32 interrupt-names: + minItems: 32 maxItems: 32 items: pattern: "^ch[0-9]+i$" From b4d41ffa750fc3403a4076d17090589d000f13ff Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 19 May 2026 09:56:19 +0200 Subject: [PATCH 168/189] soc: renesas: rcar-mfis: Add R-Car V4H/V4M support The above SoCs have a weird register layout for the mailbox registers. So, encapsulate register offset calculation in a per-SoC callback. Other than that, only a separate config struct and compatibles are needed. Signed-off-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260519075620.4128-3-wsa+renesas@sang-engineering.com Signed-off-by: Geert Uytterhoeven --- drivers/soc/renesas/rcar-mfis.c | 44 +++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/drivers/soc/renesas/rcar-mfis.c b/drivers/soc/renesas/rcar-mfis.c index b334b35306ae..3435c3e16198 100644 --- a/drivers/soc/renesas/rcar-mfis.c +++ b/drivers/soc/renesas/rcar-mfis.c @@ -41,6 +41,7 @@ struct mfis_info { unsigned int mb_reg_comes_from_dt:1; unsigned int mb_tx_uses_eicr:1; unsigned int mb_channels_are_unidir:1; + u32 (*mb_calc_reg)(u32 chan_num, bool tx_uses_eicr, bool is_only_rx); }; struct mfis_chan_priv { @@ -155,6 +156,35 @@ static const struct mbox_chan_ops mfis_iicr_ops = { .last_tx_done = mfis_mb_iicr_last_tx_done, }; +static u32 mfis_mb_r8a779g0_calc_reg(u32 chan_num, bool tx_uses_eicr, bool is_only_rx) +{ + unsigned int i, k; + u32 reg; + + i = chan_num & 3; + k = chan_num >> 2; + + if (is_only_rx) { + if (k < 2) + reg = 0x9404 + 0x1020 * k + 0x08 * i; + else + reg = 0xb504 + 0x08 * i; + } else { + if (k < 2) + reg = 0x1400 + 0x1008 * i + 0x20 * k; + else + reg = 0x1500 + 0x1008 * i; + } + + return reg; +} + +static u32 mfis_mb_r8a78000_calc_reg(u32 chan_num, bool tx_uses_eicr, bool is_only_rx) +{ + return (tx_uses_eicr ^ is_only_rx) ? MFIS_X5H_EICR(chan_num) : + MFIS_X5H_IICR(chan_num); +} + static struct mbox_chan *mfis_mb_of_xlate(struct mbox_controller *mbox, const struct of_phandle_args *sp) { @@ -191,8 +221,7 @@ static struct mbox_chan *mfis_mb_of_xlate(struct mbox_controller *mbox, } chan_priv = chan->con_priv; - chan_priv->reg = (tx_uses_eicr ^ is_only_rx) ? MFIS_X5H_EICR(chan_num) : - MFIS_X5H_IICR(chan_num); + chan_priv->reg = priv->info->mb_calc_reg(chan_num, tx_uses_eicr, is_only_rx); if (!priv->info->mb_channels_are_unidir || is_only_rx) { char irqname[8]; @@ -307,11 +336,19 @@ static int mfis_probe(struct platform_device *pdev) return mfis_mb_probe(priv); } +static const struct mfis_info mfis_info_r8a779g0 = { + .unprotect_mask = 0x0000ffff, + .mb_num_channels = 12, + .mb_channels_are_unidir = true, + .mb_calc_reg = mfis_mb_r8a779g0_calc_reg, +}; + static const struct mfis_info mfis_info_r8a78000 = { .unprotect_mask = 0x000fffff, .mb_num_channels = 64, .mb_reg_comes_from_dt = true, .mb_channels_are_unidir = true, + .mb_calc_reg = mfis_mb_r8a78000_calc_reg, }; static const struct mfis_info mfis_info_r8a78000_scp = { @@ -319,9 +356,12 @@ static const struct mfis_info mfis_info_r8a78000_scp = { .mb_num_channels = 32, .mb_tx_uses_eicr = true, .mb_channels_are_unidir = true, + .mb_calc_reg = mfis_mb_r8a78000_calc_reg, }; static const struct of_device_id mfis_mfd_of_match[] = { + { .compatible = "renesas,r8a779g0-mfis", .data = &mfis_info_r8a779g0, }, + { .compatible = "renesas,r8a779h0-mfis", .data = &mfis_info_r8a779g0, }, { .compatible = "renesas,r8a78000-mfis", .data = &mfis_info_r8a78000, }, { .compatible = "renesas,r8a78000-mfis-scp", .data = &mfis_info_r8a78000_scp, }, {} From d5cfe2a96d723b6962c5e38bc81cc9994347d326 Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:25 +0000 Subject: [PATCH 169/189] firmware: samsung: acpm: Consolidate transfer initialization helper Both the DVFS and PMIC ACPM sub-drivers implement similar local helper functions (acpm_dvfs_set_xfer and acpm_pmic_set_xfer) to initialize the acpm_xfer structure before sending an IPC message. Move this logic into a single centralized helper, acpm_set_xfer(), in the core ACPM driver to reduce boilerplate, eliminate code duplication, and prepare for the upcoming ACPM TMU helper sub-driver which will also utilize this method. Note that there is no change in underlying functionality. While the old acpm_pmic_set_xfer() unconditionally assigned the RX buffer parameters (xfer->rxd and xfer->rxcnt), the new unified helper introduces a 'response' boolean. All updated PMIC call sites now explicitly pass 'true' for this argument. This ensures the unified helper takes the 'if (response)' branch, performing the exact same assignments and preserving the original PMIC behavior. Signed-off-by: Tudor Ambarus Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260515-acpm-tmu-helpers-v2-1-8ca011d5a965@linaro.org Signed-off-by: Krzysztof Kozlowski --- drivers/firmware/samsung/exynos-acpm-dvfs.c | 20 ++-------------- drivers/firmware/samsung/exynos-acpm-pmic.c | 20 ++++------------ drivers/firmware/samsung/exynos-acpm.c | 26 +++++++++++++++++++++ drivers/firmware/samsung/exynos-acpm.h | 2 ++ 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/drivers/firmware/samsung/exynos-acpm-dvfs.c b/drivers/firmware/samsung/exynos-acpm-dvfs.c index fdea7aa24ca0..7266312ef5a6 100644 --- a/drivers/firmware/samsung/exynos-acpm-dvfs.c +++ b/drivers/firmware/samsung/exynos-acpm-dvfs.c @@ -21,22 +21,6 @@ #define ACPM_DVFS_FREQ_REQ 0 #define ACPM_DVFS_FREQ_GET 1 -static void acpm_dvfs_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen, - unsigned int acpm_chan_id, bool response) -{ - xfer->acpm_chan_id = acpm_chan_id; - xfer->txcnt = cmdlen; - xfer->txd = cmd; - - if (response) { - xfer->rxcnt = cmdlen; - xfer->rxd = cmd; - } else { - xfer->rxcnt = 0; - xfer->rxd = NULL; - } -} - static void acpm_dvfs_init_set_rate_cmd(u32 cmd[4], unsigned int clk_id, unsigned long rate) { @@ -54,7 +38,7 @@ int acpm_dvfs_set_rate(struct acpm_handle *handle, u32 cmd[4]; acpm_dvfs_init_set_rate_cmd(cmd, clk_id, rate); - acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, false); return acpm_do_xfer(handle, &xfer); } @@ -74,7 +58,7 @@ unsigned long acpm_dvfs_get_rate(struct acpm_handle *handle, int ret; acpm_dvfs_init_get_rate_cmd(cmd, clk_id); - acpm_dvfs_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) diff --git a/drivers/firmware/samsung/exynos-acpm-pmic.c b/drivers/firmware/samsung/exynos-acpm-pmic.c index 0c50993cc9a8..f032f2c69685 100644 --- a/drivers/firmware/samsung/exynos-acpm-pmic.c +++ b/drivers/firmware/samsung/exynos-acpm-pmic.c @@ -58,16 +58,6 @@ static inline u32 acpm_pmic_get_bulk(u32 data, unsigned int i) return (data >> (ACPM_PMIC_BULK_SHIFT * i)) & ACPM_PMIC_BULK_MASK; } -static void acpm_pmic_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen, - unsigned int acpm_chan_id) -{ - xfer->txd = cmd; - xfer->rxd = cmd; - xfer->txcnt = cmdlen; - xfer->rxcnt = cmdlen; - xfer->acpm_chan_id = acpm_chan_id; -} - static void acpm_pmic_init_read_cmd(u32 cmd[4], u8 type, u8 reg, u8 chan) { cmd[0] = FIELD_PREP(ACPM_PMIC_TYPE, type) | @@ -86,7 +76,7 @@ int acpm_pmic_read_reg(struct acpm_handle *handle, int ret; acpm_pmic_init_read_cmd(cmd, type, reg, chan); - acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -119,7 +109,7 @@ int acpm_pmic_bulk_read(struct acpm_handle *handle, return -EINVAL; acpm_pmic_init_bulk_read_cmd(cmd, type, reg, chan, count); - acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -159,7 +149,7 @@ int acpm_pmic_write_reg(struct acpm_handle *handle, int ret; acpm_pmic_init_write_cmd(cmd, type, reg, chan, value); - acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -199,7 +189,7 @@ int acpm_pmic_bulk_write(struct acpm_handle *handle, return -EINVAL; acpm_pmic_init_bulk_write_cmd(cmd, type, reg, chan, count, buf); - acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) @@ -229,7 +219,7 @@ int acpm_pmic_update_reg(struct acpm_handle *handle, int ret; acpm_pmic_init_update_cmd(cmd, type, reg, chan, value, mask); - acpm_pmic_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id); + acpm_set_xfer(&xfer, cmd, ARRAY_SIZE(cmd), acpm_chan_id, true); ret = acpm_do_xfer(handle, &xfer); if (ret) diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index 19db3674a28f..ec0929882b74 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -513,6 +513,32 @@ int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer) return acpm_wait_for_message_response(achan, xfer); } +/** + * acpm_set_xfer() - initialize an ACPM IPC transfer structure. + * @xfer: pointer to the ACPM transfer structure that is being initialized. + * @cmd: pointer to the buffer containing the command to be transmitted + * to the ACPM firmware. + * @cmdcnt: length of the command in 32-bit words. + * @acpm_chan_id: mailbox channel identifier. + * @response: boolean flag indicating whether the kernel expects the ACPM + * firmware to send a reply to this specific command. + */ +void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdcnt, + unsigned int acpm_chan_id, bool response) +{ + xfer->acpm_chan_id = acpm_chan_id; + xfer->txcnt = cmdcnt; + xfer->txd = cmd; + + if (response) { + xfer->rxcnt = cmdcnt; + xfer->rxd = cmd; + } else { + xfer->rxcnt = 0; + xfer->rxd = NULL; + } +} + /** * acpm_chan_shmem_get_params() - get channel parameters and addresses of the * TX/RX queues. diff --git a/drivers/firmware/samsung/exynos-acpm.h b/drivers/firmware/samsung/exynos-acpm.h index 5df8354dc96c..708f6b0102ac 100644 --- a/drivers/firmware/samsung/exynos-acpm.h +++ b/drivers/firmware/samsung/exynos-acpm.h @@ -17,6 +17,8 @@ struct acpm_xfer { struct acpm_handle; +void acpm_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdcnt, + unsigned int acpm_chan_id, bool response); int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer); From 447f406568c153819d6bfce994e422db4e95bb6f Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:26 +0000 Subject: [PATCH 170/189] firmware: samsung: acpm: Annotate rx_data->cmd with __counted_by_ptr Rename the `n_cmd` member of `struct acpm_rx_data` to `cmdcnt` to maintain consistent nomenclature across the driver (aligning with `txcnt`, `rxcnt`, and transfer helpers). With the member renamed, annotate the dynamically allocated `cmd` pointer with the `__counted_by_ptr(cmdcnt)` macro to improve runtime bounds checking. Signed-off-by: Tudor Ambarus Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260515-acpm-tmu-helpers-v2-2-8ca011d5a965@linaro.org Signed-off-by: Krzysztof Kozlowski --- drivers/firmware/samsung/exynos-acpm.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index ec0929882b74..69bb1008d1c0 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -104,14 +104,14 @@ struct acpm_queue { * struct acpm_rx_data - RX queue data. * * @cmd: pointer to where the data shall be saved. - * @n_cmd: number of 32-bit commands. + * @cmdcnt: allocated capacity of the @cmd buffer in 32-bit words. * @rxcnt: expected length of the response in 32-bit words. * @completed: flag indicating if the firmware response has been fully * processed. */ struct acpm_rx_data { - u32 *cmd; - size_t n_cmd; + u32 *cmd __counted_by_ptr(cmdcnt); + size_t cmdcnt; size_t rxcnt; bool completed; }; @@ -428,7 +428,7 @@ static int acpm_prepare_xfer(struct acpm_chan *achan, /* Clear data for upcoming responses */ rx_data = &achan->rx_data[bit]; rx_data->completed = false; - memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd); + memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->cmdcnt); /* zero means no response expected */ rx_data->rxcnt = xfer->rxcnt; @@ -580,19 +580,19 @@ static int acpm_achan_alloc_cmds(struct acpm_chan *achan) { struct device *dev = achan->acpm->dev; struct acpm_rx_data *rx_data; - size_t cmd_size, n_cmd; + size_t cmd_size, cmdcnt; int i; if (achan->mlen == 0) return 0; cmd_size = sizeof(*(achan->rx_data[0].cmd)); - n_cmd = DIV_ROUND_UP_ULL(achan->mlen, cmd_size); + cmdcnt = DIV_ROUND_UP_ULL(achan->mlen, cmd_size); for (i = 0; i < ACPM_SEQNUM_MAX; i++) { rx_data = &achan->rx_data[i]; - rx_data->n_cmd = n_cmd; - rx_data->cmd = devm_kcalloc(dev, n_cmd, cmd_size, GFP_KERNEL); + rx_data->cmdcnt = cmdcnt; + rx_data->cmd = devm_kcalloc(dev, cmdcnt, cmd_size, GFP_KERNEL); if (!rx_data->cmd) return -ENOMEM; } From 9d7a681915245e0e191abfd4b31d010c7037573c Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:27 +0000 Subject: [PATCH 171/189] firmware: samsung: acpm: Drop redundant _ops suffix in acpm_ops members Rename the `dvfs_ops` and `pmic_ops` members of `struct acpm_ops` to `dvfs` and `pmic` respectively. Since these members are housed within the `acpm_ops` structure and utilize the `acpm_*_ops` types, the `_ops` suffix on the variable names creates unnecessary redundancy (e.g., `handle.ops.dvfs_ops`). This cleanup removes the stuttering, leading to cleaner consumer code. Signed-off-by: Tudor Ambarus Reviewed-by: Peter Griffin Acked-by: Lee Jones Link: https://lore.kernel.org/linux-samsung-soc/CADrjBPqzKpcd9vuCmNUptCUPyPpPbHcc19-7kN-1c0RpW1e5DQ@mail.gmail.com/T/#mcce154a7e0c6cd1ca6cd5a1e37541ed7a85a84d4 [1] Link: https://patch.msgid.link/20260515-acpm-tmu-helpers-v2-3-8ca011d5a965@linaro.org Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/clk-acpm.c | 8 ++++---- drivers/firmware/samsung/exynos-acpm.c | 4 ++-- drivers/mfd/sec-acpm.c | 6 +++--- include/linux/firmware/samsung/exynos-acpm-protocol.h | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c index d8944160793a..93667777094c 100644 --- a/drivers/clk/samsung/clk-acpm.c +++ b/drivers/clk/samsung/clk-acpm.c @@ -68,8 +68,8 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw, { struct acpm_clk *clk = to_acpm_clk(hw); - return clk->handle->ops.dvfs_ops.get_rate(clk->handle, - clk->mbox_chan_id, clk->id); + return clk->handle->ops.dvfs.get_rate(clk->handle, clk->mbox_chan_id, + clk->id); } static int acpm_clk_determine_rate(struct clk_hw *hw, @@ -89,8 +89,8 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate, { struct acpm_clk *clk = to_acpm_clk(hw); - return clk->handle->ops.dvfs_ops.set_rate(clk->handle, - clk->mbox_chan_id, clk->id, rate); + return clk->handle->ops.dvfs.set_rate(clk->handle, clk->mbox_chan_id, + clk->id, rate); } static const struct clk_ops acpm_clk_ops = { diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index 69bb1008d1c0..bd2bfd8d8512 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -672,8 +672,8 @@ static int acpm_channels_init(struct acpm_info *acpm) */ static void acpm_setup_ops(struct acpm_info *acpm) { - struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs_ops; - struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic_ops; + struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs; + struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic; dvfs_ops->set_rate = acpm_dvfs_set_rate; dvfs_ops->get_rate = acpm_dvfs_get_rate; diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c index 0e23b9d9f7ee..9e15b260b8df 100644 --- a/drivers/mfd/sec-acpm.c +++ b/drivers/mfd/sec-acpm.c @@ -391,7 +391,7 @@ static int sec_pmic_acpm_bus_write(void *context, const void *data, { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic; size_t val_count = count - BITS_TO_BYTES(ACPM_ADDR_BITS); const u8 *d = data; const u8 *vals = &d[BITS_TO_BYTES(ACPM_ADDR_BITS)]; @@ -411,7 +411,7 @@ static int sec_pmic_acpm_bus_read(void *context, const void *reg_buf, size_t reg { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic; const u8 *r = reg_buf; u8 reg; @@ -430,7 +430,7 @@ static int sec_pmic_acpm_bus_reg_update_bits(void *context, unsigned int reg, un { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic_ops; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic; return pmic_ops->update_reg(acpm, ctx->shared->acpm_chan_id, ctx->type, reg & 0xff, ctx->shared->speedy_channel, val, mask); diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h index 13f17dc4443b..62a3eb450067 100644 --- a/include/linux/firmware/samsung/exynos-acpm-protocol.h +++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h @@ -35,8 +35,8 @@ struct acpm_pmic_ops { }; struct acpm_ops { - struct acpm_dvfs_ops dvfs_ops; - struct acpm_pmic_ops pmic_ops; + struct acpm_dvfs_ops dvfs; + struct acpm_pmic_ops pmic; }; /** From 50b400214abaec9122558e3f9c6fdd6295914c3c Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:28 +0000 Subject: [PATCH 172/189] firmware: samsung: acpm: Make acpm_ops const and access via pointer Replace the embedded `struct acpm_ops` inside `struct acpm_handle` with a pointer to a `const struct acpm_ops`. Previously, the operations structure was embedded directly within the handle and populated dynamically at runtime via `acpm_setup_ops()`. This resulted in mutable function pointers and unnecessary per-instance memory overhead. By defining `exynos_acpm_driver_ops` statically as a `const` structure, the function pointers are now safely housed in the read-only `.rodata` section. This improves security by preventing function pointer overwrites, saves memory, and slightly reduces initialization overhead in `acpm_probe()`. Consequently, update all consumer drivers (clk, mfd) to access the operations via the new pointer indirection (`->ops->`). Finally, fix the previously empty kernel-doc description for the ops member to reflect its new pointer nature. Signed-off-by: Tudor Ambarus Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260515-acpm-tmu-helpers-v2-4-8ca011d5a965@linaro.org Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/clk-acpm.c | 8 ++--- drivers/firmware/samsung/exynos-acpm.c | 36 +++++++++---------- drivers/mfd/sec-acpm.c | 6 ++-- .../firmware/samsung/exynos-acpm-protocol.h | 4 +-- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/drivers/clk/samsung/clk-acpm.c b/drivers/clk/samsung/clk-acpm.c index 93667777094c..953ca8d5720a 100644 --- a/drivers/clk/samsung/clk-acpm.c +++ b/drivers/clk/samsung/clk-acpm.c @@ -68,8 +68,8 @@ static unsigned long acpm_clk_recalc_rate(struct clk_hw *hw, { struct acpm_clk *clk = to_acpm_clk(hw); - return clk->handle->ops.dvfs.get_rate(clk->handle, clk->mbox_chan_id, - clk->id); + return clk->handle->ops->dvfs.get_rate(clk->handle, clk->mbox_chan_id, + clk->id); } static int acpm_clk_determine_rate(struct clk_hw *hw, @@ -89,8 +89,8 @@ static int acpm_clk_set_rate(struct clk_hw *hw, unsigned long rate, { struct acpm_clk *clk = to_acpm_clk(hw); - return clk->handle->ops.dvfs.set_rate(clk->handle, clk->mbox_chan_id, - clk->id, rate); + return clk->handle->ops->dvfs.set_rate(clk->handle, clk->mbox_chan_id, + clk->id, rate); } static const struct clk_ops acpm_clk_ops = { diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index bd2bfd8d8512..a1f73cc7cc8f 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -666,30 +666,26 @@ static int acpm_channels_init(struct acpm_info *acpm) return 0; } -/** - * acpm_setup_ops() - setup the operations structures. - * @acpm: pointer to the driver data. - */ -static void acpm_setup_ops(struct acpm_info *acpm) -{ - struct acpm_dvfs_ops *dvfs_ops = &acpm->handle.ops.dvfs; - struct acpm_pmic_ops *pmic_ops = &acpm->handle.ops.pmic; - - dvfs_ops->set_rate = acpm_dvfs_set_rate; - dvfs_ops->get_rate = acpm_dvfs_get_rate; - - pmic_ops->read_reg = acpm_pmic_read_reg; - pmic_ops->bulk_read = acpm_pmic_bulk_read; - pmic_ops->write_reg = acpm_pmic_write_reg; - pmic_ops->bulk_write = acpm_pmic_bulk_write; - pmic_ops->update_reg = acpm_pmic_update_reg; -} - static void acpm_clk_pdev_unregister(void *data) { platform_device_unregister(data); } +static const struct acpm_ops exynos_acpm_driver_ops = { + .dvfs = { + .set_rate = acpm_dvfs_set_rate, + .get_rate = acpm_dvfs_get_rate, + }, + + .pmic = { + .read_reg = acpm_pmic_read_reg, + .bulk_read = acpm_pmic_bulk_read, + .write_reg = acpm_pmic_write_reg, + .bulk_write = acpm_pmic_bulk_write, + .update_reg = acpm_pmic_update_reg, + }, +}; + static int acpm_probe(struct platform_device *pdev) { const struct acpm_match_data *match_data; @@ -730,7 +726,7 @@ static int acpm_probe(struct platform_device *pdev) if (ret) return ret; - acpm_setup_ops(acpm); + acpm->handle.ops = &exynos_acpm_driver_ops; platform_set_drvdata(pdev, acpm); diff --git a/drivers/mfd/sec-acpm.c b/drivers/mfd/sec-acpm.c index 9e15b260b8df..3397d13d3b7f 100644 --- a/drivers/mfd/sec-acpm.c +++ b/drivers/mfd/sec-acpm.c @@ -391,7 +391,7 @@ static int sec_pmic_acpm_bus_write(void *context, const void *data, { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic; size_t val_count = count - BITS_TO_BYTES(ACPM_ADDR_BITS); const u8 *d = data; const u8 *vals = &d[BITS_TO_BYTES(ACPM_ADDR_BITS)]; @@ -411,7 +411,7 @@ static int sec_pmic_acpm_bus_read(void *context, const void *reg_buf, size_t reg { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic; const u8 *r = reg_buf; u8 reg; @@ -430,7 +430,7 @@ static int sec_pmic_acpm_bus_reg_update_bits(void *context, unsigned int reg, un { struct sec_pmic_acpm_bus_context *ctx = context; struct acpm_handle *acpm = ctx->shared->acpm; - const struct acpm_pmic_ops *pmic_ops = &acpm->ops.pmic; + const struct acpm_pmic_ops *pmic_ops = &acpm->ops->pmic; return pmic_ops->update_reg(acpm, ctx->shared->acpm_chan_id, ctx->type, reg & 0xff, ctx->shared->speedy_channel, val, mask); diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h index 62a3eb450067..e13d9ac73ff6 100644 --- a/include/linux/firmware/samsung/exynos-acpm-protocol.h +++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h @@ -41,10 +41,10 @@ struct acpm_ops { /** * struct acpm_handle - Reference to an initialized protocol instance - * @ops: + * @ops: pointer to the constant ACPM protocol operations. */ struct acpm_handle { - struct acpm_ops ops; + const struct acpm_ops *ops; }; struct device; From 4ba23dfc49b4a3cefb4fdaa406172dfb8350098d Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:29 +0000 Subject: [PATCH 173/189] firmware: samsung: acpm: Add TMU protocol support The Thermal Management Unit (TMU) on the Google GS101 SoC is managed through a hybrid model shared between the kernel and the Alive Clock and Power Manager (ACPM) firmware. Add the protocol helpers required to communicate with the ACPM for thermal operations, including initialization, threshold configuration, temperature reading, and system suspend/resume handshakes. Signed-off-by: Tudor Ambarus Reviewed-by: Krzysztof Kozlowski Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260515-acpm-tmu-helpers-v2-5-8ca011d5a965@linaro.org Signed-off-by: Krzysztof Kozlowski --- drivers/firmware/samsung/Makefile | 1 + drivers/firmware/samsung/exynos-acpm-tmu.c | 239 ++++++++++++++++++ drivers/firmware/samsung/exynos-acpm-tmu.h | 28 ++ drivers/firmware/samsung/exynos-acpm.c | 12 + .../firmware/samsung/exynos-acpm-protocol.h | 18 ++ 5 files changed, 298 insertions(+) create mode 100644 drivers/firmware/samsung/exynos-acpm-tmu.c create mode 100644 drivers/firmware/samsung/exynos-acpm-tmu.h diff --git a/drivers/firmware/samsung/Makefile b/drivers/firmware/samsung/Makefile index 80d4f89b33a9..5a6f72bececf 100644 --- a/drivers/firmware/samsung/Makefile +++ b/drivers/firmware/samsung/Makefile @@ -3,4 +3,5 @@ acpm-protocol-objs := exynos-acpm.o acpm-protocol-objs += exynos-acpm-pmic.o acpm-protocol-objs += exynos-acpm-dvfs.o +acpm-protocol-objs += exynos-acpm-tmu.o obj-$(CONFIG_EXYNOS_ACPM_PROTOCOL) += acpm-protocol.o diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.c b/drivers/firmware/samsung/exynos-acpm-tmu.c new file mode 100644 index 000000000000..c68d60b4c0b3 --- /dev/null +++ b/drivers/firmware/samsung/exynos-acpm-tmu.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2020 Samsung Electronics Co., Ltd. + * Copyright 2020 Google LLC. + * Copyright 2026 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exynos-acpm.h" +#include "exynos-acpm-tmu.h" + +/* IPC Request Types */ +#define ACPM_TMU_INIT 0x01 +#define ACPM_TMU_READ_TEMP 0x02 +#define ACPM_TMU_SUSPEND 0x04 +#define ACPM_TMU_RESUME 0x10 +#define ACPM_TMU_THRESHOLD 0x11 +#define ACPM_TMU_INTEN 0x12 +#define ACPM_TMU_CONTROL 0x13 +#define ACPM_TMU_IRQ_CLEAR 0x14 + +#define ACPM_TMU_TX_DATA_LEN 8 +#define ACPM_TMU_RX_DATA_LEN 7 + +struct acpm_tmu_tx { + u16 ctx; + u16 fw_use; + u8 type; + u8 rsvd0; + u8 tzid; + u8 rsvd1; + u8 data[ACPM_TMU_TX_DATA_LEN]; +} __packed; + +struct acpm_tmu_rx { + u16 ctx; + u16 fw_use; + u8 type; + s8 ret; + u8 tzid; + s8 temp; + u8 rsvd; + u8 data[ACPM_TMU_RX_DATA_LEN]; +} __packed; + +union acpm_tmu_msg { + u32 data[4]; + struct acpm_tmu_tx tx; + struct acpm_tmu_rx rx; +}; + +static int acpm_tmu_to_linux_err(s8 fw_err) +{ + /* + * ACPM_TMU_INIT uses BIT(0) and BIT(1) of msg.rx.ret to flag APM + * capabilities. Treat zero and all positive values as success. + */ + if (fw_err >= 0) + return 0; + + if (fw_err == -1) + return -EACCES; + + return -EIO; +} + +int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_INIT; + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, int *temp) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_READ_TEMP; + msg.tx.tzid = tz; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + ret = acpm_tmu_to_linux_err(msg.rx.ret); + if (ret) + return ret; + + *temp = msg.rx.temp; + + return 0; +} + +int acpm_tmu_set_threshold(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, + const u8 temperature[8], size_t tlen) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + if (tlen > ACPM_TMU_TX_DATA_LEN) + return -EINVAL; + + msg.tx.type = ACPM_TMU_THRESHOLD; + msg.tx.tzid = tz; + memcpy(msg.tx.data, temperature, tlen); + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, u8 inten) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_INTEN; + msg.tx.tzid = tz; + msg.tx.data[0] = inten; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, bool enable) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_CONTROL; + msg.tx.tzid = tz; + msg.tx.data[0] = enable ? 1 : 0; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_IRQ_CLEAR; + msg.tx.tzid = tz; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_SUSPEND; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} + +int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id) +{ + union acpm_tmu_msg msg = {0}; + struct acpm_xfer xfer; + int ret; + + msg.tx.type = ACPM_TMU_RESUME; + + acpm_set_xfer(&xfer, msg.data, ARRAY_SIZE(msg.data), acpm_chan_id, + true); + + ret = acpm_do_xfer(handle, &xfer); + if (ret) + return ret; + + return acpm_tmu_to_linux_err(msg.rx.ret); +} diff --git a/drivers/firmware/samsung/exynos-acpm-tmu.h b/drivers/firmware/samsung/exynos-acpm-tmu.h new file mode 100644 index 000000000000..8b89f29fda67 --- /dev/null +++ b/drivers/firmware/samsung/exynos-acpm-tmu.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2020 Samsung Electronics Co., Ltd. + * Copyright 2020 Google LLC. + * Copyright 2026 Linaro Ltd. + */ +#ifndef __EXYNOS_ACPM_TMU_H__ +#define __EXYNOS_ACPM_TMU_H__ + +#include + +struct acpm_handle; + +int acpm_tmu_init(struct acpm_handle *handle, unsigned int acpm_chan_id); +int acpm_tmu_read_temp(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, int *temp); +int acpm_tmu_set_threshold(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, + const u8 temperature[8], size_t tlen); +int acpm_tmu_set_interrupt_enable(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, u8 inten); +int acpm_tmu_tz_control(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, bool enable); +int acpm_tmu_clear_tz_irq(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz); +int acpm_tmu_suspend(struct acpm_handle *handle, unsigned int acpm_chan_id); +int acpm_tmu_resume(struct acpm_handle *handle, unsigned int acpm_chan_id); +#endif /* __EXYNOS_ACPM_TMU_H__ */ diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index a1f73cc7cc8f..2f3b9a1bd74a 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -32,6 +32,7 @@ #include "exynos-acpm.h" #include "exynos-acpm-dvfs.h" #include "exynos-acpm-pmic.h" +#include "exynos-acpm-tmu.h" #define ACPM_PROTOCOL_SEQNUM GENMASK(21, 16) @@ -684,6 +685,17 @@ static const struct acpm_ops exynos_acpm_driver_ops = { .bulk_write = acpm_pmic_bulk_write, .update_reg = acpm_pmic_update_reg, }, + + .tmu = { + .init = acpm_tmu_init, + .read_temp = acpm_tmu_read_temp, + .set_threshold = acpm_tmu_set_threshold, + .set_interrupt_enable = acpm_tmu_set_interrupt_enable, + .tz_control = acpm_tmu_tz_control, + .clear_tz_irq = acpm_tmu_clear_tz_irq, + .suspend = acpm_tmu_suspend, + .resume = acpm_tmu_resume, + }, }; static int acpm_probe(struct platform_device *pdev) diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h index e13d9ac73ff6..8511c3c3983b 100644 --- a/include/linux/firmware/samsung/exynos-acpm-protocol.h +++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h @@ -34,9 +34,27 @@ struct acpm_pmic_ops { u8 type, u8 reg, u8 chan, u8 value, u8 mask); }; +struct acpm_tmu_ops { + int (*init)(struct acpm_handle *handle, unsigned int acpm_chan_id); + int (*read_temp)(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, int *temp); + int (*set_threshold)(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, + const u8 temperature[8], size_t tlen); + int (*set_interrupt_enable)(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz, u8 inten); + int (*tz_control)(struct acpm_handle *handle, unsigned int acpm_chan_id, + u8 tz, bool enable); + int (*clear_tz_irq)(struct acpm_handle *handle, + unsigned int acpm_chan_id, u8 tz); + int (*suspend)(struct acpm_handle *handle, unsigned int acpm_chan_id); + int (*resume)(struct acpm_handle *handle, unsigned int acpm_chan_id); +}; + struct acpm_ops { struct acpm_dvfs_ops dvfs; struct acpm_pmic_ops pmic; + struct acpm_tmu_ops tmu; }; /** From fc13a4b9b9c8cb0b8e5ba54b21712d00f810496c Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:30 +0000 Subject: [PATCH 174/189] firmware: samsung: acpm: Add devm_acpm_get_by_phandle helper Introduce devm_acpm_get_by_phandle() to standardize how consumer drivers acquire a handle to the ACPM IPC interface. Enforce the use of the "samsung,acpm-ipc" property name across the SoC and simplify the boilerplate code in client drivers. The first consumer of this helper is the Exynos ACPM Thermal Management Unit (TMU) driver. The TMU utilizes a hybrid management approach: direct register access from the Application Processor (AP) is restricted to the interrupt pending (INTPEND) registers for event identification. High-level functional tasks, such as sensor initialization, threshold programming, and temperature reads, are delegated to the ACPM firmware via this IPC interface. Signed-off-by: Tudor Ambarus Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260515-acpm-tmu-helpers-v2-6-8ca011d5a965@linaro.org Signed-off-by: Krzysztof Kozlowski --- drivers/firmware/samsung/exynos-acpm.c | 23 +++++++++++++++++++ .../firmware/samsung/exynos-acpm-protocol.h | 6 +++++ 2 files changed, 29 insertions(+) diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index 2f3b9a1bd74a..942a2e9f02f5 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -853,6 +853,29 @@ struct acpm_handle *devm_acpm_get_by_node(struct device *dev, } EXPORT_SYMBOL_GPL(devm_acpm_get_by_node); +/** + * devm_acpm_get_by_phandle - Resource managed lookup of the standardized + * "samsung,acpm-ipc" handle. + * @dev: consumer device + * + * Return: pointer to handle on success, ERR_PTR(-errno) otherwise. + */ +struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev) +{ + struct acpm_handle *handle; + struct device_node *np; + + np = of_parse_phandle(dev->of_node, "samsung,acpm-ipc", 0); + if (!np) + return ERR_PTR(-ENODEV); + + handle = devm_acpm_get_by_node(dev, np); + of_node_put(np); + + return handle; +} +EXPORT_SYMBOL_GPL(devm_acpm_get_by_phandle); + static const struct acpm_match_data acpm_gs101 = { .initdata_base = ACPM_GS101_INITDATA_BASE, .acpm_clk_dev_name = "gs101-acpm-clk", diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h index 8511c3c3983b..dbf052c4139b 100644 --- a/include/linux/firmware/samsung/exynos-acpm-protocol.h +++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h @@ -70,6 +70,7 @@ struct device; #if IS_ENABLED(CONFIG_EXYNOS_ACPM_PROTOCOL) struct acpm_handle *devm_acpm_get_by_node(struct device *dev, struct device_node *np); +struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev); #else static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev, @@ -77,6 +78,11 @@ static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev, { return NULL; } + +static inline struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev) +{ + return ERR_PTR(-ENODEV); +} #endif #endif /* __EXYNOS_ACPM_PROTOCOL_H */ From 7b661285aa7507eab79efff0a418445157db4141 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 29 May 2026 15:43:31 +0200 Subject: [PATCH 175/189] firmware: samsung: acpm: remove compile-testing stubs Sashiko reported an inconsistent use of NULL vs ERR_PTR() returns in the stub helpers in xynos-acpm-protocol.h. Since this only happens on dead code for COMPILE_TEST=y, this is not really a bug though. Having stub functions that return NULL is a common way to define optional interfaces, where callers still work when the feature is disabled, though this clearly does not work for acpm because some callers have a NULL pointer dereference when compile testing. Since CONFIG_EXYNOS_ACPM_PROTOCOL already supports compile-testing itself, and all (both) drivers using it clearly require the support, so this just simplifies the option space without losing any build coverage. Remove the stub functions entirely and adjust the one Kconfig dependency to require EXYNOS_ACPM_PROTOCOL unconditionally. Fixes: 6837c006d4e7 ("firmware: exynos-acpm: add empty method to allow compile test") Closes: https://sashiko.dev/#/patchset/20260420-acpm-tmu-v3-0-3dc8e93f0b26%40linaro.org Link: https://lore.kernel.org/all/a7994860-24a3-4f87-84bf-109ed653dda4@linaro.org/ Reviewed-by: Tudor Ambarus Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260529134454.2147446-1-arnd@kernel.org [krzk: Rebase on difference in devm_acpm_get_by_node()] Signed-off-by: Krzysztof Kozlowski --- drivers/clk/samsung/Kconfig | 2 +- .../linux/firmware/samsung/exynos-acpm-protocol.h | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/drivers/clk/samsung/Kconfig b/drivers/clk/samsung/Kconfig index 70a8b82a0136..198d8b621289 100644 --- a/drivers/clk/samsung/Kconfig +++ b/drivers/clk/samsung/Kconfig @@ -97,7 +97,7 @@ config EXYNOS_CLKOUT config EXYNOS_ACPM_CLK tristate "Clock driver controlled via ACPM interface" - depends on EXYNOS_ACPM_PROTOCOL || (COMPILE_TEST && !EXYNOS_ACPM_PROTOCOL) + depends on EXYNOS_ACPM_PROTOCOL help This driver provides support for clocks that are controlled by firmware that implements the ACPM interface. diff --git a/include/linux/firmware/samsung/exynos-acpm-protocol.h b/include/linux/firmware/samsung/exynos-acpm-protocol.h index dbf052c4139b..c6b35c0ff300 100644 --- a/include/linux/firmware/samsung/exynos-acpm-protocol.h +++ b/include/linux/firmware/samsung/exynos-acpm-protocol.h @@ -67,22 +67,8 @@ struct acpm_handle { struct device; -#if IS_ENABLED(CONFIG_EXYNOS_ACPM_PROTOCOL) struct acpm_handle *devm_acpm_get_by_node(struct device *dev, struct device_node *np); struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev); -#else - -static inline struct acpm_handle *devm_acpm_get_by_node(struct device *dev, - struct device_node *np) -{ - return NULL; -} - -static inline struct acpm_handle *devm_acpm_get_by_phandle(struct device *dev) -{ - return ERR_PTR(-ENODEV); -} -#endif #endif /* __EXYNOS_ACPM_PROTOCOL_H */ From 4d61f03426892b14d498e160be245edcb9dd8359 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Feb 2025 18:12:58 +0100 Subject: [PATCH 176/189] soc/tegra: pmc: Create PMC context dynamically For legacy purposes, an early PMC context is needed to support certain drivers and functionalities. However, when the PMC driver is probed in the later boot stages, the early context is no longer needed. Allocate the PMC context dynamically at probe time so that it can be used going forward. While at it, rename the early PMC context to more accurately reflect what it is used for. It's technically not only for early boot stages, but also to support some code that doesn't have a way of obtaining the correct context otherwise (e.g. no access to device tree). Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 208 ++++++++++++++++++++++------------------ 1 file changed, 115 insertions(+), 93 deletions(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 2ee6539d796a..ff748d209d40 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -390,8 +390,7 @@ struct tegra_pmc_soc { const struct tegra_pmc_regs *regs; void (*init)(struct tegra_pmc *pmc); void (*setup_irq_polarity)(struct tegra_pmc *pmc, - struct device_node *np, - bool invert); + struct device_node *np); void (*set_wake_filters)(struct tegra_pmc *pmc); int (*irq_set_wake)(struct irq_data *data, unsigned int on); int (*irq_set_type)(struct irq_data *data, unsigned int type); @@ -444,6 +443,7 @@ struct tegra_pmc_soc { * @cpu_pwr_good_en: CPU power good signal is enabled * @lp0_vec_phys: physical base address of the LP0 warm boot code * @lp0_vec_size: size of the LP0 warm boot code + * @invert_irq: polarity of the PMU IRQ is inverted * @powergates_available: Bitmap of available power gates * @powergates_lock: mutex for power gate register access * @pctl_dev: pin controller exposed by the PMC @@ -486,6 +486,8 @@ struct tegra_pmc { bool cpu_pwr_good_en; u32 lp0_vec_phys; u32 lp0_vec_size; + bool invert_irq; + DECLARE_BITMAP(powergates_available, TEGRA_POWERGATE_MAX); struct mutex powergates_lock; @@ -512,7 +514,7 @@ struct tegra_pmc { u32 *wake_status; }; -static struct tegra_pmc *pmc = &(struct tegra_pmc) { +static struct tegra_pmc *early_pmc = &(struct tegra_pmc) { .base = NULL, .suspend_mode = TEGRA_SUSPEND_NOT_READY, }; @@ -1069,7 +1071,7 @@ EXPORT_SYMBOL(tegra_pmc_powergate_power_on); */ int tegra_powergate_power_on(unsigned int id) { - return tegra_pmc_powergate_power_on(pmc, id); + return tegra_pmc_powergate_power_on(early_pmc, id); } EXPORT_SYMBOL(tegra_powergate_power_on); @@ -1093,7 +1095,7 @@ EXPORT_SYMBOL(tegra_pmc_powergate_power_off); */ int tegra_powergate_power_off(unsigned int id) { - return tegra_pmc_powergate_power_off(pmc, id); + return tegra_pmc_powergate_power_off(early_pmc, id); } EXPORT_SYMBOL(tegra_powergate_power_off); @@ -1130,7 +1132,7 @@ EXPORT_SYMBOL(tegra_pmc_powergate_remove_clamping); */ int tegra_powergate_remove_clamping(unsigned int id) { - return tegra_pmc_powergate_remove_clamping(pmc, id); + return tegra_pmc_powergate_remove_clamping(early_pmc, id); } EXPORT_SYMBOL(tegra_powergate_remove_clamping); @@ -1192,7 +1194,7 @@ EXPORT_SYMBOL(tegra_pmc_powergate_sequence_power_up); int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, struct reset_control *rst) { - return tegra_pmc_powergate_sequence_power_up(pmc, id, clk, rst); + return tegra_pmc_powergate_sequence_power_up(early_pmc, id, clk, rst); } EXPORT_SYMBOL(tegra_powergate_sequence_power_up); @@ -1221,11 +1223,11 @@ bool tegra_pmc_cpu_is_powered(unsigned int cpuid) { int id; - id = tegra_get_cpu_powergate_id(pmc, cpuid); + id = tegra_get_cpu_powergate_id(early_pmc, cpuid); if (id < 0) return false; - return tegra_powergate_is_powered(pmc, id); + return tegra_powergate_is_powered(early_pmc, id); } /** @@ -1236,11 +1238,11 @@ int tegra_pmc_cpu_power_on(unsigned int cpuid) { int id; - id = tegra_get_cpu_powergate_id(pmc, cpuid); + id = tegra_get_cpu_powergate_id(early_pmc, cpuid); if (id < 0) return id; - return tegra_powergate_set(pmc, id, true); + return tegra_powergate_set(early_pmc, id, true); } /** @@ -1251,11 +1253,11 @@ int tegra_pmc_cpu_remove_clamping(unsigned int cpuid) { int id; - id = tegra_get_cpu_powergate_id(pmc, cpuid); + id = tegra_get_cpu_powergate_id(early_pmc, cpuid); if (id < 0) return id; - return tegra_powergate_remove_clamping(id); + return tegra_pmc_powergate_remove_clamping(early_pmc, id); } static void tegra_pmc_program_reboot_reason(struct tegra_pmc *pmc, @@ -1533,7 +1535,7 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) bool tegra_pmc_core_domain_state_synced(void) { - return pmc->core_domain_state_synced; + return early_pmc->core_domain_state_synced; } static int @@ -1831,7 +1833,7 @@ EXPORT_SYMBOL(tegra_pmc_io_pad_power_enable); */ int tegra_io_pad_power_enable(enum tegra_io_pad id) { - return tegra_pmc_io_pad_power_enable(pmc, id); + return tegra_pmc_io_pad_power_enable(early_pmc, id); } EXPORT_SYMBOL(tegra_io_pad_power_enable); @@ -1887,7 +1889,7 @@ EXPORT_SYMBOL(tegra_pmc_io_pad_power_disable); */ int tegra_io_pad_power_disable(enum tegra_io_pad id) { - return tegra_pmc_io_pad_power_disable(pmc, id); + return tegra_pmc_io_pad_power_disable(early_pmc, id); } EXPORT_SYMBOL(tegra_io_pad_power_disable); @@ -1976,7 +1978,7 @@ static int tegra_io_pad_get_voltage(struct tegra_pmc *pmc, enum tegra_io_pad id) #ifdef CONFIG_PM_SLEEP enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) { - return pmc->suspend_mode; + return early_pmc->suspend_mode; } void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) @@ -1984,7 +1986,7 @@ void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) if (mode < TEGRA_SUSPEND_NONE || mode >= TEGRA_MAX_SUSPEND_MODE) return; - pmc->suspend_mode = mode; + early_pmc->suspend_mode = mode; } void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) @@ -1999,7 +2001,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) break; case TEGRA_SUSPEND_LP2: - rate = pmc->rate; + rate = early_pmc->rate; break; default: @@ -2009,18 +2011,18 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) if (WARN_ON_ONCE(rate == 0)) rate = 100000000; - ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1; + ticks = early_pmc->cpu_good_time * rate + USEC_PER_SEC - 1; do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER); + tegra_pmc_writel(early_pmc, ticks, PMC_CPUPWRGOOD_TIMER); - ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1; + ticks = early_pmc->cpu_off_time * rate + USEC_PER_SEC - 1; do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER); + tegra_pmc_writel(early_pmc, ticks, PMC_CPUPWROFF_TIMER); - value = tegra_pmc_readl(pmc, PMC_CNTRL); + value = tegra_pmc_readl(early_pmc, PMC_CNTRL); value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; value |= PMC_CNTRL_CPU_PWRREQ_OE; - tegra_pmc_writel(pmc, value, PMC_CNTRL); + tegra_pmc_writel(early_pmc, value, PMC_CNTRL); } #endif @@ -2094,6 +2096,8 @@ static int tegra_pmc_parse_dt(struct tegra_pmc *pmc, struct device_node *np) pmc->lp0_vec_phys = values[0]; pmc->lp0_vec_size = values[1]; + pmc->invert_irq = of_property_read_bool(np, "nvidia,invert-interrupt"); + return 0; } @@ -3110,6 +3114,51 @@ static int tegra_pmc_regmap_init(struct tegra_pmc *pmc) return 0; } +static bool tegra_pmc_detect_tz_only(struct tegra_pmc *pmc) +{ + u32 value, saved; + + saved = readl(pmc->base + pmc->soc->regs->scratch0); + value = saved ^ 0xffffffff; + + if (value == 0xffffffff) + value = 0xdeadbeef; + + /* write pattern and read it back */ + writel(value, pmc->base + pmc->soc->regs->scratch0); + value = readl(pmc->base + pmc->soc->regs->scratch0); + + /* if we read all-zeroes, access is restricted to TZ only */ + if (value == 0) { + pr_info("access to PMC is restricted to TZ\n"); + return true; + } + + /* restore original value */ + writel(saved, pmc->base + pmc->soc->regs->scratch0); + + return false; +} + +static void tegra_pmc_init_common(struct tegra_pmc *pmc, struct device_node *np) +{ + unsigned int i; + + if (pmc->soc->maybe_tz_only) + pmc->tz_only = tegra_pmc_detect_tz_only(pmc); + + /* Create a bitmap of the available and valid partitions */ + for (i = 0; i < pmc->soc->num_powergates; i++) + if (pmc->soc->powergates[i]) + set_bit(i, pmc->powergates_available); + + /* + * Invert the interrupt polarity if a PMC device tree node exists and + * contains the nvidia,invert-interrupt property. + */ + pmc->soc->setup_irq_polarity(pmc, np); +} + static void tegra_pmc_reset_suspend_mode(void *data) { struct tegra_pmc *pmc = data; @@ -3119,7 +3168,7 @@ static void tegra_pmc_reset_suspend_mode(void *data) static int tegra_pmc_probe(struct platform_device *pdev) { - void __iomem *base; + struct tegra_pmc *pmc; struct resource *res; int err; @@ -3128,12 +3177,20 @@ static int tegra_pmc_probe(struct platform_device *pdev) * register mapping and setup the soc data pointer. If these * are not valid then something went badly wrong! */ - if (WARN_ON(!pmc->base || !pmc->soc)) + if (WARN_ON(!early_pmc->base || !early_pmc->soc)) return -ENODEV; + pmc = devm_kzalloc(&pdev->dev, sizeof(*pmc), GFP_KERNEL); + if (!pmc) + return -ENOMEM; + + pmc->soc = device_get_match_data(&pdev->dev); + mutex_init(&pmc->powergates_lock); + pmc->dev = &pdev->dev; + err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node); if (err < 0) - return err; + return dev_err_probe(&pdev->dev, err, "failed to parse DT\n"); err = devm_add_action_or_reset(&pdev->dev, tegra_pmc_reset_suspend_mode, pmc); @@ -3141,14 +3198,14 @@ static int tegra_pmc_probe(struct platform_device *pdev) return err; /* take over the memory region from the early initialization */ - base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(base)) - return PTR_ERR(base); + pmc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pmc->base)) + return PTR_ERR(pmc->base); if (pmc->soc->has_single_mmio_aperture) { - pmc->wake = base; - pmc->aotag = base; - pmc->scratch = base; + pmc->wake = pmc->base; + pmc->aotag = pmc->base; + pmc->scratch = pmc->base; } else { pmc->wake = devm_platform_ioremap_resource_byname(pdev, "wake"); if (IS_ERR(pmc->wake)) @@ -3167,7 +3224,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) /* "scratch" is an optional aperture */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "scratch"); + "scratch"); if (res) { pmc->scratch = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(pmc->scratch)) @@ -3182,6 +3239,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(pmc->clk), "failed to get pclk\n"); + tegra_pmc_init_common(pmc, pdev->dev.of_node); + /* * PMC should be last resort for restarting since it soft-resets * CPU without resetting everything else. @@ -3243,8 +3302,6 @@ static int tegra_pmc_probe(struct platform_device *pdev) pmc->rate = clk_get_rate(pmc->clk); } - pmc->dev = &pdev->dev; - err = tegra_pmc_init(pmc); if (err < 0) { dev_err(&pdev->dev, "failed to initialize PMC: %d\n", err); @@ -3271,10 +3328,10 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (err < 0) goto cleanup_powergates; - mutex_lock(&pmc->powergates_lock); - iounmap(pmc->base); - pmc->base = base; - mutex_unlock(&pmc->powergates_lock); + mutex_lock(&early_pmc->powergates_lock); + iounmap(early_pmc->base); + early_pmc->base = pmc->base; + mutex_unlock(&early_pmc->powergates_lock); tegra_pmc_clock_register(pmc, pdev->dev.of_node); platform_set_drvdata(pdev, pmc); @@ -3523,14 +3580,13 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc) } static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc, - struct device_node *np, - bool invert) + struct device_node *np) { u32 value; value = tegra_pmc_readl(pmc, PMC_CNTRL); - if (invert) + if (pmc->invert_irq) value |= PMC_CNTRL_INTR_POLARITY; else value &= ~PMC_CNTRL_INTR_POLARITY; @@ -4136,8 +4192,7 @@ static void tegra186_pmc_init(struct tegra_pmc *pmc) } static void tegra186_pmc_setup_irq_polarity(struct tegra_pmc *pmc, - struct device_node *np, - bool invert) + struct device_node *np) { struct resource regs; void __iomem *wake; @@ -4160,7 +4215,7 @@ static void tegra186_pmc_setup_irq_polarity(struct tegra_pmc *pmc, value = readl(wake + pmc->soc->regs->aowake_ctrl); - if (invert) + if (pmc->invert_irq) value |= WAKE_AOWAKE_CTRL_INTR_POLARITY; else value &= ~WAKE_AOWAKE_CTRL_INTR_POLARITY; @@ -4799,6 +4854,7 @@ static const struct of_device_id tegra_pmc_match[] = { static void tegra_pmc_sync_state(struct device *dev) { + struct tegra_pmc *pmc = dev_get_drvdata(dev); struct device_node *np, *child; int err; @@ -4856,32 +4912,6 @@ static struct platform_driver tegra_pmc_driver = { }; builtin_platform_driver(tegra_pmc_driver); -static bool __init tegra_pmc_detect_tz_only(struct tegra_pmc *pmc) -{ - u32 value, saved; - - saved = readl(pmc->base + pmc->soc->regs->scratch0); - value = saved ^ 0xffffffff; - - if (value == 0xffffffff) - value = 0xdeadbeef; - - /* write pattern and read it back */ - writel(value, pmc->base + pmc->soc->regs->scratch0); - value = readl(pmc->base + pmc->soc->regs->scratch0); - - /* if we read all-zeroes, access is restricted to TZ only */ - if (value == 0) { - pr_info("access to PMC is restricted to TZ\n"); - return true; - } - - /* restore original value */ - writel(saved, pmc->base + pmc->soc->regs->scratch0); - - return false; -} - /* * Early initialization to allow access to registers in the very early boot * process. @@ -4891,10 +4921,9 @@ static int __init tegra_pmc_early_init(void) const struct of_device_id *match; struct device_node *np; struct resource regs; - unsigned int i; - bool invert; + int err; - mutex_init(&pmc->powergates_lock); + mutex_init(&early_pmc->powergates_lock); np = of_find_matching_node_and_match(NULL, tegra_pmc_match, &match); if (!np) { @@ -4935,31 +4964,24 @@ static int __init tegra_pmc_early_init(void) } } - pmc->base = ioremap(regs.start, resource_size(®s)); - if (!pmc->base) { + early_pmc->base = ioremap(regs.start, resource_size(®s)); + if (!early_pmc->base) { pr_err("failed to map PMC registers\n"); of_node_put(np); return -ENXIO; } if (of_device_is_available(np)) { - pmc->soc = match->data; + early_pmc->soc = match->data; - if (pmc->soc->maybe_tz_only) - pmc->tz_only = tegra_pmc_detect_tz_only(pmc); + err = tegra_pmc_parse_dt(early_pmc, np); + if (err < 0) { + pr_err("failed to parse DT: %d\n", err); + of_node_put(np); + return err; + } - /* Create a bitmap of the available and valid partitions */ - for (i = 0; i < pmc->soc->num_powergates; i++) - if (pmc->soc->powergates[i]) - set_bit(i, pmc->powergates_available); - - /* - * Invert the interrupt polarity if a PMC device tree node - * exists and contains the nvidia,invert-interrupt property. - */ - invert = of_property_read_bool(np, "nvidia,invert-interrupt"); - - pmc->soc->setup_irq_polarity(pmc, np, invert); + tegra_pmc_init_common(early_pmc, np); of_node_put(np); } From 72a2a9023c329aca7e7f8d3997c9a26aa317a1fd Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 4 Feb 2025 19:45:19 +0100 Subject: [PATCH 177/189] soc/tegra: pmc: Remove unused legacy functions All callers of these functions have been replaced by their variants taking a PMC context as an input, so they are no longer used and can be removed. Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 69 ----------------------------------------- include/soc/tegra/pmc.h | 47 ---------------------------- 2 files changed, 116 deletions(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index ff748d209d40..0acc25e7b732 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -1065,16 +1065,6 @@ int tegra_pmc_powergate_power_on(struct tegra_pmc *pmc, unsigned int id) } EXPORT_SYMBOL(tegra_pmc_powergate_power_on); -/** - * tegra_powergate_power_on() - power on partition - * @id: partition ID - */ -int tegra_powergate_power_on(unsigned int id) -{ - return tegra_pmc_powergate_power_on(early_pmc, id); -} -EXPORT_SYMBOL(tegra_powergate_power_on); - /** * tegra_pmc_powergate_power_off() - power off partition * @pmc: power management controller @@ -1089,16 +1079,6 @@ int tegra_pmc_powergate_power_off(struct tegra_pmc *pmc, unsigned int id) } EXPORT_SYMBOL(tegra_pmc_powergate_power_off); -/** - * tegra_powergate_power_off() - power off partition - * @id: partition ID - */ -int tegra_powergate_power_off(unsigned int id) -{ - return tegra_pmc_powergate_power_off(early_pmc, id); -} -EXPORT_SYMBOL(tegra_powergate_power_off); - /** * tegra_powergate_is_powered() - check if partition is powered * @pmc: power management controller @@ -1126,16 +1106,6 @@ int tegra_pmc_powergate_remove_clamping(struct tegra_pmc *pmc, unsigned int id) } EXPORT_SYMBOL(tegra_pmc_powergate_remove_clamping); -/** - * tegra_powergate_remove_clamping() - remove power clamps for partition - * @id: partition ID - */ -int tegra_powergate_remove_clamping(unsigned int id) -{ - return tegra_pmc_powergate_remove_clamping(early_pmc, id); -} -EXPORT_SYMBOL(tegra_powergate_remove_clamping); - /** * tegra_pmc_powergate_sequence_power_up() - power up partition * @pmc: power management controller @@ -1183,21 +1153,6 @@ int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, } EXPORT_SYMBOL(tegra_pmc_powergate_sequence_power_up); -/** - * tegra_powergate_sequence_power_up() - power up partition - * @id: partition ID - * @clk: clock for partition - * @rst: reset for partition - * - * Must be called with clk disabled, and returns with clk enabled. - */ -int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, - struct reset_control *rst) -{ - return tegra_pmc_powergate_sequence_power_up(early_pmc, id, clk, rst); -} -EXPORT_SYMBOL(tegra_powergate_sequence_power_up); - /** * tegra_get_cpu_powergate_id() - convert from CPU ID to partition ID * @pmc: power management controller @@ -1825,18 +1780,6 @@ int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id) } EXPORT_SYMBOL(tegra_pmc_io_pad_power_enable); -/** - * tegra_io_pad_power_enable() - enable power to I/O pad - * @id: Tegra I/O pad ID for which to enable power - * - * Returns: 0 on success or a negative error code on failure. - */ -int tegra_io_pad_power_enable(enum tegra_io_pad id) -{ - return tegra_pmc_io_pad_power_enable(early_pmc, id); -} -EXPORT_SYMBOL(tegra_io_pad_power_enable); - /** * tegra_pmc_io_pad_power_disable() - disable power to I/O pad * @pmc: power management controller @@ -1881,18 +1824,6 @@ int tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id) } EXPORT_SYMBOL(tegra_pmc_io_pad_power_disable); -/** - * tegra_io_pad_power_disable() - disable power to I/O pad - * @id: Tegra I/O pad ID for which to disable power - * - * Returns: 0 on success or a negative error code on failure. - */ -int tegra_io_pad_power_disable(enum tegra_io_pad id) -{ - return tegra_pmc_io_pad_power_disable(early_pmc, id); -} -EXPORT_SYMBOL(tegra_io_pad_power_disable); - static int tegra_io_pad_is_powered(struct tegra_pmc *pmc, enum tegra_io_pad id) { const struct tegra_io_pad_soc *pad; diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index 1fd21be02577..8b4bcdea849e 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -164,16 +164,6 @@ int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id); int tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id); /* legacy */ -int tegra_powergate_power_on(unsigned int id); -int tegra_powergate_power_off(unsigned int id); -int tegra_powergate_remove_clamping(unsigned int id); - -int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk, - struct reset_control *rst); - -int tegra_io_pad_power_enable(enum tegra_io_pad id); -int tegra_io_pad_power_disable(enum tegra_io_pad id); - void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode); void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode); @@ -224,43 +214,6 @@ tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id) return -ENOSYS; } -static inline int tegra_powergate_power_on(unsigned int id) -{ - return -ENOSYS; -} - -static inline int tegra_powergate_power_off(unsigned int id) -{ - return -ENOSYS; -} - -static inline int tegra_powergate_remove_clamping(unsigned int id) -{ - return -ENOSYS; -} - -static inline int tegra_powergate_sequence_power_up(unsigned int id, - struct clk *clk, - struct reset_control *rst) -{ - return -ENOSYS; -} - -static inline int tegra_io_pad_power_enable(enum tegra_io_pad id) -{ - return -ENOSYS; -} - -static inline int tegra_io_pad_power_disable(enum tegra_io_pad id) -{ - return -ENOSYS; -} - -static inline int tegra_io_pad_get_voltage(enum tegra_io_pad id) -{ - return -ENOSYS; -} - static inline void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) { } From 8318af5dd29c819d95ec5b878e7dab6090a9ed2b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 4 Feb 2025 19:04:51 +0100 Subject: [PATCH 178/189] soc/tegra: pmc: Move legacy code behind CONFIG_ARM guard None of this legacy code is needed on 64-bit ARM devices, so it can be moved behind a corresponding preprocessor guard. This more cleanly separates out the legacy code from code needed on current platforms. Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 244 +++++++++++++++++++++------------------- include/soc/tegra/pmc.h | 42 +++---- 2 files changed, 147 insertions(+), 139 deletions(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 0acc25e7b732..da60e7e5927d 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -514,10 +514,12 @@ struct tegra_pmc { u32 *wake_status; }; +#if defined(CONFIG_ARM) static struct tegra_pmc *early_pmc = &(struct tegra_pmc) { .base = NULL, .suspend_mode = TEGRA_SUSPEND_NOT_READY, }; +#endif static inline struct tegra_powergate * to_powergate(struct generic_pm_domain *domain) @@ -1153,68 +1155,6 @@ int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, } EXPORT_SYMBOL(tegra_pmc_powergate_sequence_power_up); -/** - * tegra_get_cpu_powergate_id() - convert from CPU ID to partition ID - * @pmc: power management controller - * @cpuid: CPU partition ID - * - * Returns the partition ID corresponding to the CPU partition ID or a - * negative error code on failure. - */ -static int tegra_get_cpu_powergate_id(struct tegra_pmc *pmc, - unsigned int cpuid) -{ - if (pmc->soc && cpuid < pmc->soc->num_cpu_powergates) - return pmc->soc->cpu_powergates[cpuid]; - - return -EINVAL; -} - -/** - * tegra_pmc_cpu_is_powered() - check if CPU partition is powered - * @cpuid: CPU partition ID - */ -bool tegra_pmc_cpu_is_powered(unsigned int cpuid) -{ - int id; - - id = tegra_get_cpu_powergate_id(early_pmc, cpuid); - if (id < 0) - return false; - - return tegra_powergate_is_powered(early_pmc, id); -} - -/** - * tegra_pmc_cpu_power_on() - power on CPU partition - * @cpuid: CPU partition ID - */ -int tegra_pmc_cpu_power_on(unsigned int cpuid) -{ - int id; - - id = tegra_get_cpu_powergate_id(early_pmc, cpuid); - if (id < 0) - return id; - - return tegra_powergate_set(early_pmc, id, true); -} - -/** - * tegra_pmc_cpu_remove_clamping() - remove power clamps for CPU partition - * @cpuid: CPU partition ID - */ -int tegra_pmc_cpu_remove_clamping(unsigned int cpuid) -{ - int id; - - id = tegra_get_cpu_powergate_id(early_pmc, cpuid); - if (id < 0) - return id; - - return tegra_pmc_powergate_remove_clamping(early_pmc, id); -} - static void tegra_pmc_program_reboot_reason(struct tegra_pmc *pmc, const char *cmd) { @@ -1488,11 +1428,6 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) return err; } -bool tegra_pmc_core_domain_state_synced(void) -{ - return early_pmc->core_domain_state_synced; -} - static int tegra_pmc_core_pd_set_performance_state(struct generic_pm_domain *genpd, unsigned int level) @@ -1906,57 +1841,6 @@ static int tegra_io_pad_get_voltage(struct tegra_pmc *pmc, enum tegra_io_pad id) return TEGRA_IO_PAD_VOLTAGE_3V3; } -#ifdef CONFIG_PM_SLEEP -enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) -{ - return early_pmc->suspend_mode; -} - -void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) -{ - if (mode < TEGRA_SUSPEND_NONE || mode >= TEGRA_MAX_SUSPEND_MODE) - return; - - early_pmc->suspend_mode = mode; -} - -void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) -{ - unsigned long long rate = 0; - u64 ticks; - u32 value; - - switch (mode) { - case TEGRA_SUSPEND_LP1: - rate = 32768; - break; - - case TEGRA_SUSPEND_LP2: - rate = early_pmc->rate; - break; - - default: - break; - } - - if (WARN_ON_ONCE(rate == 0)) - rate = 100000000; - - ticks = early_pmc->cpu_good_time * rate + USEC_PER_SEC - 1; - do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(early_pmc, ticks, PMC_CPUPWRGOOD_TIMER); - - ticks = early_pmc->cpu_off_time * rate + USEC_PER_SEC - 1; - do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(early_pmc, ticks, PMC_CPUPWROFF_TIMER); - - value = tegra_pmc_readl(early_pmc, PMC_CNTRL); - value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; - value |= PMC_CNTRL_CPU_PWRREQ_OE; - tegra_pmc_writel(early_pmc, value, PMC_CNTRL); -} -#endif - static int tegra_pmc_parse_dt(struct tegra_pmc *pmc, struct device_node *np) { u32 value, values[2]; @@ -3103,6 +2987,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) struct resource *res; int err; +#if defined(CONFIG_ARM) /* * Early initialisation should have configured an initial * register mapping and setup the soc data pointer. If these @@ -3110,6 +2995,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) */ if (WARN_ON(!early_pmc->base || !early_pmc->soc)) return -ENODEV; +#endif pmc = devm_kzalloc(&pdev->dev, sizeof(*pmc), GFP_KERNEL); if (!pmc) @@ -3259,10 +3145,12 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (err < 0) goto cleanup_powergates; +#if defined(CONFIG_ARM) mutex_lock(&early_pmc->powergates_lock); iounmap(early_pmc->base); early_pmc->base = pmc->base; mutex_unlock(&early_pmc->powergates_lock); +#endif tegra_pmc_clock_register(pmc, pdev->dev.of_node); platform_set_drvdata(pdev, pmc); @@ -4843,6 +4731,125 @@ static struct platform_driver tegra_pmc_driver = { }; builtin_platform_driver(tegra_pmc_driver); +#if defined(CONFIG_ARM) +/** + * tegra_get_cpu_powergate_id() - convert from CPU ID to partition ID + * @pmc: power management controller + * @cpuid: CPU partition ID + * + * Returns the partition ID corresponding to the CPU partition ID or a + * negative error code on failure. + */ +static int tegra_get_cpu_powergate_id(struct tegra_pmc *pmc, + unsigned int cpuid) +{ + if (pmc->soc && cpuid < pmc->soc->num_cpu_powergates) + return pmc->soc->cpu_powergates[cpuid]; + + return -EINVAL; +} + +/** + * tegra_pmc_cpu_is_powered() - check if CPU partition is powered + * @cpuid: CPU partition ID + */ +bool tegra_pmc_cpu_is_powered(unsigned int cpuid) +{ + int id; + + id = tegra_get_cpu_powergate_id(early_pmc, cpuid); + if (id < 0) + return false; + + return tegra_powergate_is_powered(early_pmc, id); +} + +/** + * tegra_pmc_cpu_power_on() - power on CPU partition + * @cpuid: CPU partition ID + */ +int tegra_pmc_cpu_power_on(unsigned int cpuid) +{ + int id; + + id = tegra_get_cpu_powergate_id(early_pmc, cpuid); + if (id < 0) + return id; + + return tegra_powergate_set(early_pmc, id, true); +} + +/** + * tegra_pmc_cpu_remove_clamping() - remove power clamps for CPU partition + * @cpuid: CPU partition ID + */ +int tegra_pmc_cpu_remove_clamping(unsigned int cpuid) +{ + int id; + + id = tegra_get_cpu_powergate_id(early_pmc, cpuid); + if (id < 0) + return id; + + return tegra_pmc_powergate_remove_clamping(early_pmc, id); +} + +bool tegra_pmc_core_domain_state_synced(void) +{ + return early_pmc->core_domain_state_synced; +} + +#ifdef CONFIG_PM_SLEEP +enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) +{ + return early_pmc->suspend_mode; +} + +void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) +{ + if (mode < TEGRA_SUSPEND_NONE || mode >= TEGRA_MAX_SUSPEND_MODE) + return; + + early_pmc->suspend_mode = mode; +} + +void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) +{ + unsigned long long rate = 0; + u64 ticks; + u32 value; + + switch (mode) { + case TEGRA_SUSPEND_LP1: + rate = 32768; + break; + + case TEGRA_SUSPEND_LP2: + rate = early_pmc->rate; + break; + + default: + break; + } + + if (WARN_ON_ONCE(rate == 0)) + rate = 100000000; + + ticks = early_pmc->cpu_good_time * rate + USEC_PER_SEC - 1; + do_div(ticks, USEC_PER_SEC); + tegra_pmc_writel(early_pmc, ticks, PMC_CPUPWRGOOD_TIMER); + + ticks = early_pmc->cpu_off_time * rate + USEC_PER_SEC - 1; + do_div(ticks, USEC_PER_SEC); + tegra_pmc_writel(early_pmc, ticks, PMC_CPUPWROFF_TIMER); + + value = tegra_pmc_readl(early_pmc, PMC_CNTRL); + value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; + value |= PMC_CNTRL_CPU_PWRREQ_OE; + tegra_pmc_writel(early_pmc, value, PMC_CNTRL); +} +#endif /* CONFIG_PM_SLEEP */ + /* * Early initialization to allow access to registers in the very early boot * process. @@ -4920,3 +4927,4 @@ static int __init tegra_pmc_early_init(void) return 0; } early_initcall(tegra_pmc_early_init); +#endif /* CONFIG_ARM */ diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index 8b4bcdea849e..4bcbf19d75ac 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -18,10 +18,6 @@ struct clk; struct reset_control; struct tegra_pmc; -bool tegra_pmc_cpu_is_powered(unsigned int cpuid); -int tegra_pmc_cpu_power_on(unsigned int cpuid); -int tegra_pmc_cpu_remove_clamping(unsigned int cpuid); - /* * powergate and I/O rail APIs */ @@ -163,12 +159,6 @@ int tegra_pmc_powergate_sequence_power_up(struct tegra_pmc *pmc, int tegra_pmc_io_pad_power_enable(struct tegra_pmc *pmc, enum tegra_io_pad id); int tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id); -/* legacy */ -void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode); -void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode); - -bool tegra_pmc_core_domain_state_synced(void); - #else static inline struct tegra_pmc *devm_tegra_pmc_get(struct device *dev) { @@ -213,6 +203,24 @@ tegra_pmc_io_pad_power_disable(struct tegra_pmc *pmc, enum tegra_io_pad id) { return -ENOSYS; } +#endif /* CONFIG_SOC_TEGRA_PMC */ + +/* 32-bit ARM platforms only */ +#if defined(CONFIG_ARM) +bool tegra_pmc_cpu_is_powered(unsigned int cpuid); +int tegra_pmc_cpu_power_on(unsigned int cpuid); +int tegra_pmc_cpu_remove_clamping(unsigned int cpuid); +bool tegra_pmc_core_domain_state_synced(void); + +#if defined(CONFIG_SOC_TEGRA_PMC) && defined(CONFIG_PM_SLEEP) +enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void); +void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode); +void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode); +#else +static inline enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) +{ + return TEGRA_SUSPEND_NONE; +} static inline void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) { @@ -221,21 +229,13 @@ static inline void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) static inline void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) { } - +#endif +#else +/* needed for COMPILE_TEST */ static inline bool tegra_pmc_core_domain_state_synced(void) { return false; } - -#endif /* CONFIG_SOC_TEGRA_PMC */ - -#if defined(CONFIG_SOC_TEGRA_PMC) && defined(CONFIG_PM_SLEEP) -enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void); -#else -static inline enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) -{ - return TEGRA_SUSPEND_NONE; -} #endif #endif /* __SOC_TEGRA_PMC_H__ */ From e9bfdf06fb05168e72d3d9d6163b04e2316e0aa2 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 22 May 2026 16:58:24 +0100 Subject: [PATCH 179/189] soc/tegra: pmc: Populate powergate debugfs only when needed The 'powergate' debugfs node is used to show the state of the powergates but for some devices the 'num_powergates' is 0 and so it displays nothing. Therefore, only populate this debugfs entry for devices where num_powergates is greater than 0. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index da60e7e5927d..d4edf701a40f 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -3160,7 +3160,8 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (pmc->soc->set_wake_filters) pmc->soc->set_wake_filters(pmc); - debugfs_create_file("powergate", 0444, NULL, pmc, &powergate_fops); + if (pmc->soc->num_powergates) + debugfs_create_file("powergate", 0444, NULL, pmc, &powergate_fops); return 0; From 2545b16e3ea049a266ec8b0e509d4e093b26b906 Mon Sep 17 00:00:00 2001 From: Diogo Ivo Date: Thu, 21 May 2026 15:48:48 +0200 Subject: [PATCH 180/189] soc/tegra: pmc: Restrict power-off handler to Nexus 7 The Tegra PMC power-off handler exists solely to reboot the Nexus 7 into a special bootloader mode when a USB cable is connected, so that the bootloader can display battery status instead of powering off. There is no reason to register it on any other Tegra board. Guard the registration behind of_machine_is_compatible("asus,grouper") and rename the handler to tegra_pmc_grouper_power_off_handler to make its scope explicit. The of_machine_is_compatible() check inside the handler itself is now redundant and is removed. This also avoids occupying SYS_OFF_PRIO_FIRMWARE on boards that have other handlers at that priority level. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Diogo Ivo Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index d4edf701a40f..873edc3e7a22 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -1207,7 +1207,7 @@ static int tegra_pmc_restart_handler(struct sys_off_data *data) return NOTIFY_DONE; } -static int tegra_pmc_power_off_handler(struct sys_off_data *data) +static int tegra_pmc_grouper_power_off_handler(struct sys_off_data *data) { struct tegra_pmc *pmc = data->cb_data; @@ -1215,8 +1215,7 @@ static int tegra_pmc_power_off_handler(struct sys_off_data *data) * Reboot Nexus 7 into special bootloader mode if USB cable is * connected in order to display battery status and power off. */ - if (of_machine_is_compatible("asus,grouper") && - power_supply_is_system_supplied()) { + if (power_supply_is_system_supplied()) { const u32 go_to_charger_mode = 0xa5a55a5a; tegra_pmc_writel(pmc, go_to_charger_mode, PMC_SCRATCH37); @@ -3087,18 +3086,20 @@ static int tegra_pmc_probe(struct platform_device *pdev) } /* - * PMC should be primary power-off method if it soft-resets CPU, - * asking bootloader to shutdown hardware. + * PMC should be primary power-off method on Grouper if it soft-resets + * CPU, asking bootloader to shutdown hardware. */ - err = devm_register_sys_off_handler(&pdev->dev, - SYS_OFF_MODE_POWER_OFF, - SYS_OFF_PRIO_FIRMWARE, - tegra_pmc_power_off_handler, - pmc); - if (err) { - dev_err(&pdev->dev, "failed to register sys-off handler: %d\n", - err); - return err; + if (of_machine_is_compatible("asus,grouper")) { + err = devm_register_sys_off_handler(&pdev->dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_FIRMWARE, + tegra_pmc_grouper_power_off_handler, + pmc); + if (err) { + dev_err(&pdev->dev, "failed to register sys-off handler: %d\n", + err); + return err; + } } /* From 2cee7da9743396aff1fd13124109c78975c92ad8 Mon Sep 17 00:00:00 2001 From: Prathamesh Shete Date: Mon, 18 May 2026 10:14:20 +0000 Subject: [PATCH 181/189] soc/tegra: pmc: Add Tegra238 support The Tegra238 PMC is largely similar to that found on earlier chips, but not completely compatible. Add support for the PMC on Tegra238. Signed-off-by: Prathamesh Shete Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 159 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 873edc3e7a22..f419a5395545 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -4471,6 +4471,164 @@ static const struct tegra_pmc_soc tegra234_pmc_soc = { .has_single_mmio_aperture = false, }; +static const struct tegra_io_pad_soc tegra238_io_pads[] = { + TEGRA_IO_PAD(TEGRA_IO_PAD_HDMI_DP0, 0, 0xe028, 0xe02c, "hdmi-dp0"), + TEGRA_IO_PAD(TEGRA_IO_PAD_UFS, 0, 0xe06c, 0xe070, "ufs"), + TEGRA_IO_PAD(TEGRA_IO_PAD_EDP, 2, 0xe040, 0xe044, "edp"), + TEGRA_IO_PAD(TEGRA_IO_PAD_SDMMC1_HV, 0, 0xe058, 0xe05c, "sdmmc1-hv"), + TEGRA_IO_PAD(TEGRA_IO_PAD_SDMMC3_HV, UINT_MAX, UINT_MAX, UINT_MAX, "sdmmc3-hv"), + TEGRA_IO_PAD(TEGRA_IO_PAD_AUDIO_HV, UINT_MAX, UINT_MAX, UINT_MAX, "audio-hv"), + TEGRA_IO_PAD(TEGRA_IO_PAD_AO_HV, UINT_MAX, UINT_MAX, UINT_MAX, "ao-hv"), +}; + +static const struct tegra_io_pad_vctrl tegra238_io_pad_vctrls[] = { + TEGRA_IO_PAD_VCTRL(TEGRA_IO_PAD_SDMMC1_HV, PMC_IMPL_E_33V_PWR, 4), + TEGRA_IO_PAD_VCTRL(TEGRA_IO_PAD_SDMMC3_HV, PMC_IMPL_E_33V_PWR, 6), + TEGRA_IO_PAD_VCTRL(TEGRA_IO_PAD_AUDIO_HV, PMC_IMPL_E_33V_PWR, 1), + TEGRA_IO_PAD_VCTRL(TEGRA_IO_PAD_AO_HV, PMC_IMPL_E_33V_PWR, 0), +}; + +static const struct pinctrl_pin_desc tegra238_pin_descs[] = { + TEGRA_IO_PIN_DESC(TEGRA_IO_PAD_HDMI_DP0, "hdmi-dp0"), + TEGRA_IO_PIN_DESC(TEGRA_IO_PAD_UFS, "ufs"), + TEGRA_IO_PIN_DESC(TEGRA_IO_PAD_EDP, "edp"), + TEGRA_IO_PIN_DESC(TEGRA_IO_PAD_SDMMC1_HV, "sdmmc1-hv"), + TEGRA_IO_PIN_DESC(TEGRA_IO_PAD_SDMMC3_HV, "sdmmc3-hv"), + TEGRA_IO_PIN_DESC(TEGRA_IO_PAD_AUDIO_HV, "audio-hv"), + TEGRA_IO_PIN_DESC(TEGRA_IO_PAD_AO_HV, "ao-hv"), +}; + +static const struct tegra_pmc_regs tegra238_pmc_regs = { + .scratch0 = 0x2000, + .rst_status = 0x70, + .rst_source_shift = 0x2, + .rst_source_mask = 0xfc, + .rst_level_shift = 0x0, + .rst_level_mask = 0x3, + .aowake_mask_w = 0x180, + .aowake_status_w = 0x30c, + .aowake_status_r = 0x48c, + .aowake_tier2_routing = 0x4cc, + .aowake_sw_status_w = 0x49c, + .aowake_sw_status = 0x4a0, + .aowake_latch_sw = 0x498, + .aowake_ctrl = 0x4f4, +}; + +static const char * const tegra238_reset_sources[] = { + "SYS_RESET_N", /* 0 */ + "AOWDT", + NULL, + "BPMPWDT", + NULL, + "SPEWDT", /* 5 */ + NULL, + NULL, + "SENSOR", + NULL, + NULL, /* 10 */ + "MAINSWRST", + "SC7", + NULL, + NULL, + NULL, /* 15 */ + NULL, + NULL, + "RTC_XTAL_CSDC", + "BPMPBOOT", + "FUSECRC", /* 20 */ + NULL, + "PSCWDT", + "PSC_SW", + "CSITE_SW", + NULL, /* 25 */ + NULL, + "VREFRO_POWERBAD", + NULL, + NULL, + NULL, /* 30 */ + NULL, + NULL, + NULL, + NULL, + NULL, /* 35 */ + NULL, + NULL, + "TOP0WDT", + "TOP1WDT", + "TOP2WDT", /* 40 */ + "APE_C0WDT", + "APE_C1WDT", + "APE_C2WDT", + "APE_C3WDT", + "SCPM_SOC_XTAL", /* 45 */ + "SCPM_RTC_XTAL", + "SCPM_BPMP_CORE_CLK", + "SCPM_PSC_SE_CLK", + "FMON_32K", + "FMON_OSC", /* 50 */ + "VMON_SOC", + "VMON_CPU0", + NULL, + "POD_CPU", + "POD_GPU", /* 55 */ + "POD_RTC", + NULL, + "POD_IO", + "POD_PLUS_SOC", + "POD_PLUS_IO_VMON", /* 60 */ + "POD_PLUS_IO_PSCPLL", + "VMON_PLUS_0", + "VMON_PLUS_1", /* 63 */ +}; + +static const struct tegra_wake_event tegra238_wake_events[] = { + TEGRA_WAKE_IRQ("rtc", 73, 10), + TEGRA_WAKE_IRQ("pmu", 24, 209), + TEGRA_WAKE_IRQ("usb3-port-0", 76, 167), + TEGRA_WAKE_IRQ("usb3-port-1", 77, 167), + TEGRA_WAKE_IRQ("usb3-port-2", 78, 167), + TEGRA_WAKE_IRQ("usb2-port-0", 79, 167), + TEGRA_WAKE_IRQ("usb2-port-1", 80, 167), + TEGRA_WAKE_IRQ("usb2-port-2", 81, 167), +}; + +static const struct tegra_pmc_soc tegra238_pmc_soc = { + .num_powergates = 0, + .powergates = NULL, + .num_cpu_powergates = 0, + .cpu_powergates = NULL, + .has_tsense_reset = false, + .has_gpu_clamps = false, + .needs_mbist_war = false, + .has_io_pad_wren = false, + .maybe_tz_only = false, + .num_io_pads = ARRAY_SIZE(tegra238_io_pads), + .io_pads = tegra238_io_pads, + .num_io_pad_vctrls = ARRAY_SIZE(tegra238_io_pad_vctrls), + .io_pad_vctrls = tegra238_io_pad_vctrls, + .num_pin_descs = ARRAY_SIZE(tegra238_pin_descs), + .pin_descs = tegra238_pin_descs, + .regs = &tegra238_pmc_regs, + .init = tegra186_pmc_init, + .setup_irq_polarity = tegra186_pmc_setup_irq_polarity, + .set_wake_filters = tegra186_pmc_set_wake_filters, + .irq_set_wake = tegra186_pmc_irq_set_wake, + .irq_set_type = tegra186_pmc_irq_set_type, + .reset_sources = tegra238_reset_sources, + .num_reset_sources = ARRAY_SIZE(tegra238_reset_sources), + .reset_levels = tegra186_reset_levels, + .num_reset_levels = ARRAY_SIZE(tegra186_reset_levels), + .num_wake_events = ARRAY_SIZE(tegra238_wake_events), + .wake_events = tegra238_wake_events, + .max_wake_events = 96, + .max_wake_vectors = 3, + .pmc_clks_data = NULL, + .num_pmc_clks = 0, + .has_blink_output = false, + .has_single_mmio_aperture = false, +}; + #define TEGRA264_IO_PAD_VCTRL(_id, _offset, _ena_3v3, _ena_1v8) \ ((struct tegra_io_pad_vctrl) { \ .id = (_id), \ @@ -4661,6 +4819,7 @@ static const struct tegra_pmc_soc tegra264_pmc_soc = { static const struct of_device_id tegra_pmc_match[] = { { .compatible = "nvidia,tegra264-pmc", .data = &tegra264_pmc_soc }, + { .compatible = "nvidia,tegra238-pmc", .data = &tegra238_pmc_soc }, { .compatible = "nvidia,tegra234-pmc", .data = &tegra234_pmc_soc }, { .compatible = "nvidia,tegra194-pmc", .data = &tegra194_pmc_soc }, { .compatible = "nvidia,tegra186-pmc", .data = &tegra186_pmc_soc }, From 7ba53a3d1c4dae0124b2c141ae551ab9d9d7f1f9 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 29 May 2026 18:33:36 +0100 Subject: [PATCH 182/189] firmware: tegra: bpmp: Propagate debugfs errors The Tegra BPMP debugfs code returns -ENOMEM for most cases where calls to debugfs_create_dir() or debugfs_create_file() fail. These debugfs functions return an ERR_PTR with the actual error code on failure. Therefore, update the Tegra BPMP debugfs code to propagate the actual error code on failure. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp-debugfs.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/tegra/bpmp-debugfs.c b/drivers/firmware/tegra/bpmp-debugfs.c index 4221fed70ad4..29037e2b3158 100644 --- a/drivers/firmware/tegra/bpmp-debugfs.c +++ b/drivers/firmware/tegra/bpmp-debugfs.c @@ -468,7 +468,7 @@ static int bpmp_populate_debugfs_inband(struct tegra_bpmp *bpmp, dentry = debugfs_create_file(name, mode, parent, bpmp, &bpmp_debug_fops); if (IS_ERR(dentry)) { - err = -ENOMEM; + err = PTR_ERR(dentry); goto out; } } @@ -719,7 +719,7 @@ static int bpmp_populate_dir(struct tegra_bpmp *bpmp, struct seqbuf *seqbuf, if (t & DEBUGFS_S_ISDIR) { dentry = debugfs_create_dir(name, parent); if (IS_ERR(dentry)) - return -ENOMEM; + return PTR_ERR(dentry); err = bpmp_populate_dir(bpmp, seqbuf, dentry, depth+1); if (err < 0) return err; @@ -732,7 +732,7 @@ static int bpmp_populate_dir(struct tegra_bpmp *bpmp, struct seqbuf *seqbuf, parent, bpmp, &debugfs_fops); if (IS_ERR(dentry)) - return -ENOMEM; + return PTR_ERR(dentry); } } @@ -782,11 +782,11 @@ int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) root = debugfs_create_dir("bpmp", NULL); if (IS_ERR(root)) - return -ENOMEM; + return PTR_ERR(root); bpmp->debugfs_mirror = debugfs_create_dir("debug", root); if (IS_ERR(bpmp->debugfs_mirror)) { - err = -ENOMEM; + err = PTR_ERR(bpmp->debugfs_mirror); goto out; } From 040eeafee0146b9d046caef501f387e107a59960 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 29 May 2026 18:33:37 +0100 Subject: [PATCH 183/189] firmware: tegra: bpmp: Add support for multi-socket platforms On multi-socket platforms each socket has its own BPMP that is registered with the kernel, so the existing single fixed "bpmp" debugfs directory name cannot accommodate more than one instance. Group the per-socket BPMP debugfs entries under a shared top-level /sys/kernel/debug/bpmp/ directory, with each socket's BPMP device under a "-bpmp" subdirectory: /sys/kernel/debug/bpmp/0-bpmp/... /sys/kernel/debug/bpmp/1-bpmp/... For a multi-socket platform, the root debugfs bpmp/ directory is created by the first BPMP device that is populated. For single-socket platforms, the existing directory structure is preserved. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp-debugfs.c | 45 ++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/firmware/tegra/bpmp-debugfs.c b/drivers/firmware/tegra/bpmp-debugfs.c index 29037e2b3158..33c6300af964 100644 --- a/drivers/firmware/tegra/bpmp-debugfs.c +++ b/drivers/firmware/tegra/bpmp-debugfs.c @@ -2,6 +2,8 @@ /* * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. */ + +#include #include #include #include @@ -769,9 +771,21 @@ static int bpmp_populate_debugfs_shmem(struct tegra_bpmp *bpmp) return err; } +static DEFINE_MUTEX(bpmp_debugfs_root_lock); +static struct dentry *bpmp_debugfs_root; + +static struct dentry *bpmp_debugfs_get_root(void) +{ + guard(mutex)(&bpmp_debugfs_root_lock); + if (!bpmp_debugfs_root) + bpmp_debugfs_root = debugfs_create_dir("bpmp", NULL); + return bpmp_debugfs_root; +} + int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) { - struct dentry *root; + struct dentry *root, *d; + char name[32]; bool inband; int err; @@ -780,11 +794,22 @@ int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) if (!inband && !tegra_bpmp_mrq_is_supported(bpmp, MRQ_DEBUGFS)) return 0; - root = debugfs_create_dir("bpmp", NULL); + root = bpmp_debugfs_get_root(); if (IS_ERR(root)) return PTR_ERR(root); - bpmp->debugfs_mirror = debugfs_create_dir("debug", root); + if (dev_to_node(bpmp->dev) == NUMA_NO_NODE) { + d = root; + } else { + snprintf(name, sizeof(name), "%d-bpmp", dev_to_node(bpmp->dev)); + d = debugfs_create_dir(name, root); + if (IS_ERR(d)) { + err = PTR_ERR(d); + goto out; + } + } + + bpmp->debugfs_mirror = debugfs_create_dir("debug", d); if (IS_ERR(bpmp->debugfs_mirror)) { err = PTR_ERR(bpmp->debugfs_mirror); goto out; @@ -797,8 +822,18 @@ int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) err = bpmp_populate_debugfs_shmem(bpmp); out: - if (err < 0) - debugfs_remove_recursive(root); + if (err < 0) { + if (!IS_ERR(d)) + debugfs_remove_recursive(d); + + guard(mutex)(&bpmp_debugfs_root_lock); + if (root == d) { + bpmp_debugfs_root = NULL; + } else if (simple_empty(root)) { + debugfs_remove(root); + bpmp_debugfs_root = NULL; + } + } return err; } From bc13f14f5cd3d15054de38dc1232b49343d36297 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 22 Mar 2026 23:02:23 +0000 Subject: [PATCH 184/189] soc: aspeed: cleanup dead default for ASPEED_SOCINFO The same kconfig 'default' statement appears twice for ASPEED_SOCINFO, which is unnecessary. This dead code was found by kconfirm, a static analysis tool for Kconfig. Signed-off-by: Julian Braha Signed-off-by: Andrew Jeffery --- drivers/soc/aspeed/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig index f579ee0b5afa..63a656449a1a 100644 --- a/drivers/soc/aspeed/Kconfig +++ b/drivers/soc/aspeed/Kconfig @@ -46,7 +46,6 @@ config ASPEED_P2A_CTRL config ASPEED_SOCINFO bool "ASPEED SoC Information driver" - default ARCH_ASPEED select SOC_BUS default ARCH_ASPEED help From 2e05f3d6005d9aa3e2e423d2471f290d9ccbe3d2 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Wed, 27 May 2026 19:31:25 +0530 Subject: [PATCH 185/189] memory: tegra186-emc: stop borrowing MC aggregate hook for EMC tegra186_emc_interconnect_init() copies the MC's ICC aggregate hook into the EMC provider. That hook (tegra234_mc_icc_aggregate / tegra264_mc_icc_aggregate) uses container_of() to recover 'mc', which is only valid when the icc_provider is embedded in struct tegra_mc. For an EMC node the provider is embedded in struct tegra186_emc, so 'mc' points into unrelated memory. This stayed harmless until commit faafd6ca7e6e ("memory: tegra: make icc_set_bw return zero if BWMGR not supported") added an unconditional read of mc->bwmgr_mrq_supported at the top of the hook. UBSAN catches the stray load on every EMC aggregation: UBSAN: invalid-load in drivers/memory/tegra/tegra234.c:1104:9 load of value 112 is not a valid value for type '_Bool' No functional impact in practice, since the hook's only other mc dereference (mc->num_channels) sits inside a TEGRA_ICC_MC_CPU_CLUSTER* branch that EMC nodes never enter. Fix this by setting the EMC provider's aggregate hook to icc_std_aggregate, instead of borrowing the MC's hook. The MC providers continue using their own aggregate hooks, where container_of() correctly resolves to struct tegra_mc. Reported-by: Jon Hunter Fixes: 9a38cb27668e ("memory: tegra: Add interconnect support for DRAM scaling in Tegra234") Signed-off-by: Sumit Gupta Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260527140127.49172-2-sumitg@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra186-emc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/memory/tegra/tegra186-emc.c b/drivers/memory/tegra/tegra186-emc.c index 03ebab6fbe68..f71265b303b9 100644 --- a/drivers/memory/tegra/tegra186-emc.c +++ b/drivers/memory/tegra/tegra186-emc.c @@ -258,15 +258,13 @@ static int tegra186_emc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *pe static int tegra186_emc_interconnect_init(struct tegra186_emc *emc) { - struct tegra_mc *mc = dev_get_drvdata(emc->dev->parent); - const struct tegra_mc_soc *soc = mc->soc; struct icc_node *node; int err; emc->provider.dev = emc->dev; emc->provider.set = tegra186_emc_icc_set_bw; emc->provider.data = &emc->provider; - emc->provider.aggregate = soc->icc_ops->aggregate; + emc->provider.aggregate = icc_std_aggregate; emc->provider.xlate = tegra186_emc_of_icc_xlate; emc->provider.get_bw = tegra186_emc_icc_get_init_bw; From e23d87a69e827b60fb985236a0984bacb3b68a19 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Wed, 27 May 2026 19:31:26 +0530 Subject: [PATCH 186/189] memory: tegra264: drop redundant tegra264_mc_icc_aggregate() tegra264_mc_icc_aggregate() does nothing on top of icc_std_aggregate except an early return on !mc->bwmgr_mrq_supported. tegra264_mc_icc_set() already returns early on the same condition, before reading any of the aggregated avg_bw / peak_bw values. This makes the early return in aggregate() redundant. So, set tegra264_mc_icc_ops.aggregate to icc_std_aggregate and drop the helper. Reported-by: Jon Hunter Signed-off-by: Sumit Gupta Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260527140127.49172-3-sumitg@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra264.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/drivers/memory/tegra/tegra264.c b/drivers/memory/tegra/tegra264.c index b8d6c4ce4836..790349295dcc 100644 --- a/drivers/memory/tegra/tegra264.c +++ b/drivers/memory/tegra/tegra264.c @@ -799,21 +799,6 @@ static int tegra264_mc_icc_set(struct icc_node *src, struct icc_node *dst) return ret; } -static int tegra264_mc_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, - u32 peak_bw, u32 *agg_avg, u32 *agg_peak) -{ - struct icc_provider *p = node->provider; - struct tegra_mc *mc = icc_provider_to_tegra_mc(p); - - if (!mc->bwmgr_mrq_supported) - return 0; - - *agg_avg += avg_bw; - *agg_peak = max(*agg_peak, peak_bw); - - return 0; -} - static int tegra264_mc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *peak) { *avg = 0; @@ -1130,7 +1115,7 @@ static const irq_handler_t tegra264_mc_irq_handlers[8] = { static const struct tegra_mc_icc_ops tegra264_mc_icc_ops = { .xlate = tegra_mc_icc_xlate, - .aggregate = tegra264_mc_icc_aggregate, + .aggregate = icc_std_aggregate, .get_bw = tegra264_mc_icc_get_init_bw, .set = tegra264_mc_icc_set, }; From b97f7dceb8adb2b05d556469afc6fb54947ef61c Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Wed, 27 May 2026 19:31:27 +0530 Subject: [PATCH 187/189] memory: tegra234: drop dead NULL check in tegra234_mc_icc_aggregate() In tegra234_mc_icc_aggregate(), the 'if (mc)' check inside the CPU-cluster branch is always true. 'mc' was already dereferenced via 'mc->bwmgr_mrq_supported' a few lines above, so if it were NULL the function would have faulted there. Drop the redundant check. Reported-by: Jon Hunter Signed-off-by: Sumit Gupta Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260527140127.49172-4-sumitg@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/tegra234.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/memory/tegra/tegra234.c b/drivers/memory/tegra/tegra234.c index 9fbd34d4abe0..982476b67d5a 100644 --- a/drivers/memory/tegra/tegra234.c +++ b/drivers/memory/tegra/tegra234.c @@ -1106,10 +1106,8 @@ static int tegra234_mc_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, if (node->id == TEGRA_ICC_MC_CPU_CLUSTER0 || node->id == TEGRA_ICC_MC_CPU_CLUSTER1 || - node->id == TEGRA_ICC_MC_CPU_CLUSTER2) { - if (mc) - peak_bw = peak_bw * mc->num_channels; - } + node->id == TEGRA_ICC_MC_CPU_CLUSTER2) + peak_bw = peak_bw * mc->num_channels; *agg_avg += avg_bw; *agg_peak = max(*agg_peak, peak_bw); From 218462fb8e1ae308d5c85640cc530932257a37a3 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 15 Jun 2026 13:41:02 +0200 Subject: [PATCH 188/189] Revert "Documentation: ABI: add sysfs interface for ZynqMP CSU registers" This reverts commit 8ebebccf1579f6ce92bde3ddbb13df12c080f647, which was merged by accident. Signed-off-by: Arnd Bergmann --- .../ABI/stable/sysfs-driver-firmware-zynqmp | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp index ac8c2314deee..c3fec3c835af 100644 --- a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp +++ b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp @@ -254,36 +254,3 @@ Description: The expected result is 500. Users: Xilinx - -What: /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/* -Date: May 2026 -KernelVersion: 7.2 -Contact: "Ronak Jain" -Description: - Read/Write CSU (Configuration Security Unit) registers. - - This interface provides dynamic access to CSU registers that are - discovered from the firmware at boot time using PM_QUERY_DATA API. - - The supported registers are: - - - multiboot: CSU_MULTI_BOOT register - - idcode: CSU_IDCODE register (read-only) - - pcap-status: CSU_PCAP_STATUS register (read-only) - - Read operations use the existing IOCTL_READ_REG API. - Write operations use the existing IOCTL_MASK_WRITE_REG API. - - The firmware enforces access control - read-only registers will reject - write attempts even though the sysfs permissions show write access. - - Usage for reading:: - - # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot - # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/idcode - - Usage for writing (mask and value are in hexadecimal):: - - # echo 0xFFFFFFF 0x0 > /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot - -Users: Xilinx/AMD From 2b31e94bef30f8f2e8856bd9a2e36347908bf59c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 15 Jun 2026 13:41:05 +0200 Subject: [PATCH 189/189] Revert "firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface" This reverts commit 47d7bca76dd4f36ba0525d761f247c76ec9e4b17, which was merged by accident. Signed-off-by: Arnd Bergmann --- MAINTAINERS | 10 - drivers/firmware/xilinx/Makefile | 2 +- drivers/firmware/xilinx/zynqmp-csu-reg.c | 258 ----------------------- drivers/firmware/xilinx/zynqmp-csu-reg.h | 18 -- drivers/firmware/xilinx/zynqmp.c | 6 - include/linux/firmware/xlnx-zynqmp.h | 4 +- 6 files changed, 2 insertions(+), 296 deletions(-) delete mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.c delete mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.h diff --git a/MAINTAINERS b/MAINTAINERS index afc48c0479a6..9fa506b5940a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29271,16 +29271,6 @@ F: drivers/dma/xilinx/xdma.c F: include/linux/dma/amd_xdma.h F: include/linux/platform_data/amd_xdma.h -XILINX ZYNQMP CSU REGISTER DRIVER -M: Senthil Nathan Thangaraj -R: Michal Simek -R: Ronak Jain -L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) -S: Maintained -F: Documentation/ABI/stable/sysfs-driver-firmware-zynqmp -F: drivers/firmware/xilinx/zynqmp-csu-reg.c -F: drivers/firmware/xilinx/zynqmp-csu-reg.h - XILINX ZYNQMP DPDMA DRIVER M: Laurent Pinchart L: dmaengine@vger.kernel.org diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile index 6203f41daaa6..8db0e66b6b7e 100644 --- a/drivers/firmware/xilinx/Makefile +++ b/drivers/firmware/xilinx/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for Xilinx firmwares -obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o zynqmp-csu-reg.o +obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.c b/drivers/firmware/xilinx/zynqmp-csu-reg.c deleted file mode 100644 index 6e11a9b019f7..000000000000 --- a/drivers/firmware/xilinx/zynqmp-csu-reg.c +++ /dev/null @@ -1,258 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Xilinx Zynq MPSoC CSU Register Access - * - * Copyright (C) 2026 Advanced Micro Devices, Inc. - * - * Michal Simek - * Ronak Jain - */ - -#include -#include -#include -#include - -#include "zynqmp-csu-reg.h" - -/* Node ID for CSU module in firmware */ -#define CSU_NODE_ID 0 - -/* Maximum number of CSU registers supported */ -#define MAX_CSU_REGS 50 - -/* Size of register name returned by firmware (3 u32 words = 12 bytes) */ -#define CSU_REG_NAME_LEN 12 - -/** - * struct zynqmp_csu_reg - CSU register information - * @id: Register index from firmware - * @name: Register name - * @attr: Device attribute for sysfs - */ -struct zynqmp_csu_reg { - u32 id; - char name[CSU_REG_NAME_LEN]; - struct device_attribute attr; -}; - -/** - * struct zynqmp_csu_data - Per-device CSU data - * @csu_regs: Array of CSU registers - * @csu_attr_group: Attribute group for sysfs - */ -struct zynqmp_csu_data { - struct zynqmp_csu_reg *csu_regs; - struct attribute_group csu_attr_group; -}; - -/** - * zynqmp_pm_get_node_count() - Get number of supported nodes via QUERY_DATA - * - * Return: Number of nodes on success, or negative error code - */ -static int zynqmp_pm_get_node_count(void) -{ - struct zynqmp_pm_query_data qdata = {0}; - u32 ret_payload[PAYLOAD_ARG_CNT]; - int ret; - - qdata.qid = PM_QID_GET_NODE_COUNT; - - ret = zynqmp_pm_query_data(qdata, ret_payload); - if (ret) - return ret; - - return ret_payload[1]; -} - -/** - * zynqmp_pm_get_node_name() - Get node name via QUERY_DATA - * @index: Register index - * @name: Buffer to store register name - * - * Return: 0 on success, error code otherwise - */ -static int zynqmp_pm_get_node_name(u32 index, char *name) -{ - struct zynqmp_pm_query_data qdata = {0}; - u32 ret_payload[PAYLOAD_ARG_CNT]; - int ret; - - qdata.qid = PM_QID_GET_NODE_NAME; - qdata.arg1 = index; - - ret = zynqmp_pm_query_data(qdata, ret_payload); - if (ret) - return ret; - - memcpy(name, &ret_payload[1], CSU_REG_NAME_LEN); - name[CSU_REG_NAME_LEN - 1] = '\0'; - - return 0; -} - -/** - * zynqmp_csu_reg_show() - Generic show function for all registers - * @dev: Device pointer - * @attr: Device attribute - * @buf: Output buffer - * - * Return: Number of bytes written to buffer, or error code - */ -static ssize_t zynqmp_csu_reg_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct zynqmp_csu_reg *reg; - u32 value; - int ret; - - /* Use container_of to get register directly */ - reg = container_of(attr, struct zynqmp_csu_reg, attr); - - ret = zynqmp_pm_sec_read_reg(CSU_NODE_ID, reg->id, &value); - if (ret) - return ret; - - return sysfs_emit(buf, "0x%08x\n", value); -} - -/** - * zynqmp_csu_reg_store() - Generic store function for writable registers - * @dev: Device pointer - * @attr: Device attribute - * @buf: Input buffer - * @count: Buffer size - * - * Format: "mask value" - both mask and value required - * Example: echo "0xFFFFFFFF 0x12345678" > register - * - * Return: count on success, error code otherwise - */ -static ssize_t zynqmp_csu_reg_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct zynqmp_csu_reg *reg; - u32 mask, value; - int ret; - - reg = container_of(attr, struct zynqmp_csu_reg, attr); - - if (sscanf(buf, "%x %x", &mask, &value) != 2) - return -EINVAL; - - ret = zynqmp_pm_sec_mask_write_reg(CSU_NODE_ID, reg->id, mask, value); - if (ret) - return ret; - - return count; -} - -/** - * zynqmp_csu_discover_registers() - Discover CSU registers from firmware - * @pdev: Platform device pointer - * - * This function uses PM_QUERY_DATA to discover all available CSU registers - * and creates sysfs group under /sys/devices/platform/firmware:zynqmp-firmware/ - * - * Return: 0 on success, error code otherwise - */ -int zynqmp_csu_discover_registers(struct platform_device *pdev) -{ - struct zynqmp_csu_data *csu_data; - struct attribute **attrs; - int count, ret, i; - - ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_COUNT); - if (ret) { - dev_dbg(&pdev->dev, "CSU register discovery not supported by current firmware\n"); - return 0; - } - - ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_NAME); - if (ret) { - dev_dbg(&pdev->dev, "CSU register name query not supported by current firmware\n"); - return 0; - } - - count = zynqmp_pm_get_node_count(); - if (count < 0) - return count; - if (count == 0) { - dev_dbg(&pdev->dev, "No nodes available from firmware\n"); - return 0; - } - - /* Validate count to prevent excessive memory allocation */ - if (count > MAX_CSU_REGS) { - dev_err(&pdev->dev, "Register count %d exceeds maximum %d\n", - count, MAX_CSU_REGS); - return -EINVAL; - } - - dev_dbg(&pdev->dev, "Discovered %d nodes from firmware\n", count); - - csu_data = devm_kzalloc(&pdev->dev, sizeof(*csu_data), GFP_KERNEL); - if (!csu_data) - return -ENOMEM; - - csu_data->csu_regs = devm_kcalloc(&pdev->dev, count, sizeof(*csu_data->csu_regs), - GFP_KERNEL); - if (!csu_data->csu_regs) { - devm_kfree(&pdev->dev, csu_data); - return -ENOMEM; - } - - attrs = devm_kcalloc(&pdev->dev, count + 1, sizeof(*attrs), GFP_KERNEL); - if (!attrs) { - devm_kfree(&pdev->dev, csu_data->csu_regs); - devm_kfree(&pdev->dev, csu_data); - return -ENOMEM; - } - - for (i = 0; i < count; i++) { - struct zynqmp_csu_reg *reg = &csu_data->csu_regs[i]; - struct device_attribute *dev_attr = ®->attr; - - reg->id = i; - - ret = zynqmp_pm_get_node_name(i, reg->name); - if (ret) { - dev_warn(&pdev->dev, "Failed to get name for register %d\n", i); - snprintf(reg->name, sizeof(reg->name), "csu_reg_%d", i); - } - - /* - * The firmware does not expose per-register access mode via - * PM_QUERY_DATA today, so the kernel cannot tell read-only - * registers from read-write ones at discovery time. Expose - * every register as 0644 and rely on the firmware to reject - * IOCTL_MASK_WRITE_REG on read-only registers; the error is - * propagated back to userspace from the store callback. - */ - sysfs_attr_init(&dev_attr->attr); - dev_attr->attr.name = reg->name; - dev_attr->attr.mode = 0644; - dev_attr->show = zynqmp_csu_reg_show; - dev_attr->store = zynqmp_csu_reg_store; - - attrs[i] = &dev_attr->attr; - - dev_dbg(&pdev->dev, "Register %d: id=%d name=%s\n", i, reg->id, reg->name); - } - - csu_data->csu_attr_group.name = "csu_registers"; - csu_data->csu_attr_group.attrs = attrs; - - ret = devm_device_add_group(&pdev->dev, &csu_data->csu_attr_group); - if (ret) { - devm_kfree(&pdev->dev, attrs); - devm_kfree(&pdev->dev, csu_data->csu_regs); - devm_kfree(&pdev->dev, csu_data); - } - - return ret; -} -EXPORT_SYMBOL_GPL(zynqmp_csu_discover_registers); diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.h b/drivers/firmware/xilinx/zynqmp-csu-reg.h deleted file mode 100644 index b12415db3496..000000000000 --- a/drivers/firmware/xilinx/zynqmp-csu-reg.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Xilinx Zynq MPSoC CSU Register Access - * - * Copyright (C) 2026 Advanced Micro Devices, Inc. - * - * Michal Simek - * Ronak Jain - */ - -#ifndef __ZYNQMP_CSU_REG_H__ -#define __ZYNQMP_CSU_REG_H__ - -#include - -int zynqmp_csu_discover_registers(struct platform_device *pdev); - -#endif /* __ZYNQMP_CSU_REG_H__ */ diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index b549d07f7497..fbe8510f4927 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -27,7 +27,6 @@ #include #include -#include "zynqmp-csu-reg.h" #include "zynqmp-debug.h" /* Max HashMap Order for PM API feature check (1<<7 = 128) */ @@ -2121,11 +2120,6 @@ static int zynqmp_firmware_probe(struct platform_device *pdev) dev_err_probe(&pdev->dev, PTR_ERR(em_dev), "EM register fail with error\n"); } - /* Discover CSU registers dynamically */ - ret = zynqmp_csu_discover_registers(pdev); - if (ret) - dev_warn(&pdev->dev, "CSU register discovery failed: %d\n", ret); - return of_platform_populate(dev->of_node, NULL, NULL, dev); } diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h index a4b293eb96ce..d70dcd462b44 100644 --- a/include/linux/firmware/xlnx-zynqmp.h +++ b/include/linux/firmware/xlnx-zynqmp.h @@ -3,7 +3,7 @@ * Xilinx Zynq MPSoC Firmware layer * * Copyright (C) 2014-2021 Xilinx - * Copyright (C) 2022 - 2026 Advanced Micro Devices, Inc. + * Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc. * * Michal Simek * Davorin Mista @@ -262,8 +262,6 @@ enum pm_query_id { PM_QID_CLOCK_GET_NUM_CLOCKS = 12, PM_QID_CLOCK_GET_MAX_DIVISOR = 13, PM_QID_PINCTRL_GET_ATTRIBUTES = 15, - PM_QID_GET_NODE_NAME = 16, - PM_QID_GET_NODE_COUNT = 17, }; enum rpu_oper_mode {