From 0c9f522f2d41c7e055a602a0d2c41dc7af01010b Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:32 +0000 Subject: 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) --- include/linux/fsl/mc.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/linux') 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 */ -- cgit v1.2.3 From 14b1cbcc6cec0b02298f4adf717646cd943b7ef6 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 24 Feb 2026 10:09:35 +0000 Subject: 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 (limited to 'include/linux') 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) -{ - /* - * 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) +static void fsl_mc_write_msi_msg(struct msi_desc *msi_desc, struct msi_msg *msg) { - 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, -- cgit v1.2.3 From af76cdcf02b7be9277af999f0e316943d94d7fd4 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 28 Feb 2026 17:12:28 -0800 Subject: 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(-) (limited to 'include/linux') 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 */ -- cgit v1.2.3 From dc7ccfc86c1af6406c5a0aeeac122e64711ea5da Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Thu, 5 Mar 2026 09:56:44 +0800 Subject: 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 --- .../firmware/arm_scmi/vendors/imx/imx-sm-misc.c | 86 ++++++++++++++++++++++ include/linux/scmi_imx_protocol.h | 14 ++++ 2 files changed, 100 insertions(+) (limited to 'include/linux') 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 */ -- cgit v1.2.3 From 0c6eb5d019c1e4b9cfcfc47000e08858bbbc5e52 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 6 Apr 2026 17:52:54 +0200 Subject: 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(-) (limited to 'include/linux') 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); -- cgit v1.2.3 From a4656aef98dd6f163c55063c36297d45912737f4 Mon Sep 17 00:00:00 2001 From: Jason-JH Lin Date: Wed, 25 Mar 2026 11:57:39 +0800 Subject: 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(+) (limited to 'include/linux') 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; -- cgit v1.2.3 From ecde921eb46022acbdbfff2ad4e4c6e6d0493430 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:47 +0100 Subject: 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(+) (limited to 'include/linux') 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, -- cgit v1.2.3 From 0d76f62613cafecb7d326a5a45619024fa7e6e8e Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:49 +0100 Subject: 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 | 154 ++++++++++++++++++++------------------ include/linux/scmi_protocol.h | 2 + 2 files changed, 83 insertions(+), 73 deletions(-) (limited to 'include/linux') 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) { + /* empty set ? */ + if (!clkd->num_rates) + 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", - 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); + 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 ret; + 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; -- cgit v1.2.3 From 2e757f71a5ab861478204e2907bb373ccb3ca087 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:51 +0100 Subject: 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(-) (limited to 'include/linux') 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; }; -- cgit v1.2.3 From 62ba967595e0b2599768f886851ba5e0d4bfb55b Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:32:52 +0100 Subject: 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(-) (limited to 'include/linux') 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 -- cgit v1.2.3 From d2488ff1a257342111e1be1348d52e8b4ecfaa36 Mon Sep 17 00:00:00 2001 From: Cristian Marussi Date: Fri, 8 May 2026 16:33:00 +0100 Subject: 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(-) (limited to 'include/linux') 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, -- cgit v1.2.3 From ac23106a9b9a0f6ed90002ae3d6fe7c14e97e6bf Mon Sep 17 00:00:00 2001 From: Francisco Munoz Ruiz Date: Tue, 7 Apr 2026 14:51:48 -0700 Subject: 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 | 269 ++++++++++++++++++++++++++++++++----- include/linux/soc/qcom/llcc-qcom.h | 8 +- 2 files changed, 240 insertions(+), 37 deletions(-) (limited to 'include/linux') 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; + if (IS_ERR_OR_NULL(drv_data->desc)) + return ERR_PTR(-ENODEV); - for (i = 0; cfg && i < sz; i++, cfg++) - if (cfg->usecase_id == uid) - break; + for (u32 i = 0; i < drv_data->cfg_size; i++) { + if (uid == drv_data->desc[i].uid) + return &drv_data->desc[i]; + } - if (i == sz) - return ERR_PTR(-ENODEV); + dev_err(drv_data->dev, "Failed to get slice desc for uid: %u\n", uid); - return &drv_data->desc[i]; + 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; - } + mutex_init(&drv_data->lock); + 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); - 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); + if (!drv_data->desc) { + ret = -ENOMEM; + goto err; + } + + ret = qcom_llcc_cfg_program(pdev, cfg); + if (ret) + goto err; } - drv_data->cfg = llcc_cfg; - drv_data->cfg_size = sz; + 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; - mutex_init(&drv_data->lock); - platform_set_drvdata(pdev, drv_data); - - ret = qcom_llcc_cfg_program(pdev, cfg); - if (ret) - goto err; - - drv_data->ecc_irq = platform_get_irq_optional(pdev, 0); + 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 @@ err: 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); -- cgit v1.2.3 From 06a84c2d3ca4b9b54724e79717c2cfd9a59947d9 Mon Sep 17 00:00:00 2001 From: Francisco Munoz Ruiz Date: Tue, 7 Apr 2026 14:51:49 -0700 Subject: 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(-) (limited to 'include/linux') 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); -- cgit v1.2.3 From 7fe2ec9fb8e9a78dad8d6b1e551cb4d126e36f1e Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 8 May 2026 18:54:17 +0100 Subject: 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(-) (limited to 'include/linux') 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; } -- cgit v1.2.3 From 70492cfce2a4d41e87bf46989028a90f4bc6b38f Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Tue, 2 Sep 2025 18:20:53 +0100 Subject: 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(-) (limited to 'include/linux') 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, \ -- cgit v1.2.3 From f746fedb892872cbcee698696303609f0ea76fd7 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:33 +0530 Subject: 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(+) (limited to 'include/linux') 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); -- cgit v1.2.3 From 5b8a39dcf909a4723a58e424ae1b0595d6f5032d Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:34 +0530 Subject: 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(+) (limited to 'include/linux') 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 -- cgit v1.2.3 From 8f4ce470ff8c0d7d820d319f64f05ed9a239a240 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:36 +0530 Subject: 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(+) (limited to 'include/linux') 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 -- cgit v1.2.3 From f1a325d2812797166c744ff7ffb2ccbafbac20ad Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:37 +0530 Subject: 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(+) (limited to 'include/linux') 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 @@ out_icc_disable: } 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 -- cgit v1.2.3 From c012e28e9a4a84b0fe7bf9ef7db334a9a4f31687 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 27 Feb 2026 11:45:38 +0530 Subject: 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(+) (limited to 'include/linux') 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 @@ out_icc_disable: } 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 -- cgit v1.2.3 From 47d7bca76dd4f36ba0525d761f247c76ec9e4b17 Mon Sep 17 00:00:00 2001 From: Ronak Jain Date: Wed, 20 May 2026 02:36:54 -0700 Subject: 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 (limited to 'include/linux') 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 { -- cgit v1.2.3 From 9d7a681915245e0e191abfd4b31d010c7037573c Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:27 +0000 Subject: 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(-) (limited to 'include/linux') 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; }; /** -- cgit v1.2.3 From 50b400214abaec9122558e3f9c6fdd6295914c3c Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:28 +0000 Subject: 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 ++-- .../linux/firmware/samsung/exynos-acpm-protocol.h | 4 +-- 4 files changed, 25 insertions(+), 29 deletions(-) (limited to 'include/linux') 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; -- cgit v1.2.3 From 4ba23dfc49b4a3cefb4fdaa406172dfb8350098d Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:29 +0000 Subject: 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 ++ .../linux/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 (limited to 'include/linux') 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; }; /** -- cgit v1.2.3 From fc13a4b9b9c8cb0b8e5ba54b21712d00f810496c Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 15 May 2026 09:32:30 +0000 Subject: 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 ++++++++++++++++++++++ .../linux/firmware/samsung/exynos-acpm-protocol.h | 6 ++++++ 2 files changed, 29 insertions(+) (limited to 'include/linux') 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 */ -- cgit v1.2.3 From 7b661285aa7507eab79efff0a418445157db4141 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 29 May 2026 15:43:31 +0200 Subject: 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 +- include/linux/firmware/samsung/exynos-acpm-protocol.h | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) (limited to 'include/linux') 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 */ -- cgit v1.2.3 From 2b31e94bef30f8f2e8856bd9a2e36347908bf59c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 15 Jun 2026 13:41:05 +0200 Subject: 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 (limited to 'include/linux') 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 { -- cgit v1.2.3