From 3300a285c28529ca1a178f01b4cc046fd3547dbd Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Tue, 7 Jul 2026 16:58:14 +0200 Subject: [PATCH 01/29] soc: fsl: qe_ports_ic: Use generic I/O helper instead of specific powerPC ones Use ioread32be() and iowrite32be() instead of in_be32() and out_be32() to allow build on other platforms than powerPC. Link: https://lore.kernel.org/r/b08f76c1d8ff864774246f1e2c2158c223c001be.1783435914.git.chleroy@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 9b0bba64e91e..33ca1ddafe18 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -23,36 +23,39 @@ struct qepic_data { static void qepic_mask(struct irq_data *d) { struct qepic_data *data = irq_data_get_irq_chip_data(d); + u32 val = ioread32be(data->reg + CEPIMR); - clrbits32(data->reg + CEPIMR, 1 << (31 - irqd_to_hwirq(d))); + iowrite32be(val & ~(1 << (31 - irqd_to_hwirq(d))), data->reg + CEPIMR); } static void qepic_unmask(struct irq_data *d) { struct qepic_data *data = irq_data_get_irq_chip_data(d); + u32 val = ioread32be(data->reg + CEPIMR); - setbits32(data->reg + CEPIMR, 1 << (31 - irqd_to_hwirq(d))); + iowrite32be(val | 1 << (31 - irqd_to_hwirq(d)), data->reg + CEPIMR); } static void qepic_end(struct irq_data *d) { struct qepic_data *data = irq_data_get_irq_chip_data(d); - out_be32(data->reg + CEPIER, 1 << (31 - irqd_to_hwirq(d))); + iowrite32be(1 << (31 - irqd_to_hwirq(d)), data->reg + CEPIER); } static int qepic_set_type(struct irq_data *d, unsigned int flow_type) { struct qepic_data *data = irq_data_get_irq_chip_data(d); unsigned int vec = (unsigned int)irqd_to_hwirq(d); + u32 val = ioread32be(data->reg + CEPICR); switch (flow_type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_EDGE_FALLING: - setbits32(data->reg + CEPICR, 1 << (31 - vec)); + iowrite32be(val | 1 << (31 - vec), data->reg + CEPICR); return 0; case IRQ_TYPE_EDGE_BOTH: case IRQ_TYPE_NONE: - clrbits32(data->reg + CEPICR, 1 << (31 - vec)); + iowrite32be(val & ~(1 << (31 - vec)), data->reg + CEPICR); return 0; } return -EINVAL; @@ -69,7 +72,7 @@ static struct irq_chip qepic = { static int qepic_get_irq(struct irq_desc *desc) { struct qepic_data *data = irq_desc_get_handler_data(desc); - u32 event = in_be32(data->reg + CEPIER); + u32 event = ioread32be(data->reg + CEPIER); if (!event) return -1; From 21d055109b2352542ac293872ca9113b42b6c55e Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Mon, 27 Jul 2026 10:45:37 +0200 Subject: [PATCH 02/29] powerpc: Move CONFIG_QE_GPIO to SoC Commit 7aa1aa6ecec2 ("QE: Move QE from arch/powerpc to drivers/soc") moved QE into drivers/soc including gpio.c but left CONFIG_QE_GPIO in powerpc's Kconfig. Move it to SoC as well as it is the only place it is used: drivers/soc/fsl/qe/Makefile:obj-$(CONFIG_QE_GPIO) += gpio.o qe_ports_ic.o include/soc/fsl/qe/qe.h:#ifdef CONFIG_QE_GPIO include/soc/fsl/qe/qe.h:#endif /* CONFIG_QE_GPIO */ There is also a dependency in USB subsystem for CONFIG_USB_FHCI_HCD: drivers/usb/host/Kconfig: depends on QE_GPIO && QUICC_ENGINE Because that USB driver selects CONFIG_FSL_GTM which is specific to powerpc, it has to depend on CONFIG_PPC. Acked-by: Madhavan Srinivasan Link: https://lore.kernel.org/r/9a7131d15a94e2017877e628f0e97f9aa2e0aae9.1785140877.git.chleroy@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- arch/powerpc/platforms/Kconfig | 8 -------- drivers/soc/fsl/qe/Kconfig | 8 ++++++++ drivers/usb/host/Kconfig | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/arch/powerpc/platforms/Kconfig b/arch/powerpc/platforms/Kconfig index c4e61843d9d9..2f797ac6f1b3 100644 --- a/arch/powerpc/platforms/Kconfig +++ b/arch/powerpc/platforms/Kconfig @@ -228,14 +228,6 @@ config TAU_AVERAGE If in doubt, say N here. -config QE_GPIO - bool "QE GPIO support" - depends on QUICC_ENGINE - select GPIOLIB - help - Say Y here if you're going to use hardware that connects to the - QE GPIOs. - config CPM2 bool "Enable support for the CPM2 (Communications Processor Module)" depends on (FSL_SOC_BOOKE && PPC32) || PPC_82xx diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig index eb03f42ab978..b35a8fd30ebf 100644 --- a/drivers/soc/fsl/qe/Kconfig +++ b/drivers/soc/fsl/qe/Kconfig @@ -67,3 +67,11 @@ config QE_USB default y if USB_FSL_QE help QE USB Controller support + +config QE_GPIO + bool "QE GPIO support" + depends on QUICC_ENGINE + select GPIOLIB + help + Say Y here if you're going to use hardware that connects to the + QE GPIOs. diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index b3b1ec696bf5..10cff12a3f31 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -611,6 +611,7 @@ config USB_UHCI_ASPEED config USB_FHCI_HCD tristate "Freescale QE USB Host Controller support" depends on QE_GPIO && QUICC_ENGINE + depends on PPC select FSL_GTM select QE_USB help From 43d5ffa6a041a407fdd7866b9839f5cbd9d95c42 Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Thu, 30 Jul 2026 14:29:36 +0200 Subject: [PATCH 03/29] soc: fsl: qe: properly scan GPIO nodes at startup Before commit 156460811def ("soc: fsl: qe: Change GPIO driver to a proper platform driver") qe_add_gpiochips() was walking the device tree to find all nodes with compatible "fsl,mpc8323-qe-pario-bank". After that commit the discovery is handled by the platform core, therefore it is necessary to call of_platform_default_populate() on the par_io node. Fixes: 156460811def ("soc: fsl: qe: Change GPIO driver to a proper platform driver") Link: https://lore.kernel.org/r/a1db12ef75bf881dd5fba893a37db0c8517eca1b.1785414349.git.chleroy@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_io.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/soc/fsl/qe/qe_io.c b/drivers/soc/fsl/qe/qe_io.c index a5e2d0e5ab51..150913fce981 100644 --- a/drivers/soc/fsl/qe/qe_io.c +++ b/drivers/soc/fsl/qe/qe_io.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -184,3 +185,17 @@ int par_io_of_config(struct device_node *np) return 0; } EXPORT_SYMBOL(par_io_of_config); + +static int __init par_io_populate(void) +{ + struct device_node *np = of_find_node_by_type(NULL, "par_io"); + + if (!np) + return 0; + + of_platform_default_populate(np, NULL, NULL); + of_node_put(np); + + return 0; +} +arch_initcall(par_io_populate); From 9fc72aa58cbb4b241127d3a823885fb63927893f Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 10:54:56 +0100 Subject: [PATCH 04/29] drivers/bus/fsl-mc: Use strscpy() to copy strings into arrays Replacing strcpy() with strscpy() ensures than overflow of the target buffer cannot happen. Signed-off-by: David Laight Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260608095500.2567-1-david.laight.linux@gmail.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/fsl-mc-bus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index 64d75eed0d34..8c203cd47534 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -908,7 +908,7 @@ struct fsl_mc_device *fsl_mc_get_endpoint(struct fsl_mc_device *mc_dev, int state, err; mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); - strcpy(endpoint1.type, mc_dev->obj_desc.type); + strscpy(endpoint1.type, mc_dev->obj_desc.type); endpoint1.id = mc_dev->obj_desc.id; endpoint1.if_id = if_id; @@ -925,7 +925,7 @@ struct fsl_mc_device *fsl_mc_get_endpoint(struct fsl_mc_device *mc_dev, return ERR_PTR(err); } - strcpy(endpoint_desc.type, endpoint2.type); + strscpy(endpoint_desc.type, endpoint2.type); endpoint_desc.id = endpoint2.id; endpoint = fsl_mc_device_lookup(&endpoint_desc, mc_bus_dev); if (endpoint) From 0f75bb64b427a2188f4befd610ac6298686868d9 Mon Sep 17 00:00:00 2001 From: Roy Pledge Date: Wed, 28 Sep 2022 11:25:29 -0400 Subject: [PATCH 05/29] MAINTAINERS: Update drivers/soc/fsl/dpio maintainer Update the drivers/soc/fsl/dpio maintainer to be Ioana Ciornei Signed-off-by: Roy Pledge Acked-by: Ioana Ciornei Link: https://lore.kernel.org/r/1664378729-24815-1-git-send-email-roy.pledge@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..664d5f956650 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7823,7 +7823,7 @@ F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml F: drivers/media/i2c/dw9807-vcm.c DPAA2 DATAPATH I/O (DPIO) DRIVER -M: Roy Pledge +M: Ioana Ciornei L: linux-kernel@vger.kernel.org S: Maintained F: drivers/soc/fsl/dpio From 020f8b2bd150fc042cc55200334f35b0057bf937 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 23 Jul 2026 10:47:48 +0800 Subject: [PATCH 06/29] soc: fsl: dpio: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Acked-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260723024748.30105-1-panchuang@vivo.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/dpio/dpio-driver.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/soc/fsl/dpio/dpio-driver.c b/drivers/soc/fsl/dpio/dpio-driver.c index 9e3fddd8f5a9..d5198ffc4455 100644 --- a/drivers/soc/fsl/dpio/dpio-driver.c +++ b/drivers/soc/fsl/dpio/dpio-driver.c @@ -102,12 +102,8 @@ static int register_dpio_irq_handlers(struct fsl_mc_device *dpio_dev, int cpu) 0, dev_name(&dpio_dev->dev), &dpio_dev->dev); - if (error < 0) { - dev_err(&dpio_dev->dev, - "devm_request_irq() failed: %d\n", - error); + if (error < 0) return error; - } /* set the affinity hint */ if (irq_set_affinity_hint(irq->virq, cpumask_of(cpu))) From 859695749ba0d06cbb5d64b4aad95ce4546dfee4 Mon Sep 17 00:00:00 2001 From: "Christophe Leroy (CS GROUP)" Date: Thu, 30 Jul 2026 12:09:40 +0200 Subject: [PATCH 07/29] soc: fsl: qe: implement get_direction() The lack of get_direction() callback in this driver causes GPIOLIB to emit a warning. Implement it. Fixes: e623c4303ed1 ("gpiolib: sanitize the return value of gpio_chip::get_direction()") Reviewed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/30b3f278a10b46252783458c81dc438df176f86c.1785405882.git.chleroy@kernel.org Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/gpio.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/soc/fsl/qe/gpio.c b/drivers/soc/fsl/qe/gpio.c index 66828f2a3577..6d8f4d549fe2 100644 --- a/drivers/soc/fsl/qe/gpio.c +++ b/drivers/soc/fsl/qe/gpio.c @@ -135,6 +135,30 @@ static int qe_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) return 0; } +static int qe_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) +{ + struct qe_gpio_chip *qe_gc = gpiochip_get_data(gc); + struct qe_pio_regs __iomem *regs = qe_gc->regs; + unsigned long flags; + u32 val, mask; + + spin_lock_irqsave(&qe_gc->lock, flags); + + if (gpio < QE_PIO_PINS / 2) + val = ioread32be(®s->cpdir1); + else + val = ioread32be(®s->cpdir2); + + spin_unlock_irqrestore(&qe_gc->lock, flags); + + mask = (u32)QE_PIO_DIR_OUT << (QE_PIO_PINS - 2 - (gpio % (QE_PIO_PINS / 2)) * 2); + + if (val & mask) + return GPIO_LINE_DIRECTION_OUT; + else + return GPIO_LINE_DIRECTION_IN; +} + struct qe_pin { /* * The qe_gpio_chip name is unfortunate, we should change that to @@ -308,6 +332,7 @@ static int qe_gpio_probe(struct platform_device *ofdev) gc->ngpio = QE_PIO_PINS; gc->direction_input = qe_gpio_dir_in; gc->direction_output = qe_gpio_dir_out; + gc->get_direction = qe_gpio_get_direction; gc->get = qe_gpio_get; gc->set = qe_gpio_set; gc->set_multiple = qe_gpio_set_multiple; From af13753e91855a85078d08fb1add0f5e18474850 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:14 +0200 Subject: [PATCH 08/29] soc: fsl: qe: Add chained_irq_{enter,exit}() calls in cascade handler Wrap the cascade handler body with chained_irq_{enter,exit}() to properly inform the parent IRQ chip that a chained interrupt is being serviced. Fixes: f0bcd784e1b76 ("soc: fsl: qe: Add an interrupt controller for QUICC Engine Ports") Signed-off-by: Paul Louvel Cc: stable@kernel.org Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-1-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 33ca1ddafe18..c3768c82a58c 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -82,7 +83,13 @@ static int qepic_get_irq(struct irq_desc *desc) static void qepic_cascade(struct irq_desc *desc) { + struct irq_chip *chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + generic_handle_irq(qepic_get_irq(desc)); + + chained_irq_exit(chip, desc); } static int qepic_host_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) From 24be94b22d4145106bd784e58a0c93e7a0cecbd5 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:15 +0200 Subject: [PATCH 09/29] dt-bindings: soc: fsl: qe: Set #interrupt-cells to 2 to support interrupt type encoding The QUICC Engine port interrupt controller can be configured to generate an interrupt on either a high-to-low transition or any change in the signal state on the related GPIOs. Update the #interrupt-cells property to 2 so consumers can encode interrupt level information. Acked-by: Krzysztof Kozlowski Signed-off-by: Paul Louvel Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-2-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- .../bindings/interrupt-controller/fsl,qe-ports-ic.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/fsl,qe-ports-ic.yaml b/Documentation/devicetree/bindings/interrupt-controller/fsl,qe-ports-ic.yaml index 2b8e7b9c6d7a..2b7c6b4f0389 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/fsl,qe-ports-ic.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/fsl,qe-ports-ic.yaml @@ -23,7 +23,7 @@ properties: const: 0 '#interrupt-cells': - const: 1 + const: 2 interrupts: maxItems: 1 @@ -45,7 +45,7 @@ examples: reg = <0xc00 0x18>; interrupt-controller; #address-cells = <0>; - #interrupt-cells = <1>; + #interrupt-cells = <2>; interrupts = <74 0x8>; interrupt-parent = <&ipic>; }; From 465274a3282c0fd89332bad494cc12910a031c62 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Wed, 8 Jul 2026 12:15:16 +0200 Subject: [PATCH 10/29] dt-bindings: soc: fsl: qe: Convert QE GPIO to DT schema Convert QE GPIO devicetree binding to DT schema. The old binding uses fsl,-qe-pario-bank because multiple MCP83XX SoC has support for these GPIO banks. The best practice is to list out every instead. Signed-off-by: Christophe Leroy Signed-off-by: Paul Louvel Reviewed-by: Krzysztof Kozlowski Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-3-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- .../gpio/fsl,mpc8323-qe-pario-bank.yaml | 45 +++++++++++++++++++ .../bindings/soc/fsl/cpm_qe/qe/par_io.txt | 26 +---------- 2 files changed, 46 insertions(+), 25 deletions(-) create mode 100644 Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml diff --git a/Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml b/Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml new file mode 100644 index 000000000000..1af99339ff40 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/fsl,mpc8323-qe-pario-bank.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Freescale QUICC Engine Parallel I/O (QE PARIO) GPIO Bank + +maintainers: + - Christophe Leroy + +properties: + compatible: + oneOf: + - items: + - enum: + - fsl,mpc8360-qe-pario-bank + - fsl,mpc8569-qe-pario-bank + - const: fsl,mpc8323-qe-pario-bank + - const: fsl,mpc8323-qe-pario-bank + + reg: + maxItems: 1 + + gpio-controller: true + + "#gpio-cells": + const: 2 + +required: + - compatible + - reg + - gpio-controller + - "#gpio-cells" + +additionalProperties: false + +examples: + - | + gpio-controller@1400 { + compatible = "fsl,mpc8360-qe-pario-bank", "fsl,mpc8323-qe-pario-bank"; + reg = <0x1400 0x18>; + gpio-controller; + #gpio-cells = <2>; + }; diff --git a/Documentation/devicetree/bindings/soc/fsl/cpm_qe/qe/par_io.txt b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/qe/par_io.txt index 09b1b05fa677..782699c14567 100644 --- a/Documentation/devicetree/bindings/soc/fsl/cpm_qe/qe/par_io.txt +++ b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/qe/par_io.txt @@ -24,28 +24,4 @@ par_io@1400 { Note that "par_io" nodes are obsolete, and should not be used for the new device trees. Instead, each Par I/O bank should be represented -via its own gpio-controller node: - -Required properties: -- #gpio-cells : should be "2". -- compatible : should be "fsl,-qe-pario-bank", - "fsl,mpc8323-qe-pario-bank". -- reg : offset to the register set and its length. -- gpio-controller : node to identify gpio controllers. - -Example: - qe_pio_a: gpio-controller@1400 { - #gpio-cells = <2>; - compatible = "fsl,mpc8360-qe-pario-bank", - "fsl,mpc8323-qe-pario-bank"; - reg = <0x1400 0x18>; - gpio-controller; - }; - - qe_pio_e: gpio-controller@1460 { - #gpio-cells = <2>; - compatible = "fsl,mpc8360-qe-pario-bank", - "fsl,mpc8323-qe-pario-bank"; - reg = <0x1460 0x18>; - gpio-controller; - }; +via its own gpio-controller node. From 04ba9aab86b3057e112d3ef6028ead80ddbd2780 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:17 +0200 Subject: [PATCH 11/29] dt-bindings: soc: fsl: qe: Add support of IRQ in QE GPIO Some QE GPIO pins have an associated interrupt line in the QE PIC to signal state changes on the pin. Add the corresponding interrupt-controller / nexus properties to the QE GPIO binding. Because the GPIO controller does not perform any interrupt handling itself, a nexus node (interrupt-map) is used to map each GPIO line supporting IRQ to the parent QE PIC interrupt domain. As the QE PIC can be configured to generate an interrupt on either a high-to-low transition or any change in signal state, three interrupt-map entries are needed per GPIO pin that can yield an interrupt (falling, both, and the "none" case which defaults to both in QE PIC). This overhead is necessary because the interrupt-map-pass-thru property is not part of the DT specification. The interrupt-map property is optional: it is not required for GPIO banks that have no interrupt capable GPIO line (e.g. port D on MPC8323), or when interrupt functionality is not used. Update the example to show a scenario where each bank supports a different numbers of IRQs, or no IRQs at all. Signed-off-by: Paul Louvel Reviewed-by: Krzysztof Kozlowski Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-4-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- .../gpio/fsl,mpc8323-qe-pario-bank.yaml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml b/Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml index 1af99339ff40..1d2ab44fcd3c 100644 --- a/Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml +++ b/Documentation/devicetree/bindings/gpio/fsl,mpc8323-qe-pario-bank.yaml @@ -27,6 +27,17 @@ properties: "#gpio-cells": const: 2 + "#address-cells": + const: 0 + + "#interrupt-cells": + const: 2 + + interrupt-map: + description: | + Specifies the mapping of GPIO lines to the parent interrupt controller, as the + GPIO controller does not do interrupt handling itself. + required: - compatible - reg @@ -37,9 +48,37 @@ additionalProperties: false examples: - | + #include + gpio-controller@1400 { compatible = "fsl,mpc8360-qe-pario-bank", "fsl,mpc8323-qe-pario-bank"; reg = <0x1400 0x18>; gpio-controller; #gpio-cells = <2>; }; + + gpio-controller@1418 { + compatible = "fsl,mpc8323-qe-pario-bank"; + reg = <0x1418 0x18>; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <0>; + #interrupt-cells = <2>; + interrupt-map = < + 7 IRQ_TYPE_EDGE_FALLING &pic 4 IRQ_TYPE_EDGE_FALLING + 7 IRQ_TYPE_EDGE_BOTH &pic 4 IRQ_TYPE_EDGE_BOTH + 7 0 &pic 4 IRQ_TYPE_NONE + + 9 IRQ_TYPE_EDGE_FALLING &pic 5 IRQ_TYPE_EDGE_FALLING + 9 IRQ_TYPE_EDGE_BOTH &pic 5 IRQ_TYPE_EDGE_BOTH + 9 0 &pic 5 IRQ_TYPE_NONE + + 25 IRQ_TYPE_EDGE_FALLING &pic 6 IRQ_TYPE_EDGE_FALLING + 25 IRQ_TYPE_EDGE_BOTH &pic 6 IRQ_TYPE_EDGE_BOTH + 25 0 &pic 6 IRQ_TYPE_NONE + + 27 IRQ_TYPE_EDGE_FALLING &pic 7 IRQ_TYPE_EDGE_FALLING + 27 IRQ_TYPE_EDGE_BOTH &pic 7 IRQ_TYPE_EDGE_BOTH + 27 0 &pic 7 IRQ_TYPE_NONE + >; + }; From 292bdbc172c58921b385a78bd37799bafe2dc921 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:18 +0200 Subject: [PATCH 12/29] soc: fsl: qe: Use generic_handle_domain_irq() Replace the irq_find_mapping() + generic_handle_irq() pattern with generic_handle_domain_irq(), which handles the IRQ domain lookup internally. This is less error-prone and more idiomatic. Remove the now-unused irq_find_mapping() call from qepic_get_irq(). Signed-off-by: Paul Louvel Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-5-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index c3768c82a58c..c8fff45e353c 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -78,16 +78,17 @@ static int qepic_get_irq(struct irq_desc *desc) if (!event) return -1; - return irq_find_mapping(data->host, 32 - ffs(event)); + return 32 - ffs(event); } static void qepic_cascade(struct irq_desc *desc) { + struct qepic_data *data = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); chained_irq_enter(chip, desc); - generic_handle_irq(qepic_get_irq(desc)); + generic_handle_domain_irq(data->host, qepic_get_irq(desc)); chained_irq_exit(chip, desc); } From 2d3e3b4684980a12d247314f8f23d7fb38e1c21c Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:19 +0200 Subject: [PATCH 13/29] soc: fsl: qe: Iterate over all pending interrupts in cascade handler Instead of only servicing a single interrupt, the chained handler can handle all IRQs that have their bit set in the event register. This avoid multiple parent IRQ handler being serviced if more than one interrupt are pending on the QE PIC. Remove unused code. Signed-off-by: Paul Louvel Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-6-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index c8fff45e353c..bc8b68e5d1a9 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -5,6 +5,7 @@ * Copyright (c) 2025 Christophe Leroy CS GROUP France (christophe.leroy@csgroup.eu) */ +#include #include #include #include @@ -70,25 +71,17 @@ static struct irq_chip qepic = { .irq_set_type = qepic_set_type, }; -static int qepic_get_irq(struct irq_desc *desc) -{ - struct qepic_data *data = irq_desc_get_handler_data(desc); - u32 event = ioread32be(data->reg + CEPIER); - - if (!event) - return -1; - - return 32 - ffs(event); -} - static void qepic_cascade(struct irq_desc *desc) { struct qepic_data *data = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned long event, bit; chained_irq_enter(chip, desc); - generic_handle_domain_irq(data->host, qepic_get_irq(desc)); + event = ioread32be(data->reg + CEPIER); + for_each_set_bit(bit, &event, 32) + generic_handle_domain_irq(data->host, 31 - bit); chained_irq_exit(chip, desc); } From 3b92e0a14035ec067f31bb3a49055517b1649d16 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:20 +0200 Subject: [PATCH 14/29] soc: fsl: qe: Handle spurious interrupts When no interrupt bits are set in the event register, call handle_bad_irq() to account for the spurious interrupt before exiting the cascade handler. Signed-off-by: Paul Louvel Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-7-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index bc8b68e5d1a9..29f4334db5a0 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -80,9 +80,15 @@ static void qepic_cascade(struct irq_desc *desc) chained_irq_enter(chip, desc); event = ioread32be(data->reg + CEPIER); + if (!event) { + handle_bad_irq(desc); + goto out; + } + for_each_set_bit(bit, &event, 32) generic_handle_domain_irq(data->host, 31 - bit); +out: chained_irq_exit(chip, desc); } From c499c4504fa942779c6b25cd269cd42b40075f1c Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:21 +0200 Subject: [PATCH 15/29] soc: fsl: qe: Convert to generic IRQ chip The generic IRQ chip framework is available to handle IRQ chips. Using this framework for the QE interrupt controller allows to simplify the driver. Indeed, the framework internally handles operations coded directly in the driver. Signed-off-by: Paul Louvel Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-8-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/Kconfig | 1 + drivers/soc/fsl/qe/qe_ports_ic.c | 118 ++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig index b35a8fd30ebf..87cd1662e168 100644 --- a/drivers/soc/fsl/qe/Kconfig +++ b/drivers/soc/fsl/qe/Kconfig @@ -71,6 +71,7 @@ config QE_USB config QE_GPIO bool "QE GPIO support" depends on QUICC_ENGINE + select GENERIC_IRQ_CHIP select GPIOLIB help Say Y here if you're going to use hardware that connects to the diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 29f4334db5a0..4f6b75439f9f 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -18,107 +18,144 @@ struct qepic_data { void __iomem *reg; - struct irq_domain *host; int irq; }; static void qepic_mask(struct irq_data *d) { - struct qepic_data *data = irq_data_get_irq_chip_data(d); - u32 val = ioread32be(data->reg + CEPIMR); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + u32 val; - iowrite32be(val & ~(1 << (31 - irqd_to_hwirq(d))), data->reg + CEPIMR); + guard(raw_spinlock)(&gc->lock); + + val = ioread32be(gc->reg_base + CEPIMR); + iowrite32be(val & ~d->mask, gc->reg_base + CEPIMR); } static void qepic_unmask(struct irq_data *d) { - struct qepic_data *data = irq_data_get_irq_chip_data(d); - u32 val = ioread32be(data->reg + CEPIMR); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + u32 val; - iowrite32be(val | 1 << (31 - irqd_to_hwirq(d)), data->reg + CEPIMR); + guard(raw_spinlock)(&gc->lock); + + val = ioread32be(gc->reg_base + CEPIMR); + iowrite32be(val | d->mask, gc->reg_base + CEPIMR); } static void qepic_end(struct irq_data *d) { - struct qepic_data *data = irq_data_get_irq_chip_data(d); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); - iowrite32be(1 << (31 - irqd_to_hwirq(d)), data->reg + CEPIER); + iowrite32be(d->mask, gc->reg_base + CEPIER); +} + +static void qepic_calc_mask(struct irq_data *d) +{ + d->mask = 1 << (31 - irqd_to_hwirq(d)); } static int qepic_set_type(struct irq_data *d, unsigned int flow_type) { - struct qepic_data *data = irq_data_get_irq_chip_data(d); - unsigned int vec = (unsigned int)irqd_to_hwirq(d); - u32 val = ioread32be(data->reg + CEPICR); + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + u32 val; + guard(raw_spinlock)(&gc->lock); + + val = ioread32be(gc->reg_base + CEPICR); switch (flow_type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_EDGE_FALLING: - iowrite32be(val | 1 << (31 - vec), data->reg + CEPICR); + iowrite32be(val | d->mask, gc->reg_base + CEPICR); return 0; case IRQ_TYPE_EDGE_BOTH: case IRQ_TYPE_NONE: - iowrite32be(val & ~(1 << (31 - vec)), data->reg + CEPICR); + iowrite32be(val & ~d->mask, gc->reg_base + CEPICR); return 0; } return -EINVAL; } -static struct irq_chip qepic = { - .name = "QEPIC", - .irq_mask = qepic_mask, - .irq_unmask = qepic_unmask, - .irq_eoi = qepic_end, - .irq_set_type = qepic_set_type, -}; - static void qepic_cascade(struct irq_desc *desc) { - struct qepic_data *data = irq_desc_get_handler_data(desc); + struct irq_domain *domain = irq_desc_get_handler_data(desc); + struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); struct irq_chip *chip = irq_desc_get_chip(desc); unsigned long event, bit; chained_irq_enter(chip, desc); - event = ioread32be(data->reg + CEPIER); + event = ioread32be(gc->reg_base + CEPIER); if (!event) { handle_bad_irq(desc); goto out; } for_each_set_bit(bit, &event, 32) - generic_handle_domain_irq(data->host, 31 - bit); + generic_handle_domain_irq(domain, 31 - bit); out: chained_irq_exit(chip, desc); } -static int qepic_host_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) +static int qepic_chip_init(struct irq_chip_generic *gc) { - irq_set_chip_data(virq, h->host_data); - irq_set_chip_and_handler(virq, &qepic, handle_fasteoi_irq); + struct qepic_data *data = gc->domain->host_data; + struct irq_chip_type *ct = gc->chip_types; + + gc->reg_base = data->reg; + + ct->chip.irq_mask = qepic_mask; + ct->chip.irq_unmask = qepic_unmask; + ct->chip.irq_eoi = qepic_end; + ct->chip.irq_set_type = qepic_set_type; + ct->chip.irq_calc_mask = qepic_calc_mask; + return 0; } -static const struct irq_domain_ops qepic_host_ops = { - .map = qepic_host_map, -}; - -static void qepic_remove(void *res) +static int qepic_domain_init(struct irq_domain *d) { - struct qepic_data *data = res; + struct qepic_data *data = d->host_data; + + irq_set_chained_handler_and_data(data->irq, qepic_cascade, d); + + return 0; +} + +static void qepic_domain_exit(struct irq_domain *d) +{ + struct qepic_data *data = d->host_data; irq_set_chained_handler_and_data(data->irq, NULL, NULL); - irq_domain_remove(data->host); } static int qepic_probe(struct platform_device *pdev) { + struct irq_domain_chip_generic_info dgc_info = { + .name = "QEPIC", + .handler = handle_fasteoi_irq, + .irqs_per_chip = 32, + .num_ct = 1, + .init = qepic_chip_init, + }; + struct irq_domain_info d_info = { + .fwnode = of_fwnode_handle(pdev->dev.of_node), + .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC, + .size = 32, + .hwirq_max = 32, + .ops = &irq_generic_chip_ops, + .dgc_info = &dgc_info, + .init = qepic_domain_init, + .exit = qepic_domain_exit, + }; struct device *dev = &pdev->dev; + struct irq_domain *domain; struct qepic_data *data; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; + d_info.host_data = data; data->reg = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(data->reg)) @@ -128,14 +165,11 @@ static int qepic_probe(struct platform_device *pdev) if (data->irq < 0) return data->irq; - data->host = irq_domain_create_linear(dev_fwnode(dev), 32, &qepic_host_ops, data); - if (!data->host) - return -ENODEV; - - irq_set_chained_handler_and_data(data->irq, qepic_cascade, data); - - return devm_add_action_or_reset(dev, qepic_remove, data); + domain = devm_irq_domain_instantiate(dev, &d_info); + if (IS_ERR(domain)) + return PTR_ERR(domain); + return 0; } static const struct of_device_id qepic_match[] = { From c02e80a27cac4f5b9b99009cd532adcbc7c783db Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:22 +0200 Subject: [PATCH 16/29] soc: fsl: qe: Rename irq variable to parent_irq Rename the local variable holding the platform IRQ to parent_irq, which better describes its role as the upstream/chained interrupt in the hierarchy. Signed-off-by: Paul Louvel Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-9-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/qe_ports_ic.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 4f6b75439f9f..7375f92f528b 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -18,7 +18,7 @@ struct qepic_data { void __iomem *reg; - int irq; + int parent_irq; }; static void qepic_mask(struct irq_data *d) @@ -117,7 +117,7 @@ static int qepic_domain_init(struct irq_domain *d) { struct qepic_data *data = d->host_data; - irq_set_chained_handler_and_data(data->irq, qepic_cascade, d); + irq_set_chained_handler_and_data(data->parent_irq, qepic_cascade, d); return 0; } @@ -126,7 +126,7 @@ static void qepic_domain_exit(struct irq_domain *d) { struct qepic_data *data = d->host_data; - irq_set_chained_handler_and_data(data->irq, NULL, NULL); + irq_set_chained_handler_and_data(data->parent_irq, NULL, NULL); } static int qepic_probe(struct platform_device *pdev) @@ -161,9 +161,9 @@ static int qepic_probe(struct platform_device *pdev) if (IS_ERR(data->reg)) return PTR_ERR(data->reg); - data->irq = platform_get_irq(pdev, 0); - if (data->irq < 0) - return data->irq; + data->parent_irq = platform_get_irq(pdev, 0); + if (data->parent_irq < 0) + return data->parent_irq; domain = devm_irq_domain_instantiate(dev, &d_info); if (IS_ERR(domain)) From 52cb07cd7ab99dbee2839c5147b20d9247983133 Mon Sep 17 00:00:00 2001 From: Paul Louvel Date: Wed, 8 Jul 2026 12:15:23 +0200 Subject: [PATCH 17/29] soc: fsl: qe: Add support of IRQs in QE GPIO Some QE GPIO pins have an associated interrupt line in the QE PIC to signal state changes on the pin. Because the GPIO controller does not perform any interrupt handling itself, a nexus node (interrupt-map) is used to map each GPIO line supporting IRQ to the parent QE PIC interrupt domain. Add the to_irq() method in the corresponding GPIO controller driver, that uses the nexus node to perform the translation. Signed-off-by: Paul Louvel Reviewed-by: Christophe Leroy (CS GROUP) Link: https://lore.kernel.org/r/20260708-qe-pic-gpios-v2-10-1972044cfbd1@bootlin.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/qe/gpio.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/soc/fsl/qe/gpio.c b/drivers/soc/fsl/qe/gpio.c index 6d8f4d549fe2..5cd59cfbf22f 100644 --- a/drivers/soc/fsl/qe/gpio.c +++ b/drivers/soc/fsl/qe/gpio.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,7 @@ #define PIN_MASK(gpio) (1UL << (QE_PIO_PINS - 1 - (gpio))) struct qe_gpio_chip { + struct device_node *np; struct gpio_chip gc; void __iomem *regs; spinlock_t lock; @@ -159,6 +161,29 @@ static int qe_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) return GPIO_LINE_DIRECTION_IN; } +static int qe_gpio_to_irq(struct gpio_chip *gc, unsigned int gpio) +{ + struct qe_gpio_chip *qe_gc = gpiochip_get_data(gc); + struct of_phandle_args oirq; + struct irq_domain *domain; + int ret; + + oirq.np = qe_gc->np; + oirq.args_count = 2; + oirq.args[0] = gpio; + oirq.args[1] = 0; + + ret = of_irq_parse_raw(NULL, &oirq); + if (ret) + return ret; + + domain = irq_find_host(oirq.np); + if (!domain) + return -EPROBE_DEFER; + + return irq_create_of_mapping(&oirq); +} + struct qe_pin { /* * The qe_gpio_chip name is unfortunate, we should change that to @@ -323,7 +348,7 @@ static int qe_gpio_probe(struct platform_device *ofdev) qe_gc = devm_kzalloc(dev, sizeof(*qe_gc), GFP_KERNEL); if (!qe_gc) return -ENOMEM; - + qe_gc->np = np; spin_lock_init(&qe_gc->lock); gc = &qe_gc->gc; @@ -336,6 +361,7 @@ static int qe_gpio_probe(struct platform_device *ofdev) gc->get = qe_gpio_get; gc->set = qe_gpio_set; gc->set_multiple = qe_gpio_set_multiple; + gc->to_irq = qe_gpio_to_irq; gc->parent = dev; gc->owner = THIS_MODULE; From a9f1f4a2c7db6928a4cf0815d252da9639cdd662 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 10 Jul 2026 19:09:26 +0800 Subject: [PATCH 18/29] bus: fsl-mc: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_threaded_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260710110930.462109-2-panchuang@vivo.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/bus/fsl-mc/dprc-driver.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/bus/fsl-mc/dprc-driver.c b/drivers/bus/fsl-mc/dprc-driver.c index a85706826fa0..a81188cb06f2 100644 --- a/drivers/bus/fsl-mc/dprc-driver.c +++ b/drivers/bus/fsl-mc/dprc-driver.c @@ -521,12 +521,8 @@ static int register_dprc_irq_handler(struct fsl_mc_device *mc_dev) IRQF_NO_SUSPEND | IRQF_ONESHOT, dev_name(&mc_dev->dev), &mc_dev->dev); - if (error < 0) { - dev_err(&mc_dev->dev, - "devm_request_threaded_irq() failed: %d\n", - error); + if (error < 0) return error; - } return 0; } From 30fffedd8fddb8763fabefff5439f4fa33183007 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 27 Jul 2026 17:49:24 -0700 Subject: [PATCH 19/29] soc: fsl: fix kernel-doc warnings and typos Correct spelling of "list". Fix a kernel-doc warning by describing the nested structure completely: include/soc/fsl/dpaa2-fd.h:52: warning: Function parameter or member 'simple' not described in 'dpaa2_fd' Signed-off-by: Randy Dunlap Cc: Li Yang Cc: linuxppc-dev@lists.ozlabs.org Cc: linux-arm-kernel@lists.infradead.org Cc: Frank Li Cc: Guanhua Gao Cc: Roy Pledge Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260728004924.904210-1-rdunlap@infradead.org Signed-off-by: Christophe Leroy (CS GROUP) --- include/soc/fsl/dpaa2-fd.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/include/soc/fsl/dpaa2-fd.h b/include/soc/fsl/dpaa2-fd.h index bae490cac0aa..0ca0766b2b0d 100644 --- a/include/soc/fsl/dpaa2-fd.h +++ b/include/soc/fsl/dpaa2-fd.h @@ -25,14 +25,15 @@ /** * struct dpaa2_fd - Struct describing FDs - * @words: for easier/faster copying the whole FD structure - * @addr: address in the FD - * @len: length in the FD - * @bpid: buffer pool ID - * @format_offset: format, offset, and short-length fields - * @frc: frame context - * @ctrl: control bits...including dd, sc, va, err, etc - * @flc: flow context address + * @words: for easier/faster copying the whole FD structure + * @simple: struct for the FD fields + * @simple.addr: address in the FD + * @simple.len: length in the FD + * @simple.bpid: buffer pool ID + * @simple.format_offset: format, offset, and short-length fields + * @simple.frc: frame context + * @simple.ctrl: control bits...including dd, sc, va, err, etc + * @simple.flc: flow context address * * This structure represents the basic Frame Descriptor used in the system. */ @@ -497,7 +498,7 @@ static inline void dpaa2_fl_set_addr(struct dpaa2_fl_entry *fle, * dpaa2_fl_get_frc() - Get the frame context in the FLE * @fle: the given frame list entry * - * Return the frame context field in the frame lsit entry. + * Return the frame context field in the frame list entry. */ static inline u32 dpaa2_fl_get_frc(const struct dpaa2_fl_entry *fle) { From 09d2f78aef144334b404b16d3e0777037dbbc01a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 27 Jul 2026 17:49:38 -0700 Subject: [PATCH 20/29] soc: fsl: dpio: fix kernel-doc typos Correct spelling of 2 words. Signed-off-by: Randy Dunlap Cc: Li Yang Cc: linuxppc-dev@lists.ozlabs.org Cc: linux-arm-kernel@lists.infradead.org Cc: Frank Li Cc: Guanhua Gao Cc: Roy Pledge Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260728004938.905415-1-rdunlap@infradead.org Signed-off-by: Christophe Leroy (CS GROUP) --- include/soc/fsl/dpaa2-io.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/soc/fsl/dpaa2-io.h b/include/soc/fsl/dpaa2-io.h index 4bf62de2e00e..2a748fafa5b7 100644 --- a/include/soc/fsl/dpaa2-io.h +++ b/include/soc/fsl/dpaa2-io.h @@ -22,7 +22,7 @@ struct device; * DOC: DPIO Service * * The DPIO service provides APIs for users to interact with the datapath - * by enqueueing and dequeing frame descriptors. + * by enqueueing and dequeueing frame descriptors. * * The following set of APIs can be used to enqueue and dequeue frames * as well as producing notification callbacks when data is available @@ -33,7 +33,7 @@ struct device; /** * struct dpaa2_io_desc - The DPIO descriptor - * @receives_notifications: Use notificaton mode. Non-zero if the DPIO + * @receives_notifications: Use notification mode. Non-zero if the DPIO * has a channel. * @has_8prio: Set to non-zero for channel with 8 priority WQs. Ignored * unless receives_notification is TRUE. From 33c93ae2ce08aa4a13e8a627d90f2dbd9f18c755 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 22 Jul 2026 02:15:55 +0300 Subject: [PATCH 21/29] soc: fsl: guts: perform fsl_guts_init() error teardown in reverse order of setup fsl_guts_init() is about to get much more complicated and the central error handling procedure cannot scale in its current design, unless we add a lot of "if" conditions to detect what has been allocated and what hasn't. Currently the code relies on the fact that kfree(NULL) is safe, but this doesn't scale to the case where "soc_dev_attr" itself is NULL, because this would dereference "soc_dev_attr->family" and friends of a NULL pointer. Convert to the more typical error handling pattern where the teardown is in the strict reverse order of setup, and a teardown step is only called if its corresponding setup step was executed. At the same time, maintain the optionality of soc_dev_attr->serial_number by not checking whether that kasprintf() has returned NULL. In the error path, kfree(NULL) is safe, so we don't need to add an "if" condition for it. Michael Walle has confirmed that ignoring the error was intentional, and we preserve that: https://lore.kernel.org/linux-phy/DK44809N7Y8I.J2Z3U4N32H0Q@kernel.org/ Signed-off-by: Vladimir Oltean Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260721231603.67865-2-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/guts.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index 9bee7baec2b9..453456f31800 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -227,17 +227,23 @@ static int __init fsl_guts_init(void) } else { soc_dev_attr->family = kasprintf(GFP_KERNEL, "QorIQ"); } - if (!soc_dev_attr->family) - goto err_nomem; + if (!soc_dev_attr->family) { + ret = -ENOMEM; + goto err_free_soc_dev_attr; + } soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "svr:0x%08x", svr); - if (!soc_dev_attr->soc_id) - goto err_nomem; + if (!soc_dev_attr->soc_id) { + ret = -ENOMEM; + goto err_free_family; + } soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d.%d", (svr >> 4) & 0xf, svr & 0xf); - if (!soc_dev_attr->revision) - goto err_nomem; + if (!soc_dev_attr->revision) { + ret = -ENOMEM; + goto err_free_soc_id; + } if (soc_data) soc_uid = fsl_guts_get_soc_uid(soc_data->sfp_compat, @@ -249,7 +255,7 @@ static int __init fsl_guts_init(void) soc_dev = soc_device_register(soc_dev_attr); if (IS_ERR(soc_dev)) { ret = PTR_ERR(soc_dev); - goto err; + goto err_free_serial_number; } pr_info("Machine: %s\n", soc_dev_attr->machine); @@ -259,13 +265,14 @@ static int __init fsl_guts_init(void) return 0; -err_nomem: - ret = -ENOMEM; -err: - kfree(soc_dev_attr->family); - kfree(soc_dev_attr->soc_id); - kfree(soc_dev_attr->revision); +err_free_serial_number: kfree(soc_dev_attr->serial_number); + kfree(soc_dev_attr->revision); +err_free_soc_id: + kfree(soc_dev_attr->soc_id); +err_free_family: + kfree(soc_dev_attr->family); +err_free_soc_dev_attr: kfree(soc_dev_attr); return ret; From 77680625808b611d4305d96d94a16703d0ba6c84 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Wed, 22 Jul 2026 02:15:56 +0300 Subject: [PATCH 22/29] soc: fsl: guts: use a macro to encode the DCFG CCSR space Instead of using a hardcoded value when iomapping the DCFG CCSR space, add a new macro for it. The code will be easier to follow this way, especially when we add support for the DCFG DCSR space as well. Signed-off-by: Ioana Ciornei Signed-off-by: Vladimir Oltean Link: https://lore.kernel.org/r/20260721231603.67865-3-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/guts.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index 453456f31800..b97eb80cea95 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -14,6 +14,8 @@ #include #include +#define DCFG_CCSR 0 + struct fsl_soc_die_attr { char *die; u32 svr; @@ -197,7 +199,7 @@ static int __init fsl_guts_init(void) return 0; soc_data = match->data; - regs = of_iomap(np, 0); + regs = of_iomap(np, DCFG_CCSR); if (!regs) { of_node_put(np); return -ENOMEM; From 93b8ffb8c901cc2d34682140820a93ca00f2edab Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Wed, 22 Jul 2026 02:15:57 +0300 Subject: [PATCH 23/29] soc: fsl: guts: add a global structure to hold state Add the fsl_soc_guts structure in order to pass information like base addresses, endianness etc between the init time and the runtime operations (RCW override) which will get added in future patches. There is no point in mapping and unmapping the DCFG CCSR space every time we need to make a read, just map it once and keep its reference in this new global structure. Signed-off-by: Ioana Ciornei Signed-off-by: Vladimir Oltean Link: https://lore.kernel.org/r/20260721231603.67865-4-vladimir.oltean@nxp.com [chleroy: fixed typo on 'structure' in commit message] Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/guts.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index b97eb80cea95..bb65b62ed805 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -106,6 +106,11 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = { { }, }; +static struct fsl_soc_guts { + struct ccsr_guts __iomem *dcfg_ccsr; + bool little_endian; +} soc; + static const struct fsl_soc_die_attr *fsl_soc_die_match( u32 svr, const struct fsl_soc_die_attr *matches) { @@ -187,9 +192,7 @@ static int __init fsl_guts_init(void) const struct fsl_soc_die_attr *soc_die; const struct fsl_soc_data *soc_data; const struct of_device_id *match; - struct ccsr_guts __iomem *regs; struct device_node *np; - bool little_endian; u64 soc_uid = 0; u32 svr; int ret; @@ -199,24 +202,25 @@ static int __init fsl_guts_init(void) return 0; soc_data = match->data; - regs = of_iomap(np, DCFG_CCSR); - if (!regs) { + soc.dcfg_ccsr = of_iomap(np, DCFG_CCSR); + if (!soc.dcfg_ccsr) { of_node_put(np); return -ENOMEM; } - little_endian = of_property_read_bool(np, "little-endian"); - if (little_endian) - svr = ioread32(®s->svr); + soc.little_endian = of_property_read_bool(np, "little-endian"); + if (soc.little_endian) + svr = ioread32(&soc.dcfg_ccsr->svr); else - svr = ioread32be(®s->svr); - iounmap(regs); + svr = ioread32be(&soc.dcfg_ccsr->svr); of_node_put(np); /* Register soc device */ soc_dev_attr = kzalloc_obj(*soc_dev_attr); - if (!soc_dev_attr) - return -ENOMEM; + if (!soc_dev_attr) { + ret = -ENOMEM; + goto err_unmap_dcfg_ccsr; + } ret = soc_attr_read_machine(soc_dev_attr); if (ret) @@ -276,6 +280,9 @@ err_free_family: kfree(soc_dev_attr->family); err_free_soc_dev_attr: kfree(soc_dev_attr); +err_unmap_dcfg_ccsr: + iounmap(soc.dcfg_ccsr); + soc.dcfg_ccsr = NULL; return ret; } From 153b53e6889f2053f904db61da7a54f58d39d775 Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Wed, 22 Jul 2026 02:15:58 +0300 Subject: [PATCH 24/29] soc: fsl: guts: add a central fsl_guts_read() function Add a central fsl_guts_read() function which will take into account the endianness that was already determined. No point is duplicating the if-else statement each time we need to read a DCFG register. Signed-off-by: Ioana Ciornei Signed-off-by: Vladimir Oltean Link: https://lore.kernel.org/r/20260721231603.67865-5-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/guts.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index bb65b62ed805..8e5c3cae811e 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -111,6 +111,14 @@ static struct fsl_soc_guts { bool little_endian; } soc; +static unsigned int fsl_guts_read(const void __iomem *reg) +{ + if (soc.little_endian) + return ioread32(reg); + + return ioread32be(reg); +} + static const struct fsl_soc_die_attr *fsl_soc_die_match( u32 svr, const struct fsl_soc_die_attr *matches) { @@ -209,10 +217,7 @@ static int __init fsl_guts_init(void) } soc.little_endian = of_property_read_bool(np, "little-endian"); - if (soc.little_endian) - svr = ioread32(&soc.dcfg_ccsr->svr); - else - svr = ioread32be(&soc.dcfg_ccsr->svr); + svr = fsl_guts_read(&soc.dcfg_ccsr->svr); of_node_put(np); /* Register soc device */ From cbef03b97ac0d568e938ff6381d11c9f33a1084d Mon Sep 17 00:00:00 2001 From: Ioana Ciornei Date: Wed, 22 Jul 2026 02:15:59 +0300 Subject: [PATCH 25/29] soc: fsl: guts: make it easier to determine on which SoC we are running The guts driver will need to easily determine on which SoC it's running when it will need to perform RCW override at runtime. The guts driver knows this already because fsl_guts_init() reads the QorIQ/Layerscape architectural System Version Register (SVR), but it doesn't save this for later lookups. Add a new qoriq_die enum to be used as an index in the fsl_soc_die array. A new fsl_soc_die_match_one() function is also added so that we can directly determine if the SVR is a match with a specific die. The SVR value read from the DCFG CCSR is also kept in the global soc structure so that it can be accessed when needed. Signed-off-by: Ioana Ciornei Signed-off-by: Vladimir Oltean Link: https://lore.kernel.org/r/20260721231603.67865-6-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/guts.c | 47 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index 8e5c3cae811e..15674c6734c6 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -27,6 +27,23 @@ struct fsl_soc_data { u32 uid_offset; }; +enum qoriq_die { + DIE_T4240, + DIE_T1040, + DIE_T2080, + DIE_T1024, + DIE_LS1043A, + DIE_LS2080A, + DIE_LS1088A, + DIE_LS1012A, + DIE_LS1046A, + DIE_LS2088A, + DIE_LS1021A, + DIE_LX2160A, + DIE_LS1028A, + DIE_MAX, +}; + /* SoC die attribute definition for QorIQ platform */ static const struct fsl_soc_die_attr fsl_soc_die[] = { /* @@ -34,21 +51,25 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = { */ /* Die: T4240, SoC: T4240/T4160/T4080 */ + [DIE_T4240] = { .die = "T4240", .svr = 0x82400000, .mask = 0xfff00000, }, /* Die: T1040, SoC: T1040/T1020/T1042/T1022 */ + [DIE_T1040] = { .die = "T1040", .svr = 0x85200000, .mask = 0xfff00000, }, /* Die: T2080, SoC: T2080/T2081 */ + [DIE_T2080] = { .die = "T2080", .svr = 0x85300000, .mask = 0xfff00000, }, /* Die: T1024, SoC: T1024/T1014/T1023/T1013 */ + [DIE_T1024] = { .die = "T1024", .svr = 0x85400000, .mask = 0xfff00000, @@ -59,46 +80,55 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = { */ /* Die: LS1043A, SoC: LS1043A/LS1023A */ + [DIE_LS1043A] = { .die = "LS1043A", .svr = 0x87920000, .mask = 0xffff0000, }, /* Die: LS2080A, SoC: LS2080A/LS2040A/LS2085A */ + [DIE_LS2080A] = { .die = "LS2080A", .svr = 0x87010000, .mask = 0xff3f0000, }, /* Die: LS1088A, SoC: LS1088A/LS1048A/LS1084A/LS1044A */ + [DIE_LS1088A] = { .die = "LS1088A", .svr = 0x87030000, .mask = 0xff3f0000, }, /* Die: LS1012A, SoC: LS1012A */ + [DIE_LS1012A] = { .die = "LS1012A", .svr = 0x87040000, .mask = 0xffff0000, }, /* Die: LS1046A, SoC: LS1046A/LS1026A */ + [DIE_LS1046A] = { .die = "LS1046A", .svr = 0x87070000, .mask = 0xffff0000, }, /* Die: LS2088A, SoC: LS2088A/LS2048A/LS2084A/LS2044A */ + [DIE_LS2088A] = { .die = "LS2088A", .svr = 0x87090000, .mask = 0xff3f0000, }, /* Die: LS1021A, SoC: LS1021A/LS1020A/LS1022A */ + [DIE_LS1021A] = { .die = "LS1021A", .svr = 0x87000000, .mask = 0xfff70000, }, /* Die: LX2160A, SoC: LX2160A/LX2120A/LX2080A */ + [DIE_LX2160A] = { .die = "LX2160A", .svr = 0x87360000, .mask = 0xff3f0000, }, /* Die: LS1028A, SoC: LS1028A */ + [DIE_LS1028A] = { .die = "LS1028A", .svr = 0x870b0000, .mask = 0xff3f0000, @@ -109,6 +139,7 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = { static struct fsl_soc_guts { struct ccsr_guts __iomem *dcfg_ccsr; bool little_endian; + u32 svr; } soc; static unsigned int fsl_guts_read(const void __iomem *reg) @@ -119,11 +150,16 @@ static unsigned int fsl_guts_read(const void __iomem *reg) return ioread32be(reg); } +static bool fsl_soc_die_match_one(u32 svr, const struct fsl_soc_die_attr *match) +{ + return match->svr == (svr & match->mask); +} + static const struct fsl_soc_die_attr *fsl_soc_die_match( u32 svr, const struct fsl_soc_die_attr *matches) { while (matches->svr) { - if (matches->svr == (svr & matches->mask)) + if (fsl_soc_die_match_one(svr, matches)) return matches; matches++; } @@ -202,7 +238,6 @@ static int __init fsl_guts_init(void) const struct of_device_id *match; struct device_node *np; u64 soc_uid = 0; - u32 svr; int ret; np = of_find_matching_node_and_match(NULL, fsl_guts_of_match, &match); @@ -217,7 +252,7 @@ static int __init fsl_guts_init(void) } soc.little_endian = of_property_read_bool(np, "little-endian"); - svr = fsl_guts_read(&soc.dcfg_ccsr->svr); + soc.svr = fsl_guts_read(&soc.dcfg_ccsr->svr); of_node_put(np); /* Register soc device */ @@ -231,7 +266,7 @@ static int __init fsl_guts_init(void) if (ret) of_machine_read_compatible(&soc_dev_attr->machine, 0); - soc_die = fsl_soc_die_match(svr, fsl_soc_die); + soc_die = fsl_soc_die_match(soc.svr, fsl_soc_die); if (soc_die) { soc_dev_attr->family = kasprintf(GFP_KERNEL, "QorIQ %s", soc_die->die); @@ -243,14 +278,14 @@ static int __init fsl_guts_init(void) goto err_free_soc_dev_attr; } - soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "svr:0x%08x", svr); + soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "svr:0x%08x", soc.svr); if (!soc_dev_attr->soc_id) { ret = -ENOMEM; goto err_free_family; } soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d.%d", - (svr >> 4) & 0xf, svr & 0xf); + (soc.svr >> 4) & 0xf, soc.svr & 0xf); if (!soc_dev_attr->revision) { ret = -ENOMEM; goto err_free_soc_id; From 8e0f607a051935f027f8bfb5df52e073def7cc47 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 22 Jul 2026 02:16:00 +0300 Subject: [PATCH 26/29] soc: fsl: guts: make fsl_soc_data available after fsl_guts_init() In a future change, struct fsl_soc_data will be extended with methods for performing RCW override. Since this will be performed from a calling context outside fsl_guts_init(), we need to keep track of the soc_data that we determine at fsl_guts_init() time, so we can reference it later. Signed-off-by: Vladimir Oltean Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260721231603.67865-7-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/guts.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index 15674c6734c6..c283d44b68a3 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -138,6 +138,7 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = { static struct fsl_soc_guts { struct ccsr_guts __iomem *dcfg_ccsr; + const struct fsl_soc_data *data; bool little_endian; u32 svr; } soc; @@ -231,10 +232,9 @@ static const struct of_device_id fsl_guts_of_match[] = { static int __init fsl_guts_init(void) { - struct soc_device_attribute *soc_dev_attr; + struct soc_device_attribute *soc_dev_attr = NULL; static struct soc_device *soc_dev; const struct fsl_soc_die_attr *soc_die; - const struct fsl_soc_data *soc_data; const struct of_device_id *match; struct device_node *np; u64 soc_uid = 0; @@ -243,12 +243,13 @@ static int __init fsl_guts_init(void) np = of_find_matching_node_and_match(NULL, fsl_guts_of_match, &match); if (!np) return 0; - soc_data = match->data; + soc.data = match->data; soc.dcfg_ccsr = of_iomap(np, DCFG_CCSR); if (!soc.dcfg_ccsr) { of_node_put(np); - return -ENOMEM; + ret = -ENOMEM; + goto err_clear_soc_data; } soc.little_endian = of_property_read_bool(np, "little-endian"); @@ -291,9 +292,9 @@ static int __init fsl_guts_init(void) goto err_free_soc_id; } - if (soc_data) - soc_uid = fsl_guts_get_soc_uid(soc_data->sfp_compat, - soc_data->uid_offset); + if (soc.data) + soc_uid = fsl_guts_get_soc_uid(soc.data->sfp_compat, + soc.data->uid_offset); if (soc_uid) soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", soc_uid); @@ -323,6 +324,8 @@ err_free_soc_dev_attr: err_unmap_dcfg_ccsr: iounmap(soc.dcfg_ccsr); soc.dcfg_ccsr = NULL; +err_clear_soc_data: + soc.data = NULL; return ret; } From ff184dcddb6e45178ae77a3f8c5130635484a472 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 22 Jul 2026 02:16:01 +0300 Subject: [PATCH 27/29] dt-bindings: fsl: layerscape-dcfg: define DCFG_DCSR region In Layerscape (Arm) and QorIQ (PowerPC) devices, hardware peripherals are accessed by the CPU through a portion of the SoC address space called CCSR ("Configuration, Control, and Status Registers"). All hardware IP blocks have their registers mapped here, and the Device Configuration block makes no exception. However, there exists a secondary range of the address space named DCSR ("Debug Control and Status Registers") which, like CCSR, also holds registers of hardware IP blocks, except the DCSR contents is hidden in all public reference manuals. The intention of the CCSR/DCSR split, to the best of my knowledge, was to place the functionality that is too low level for normal use, and which is necessary only for debug, in a completely separate address space which can be hidden. A use case has appeared where networking SerDes lanes need to be reconfigured at runtime for a different protocol (example: 10GBase-R to SGMII), and the architecture of the SoCs does not normally permit that. The Reset Configuration Word (RCW) is a data structure read by the SoC preboot loader (PBL) which contains stuff like pinmuxing and SerDes protocol mapping for each lane. The RCW that the PBL has loaded is visible in the DCFG block's normal status registers (from CCSR), as read only. Turns out, the RCW is also mapped in the DCFG's shadow register map (in DCSR), in a write-only form. Writing to the RCW registers from the DCFG's DCSR space to change what the PBL has loaded is called "RCW override". It has been validated that the RCW override procedure is necessary to reconfigure the networking data path when a SerDes lane performs a major protocol change. It changes some internal muxes which connect the PCS to either the 10G MAC or to the 1G MAC. Defining the DCSR area of the DCFG as a secondary 'reg' array element allows operating systems to perform RCW overrides. Since it is introduced late in the binding's lifetime, it is optional. It can be identified by name, but also by index (first 'reg' is CCSR). Note that while all SoCs should have a DCFG register block in DCSR, we only need to expose it for the SoCs where the RCW override procedure is known to be needed and has been validated. Signed-off-by: Vladimir Oltean Reviewed-by: Conor Dooley Link: https://lore.kernel.org/r/20260721231603.67865-8-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- .../bindings/soc/fsl/fsl,layerscape-dcfg.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml b/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml index 3fb0534ea597..fc14fd0bf84b 100644 --- a/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml +++ b/Documentation/devicetree/bindings/soc/fsl/fsl,layerscape-dcfg.yaml @@ -36,7 +36,20 @@ properties: - const: simple-mfd reg: - maxItems: 1 + minItems: 1 + items: + - description: + Customer-visible DCFG register map from CCSR address space + (Configuration, Control and Status Registers) + - description: + Customer-hidden DCFG register map from DCSR address space + (Debug Control and Status Registers) + + reg-names: + minItems: 1 + items: + - const: dcfg_ccsr + - const: dcfg_dcsr little-endian: true big-endian: true From 3af9f5f742a294336ca8e37064ce01dd89cf87ca Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 22 Jul 2026 02:16:02 +0300 Subject: [PATCH 28/29] soc: fsl: guts: implement the RCW override procedure Add support for the RCW override procedure which enables runtime reconfiguration of the protocol running on a SerDes lane. The procedure is done through the DCFG DCSR space which now can be defined as the second memory region of the guts DT node. Support is added on the following SoCs: LS1046A, LS1088A, LS2088A. The procedure is exported to the "client" driver - the Lynx10G SerDes PHY driver - through the following functions: - fsl_guts_lane_validate() used to validate that changing the protocol on a specific lane is supported. - fsl_guts_lane_set_mode() which can be used to request the RCW procedure be executed for a specific lane. Since the RCW override procedure is different depending on the SoC, the private fsl_soc_data structure is updated with two new per SoC callbacks (.serdes_get_rcw_override() and .serdes_init_rcwcr()) which get used from the generic fsl_guts_lane_set_mode() function. These two callbacks hide all the SoC specific register offsets, masks and values so that the _set_mode() procedure is straightforward. Signed-off-by: Ioana Ciornei Signed-off-by: Vladimir Oltean Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260721231603.67865-9-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/soc/fsl/guts.c | 399 ++++++++++++++++++++++++++++++++++++++- include/linux/fsl/guts.h | 18 +- 2 files changed, 409 insertions(+), 8 deletions(-) diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index c283d44b68a3..28361c63e26c 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -5,7 +5,10 @@ * Copyright (C) 2016 Freescale Semiconductor, Inc. */ +#include +#include #include +#include #include #include #include @@ -15,6 +18,28 @@ #include #define DCFG_CCSR 0 +#define DCFG_DCSR 1 + +#define RCW_TIMEOUT_US 1 + +#define LS1046A_RCWSR5_SRDS_PRTCL_S1(lane) \ + GENMASK(19 + 4 * (lane), 16 + 4 * (lane)) +#define LS1046A_SRDS_PRTCL_XFI 1 +#define LS1046A_SRDS_PRTCL_100BASEX_SGMII 3 + +#define LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane) \ + GENMASK(19 + 4 * (3 - (lane)), 16 + 4 * (3 - (lane))) +#define LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane) \ + GENMASK(3 + 4 * (3 - (lane)), 4 * (3 - (lane))) +#define LS1088A_SRDS_PRTCL_XFI 1 +#define LS1088A_SRDS_PRTCL_100BASEX_SGMII 3 + +#define LS2088A_RCWSR29_SRDS_PRTCL_S1 GENMASK(23, 16) +#define LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 BIT(14) +#define LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane) BIT(6 + (7 - (lane))) +#define LS2088A_RCWSR30_SRDS_CLK_SEL_MSK GENMASK(13, 6) +#define LS2088A_SRDS_CLK_SEL_XGMII 1 +#define LS2088A_SRDS_CLK_SEL_GMII 0 struct fsl_soc_die_attr { char *die; @@ -22,9 +47,21 @@ struct fsl_soc_die_attr { u32 mask; }; +struct fsl_soc_serdes_rcw_override { + int offset; + int mask; + int val; +}; + struct fsl_soc_data { const char *sfp_compat; u32 uid_offset; + int num_serdes_blocks; + int num_serdes_lanes; + int (*serdes_init_rcwcr)(int index); + int (*serdes_get_rcw_override)(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override); }; enum qoriq_die { @@ -44,6 +81,48 @@ enum qoriq_die { DIE_MAX, }; +struct rcw_full_srds_proto { + int srds_proto_val; + unsigned long xgmii_lanes; +}; + +static const struct rcw_full_srds_proto ls2088a_rcw_full_srds1_protos[] = { + { 0x05, 0 }, + { 0x07, 0 }, + { 0x09, 0 }, + { 0x0a, 0 }, + { 0x0c, 0 }, + { 0x0e, 0 }, + { 0x10, 0 }, + { 0x12, 0 }, + { 0x14, 0 }, + { 0x16, 0 }, + { 0x18, 0 }, + { 0x1a, 0 }, + { 0x1c, 0 }, + { 0x1e, 0 }, + { 0x20, 0 }, + { 0x22, 0 }, + { 0x24, 0 }, + { 0x26, GENMASK(7, 6) }, + { 0x28, GENMASK(7, 4) }, + { 0x2a, GENMASK(7, 0) }, + /* 0x2b: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x2d: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x2e: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x30: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x32: unimplemented because of XAUI1 (lanes E-H) and XAUI2 (A-D) */ + /* 0x33: unimplemented because of QSGMII (lanes E-H) */ + /* 0x35: unimplemented because of QSGMII (lanes A-C) */ + /* 0x37: unimplemented because of QSGMII (lanes E-F) */ + { 0x39, 0 }, + { 0x3b, GENMASK(6, 4) | GENMASK(2, 0) }, + { 0x4b, GENMASK(7, 4) }, + { 0x4c, GENMASK(3, 0) }, + { 0x4d, 0 }, + { 0x58, 0 }, +}; + /* SoC die attribute definition for QorIQ platform */ static const struct fsl_soc_die_attr fsl_soc_die[] = { /* @@ -138,9 +217,11 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = { static struct fsl_soc_guts { struct ccsr_guts __iomem *dcfg_ccsr; + struct ccsr_guts __iomem *dcfg_dcsr; const struct fsl_soc_data *data; bool little_endian; u32 svr; + spinlock_t rcwcr_lock; /* serializes concurrent writes to the RCWCR */ } soc; static unsigned int fsl_guts_read(const void __iomem *reg) @@ -151,6 +232,36 @@ static unsigned int fsl_guts_read(const void __iomem *reg) return ioread32be(reg); } +static void fsl_guts_write(void __iomem *reg, u32 val) +{ + if (soc.little_endian) + iowrite32(val, reg); + else + iowrite32be(val, reg); +} + +/* Some fields of the Reset Configuration Word (RCW) can be overridden at + * runtime by writing to the RCWCRn registers contained within the DCSR space + * of the Device Configuration (DCFG) block. The layout of the RCWCRn registers + * is identical with the read-only RCWSRn from the CCSR space. + */ +static int fsl_guts_rcw_rmw(int offset, u32 val, u32 mask) +{ + u32 rcwcr, rcwsr = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[offset]); + + rcwcr = rcwsr & ~mask; + rcwcr |= val; + fsl_guts_write(&soc.dcfg_dcsr->rcwcr[offset], rcwcr); + + pr_debug("RCW override: RCWSR%d 0x%x -> RCWCR%d 0x%x\n", + offset + 1, rcwsr, offset + 1, rcwcr); + + /* Updates to RCWCR should be visible back in RCWSR immediately */ + return read_poll_timeout_atomic(fsl_guts_read, rcwsr, rcwsr == rcwcr, + 0, RCW_TIMEOUT_US, false, + &soc.dcfg_ccsr->rcwsr[offset]); +} + static bool fsl_soc_die_match_one(u32 svr, const struct fsl_soc_die_attr *match) { return match->svr == (svr & match->mask); @@ -167,6 +278,97 @@ static const struct fsl_soc_die_attr *fsl_soc_die_match( return NULL; } +static int +fsl_guts_serdes_get_rcw_override(int serdes_idx, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + const struct fsl_soc_data *soc_data = soc.data; + + if (!soc_data) + return -ENODEV; + + /* serdes_idx is one-based */ + if (serdes_idx > soc_data->num_serdes_blocks || serdes_idx <= 0) + return -ERANGE; + + if (lane >= soc_data->num_serdes_lanes || lane < 0) + return -ERANGE; + + if (!soc_data->serdes_get_rcw_override) { + pr_debug("RCW override not implemented for SoC\n"); + return -EINVAL; + } + + if (!soc.dcfg_dcsr) { + pr_debug("Device tree does not define DCFG_DCSR region necessary for RCW override\n"); + return -EINVAL; + } + + return soc_data->serdes_get_rcw_override(serdes_idx, lane, lane_mode, + override); +} + +/** + * fsl_guts_lane_validate() - Validate that SerDes protocol is implemented and + * supported on current SoC + * @serdes_idx: one-based SerDes block index + * @lane: zero-based lane index within SerDes + * @lane_mode: requested SerDes protocol + * + * Should be called before actually requesting the RCW override procedure to be + * applied using %fsl_guts_lane_set_mode() + * + * Return: 0 if RCW override to protocol is possible, negative error otherwise + */ +int fsl_guts_lane_validate(int serdes_idx, int lane, enum lynx_lane_mode lane_mode) +{ + struct fsl_soc_serdes_rcw_override override; + + return fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode, + &override); +} +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_validate, "FSL_GUTS"); + +/** + * fsl_guts_lane_set_mode() - apply RCW override procedure for SerDes lane + * @serdes_idx: one-based SerDes block index + * @lane: zero-based lane index within SerDes + * @lane_mode: requested SerDes protocol + * + * Return: 0 on success, negative error otherwise + */ +int fsl_guts_lane_set_mode(int serdes_idx, int lane, enum lynx_lane_mode lane_mode) +{ + struct fsl_soc_serdes_rcw_override override; + int err; + + err = fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode, + &override); + if (err) + return err; + + spin_lock(&soc.rcwcr_lock); + + if (soc.data->serdes_init_rcwcr) { + err = soc.data->serdes_init_rcwcr(serdes_idx); + if (err) + goto out_unlock; + } + + err = fsl_guts_rcw_rmw(override.offset, + override.val << __ffs(override.mask), + override.mask); + if (err) + pr_err("RCW override failed: %pe\n", ERR_PTR(err)); + +out_unlock: + spin_unlock(&soc.rcwcr_lock); + + return err; +} +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_set_mode, "FSL_GUTS"); + static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset) { struct device_node *np; @@ -193,9 +395,188 @@ static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset) return uid; } +static int ls1046a_serdes_get_rcw_override(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + /* The RCW override procedure has to write to different registers + * depending on the SerDes block index. + */ + switch (index) { + case 1: + override->offset = 4; + override->mask = LS1046A_RCWSR5_SRDS_PRTCL_S1(lane); + break; + default: + return -EINVAL; + } + + if (lynx_lane_mode_uses_xgmii_mac(lane_mode)) + override->val = LS1046A_SRDS_PRTCL_XFI; + else if (lynx_lane_mode_uses_gmii_mac(lane_mode)) + override->val = LS1046A_SRDS_PRTCL_100BASEX_SGMII; + else + return -EINVAL; + + return 0; +} + +static int ls1088a_serdes_get_rcw_override(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + /* The RCW override procedure has to write to different registers + * depending on the SerDes block index. + */ + switch (index) { + case 1: + override->offset = 28; + override->mask = LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane); + break; + case 2: + override->offset = 29; + override->mask = LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane); + break; + default: + return -EINVAL; + } + + if (lynx_lane_mode_uses_xgmii_mac(lane_mode)) + override->val = LS1088A_SRDS_PRTCL_XFI; + else if (lynx_lane_mode_uses_gmii_mac(lane_mode)) + override->val = LS1088A_SRDS_PRTCL_100BASEX_SGMII; + else + return -EINVAL; + + return 0; +} + +static const struct rcw_full_srds_proto *ls2088a_get_full_serdes1_proto(void) +{ + u32 rcwsr29 = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[28]); + u32 srds_prtcl_s1 = FIELD_GET(LS2088A_RCWSR29_SRDS_PRTCL_S1, rcwsr29); + + for (int i = 0; i < ARRAY_SIZE(ls2088a_rcw_full_srds1_protos); i++) { + const struct rcw_full_srds_proto *proto; + + proto = &ls2088a_rcw_full_srds1_protos[i]; + if (proto->srds_proto_val == srds_prtcl_s1) + return proto; + } + + return NULL; +} + +static int ls2088a_serdes_get_rcw_override(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + switch (index) { + case 1: + override->offset = 29; + override->mask = LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane); + break; + default: + return -EINVAL; + } + + /* RCW override only supported if we know how to handle the initial + * RCWSR29[SRDS_PRTCL_S1] value and turn it into an override. + */ + if (!ls2088a_get_full_serdes1_proto()) { + u32 rcwsr30 = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[29]); + + /* If a SerDes-level override is already in place (probably + * left there by a previous boot stage), use it. + */ + if (!(rcwsr30 & LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1)) + return -EINVAL; + } + + if (lynx_lane_mode_uses_xgmii_mac(lane_mode)) + override->val = LS2088A_SRDS_CLK_SEL_XGMII; + else if (lynx_lane_mode_uses_gmii_mac(lane_mode)) + override->val = LS2088A_SRDS_CLK_SEL_GMII; + else + return -EINVAL; + + return 0; +} + +static int ls2088a_serdes_init_rcwcr(int serdes_idx) +{ + const struct rcw_full_srds_proto *srds_prtcl_s1; + const struct fsl_soc_data *soc_data = soc.data; + u32 rcwsr30; + int i, err; + + /* SerDes 2 supports only SGMII for networking. There should be + * no need for RCW override + */ + if (serdes_idx != 1) + return -EINVAL; + + /* SRDS_CLK_EN_SEL_XGMII_S1: SerDes Clock Enable Select XGMII Serdes 1: + * Enables to select GMII/XGMII clock according to + * SRDS_CLK_SEL_XGMII_Ln_S1. + * If the GMII/XGMII select override has already been set, use it. + * Otherwise, derive an initial override for all lanes based on the + * full SerDes protocol table. + */ + rcwsr30 = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[29]); + if (rcwsr30 & LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1) { + pr_debug("RCWSR30 = 0x%x, using this.\n", rcwsr30); + return 0; + } + + srds_prtcl_s1 = ls2088a_get_full_serdes1_proto(); + + rcwsr30 = LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1; + + /* We need to configure the initial state of all lanes for + * the SerDes block #1 + */ + for_each_set_bit(i, &srds_prtcl_s1->xgmii_lanes, soc_data->num_serdes_lanes) + rcwsr30 |= LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(i); + + pr_debug("Setting initial RCWSR30 = 0x%x based on SRDS_PRTCL_S1 = 0x%x\n", + rcwsr30, srds_prtcl_s1->srds_proto_val); + + err = fsl_guts_rcw_rmw(29, rcwsr30, + LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 | + LS2088A_RCWSR30_SRDS_CLK_SEL_MSK); + if (err) { + pr_err("Setting up initial RCWCR failed: %pe\n", ERR_PTR(err)); + return err; + } + + return 0; +} + +static const struct fsl_soc_data ls1088a_data = { + .serdes_get_rcw_override = ls1088a_serdes_get_rcw_override, + .num_serdes_blocks = 2, + .num_serdes_lanes = 4, +}; + +static const struct fsl_soc_data ls1046a_data = { + .serdes_get_rcw_override = ls1046a_serdes_get_rcw_override, + .num_serdes_blocks = 2, + .num_serdes_lanes = 4, +}; + +static const struct fsl_soc_data ls2088a_data = { + .serdes_get_rcw_override = ls2088a_serdes_get_rcw_override, + .serdes_init_rcwcr = ls2088a_serdes_init_rcwcr, + .num_serdes_blocks = 2, + .num_serdes_lanes = 8, +}; + static const struct fsl_soc_data ls1028a_data = { .sfp_compat = "fsl,ls1028a-sfp", .uid_offset = 0x21c, + .num_serdes_blocks = 1, + .num_serdes_lanes = 4, }; /* @@ -221,10 +602,10 @@ static const struct of_device_id fsl_guts_of_match[] = { { .compatible = "fsl,mpc8572-guts", }, { .compatible = "fsl,ls1021a-dcfg", }, { .compatible = "fsl,ls1043a-dcfg", }, - { .compatible = "fsl,ls2080a-dcfg", }, - { .compatible = "fsl,ls1088a-dcfg", }, + { .compatible = "fsl,ls2080a-dcfg", .data = &ls2088a_data}, + { .compatible = "fsl,ls1088a-dcfg", .data = &ls1088a_data}, { .compatible = "fsl,ls1012a-dcfg", }, - { .compatible = "fsl,ls1046a-dcfg", }, + { .compatible = "fsl,ls1046a-dcfg", .data = &ls1046a_data}, { .compatible = "fsl,lx2160a-dcfg", }, { .compatible = "fsl,ls1028a-dcfg", .data = &ls1028a_data}, {} @@ -240,6 +621,8 @@ static int __init fsl_guts_init(void) u64 soc_uid = 0; int ret; + spin_lock_init(&soc.rcwcr_lock); + np = of_find_matching_node_and_match(NULL, fsl_guts_of_match, &match); if (!np) return 0; @@ -251,6 +634,8 @@ static int __init fsl_guts_init(void) ret = -ENOMEM; goto err_clear_soc_data; } + /* DCFG_DCSR is optional */ + soc.dcfg_dcsr = of_iomap(np, DCFG_DCSR); soc.little_endian = of_property_read_bool(np, "little-endian"); soc.svr = fsl_guts_read(&soc.dcfg_ccsr->svr); @@ -260,7 +645,7 @@ static int __init fsl_guts_init(void) soc_dev_attr = kzalloc_obj(*soc_dev_attr); if (!soc_dev_attr) { ret = -ENOMEM; - goto err_unmap_dcfg_ccsr; + goto err_unmap_dcfg_ccsr_dcsr; } ret = soc_attr_read_machine(soc_dev_attr); @@ -321,7 +706,11 @@ err_free_family: kfree(soc_dev_attr->family); err_free_soc_dev_attr: kfree(soc_dev_attr); -err_unmap_dcfg_ccsr: +err_unmap_dcfg_ccsr_dcsr: + if (soc.dcfg_dcsr) { + iounmap(soc.dcfg_dcsr); + soc.dcfg_dcsr = NULL; + } iounmap(soc.dcfg_ccsr); soc.dcfg_ccsr = NULL; err_clear_soc_data: diff --git a/include/linux/fsl/guts.h b/include/linux/fsl/guts.h index fdb55ca47a4f..8d5ffb985fee 100644 --- a/include/linux/fsl/guts.h +++ b/include/linux/fsl/guts.h @@ -13,6 +13,7 @@ #include #include +#include /* * Global Utility Registers. @@ -91,9 +92,15 @@ struct ccsr_guts { u32 iovselsr; /* 0x.00c0 - I/O voltage select status register Called 'elbcvselcr' on 86xx SOCs */ u8 res0c4[0x100 - 0xc4]; - u32 rcwsr[16]; /* 0x.0100 - Reset Control Word Status registers - There are 16 registers */ - u8 res140[0x224 - 0x140]; + /* 0x.0100 - read-only Reset Configuration Word Status registers in + * CCSR, or write-only Reset Configuration Word Control registers in + * DCSR. In both cases there are 32 registers. + */ + union { + u32 rcwsr[32]; + u32 rcwcr[32]; + }; + u8 res180[0x224 - 0x180]; u32 iodelay1; /* 0x.0224 - IO delay control register 1 */ u32 iodelay2; /* 0x.0228 - IO delay control register 2 */ u8 res22c[0x604 - 0x22c]; @@ -131,6 +138,11 @@ struct ccsr_guts { u32 srds2cr1; /* 0x.0f44 - SerDes2 Control Register 0 */ } __attribute__ ((packed)); +int fsl_guts_lane_validate(int serdes_idx, int lane, + enum lynx_lane_mode lane_mode); +int fsl_guts_lane_set_mode(int serdes_idx, int lane, + enum lynx_lane_mode lane_mode); + /* Alternate function signal multiplex control */ #define MPC85xx_PMUXCR_QE(x) (0x8000 >> (x)) From 044667223313f901454960a572120b0a83f705ce Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Wed, 22 Jul 2026 02:16:03 +0300 Subject: [PATCH 29/29] phy: lynx-10g: use RCW override procedure for dynamic protocol change Up until this patch, the only protocol change supported was between 1000Base-X/SGMII and 2500Base-X. The others require an RCW override procedure which was lacking. Since now the guts driver provides the means of applying this procedure, make use of it and remove any comment which mentioned the limitation. Signed-off-by: Vladimir Oltean Acked-by: Vinod Koul Reviewed-by: Ioana Ciornei Link: https://lore.kernel.org/r/20260721231603.67865-10-vladimir.oltean@nxp.com Signed-off-by: Christophe Leroy (CS GROUP) --- drivers/phy/freescale/Kconfig | 1 + drivers/phy/freescale/phy-fsl-lynx-10g.c | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig index 5bf3864fbe64..d4e189fffbf8 100644 --- a/drivers/phy/freescale/Kconfig +++ b/drivers/phy/freescale/Kconfig @@ -58,6 +58,7 @@ config PHY_FSL_LYNX_10G tristate "Freescale Layerscape Lynx 10G SerDes PHY support" depends on OF depends on ARCH_LAYERSCAPE || COMPILE_TEST + select FSL_GUTS select GENERIC_PHY select PHY_FSL_LYNX_CORE help diff --git a/drivers/phy/freescale/phy-fsl-lynx-10g.c b/drivers/phy/freescale/phy-fsl-lynx-10g.c index 38def160ef1a..60c85d08c33e 100644 --- a/drivers/phy/freescale/phy-fsl-lynx-10g.c +++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "phy-fsl-lynx-core.h" @@ -1167,14 +1168,7 @@ static bool lynx_10g_lane_mode_needs_rcw_override(struct lynx_lane *lane, /* Major protocol changes, which involve changing the PCS connection to * the GMII MAC with the one to the XGMII MAC, require an RCW override - * procedure to reconfigure an internal mux, as documented here: - * https://lore.kernel.org/linux-phy/20230810102631.bvozjer3t67r67iy@skbuf/ - * This is SoC-specific, and not yet implemented in drivers/soc/fsl/guts.c. - * - * So the supported set of protocols depends on the initial lane mode. - * - * Minor protocol changes (SGMII <-> 1000Base-X <-> 2500Base-X or - * 10GBase-R <-> USXGMII) are supported. + * procedure to reconfigure an internal mux. */ if ((lynx_lane_mode_uses_gmii_mac(curr) && lynx_lane_mode_uses_xgmii_mac(new)) || @@ -1189,6 +1183,7 @@ static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode, union phy_configure_opts *opts) { struct lynx_lane *lane = phy_get_drvdata(phy); + struct lynx_priv *priv = lane->priv; enum lynx_lane_mode lane_mode; int err; @@ -1197,7 +1192,8 @@ static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode, return err; if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode)) - return -EINVAL; + return fsl_guts_lane_validate(priv->info->index, lane->id, + lane_mode); return 0; } @@ -1205,6 +1201,7 @@ static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode, static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode) { struct lynx_lane *lane = phy_get_drvdata(phy); + struct lynx_priv *priv = lane->priv; bool powered_up = lane->powered_up; enum lynx_lane_mode lane_mode; int err; @@ -1225,6 +1222,13 @@ static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode) if (powered_up) lynx_10g_lane_halt(phy); + if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode)) { + err = fsl_guts_lane_set_mode(priv->info->index, lane->id, + lane_mode); + if (err) + goto out; + } + err = lynx_10g_lane_disable_pcvt(lane, lane->mode); if (err) goto out; @@ -1314,6 +1318,7 @@ static struct platform_driver lynx_10g_driver = { }; module_platform_driver(lynx_10g_driver); +MODULE_IMPORT_NS("FSL_GUTS"); MODULE_IMPORT_NS("PHY_FSL_LYNX"); MODULE_AUTHOR("Ioana Ciornei "); MODULE_AUTHOR("Vladimir Oltean ");